chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
123 lines (122 loc) • 5.07 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOMDigestCollector = void 0;
class DOMDigestCollector {
page;
static REJECTED_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG', 'IMG', 'HEAD'];
static MAX_PATTERNS = 30;
static MAX_TEXTS = 30;
static MAX_ATTRS = 30;
static MAX_TEXT_LENGTH = 100;
static MIN_PATTERN_COUNT = 3;
constructor(page) {
this.page = page;
}
async collect() {
return await this.page.evaluate(() => {
const digestKey = '__chromancerDigest';
const cached = window[digestKey];
if (cached && cached.url === window.location.href) {
return cached;
}
const rejected = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG', 'IMG', 'HEAD'];
const elements = Array.from(document.querySelectorAll('*'))
.filter(el => !rejected.includes(el.tagName));
// Collect patterns
const classPatterns = new Map();
const rolePatterns = new Map();
const tagPatterns = new Map();
elements.forEach(el => {
// Count class patterns
if (el.className && typeof el.className === 'string') {
el.className.split(/\s+/).filter(c => c).forEach(cls => {
const selector = `.${cls}`;
classPatterns.set(selector, (classPatterns.get(selector) || 0) + 1);
});
}
// Count role patterns
const role = el.getAttribute('role');
if (role) {
const selector = `[role="${role}"]`;
rolePatterns.set(selector, (rolePatterns.get(selector) || 0) + 1);
}
// Count tag patterns for meaningful tags
const meaningfulTags = ['article', 'section', 'aside', 'nav', 'header', 'footer', 'main'];
if (meaningfulTags.includes(el.tagName.toLowerCase())) {
tagPatterns.set(el.tagName.toLowerCase(), (tagPatterns.get(el.tagName.toLowerCase()) || 0) + 1);
}
});
// Combine and sort patterns
const allPatterns = [
...Array.from(classPatterns.entries()),
...Array.from(rolePatterns.entries()),
...Array.from(tagPatterns.entries())
]
.filter(([_, count]) => count >= 3)
.sort((a, b) => b[1] - a[1])
.slice(0, 30)
.map(([selector, count]) => ({ selector, count }));
// Collect meaningful text nodes
const texts = [];
const textNodes = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => {
const parent = node.parentElement;
if (!parent || rejected.includes(parent.tagName)) {
return NodeFilter.FILTER_REJECT;
}
const text = node.textContent?.trim() || '';
if (text.length > 10 && text.length < 100) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_REJECT;
}
});
let textNode;
while ((textNode = textNodes.nextNode()) && texts.length < 30) {
const text = textNode.textContent?.trim() || '';
if (text && !texts.includes(text)) {
texts.push(text);
}
}
// Collect data attributes
const attrs = new Set();
elements.forEach(el => {
Array.from(el.attributes).forEach(attr => {
if (attr.name.startsWith('data-') || attr.name.startsWith('aria-')) {
attrs.add(attr.name);
}
});
});
const digest = {
url: window.location.href,
title: document.title,
patterns: allPatterns,
texts: texts.slice(0, 30),
attrs: Array.from(attrs).slice(0, 30),
timestamp: Date.now()
};
window[digestKey] = digest;
return digest;
});
}
async getDigestSize(digest) {
return JSON.stringify(digest).length;
}
formatForClaude(digest) {
const lines = [
`URL: ${digest.url}`,
`Title: ${digest.title}`,
'',
'Top Patterns:',
...digest.patterns.slice(0, 10).map(p => ` ${p.selector} (${p.count} elements)`),
'',
'Sample Text Content:',
...digest.texts.slice(0, 10).map(t => ` "${t}"`),
'',
'Available Attributes:',
` ${digest.attrs.slice(0, 20).join(', ')}`
];
return lines.join('\n');
}
}
exports.DOMDigestCollector = DOMDigestCollector;