claude-playwright
Version:
Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing
202 lines • 5.31 kB
JavaScript
// src/core/selector-cache.ts
var SelectorCache = class {
constructor(cacheManager) {
this.currentUrl = "";
this.cacheManager = cacheManager;
}
setContext(page, url, profile) {
this.page = page;
this.currentUrl = url;
this.currentProfile = profile;
}
async getCachedSelector(elementDescription, elementRef) {
const cacheKey = {
description: elementDescription,
ref: elementRef,
url: this.currentUrl
};
const cached = await this.cacheManager.get(
cacheKey,
"selector",
this.currentProfile
);
if (cached) {
if (this.page && await this.validateSelector(cached)) {
return cached;
} else {
await this.invalidateSelector(elementDescription, elementRef);
return null;
}
}
return null;
}
async cacheSelector(elementDescription, selector, strategy, elementRef, additionalData) {
const cacheKey = {
description: elementDescription,
ref: elementRef,
url: this.currentUrl
};
const entry = {
selector,
strategy,
...additionalData
};
await this.cacheManager.set(
cacheKey,
entry,
"selector",
{
url: this.currentUrl,
profile: this.currentProfile
}
);
}
async getCachedElementState(selector) {
const cacheKey = {
selector,
url: this.currentUrl
};
return await this.cacheManager.get(
cacheKey,
"state",
this.currentProfile
);
}
async cacheElementState(selector, state) {
const cacheKey = {
selector,
url: this.currentUrl
};
await this.cacheManager.set(
cacheKey,
state,
"state",
{
url: this.currentUrl,
profile: this.currentProfile
}
);
}
async getElementState(locator) {
const selector = locator.toString();
const cached = await this.getCachedElementState(selector);
if (cached) {
return cached;
}
const [isVisible, isEnabled, isEditable, boundingBox, text, value] = await Promise.all([
locator.isVisible(),
locator.isEnabled(),
locator.isEditable(),
locator.boundingBox(),
locator.textContent(),
locator.inputValue().catch(() => "")
]);
const attributes = await locator.evaluate((el) => {
const attrs = {};
for (const attr of el.attributes) {
attrs[attr.name] = attr.value;
}
return attrs;
});
const state = {
isVisible,
isEnabled,
isEditable,
boundingBox,
text: text || "",
value,
attributes
};
await this.cacheElementState(selector, state);
return state;
}
async validateSelector(entry) {
if (!this.page) return false;
try {
let locator;
switch (entry.strategy) {
case "css":
locator = this.page.locator(entry.selector);
break;
case "xpath":
locator = this.page.locator(`xpath=${entry.selector}`);
break;
case "text":
locator = this.page.getByText(entry.selector);
break;
case "role":
locator = this.page.getByRole(entry.selector);
break;
case "testid":
locator = this.page.getByTestId(entry.selector);
break;
default:
return false;
}
const count = await locator.count();
return count > 0;
} catch {
return false;
}
}
async invalidateSelector(elementDescription, elementRef) {
const cacheKey = {
description: elementDescription,
ref: elementRef,
url: this.currentUrl
};
await this.cacheManager.invalidate({
url: this.currentUrl,
type: "selector",
profile: this.currentProfile
});
}
async invalidateForUrl(url) {
await this.cacheManager.invalidate({
url,
profile: this.currentProfile
});
}
async invalidateAll() {
await this.cacheManager.invalidate({
profile: this.currentProfile
});
}
async batchCacheSelectors(entries) {
await Promise.all(
entries.map(
(entry) => this.cacheSelector(
entry.description,
entry.selector,
entry.strategy,
entry.ref
)
)
);
}
async preloadCommonSelectors() {
if (!this.page) return;
const commonSelectors = [
{ selector: 'input[type="text"]', strategy: "css", description: "text input" },
{ selector: 'input[type="email"]', strategy: "css", description: "email input" },
{ selector: 'input[type="password"]', strategy: "css", description: "password input" },
{ selector: 'button[type="submit"]', strategy: "css", description: "submit button" },
{ selector: "form", strategy: "css", description: "form" },
{ selector: "a", strategy: "css", description: "link" },
{ selector: "select", strategy: "css", description: "dropdown" }
];
const validSelectors = [];
for (const entry of commonSelectors) {
const locator = this.page.locator(entry.selector);
const count = await locator.count();
if (count > 0) {
validSelectors.push(entry);
}
}
await this.batchCacheSelectors(validSelectors);
}
};
export {
SelectorCache
};
//# sourceMappingURL=selector-cache.js.map