UNPKG

chromancer

Version:

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

143 lines (142 loc) 5.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeSelector = normalizeSelector; exports.isValidSelector = isValidSelector; exports.formatSelectorForError = formatSelectorForError; exports.suggestSelectorFix = suggestSelectorFix; /** * Normalizes CSS selectors to ensure they work correctly when passed through YAML */ function normalizeSelector(selector) { // If not a string, return as-is (could be an object with selector property) if (typeof selector !== 'string') { return selector; } // Trim whitespace selector = selector.trim(); // Handle attribute selectors with quotes // Convert variations to a consistent format selector = selector // First, handle attribute selectors with escaped quotes (e.g., type=\'search\') .replace(/\[([^=\]]+)=\\(['"])([^'"]+)\\(['"])\]/g, (match, attr, q1, value, q2) => { return `[${attr}="${value}"]`; }) // Then handle attribute selectors with normal quotes .replace(/\[([^=\]]+)=(['"]?)([^'"\]]+)(['"]?)\]/g, (match, attr, q1, value, q2) => { // Only process if quotes match or no quotes if ((q1 === q2) || (!q1 && !q2)) { return `[${attr}="${value}"]`; } return match; }); return selector; } /** * Validates a CSS selector to ensure it's properly formatted */ function isValidSelector(selector) { if (!selector || typeof selector !== 'string') { return false; } try { // Basic validation patterns const invalidPatterns = [ /^\s*$/, // Empty or whitespace only /[\n\r\t]/, // Contains newlines or tabs /['"]\s*['"]/ // Mismatched quotes ]; for (const pattern of invalidPatterns) { if (pattern.test(selector)) { return false; } } // Check for balanced brackets and quotes let brackets = 0; let singleQuotes = 0; let doubleQuotes = 0; let inEscape = false; for (let i = 0; i < selector.length; i++) { const char = selector[i]; const prevChar = i > 0 ? selector[i - 1] : ''; if (inEscape) { inEscape = false; continue; } if (char === '\\') { inEscape = true; continue; } switch (char) { case '[': if (singleQuotes % 2 === 0 && doubleQuotes % 2 === 0) brackets++; break; case ']': if (singleQuotes % 2 === 0 && doubleQuotes % 2 === 0) brackets--; break; case "'": if (doubleQuotes % 2 === 0 && prevChar !== '\\') singleQuotes++; break; case '"': if (singleQuotes % 2 === 0 && prevChar !== '\\') doubleQuotes++; break; } } return brackets === 0 && singleQuotes % 2 === 0 && doubleQuotes % 2 === 0; } catch { return false; } } /** * Formats a selector for display in error messages */ function formatSelectorForError(selector) { if (typeof selector !== 'string') { return JSON.stringify(selector); } // Truncate long selectors if (selector.length > 100) { return selector.substring(0, 97) + '...'; } return selector; } /** * Suggests corrections for common selector issues */ function suggestSelectorFix(selector, error) { const suggestions = []; // Check for quote issues if (selector.includes("'") && selector.includes('"')) { suggestions.push('Try using consistent quote types in your selector'); } // Check for attribute selector issues const attrMatch = selector.match(/\[([^=\]]+)=([^[\]]+)\]/); if (attrMatch && !attrMatch[2].match(/^["'].*["']$/)) { suggestions.push(`Try wrapping the attribute value in quotes: [${attrMatch[1]}="${attrMatch[2]}"]`); } // Check for pseudo-selectors if (selector.includes(':visible') || selector.includes(':hidden')) { suggestions.push('Playwright does not support jQuery pseudo-selectors. Use waitForSelector with visible/hidden options instead'); } // Check for XPath if (selector.startsWith('//') || selector.includes('xpath=')) { suggestions.push('Consider using CSS selectors instead of XPath for better performance and readability'); } // Error-specific suggestions if (error) { if (error.toLowerCase().includes('not visible')) { suggestions.push('The element exists but is hidden. Try waiting for it to become visible or check if it needs to be revealed by another action'); } else if (error.toLowerCase().includes('timeout')) { suggestions.push('The selector did not match any elements. Verify the selector is correct and the page has loaded'); } else if (error.toLowerCase().includes('multiple elements')) { suggestions.push('The selector matches multiple elements. Make it more specific or use :first-child, :nth-child(), etc.'); } } return suggestions; }