fix(browser): reject ax<N> refs in act path instead of timing out (#69924)

This commit is contained in:
Patrick Erichsen
2026-04-21 18:43:27 -07:00
committed by GitHub
parent 6a68f1dd57
commit efb7d426cf
5 changed files with 28 additions and 3 deletions

View File

@@ -296,6 +296,9 @@ export type AriaSnapshotNode = {
depth: number;
};
export const AX_REF_PREFIX = "ax";
export const AX_REF_PATTERN = new RegExp(`^${AX_REF_PREFIX}\\d+$`);
export type RawAXNode = {
nodeId?: string;
role?: { value?: string };
@@ -362,7 +365,7 @@ export function formatAriaSnapshot(nodes: RawAXNode[], limit: number): AriaSnaps
const name = axValue(n.name);
const value = axValue(n.value);
const description = axValue(n.description);
const ref = `ax${out.length + 1}`;
const ref = `${AX_REF_PREFIX}${out.length + 1}`;
out.push({
ref,
role: role || "unknown",

View File

@@ -71,6 +71,13 @@ describe("pw-session refLocator", () => {
expect(mocks.locator).toHaveBeenCalledWith("aria-ref=e1");
});
it("rejects axN refs from format=aria snapshots instead of timing out", () => {
const { page, mocks } = fakePage();
expect(() => refLocator(page, "ax12")).toThrow(/format=aria snapshot/);
expect(mocks.locator).not.toHaveBeenCalled();
});
});
describe("pw-session role refs cache", () => {

View File

@@ -20,7 +20,7 @@ import {
normalizeCdpHttpBaseForJsonEndpoints,
withCdpSocket,
} from "./cdp.helpers.js";
import { normalizeCdpWsUrl } from "./cdp.js";
import { AX_REF_PATTERN, normalizeCdpWsUrl } from "./cdp.js";
import { getChromeWebSocketUrl } from "./chrome.js";
import { BrowserTabNotFoundError } from "./errors.js";
import {
@@ -884,6 +884,13 @@ export function refLocator(page: Page, ref: string) {
return info.nth !== undefined ? locator.nth(info.nth) : locator;
}
if (AX_REF_PATTERN.test(normalized)) {
throw new Error(
`Ref "${normalized}" comes from a format=aria snapshot and cannot be used with act. ` +
`Re-snapshot with format=ai and use the eN refs from that snapshot.`,
);
}
return page.locator(`aria-ref=${normalized}`);
}

View File

@@ -60,6 +60,12 @@ describe("pw-tools-core", () => {
errorMessage: 'Timeout 5000ms exceeded. waiting for locator("aria-ref=1") to be visible',
expectedMessage: /not found or not visible/i,
},
{
name: "bare locator timeouts into snapshot hints",
errorMessage:
"locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for locator('aria-ref=ax13')",
expectedMessage: /not found or not visible/i,
},
])("rewrites $name", async ({ errorMessage, expectedMessage }) => {
const click = vi.fn(async () => {
throw new Error(errorMessage);

View File

@@ -64,7 +64,9 @@ export function toAIFriendlyError(error: unknown, selector: string): Error {
if (
(message.includes("Timeout") || message.includes("waiting for")) &&
(message.includes("to be visible") || message.includes("not visible"))
(message.includes("to be visible") ||
message.includes("not visible") ||
message.includes("waiting for locator("))
) {
return new Error(
`Element "${selector}" not found or not visible. ` +