claude-playwright
Version:
Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing
227 lines (226 loc) • 7.95 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/core/tiered-cache.ts
var tiered_cache_exports = {};
__export(tiered_cache_exports, {
TieredCache: () => TieredCache
});
module.exports = __toCommonJS(tiered_cache_exports);
var import_lru_cache = require("lru-cache");
var TieredCache = class {
constructor(bidirectionalCache, options = {}) {
this.stats = {
memoryHits: 0,
sqliteHits: 0,
misses: 0,
totalRequests: 0
};
this.bidirectionalCache = bidirectionalCache;
this.memoryCache = new import_lru_cache.LRUCache({
max: options.memorySize ?? 100,
ttl: options.memoryTTL ?? 3e5,
// 5 minutes
updateAgeOnGet: true,
updateAgeOnHas: true
});
if (options.preloadCommonSelectors) {
this.preloadCommonSelectors();
}
}
async get(input, url) {
this.stats.totalRequests++;
const memoryKey = this.createMemoryKey(input, url);
if (this.memoryCache.has(memoryKey)) {
const cached = this.memoryCache.get(memoryKey);
if (cached) {
this.stats.memoryHits++;
console.error(`[TieredCache] Memory HIT for "${input}": ${cached.selector} (${cached.source})`);
return cached;
}
}
const result = await this.bidirectionalCache.get(input, url);
if (result) {
this.stats.sqliteHits++;
const cacheEntry = {
...result,
timestamp: Date.now()
};
this.memoryCache.set(memoryKey, cacheEntry);
await this.cacheVariations(input, url, cacheEntry);
console.error(`[TieredCache] SQLite HIT for "${input}": ${result.selector} (${result.source})`);
return cacheEntry;
}
this.stats.misses++;
console.error(`[TieredCache] MISS for "${input}"`);
return null;
}
async set(input, url, selector) {
await this.bidirectionalCache.set(input, url, selector);
const memoryKey = this.createMemoryKey(input, url);
const cacheEntry = {
selector,
confidence: 0.8,
// New entries start with good confidence
source: "exact",
cached: true,
timestamp: Date.now()
};
this.memoryCache.set(memoryKey, cacheEntry);
await this.cacheVariations(input, url, cacheEntry);
console.error(`[TieredCache] STORED "${input}" \u2192 ${selector}`);
}
createMemoryKey(input, url) {
return `${input.toLowerCase().trim()}|${url}`;
}
async cacheVariations(input, url, cacheEntry) {
const variations = this.generateInputVariations(input);
for (const variation of variations) {
const varKey = this.createMemoryKey(variation, url);
if (!this.memoryCache.has(varKey)) {
const varEntry = {
...cacheEntry,
confidence: cacheEntry.confidence * 0.95,
source: "normalized"
};
this.memoryCache.set(varKey, varEntry);
}
}
}
generateInputVariations(input) {
const variations = /* @__PURE__ */ new Set();
variations.add(input.toLowerCase());
variations.add(input.toLowerCase().trim());
variations.add(input.replace(/\s+/g, " ").trim());
variations.add(input.replace(/\b(the|a|an)\s+/gi, "").trim());
const actionMappings = [
["click", "press", "tap", "hit"],
["type", "enter", "input", "fill"],
["select", "choose", "pick"]
];
for (const synonyms of actionMappings) {
for (let i = 0; i < synonyms.length; i++) {
for (let j = 0; j < synonyms.length; j++) {
if (i !== j) {
const regex = new RegExp(`\\b${synonyms[i]}\\b`, "gi");
if (regex.test(input)) {
variations.add(input.replace(regex, synonyms[j]));
}
}
}
}
}
variations.add(input.replace(/\s+button\s*$/i, "").trim());
return Array.from(variations).slice(0, 8);
}
async preloadCommonSelectors() {
try {
const stats = await this.bidirectionalCache.getStats();
console.error(`[TieredCache] Preloading enabled - ${stats.storage?.unique_selectors || 0} selectors available`);
} catch (error) {
console.error("[TieredCache] Preload failed:", error);
}
}
async invalidateForUrl(url) {
const keysToDelete = [];
for (const [key] of this.memoryCache.entries()) {
if (key.endsWith(`|${url}`)) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
this.memoryCache.delete(key);
}
console.error(`[TieredCache] Invalidated ${keysToDelete.length} memory entries for ${url}`);
}
async clear() {
this.memoryCache.clear();
await this.bidirectionalCache.clear();
this.stats = {
memoryHits: 0,
sqliteHits: 0,
misses: 0,
totalRequests: 0
};
console.error("[TieredCache] All caches cleared");
}
getStats() {
const memoryHitRate = this.stats.totalRequests > 0 ? this.stats.memoryHits / this.stats.totalRequests * 100 : 0;
const sqliteHitRate = this.stats.totalRequests > 0 ? this.stats.sqliteHits / this.stats.totalRequests * 100 : 0;
const overallHitRate = this.stats.totalRequests > 0 ? (this.stats.memoryHits + this.stats.sqliteHits) / this.stats.totalRequests * 100 : 0;
return {
tiered: {
memoryHitRate: Math.round(memoryHitRate * 10) / 10,
sqliteHitRate: Math.round(sqliteHitRate * 10) / 10,
overallHitRate: Math.round(overallHitRate * 10) / 10,
totalRequests: this.stats.totalRequests,
memorySize: this.memoryCache.size,
memoryMax: this.memoryCache.max
},
breakdown: {
memoryHits: this.stats.memoryHits,
sqliteHits: this.stats.sqliteHits,
misses: this.stats.misses
}
};
}
// Enhanced wrapper for MCP server integration
async wrapSelectorOperation(description, url, operation, fallbackSelector) {
const cached = await this.get(description, url);
if (cached) {
try {
const result = await operation(cached.selector);
console.error(`[TieredCache] Operation SUCCESS with cached selector: ${cached.selector}`);
return {
result,
cached: true,
selector: cached.selector
};
} catch (error) {
console.error(`[TieredCache] Cached selector FAILED, invalidating: ${cached.selector}`);
const memKey = this.createMemoryKey(description, url);
this.memoryCache.delete(memKey);
}
}
if (!fallbackSelector) {
throw new Error(`No cached selector found for "${description}" and no fallback provided`);
}
try {
const result = await operation(fallbackSelector);
await this.set(description, url, fallbackSelector);
console.error(`[TieredCache] Operation SUCCESS with fallback, now cached: ${fallbackSelector}`);
return {
result,
cached: false,
selector: fallbackSelector
};
} catch (error) {
console.error(`[TieredCache] Fallback selector also FAILED: ${fallbackSelector}`);
throw error;
}
}
close() {
this.memoryCache.clear();
this.bidirectionalCache.close();
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TieredCache
});
//# sourceMappingURL=tiered-cache.cjs.map
;