UNPKG

chromancer

Version:

A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.

166 lines (165 loc) 6.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectorRanker = void 0; class SelectorRanker { page; constructor(page) { this.page = page; } async rankSelectors(candidates, sampleSize = 2) { const results = await Promise.all(candidates.map(async (selector) => { try { const elements = await this.page.$$(selector); const count = elements.length; if (count === 0) { return { selector, count: 0, samples: [], confidence: 0 }; } // Collect sample texts const samples = []; for (let i = 0; i < Math.min(sampleSize, elements.length); i++) { const text = await elements[i].innerText().catch(() => ''); if (text && text.trim()) { samples.push(text.trim().substring(0, 100)); } } // Calculate confidence based on various factors const confidence = this.calculateConfidence(selector, count, samples); return { selector, count, samples, confidence }; } catch (error) { return { selector, count: 0, samples: [], confidence: 0 }; } })); // Filter out failed selectors and sort by confidence, then count return results .filter(r => r.count > 0) .sort((a, b) => { if (Math.abs(a.confidence - b.confidence) > 0.1) { return b.confidence - a.confidence; } return b.count - a.count; }); } calculateConfidence(selector, count, samples) { let confidence = 0.5; // Higher count generally means better pattern if (count >= 10) confidence += 0.2; else if (count >= 5) confidence += 0.1; // Consistent sample lengths suggest uniform data if (samples.length >= 2) { const lengths = samples.map(s => s.length); const avgLength = lengths.reduce((a, b) => a + b, 0) / lengths.length; const variance = lengths.reduce((sum, len) => sum + Math.pow(len - avgLength, 2), 0) / lengths.length; if (variance < avgLength * 0.3) { confidence += 0.1; } } // Prefer specific selectors over generic ones if (selector.includes('.') || selector.includes('[')) { confidence += 0.1; } // Penalize overly generic selectors if (selector === 'div' || selector === 'span') { confidence -= 0.3; } // Prefer selectors that look like data containers if (selector.includes('item') || selector.includes('card') || selector.includes('row') || selector.includes('result')) { confidence += 0.1; } return Math.max(0, Math.min(1, confidence)); } async testSelector(selector) { try { const elements = await this.page.$$(selector); const count = elements.length; if (count === 0) { return { valid: false, count: 0, error: 'No elements found' }; } const sample = await elements[0].innerText().catch(() => ''); return { valid: true, count, sample: sample?.trim().substring(0, 100) }; } catch (error) { return { valid: false, count: 0, error: error instanceof Error ? error.message : 'Invalid selector' }; } } async findAlternatives(failedSelector, limit = 5) { // Extract key parts from the failed selector const parts = this.extractSelectorParts(failedSelector); return await this.page.evaluate(({ parts, limit }) => { const alternatives = new Set(); // Try each part individually parts.forEach(part => { if (part.startsWith('.')) { // Class selector const elements = document.querySelectorAll(part); if (elements.length > 0) { alternatives.add(part); } } else if (part.startsWith('#')) { // ID selector const element = document.querySelector(part); if (element) { alternatives.add(part); } } else if (part.includes('=')) { // Attribute selector try { const elements = document.querySelectorAll(`[${part}]`); if (elements.length > 0) { alternatives.add(`[${part}]`); } } catch { } } }); // Look for similar classes if (failedSelector.includes('.')) { const className = failedSelector.match(/\.([a-zA-Z0-9_-]+)/)?.[1]; if (className) { document.querySelectorAll('[class*="' + className.substring(0, 3) + '"]').forEach(el => { Array.from(el.classList).forEach(cls => { if (cls.includes(className.substring(0, 3))) { alternatives.add(`.${cls}`); } }); }); } } return Array.from(alternatives).slice(0, limit); }, { parts, limit }); } extractSelectorParts(selector) { const parts = []; // Extract classes const classes = selector.match(/\.[a-zA-Z0-9_-]+/g) || []; parts.push(...classes); // Extract IDs const ids = selector.match(/#[a-zA-Z0-9_-]+/g) || []; parts.push(...ids); // Extract attributes const attrs = selector.match(/\[([^\]]+)\]/g) || []; attrs.forEach(attr => { parts.push(attr.slice(1, -1)); }); // Extract tag names const tagMatch = selector.match(/^([a-zA-Z]+)/); if (tagMatch) { parts.push(tagMatch[1]); } return parts; } } exports.SelectorRanker = SelectorRanker;