healenium-global
Version:
Globally self-healing wrapper for Playwright actions (click, fill, etc.)
97 lines (81 loc) • 2.45 kB
text/typescript
import { Page } from 'playwright';
import fs from 'fs';
import path from 'path';
import levenshtein from 'js-levenshtein';
const fallbackPath = path.join(__dirname, 'fallback-selectors.json');
function loadFallbacks(): Record<string, string> {
return fs.existsSync(fallbackPath)
? JSON.parse(fs.readFileSync(fallbackPath, 'utf-8'))
: {};
}
function saveFallback(original: string, healed: string) {
const data = loadFallbacks();
data[original] = healed;
fs.writeFileSync(fallbackPath, JSON.stringify(data, null, 2));
}
async function findHealedSelector(
page: Page,
selector: string
): Promise<string | null> {
const failedText = await page
.locator(selector)
.textContent()
.catch(() => '');
if (!failedText) return null;
const elements = await page.$$('body *');
let best = { selector: '', score: Infinity };
for (const el of elements) {
const text = await el.textContent();
if (!text) continue;
const score = levenshtein(failedText.trim(), text.trim());
if (score < best.score && score < 10) {
const tag = await el.evaluate((e) => e.tagName.toLowerCase());
best = { selector: `${tag}:has-text("${text.trim()}")`, score };
}
}
if (best.selector) {
saveFallback(selector, best.selector);
return best.selector;
}
return null;
}
export async function patchPageGlobally(page: Page) {
const actions = [
'click',
'fill',
'check',
'uncheck',
'hover',
'dblclick',
];
const fallbacks = loadFallbacks();
for (const method of actions) {
const original = (page as any)[method].bind(page);
(page as any)[method] = async (
selector: string,
...args: any[]
) => {
try {
return await original(selector, ...args);
} catch (err) {
console.warn(`[Healenium] ${method} failed on: ${selector}`);
const fallbacks = loadFallbacks();
if (fallbacks[selector]) {
console.log(
`[Healenium] Retrying with fallback: ${fallbacks[selector]}`
);
return await original(fallbacks[selector], ...args);
}
const healed = await findHealedSelector(page, selector);
if (healed) {
console.log(
`[Healenium] Found and saved fallback: ${healed}`
);
saveFallback(selector, healed);
return await original(healed, ...args);
}
throw err;
}
};
}
}