Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
6.8 KiB
JavaScript
Executable File
197 lines
6.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
/**
|
||
* Inspect Routing Page - Find Add Button
|
||
*
|
||
* This script opens the UDM Pro routing page and inspects all elements
|
||
* to help identify the Add button selector.
|
||
*/
|
||
|
||
import { chromium } from 'playwright';
|
||
import { readFileSync } from 'fs';
|
||
import { join } from 'path';
|
||
import { homedir } from 'os';
|
||
|
||
// Load environment variables
|
||
const envPath = join(homedir(), '.env');
|
||
function loadEnvFile(filePath) {
|
||
try {
|
||
const envFile = readFileSync(filePath, 'utf8');
|
||
const envVars = envFile.split('\n').filter(
|
||
(line) => line.includes('=') && !line.trim().startsWith('#')
|
||
);
|
||
for (const line of envVars) {
|
||
const [key, ...values] = line.split('=');
|
||
if (key && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
|
||
let value = values.join('=').trim();
|
||
if (
|
||
(value.startsWith('"') && value.endsWith('"')) ||
|
||
(value.startsWith("'") && value.endsWith("'"))
|
||
) {
|
||
value = value.slice(1, -1);
|
||
}
|
||
process.env[key.trim()] = value;
|
||
}
|
||
}
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
loadEnvFile(envPath);
|
||
|
||
const UDM_URL = process.env.UNIFI_UDM_URL || 'https://192.168.0.1';
|
||
const USERNAME = process.env.UNIFI_BROWSER_USERNAME || process.env.UNIFI_USERNAME || 'unifi_api';
|
||
const PASSWORD = process.env.UNIFI_BROWSER_PASSWORD || process.env.UNIFI_PASSWORD;
|
||
|
||
console.log('🔍 UDM Pro Routing Page Inspector');
|
||
console.log('==================================\n');
|
||
|
||
if (!PASSWORD) {
|
||
console.error('❌ UNIFI_PASSWORD must be set in ~/.env');
|
||
process.exit(1);
|
||
}
|
||
|
||
(async () => {
|
||
const browser = await chromium.launch({ headless: false });
|
||
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
||
const page = await context.newPage();
|
||
|
||
try {
|
||
console.log('1. Logging in...');
|
||
await page.goto(UDM_URL, { waitUntil: 'networkidle' });
|
||
await page.waitForSelector('input[type="text"]');
|
||
await page.fill('input[type="text"]', USERNAME);
|
||
await page.fill('input[type="password"]', PASSWORD);
|
||
await page.click('button[type="submit"]');
|
||
await page.waitForTimeout(3000);
|
||
|
||
console.log('2. Navigating to Routing page...');
|
||
await page.goto(`${UDM_URL}/network/default/settings/routing`, { waitUntil: 'networkidle' });
|
||
await page.waitForTimeout(5000);
|
||
|
||
console.log('3. Inspecting page elements...\n');
|
||
|
||
// Get all clickable elements
|
||
const clickableElements = await page.evaluate(() => {
|
||
const elements = [];
|
||
const allElements = document.querySelectorAll('button, a, [role="button"], [onclick], [class*="button"], [class*="click"]');
|
||
|
||
allElements.forEach((el, index) => {
|
||
const rect = el.getBoundingClientRect();
|
||
if (rect.width > 0 && rect.height > 0) {
|
||
const styles = window.getComputedStyle(el);
|
||
if (styles.display !== 'none' && styles.visibility !== 'hidden') {
|
||
const text = el.textContent?.trim() || '';
|
||
const tagName = el.tagName.toLowerCase();
|
||
const className = el.className || '';
|
||
const id = el.id || '';
|
||
const ariaLabel = el.getAttribute('aria-label') || '';
|
||
const dataTestId = el.getAttribute('data-testid') || '';
|
||
const onclick = el.getAttribute('onclick') || '';
|
||
|
||
// Get parent info
|
||
const parent = el.parentElement;
|
||
const parentClass = parent?.className || '';
|
||
const parentTag = parent?.tagName.toLowerCase() || '';
|
||
|
||
elements.push({
|
||
index,
|
||
tagName,
|
||
text: text.substring(0, 50),
|
||
className: className.substring(0, 100),
|
||
id,
|
||
ariaLabel,
|
||
dataTestId,
|
||
onclick: onclick.substring(0, 50),
|
||
parentTag,
|
||
parentClass: parentClass.substring(0, 100),
|
||
xpath: getXPath(el),
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
function getXPath(element) {
|
||
if (element.id !== '') {
|
||
return `//*[@id="${element.id}"]`;
|
||
}
|
||
if (element === document.body) {
|
||
return '/html/body';
|
||
}
|
||
let ix = 0;
|
||
const siblings = element.parentNode.childNodes;
|
||
for (let i = 0; i < siblings.length; i++) {
|
||
const sibling = siblings[i];
|
||
if (sibling === element) {
|
||
return getXPath(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
|
||
}
|
||
if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
|
||
ix++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return elements;
|
||
});
|
||
|
||
console.log(`Found ${clickableElements.length} clickable elements:\n`);
|
||
|
||
// Filter for potential Add buttons
|
||
const potentialAddButtons = clickableElements.filter(el => {
|
||
const text = el.text.toLowerCase();
|
||
const className = el.className.toLowerCase();
|
||
const ariaLabel = el.ariaLabel.toLowerCase();
|
||
return (
|
||
text.includes('add') ||
|
||
text.includes('create') ||
|
||
text.includes('new') ||
|
||
text === '+' ||
|
||
text === '' ||
|
||
className.includes('add') ||
|
||
className.includes('create') ||
|
||
ariaLabel.includes('add') ||
|
||
ariaLabel.includes('create')
|
||
);
|
||
});
|
||
|
||
console.log('🎯 Potential Add Buttons:');
|
||
console.log('='.repeat(80));
|
||
potentialAddButtons.forEach((el, i) => {
|
||
console.log(`\n${i + 1}. Element ${el.index}:`);
|
||
console.log(` Tag: ${el.tagName}`);
|
||
console.log(` Text: "${el.text}"`);
|
||
console.log(` Class: ${el.className}`);
|
||
console.log(` ID: ${el.id || 'none'}`);
|
||
console.log(` Aria Label: ${el.ariaLabel || 'none'}`);
|
||
console.log(` Data Test ID: ${el.dataTestId || 'none'}`);
|
||
console.log(` Parent: <${el.parentTag}> ${el.parentClass}`);
|
||
console.log(` XPath: ${el.xpath}`);
|
||
console.log(` Selector: ${el.tagName}${el.id ? `#${el.id}` : ''}${el.className ? `.${el.className.split(' ')[0]}` : ''}`);
|
||
});
|
||
|
||
console.log('\n\n📋 All Buttons on Page:');
|
||
console.log('='.repeat(80));
|
||
const buttons = clickableElements.filter(el => el.tagName === 'button');
|
||
buttons.forEach((el, i) => {
|
||
console.log(`\n${i + 1}. Button ${el.index}:`);
|
||
console.log(` Text: "${el.text}"`);
|
||
console.log(` Class: ${el.className}`);
|
||
console.log(` Aria Label: ${el.ariaLabel || 'none'}`);
|
||
console.log(` XPath: ${el.xpath}`);
|
||
});
|
||
|
||
console.log('\n\n⏸️ Page is open in browser. Inspect elements manually if needed.');
|
||
console.log('Press Ctrl+C to close...\n');
|
||
|
||
// Keep browser open
|
||
await page.waitForTimeout(60000);
|
||
|
||
} catch (error) {
|
||
console.error('❌ Error:', error.message);
|
||
await page.screenshot({ path: 'inspect-error.png', fullPage: true });
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
})();
|