chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
164 lines (163 loc) • 6.93 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkMultipleMatches = checkMultipleMatches;
exports.formatElementMatches = formatElementMatches;
exports.getInteractiveSelection = getInteractiveSelection;
/**
* Check if a selector matches multiple elements and return disambiguation info
*/
async function checkMultipleMatches(page, selector) {
const count = await page.locator(selector).count();
if (count <= 1) {
return { count };
}
// Get info about first few matches for disambiguation
const elements = [];
const limit = Math.min(count, 10); // Show max 10 options
for (let i = 0; i < limit; i++) {
const locator = page.locator(selector).nth(i);
const elementInfo = await locator.evaluate((el, index) => {
const rect = el.getBoundingClientRect();
// Generate unique selector for this specific element
function getUniqueSelector(element) {
// Priority 1: ID
if (element.id) {
return '#' + element.id;
}
// Priority 2: Unique class combination
if (element.className) {
const classes = element.className.split(' ').filter(c => c.trim());
const validClasses = classes.filter(c => /^[a-zA-Z_-][a-zA-Z0-9_-]*$/.test(c));
if (validClasses.length > 0) {
const firstClassSelector = '.' + validClasses[0];
if (document.querySelectorAll(firstClassSelector).length === 1) {
return firstClassSelector;
}
// Try combination of classes
for (let i = 2; i <= Math.min(validClasses.length, 3); i++) {
const classSelector = '.' + validClasses.slice(0, i).join('.');
try {
if (document.querySelectorAll(classSelector).length === 1) {
return classSelector;
}
}
catch (e) {
// Invalid selector, skip
}
}
}
}
// Priority 3: nth-child based on original selector
const parent = element.parentElement;
if (parent) {
const siblings = Array.from(parent.querySelectorAll(':scope > ' + element.tagName));
const index = siblings.indexOf(element) + 1;
return `${element.tagName.toLowerCase()}:nth-child(${index})`;
}
// Fallback: Use nth-of-type
return `${element.tagName.toLowerCase()}:nth-of-type(${index + 1})`;
}
return {
index: index,
selector: getUniqueSelector(el),
tagName: el.tagName.toLowerCase(),
textContent: el.textContent?.trim().substring(0, 50) || '',
visible: rect.width > 0 && rect.height > 0,
id: el.id || undefined,
classes: el.className ? el.className.split(' ').filter((c) => c.trim()) : undefined,
position: {
top: Math.round(rect.top),
left: Math.round(rect.left),
width: Math.round(rect.width),
height: Math.round(rect.height),
},
};
}, i);
elements.push(elementInfo);
}
return { count, elements };
}
/**
* Format element matches for display
*/
function formatElementMatches(elements) {
const lines = [];
lines.push(`Found ${elements.length} elements. Please use a more specific selector:`);
lines.push('---');
for (const el of elements) {
const visibilityIcon = el.visible ? '✓ visible' : '✗ hidden';
const text = el.textContent ? ` - "${el.textContent}"` : '';
lines.push(`[${el.index}] <${el.tagName}> ${visibilityIcon}`);
lines.push(` Suggested selector: ${el.selector}`);
if (el.id) {
lines.push(` ID: ${el.id}`);
}
if (el.classes && el.classes.length > 0) {
lines.push(` Classes: ${el.classes.join(', ')}`);
}
if (text) {
lines.push(` Text: ${text}`);
}
lines.push(` Position: ${el.position.left}x${el.position.top}`);
lines.push('');
}
return lines.join('\n');
}
/**
* Get interactive selection from user (requires inquirer)
*/
async function getInteractiveSelection(elements) {
try {
const { select } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
const choices = elements.map((el) => {
const visibilityIcon = el.visible ? '✓' : '✗';
const text = el.textContent ? ` - "${el.textContent}"` : '';
return {
name: `[${el.index}] <${el.tagName}> ${visibilityIcon} ${el.selector}${text}`,
value: el.selector
};
});
const selectedSelector = await select({
message: 'Multiple elements found. Select one:',
choices: choices.map(c => ({ name: c.name, value: c.value }))
});
return selectedSelector;
}
catch (error) {
// If interactive mode fails, return null
return null;
}
}