167 lines
5.5 KiB
TypeScript
167 lines
5.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { chromium, type Page } from "playwright";
|
|
import { generateLocators, verifyLocators } from "../src/locator.js";
|
|
import type { RawActionableElement } from "../src/types.js";
|
|
|
|
function raw(overrides: Partial<RawActionableElement>): RawActionableElement {
|
|
return {
|
|
uid: "button_0_Open settings",
|
|
candidateIndex: 0,
|
|
tag: "button",
|
|
role: "button",
|
|
accessibleName: "Open settings",
|
|
identityText: "Open settings",
|
|
text: "Open settings",
|
|
label: null,
|
|
placeholder: null,
|
|
attributes: { "data-testid": "open-settings", id: "openSettings" },
|
|
bbox: { x: 0, y: 0, width: 100, height: 30 },
|
|
visibility: { visible: true, enabled: true, editable: false, receivesEvents: true },
|
|
context: {
|
|
dialog: null,
|
|
form: null,
|
|
section: null,
|
|
row: null,
|
|
landmark: null,
|
|
listitem: null,
|
|
},
|
|
parameterSurface: null,
|
|
evidence: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function withPage<T>(html: string, callback: (page: Page) => Promise<T>): Promise<T> {
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage();
|
|
try {
|
|
await page.setContent(html);
|
|
return await callback(page);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
test("locator generation prioritizes user-facing Playwright locators before CSS fallbacks", () => {
|
|
const candidates = generateLocators("el_test", raw({}));
|
|
assert.equal(candidates[0].strategy, "role");
|
|
assert.equal(candidates[0].framework, "playwright");
|
|
assert.equal(candidates.some((candidate) => candidate.strategy === "testId"), true);
|
|
assert.equal(candidates.at(-1)?.strategy, "css");
|
|
});
|
|
|
|
test("locator generation keeps CSS as fallback for id and name", () => {
|
|
const candidates = generateLocators(
|
|
"el_input",
|
|
raw({
|
|
tag: "input",
|
|
role: "textbox",
|
|
accessibleName: "Query",
|
|
text: null,
|
|
label: "Query",
|
|
placeholder: "Search customers",
|
|
attributes: { id: "queryInput", name: "q" },
|
|
}),
|
|
);
|
|
assert.equal(candidates.some((candidate) => candidate.expression.includes("#queryInput")), true);
|
|
assert.equal(candidates.some((candidate) => candidate.expression.includes("[name=")), true);
|
|
});
|
|
|
|
test("locator candidates carry context-scope evidence for duplicated actions", () => {
|
|
const candidates = generateLocators(
|
|
"el_edit",
|
|
raw({
|
|
uid: "button_4_Edit",
|
|
tag: "button",
|
|
role: "button",
|
|
accessibleName: "Edit",
|
|
text: "Edit",
|
|
attributes: {},
|
|
context: {
|
|
dialog: null,
|
|
form: null,
|
|
section: { kind: "section", name: "Customers", text: "Customers Acme Co Active Edit" },
|
|
row: { kind: "row", name: null, text: "Acme Co Active Edit" },
|
|
landmark: null,
|
|
listitem: null,
|
|
},
|
|
}),
|
|
);
|
|
assert.equal(candidates[0].scopes.some((scope) => scope.kind === "row"), true);
|
|
});
|
|
|
|
test("placeholder-only input locator verifies identity from raw placeholder evidence", async () => {
|
|
await withPage(`<input placeholder="Search customers" />`, async (page) => {
|
|
const rawElement = raw({
|
|
uid: "input_0_Search customers",
|
|
tag: "input",
|
|
role: "textbox",
|
|
accessibleName: "Search customers",
|
|
identityText: "Search customers",
|
|
text: null,
|
|
label: null,
|
|
placeholder: "Search customers",
|
|
attributes: {},
|
|
visibility: { visible: true, enabled: true, editable: true, receivesEvents: true },
|
|
});
|
|
const placeholder = generateLocators("el_placeholder", rawElement).find((candidate) => candidate.strategy === "placeholder");
|
|
assert.ok(placeholder);
|
|
|
|
const [verified] = await verifyLocators(page, rawElement, [placeholder]);
|
|
|
|
assert.equal(verified.verified, true);
|
|
assert.equal(verified.matchCount, 1);
|
|
assert.equal(verified.identity?.sameAccessibleName, true);
|
|
});
|
|
});
|
|
|
|
test("label-derived input locator verifies identity from raw label evidence", async () => {
|
|
await withPage(`<label for="query">Query</label><input id="query" />`, async (page) => {
|
|
const rawElement = raw({
|
|
uid: "input_0_Query",
|
|
tag: "input",
|
|
role: "textbox",
|
|
accessibleName: "Query",
|
|
identityText: "Query",
|
|
text: null,
|
|
label: "Query",
|
|
placeholder: null,
|
|
attributes: { id: "query" },
|
|
visibility: { visible: true, enabled: true, editable: true, receivesEvents: true },
|
|
});
|
|
const label = generateLocators("el_label", rawElement).find((candidate) => candidate.strategy === "label");
|
|
assert.ok(label);
|
|
|
|
const [verified] = await verifyLocators(page, rawElement, [label]);
|
|
|
|
assert.equal(verified.verified, true);
|
|
assert.equal(verified.matchCount, 1);
|
|
assert.equal(verified.identity?.sameAccessibleName, true);
|
|
});
|
|
});
|
|
|
|
test("duplicate Edit locators are unverified and omit non-unique identity evidence", async () => {
|
|
await withPage(`<button>Edit</button><button>Edit</button>`, async (page) => {
|
|
const rawElement = raw({
|
|
uid: "button_0_Edit",
|
|
tag: "button",
|
|
role: "button",
|
|
accessibleName: "Edit",
|
|
identityText: "Edit",
|
|
text: "Edit",
|
|
attributes: {},
|
|
});
|
|
const duplicateLocators = generateLocators("el_edit", rawElement).filter((candidate) => ["role", "text"].includes(candidate.strategy));
|
|
assert.equal(duplicateLocators.length, 2);
|
|
|
|
const verified = await verifyLocators(page, rawElement, duplicateLocators);
|
|
|
|
for (const locator of verified) {
|
|
assert.equal(locator.verified, false);
|
|
assert.equal(locator.matchCount, 2);
|
|
assert.equal(locator.identity, null);
|
|
}
|
|
});
|
|
});
|