healenium-global
Version:
Globally self-healing wrapper for Playwright actions (click, fill, etc.)
93 lines (92 loc) • 3.97 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.patchPageGlobally = patchPageGlobally;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const js_levenshtein_1 = __importDefault(require("js-levenshtein"));
const fallbackPath = path_1.default.join(__dirname, 'fallback-selectors.json');
function loadFallbacks() {
return fs_1.default.existsSync(fallbackPath)
? JSON.parse(fs_1.default.readFileSync(fallbackPath, 'utf-8'))
: {};
}
function saveFallback(original, healed) {
const data = loadFallbacks();
data[original] = healed;
fs_1.default.writeFileSync(fallbackPath, JSON.stringify(data, null, 2));
}
function findHealedSelector(page, selector) {
return __awaiter(this, void 0, void 0, function* () {
const failedText = yield page
.locator(selector)
.textContent()
.catch(() => '');
if (!failedText)
return null;
const elements = yield page.$$('body *');
let best = { selector: '', score: Infinity };
for (const el of elements) {
const text = yield el.textContent();
if (!text)
continue;
const score = (0, js_levenshtein_1.default)(failedText.trim(), text.trim());
if (score < best.score && score < 10) {
const tag = yield 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;
});
}
function patchPageGlobally(page) {
return __awaiter(this, void 0, void 0, function* () {
const actions = [
'click',
'fill',
'check',
'uncheck',
'hover',
'dblclick',
];
const fallbacks = loadFallbacks();
for (const method of actions) {
const original = page[method].bind(page);
page[method] = (selector, ...args) => __awaiter(this, void 0, void 0, function* () {
try {
return yield 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 yield original(fallbacks[selector], ...args);
}
const healed = yield findHealedSelector(page, selector);
if (healed) {
console.log(`[Healenium] Found and saved fallback: ${healed}`);
saveFallback(selector, healed);
return yield original(healed, ...args);
}
throw err;
}
});
}
});
}