chore: experiment with stable aria refs (#35900)
infra / docs & lint (push) Waiting to run Details
infra / Lint snippets (push) Waiting to run Details
Check client side changes / Check (push) Waiting to run Details
components / ${{ matrix.os }} - Node.js ${{ matrix.node-version }} (18, macos-latest) (push) Waiting to run Details
components / ${{ matrix.os }} - Node.js ${{ matrix.node-version }} (18, ubuntu-latest) (push) Waiting to run Details
components / ${{ matrix.os }} - Node.js ${{ matrix.node-version }} (18, windows-latest) (push) Waiting to run Details
components / ${{ matrix.os }} - Node.js ${{ matrix.node-version }} (20, ubuntu-latest) (push) Waiting to run Details
components / ${{ matrix.os }} - Node.js ${{ matrix.node-version }} (22, ubuntu-latest) (push) Waiting to run Details
tests others / Stress - ${{ matrix.os }} (macos-latest) (push) Waiting to run Details
tests others / Stress - ${{ matrix.os }} (ubuntu-latest) (push) Waiting to run Details
tests others / Stress - ${{ matrix.os }} (windows-latest) (push) Waiting to run Details
tests others / WebView2 (push) Waiting to run Details
tests others / time library - ${{ matrix.clock }} (frozen) (push) Waiting to run Details
tests others / time library - ${{ matrix.clock }} (realtime) (push) Waiting to run Details
tests others / time test runner - ${{ matrix.clock }} (frozen) (push) Waiting to run Details
tests others / time test runner - ${{ matrix.clock }} (realtime) (push) Waiting to run Details
tests others / Electron - ${{ matrix.os }} (macos-latest) (push) Waiting to run Details
tests others / Electron - ${{ matrix.os }} (ubuntu-latest) (push) Waiting to run Details
tests others / Electron - ${{ matrix.os }} (windows-latest) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) (chromium, 18, ubuntu-22.04) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) (chromium, 20, ubuntu-22.04) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) (chromium, 22, ubuntu-22.04) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) (firefox, 18, ubuntu-22.04) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) (webkit, 18, ubuntu-22.04) (push) Waiting to run Details
tests 1 / ${{ matrix.os }} (chromium tip-of-tree) (ubuntu-22.04) (push) Waiting to run Details
tests 1 / Test Runner (18, macos-latest, 1, 2) (push) Waiting to run Details
tests 1 / Test Runner (18, macos-latest, 2, 2) (push) Waiting to run Details
tests 1 / Test Runner (18, ubuntu-latest, 1, 2) (push) Waiting to run Details
tests 1 / Test Runner (18, ubuntu-latest, 2, 2) (push) Waiting to run Details
tests 1 / Test Runner (18, windows-latest, 1, 2) (push) Waiting to run Details
tests 1 / Test Runner (18, windows-latest, 2, 2) (push) Waiting to run Details
tests 1 / Test Runner (20, ubuntu-latest, 1, 2) (push) Waiting to run Details
tests 1 / Test Runner (20, ubuntu-latest, 2, 2) (push) Waiting to run Details
tests 1 / Test Runner (22, ubuntu-latest, 1, 2) (push) Waiting to run Details
tests 1 / Test Runner (22, ubuntu-latest, 2, 2) (push) Waiting to run Details
tests 1 / Web Components (push) Waiting to run Details
tests 1 / VSCode Extension (push) Waiting to run Details
tests 1 / Installation Test ${{ matrix.os }} (macos-latest) (push) Waiting to run Details
tests 1 / Installation Test ${{ matrix.os }} (ubuntu-latest) (push) Waiting to run Details
tests 1 / Installation Test ${{ matrix.os }} (windows-latest) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (chromium, ubuntu-24.04) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (firefox, ubuntu-24.04) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, ubuntu-24.04) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (chromium, macos-13-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (chromium, macos-13-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (chromium, macos-14-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (chromium, macos-14-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (firefox, macos-13-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (firefox, macos-13-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (firefox, macos-14-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (firefox, macos-14-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-13-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-13-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-14-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-14-xlarge) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-15-large) (push) Waiting to run Details
tests 2 / ${{ matrix.os }} (${{ matrix.browser }}) (webkit, macos-15-xlarge) (push) Waiting to run Details
tests 2 / Windows (chromium) (push) Waiting to run Details
tests 2 / Windows (firefox) (push) Waiting to run Details
tests 2 / Windows (webkit) (push) Waiting to run Details
tests 2 / Installation Test ${{ matrix.os }} (${{ matrix.node_version }}) (20, ubuntu-latest) (push) Waiting to run Details
tests 2 / Installation Test ${{ matrix.os }} (${{ matrix.node_version }}) (22, ubuntu-latest) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (chromium, macos-14-xlarge) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (chromium, ubuntu-24.04) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (chromium, windows-latest) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (firefox, macos-14-xlarge) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (firefox, ubuntu-24.04) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (firefox, windows-latest) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (webkit, macos-14-xlarge) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (webkit, ubuntu-22.04) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (webkit, ubuntu-24.04) (push) Waiting to run Details
tests 2 / headed ${{ matrix.browser }} (${{ matrix.os }}) (webkit, windows-latest) (push) Waiting to run Details
tests 2 / Transport (driver) (push) Waiting to run Details
tests 2 / Transport (service) (push) Waiting to run Details
tests 2 / Tracing ${{ matrix.browser }} ${{ matrix.channel }} (chromium) (push) Waiting to run Details
tests 2 / Tracing ${{ matrix.browser }} ${{ matrix.channel }} (chromium, chromium-tip-of-tree) (push) Waiting to run Details
tests 2 / Tracing ${{ matrix.browser }} ${{ matrix.channel }} (firefox) (push) Waiting to run Details
tests 2 / Tracing ${{ matrix.browser }} ${{ matrix.channel }} (webkit) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome, macos-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome, windows-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome-beta, macos-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome-beta, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (chrome-beta, windows-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge, macos-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge, windows-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-beta, macos-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-beta, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-beta, windows-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-dev, macos-latest) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-dev, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Test ${{ matrix.channel }} on ${{ matrix.runs-on }} (msedge-dev, windows-latest) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} (, macos-13) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} (, windows-latest) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} (--headed, macos-13) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} (--headed, ubuntu-22.04) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree ${{ matrix.os }}${{ matrix.headed }} (--headed, windows-latest) (push) Waiting to run Details
tests 2 / Chromium tip-of-tree headless-shell-${{ matrix.os }} (ubuntu-22.04) (push) Waiting to run Details
tests 2 / Firefox Beta ${{ matrix.os }} (macos-latest) (push) Waiting to run Details
tests 2 / Firefox Beta ${{ matrix.os }} (ubuntu-22.04) (push) Waiting to run Details
tests 2 / Firefox Beta ${{ matrix.os }} (windows-latest) (push) Waiting to run Details
tests 2 / build-playwright-driver (push) Waiting to run Details
tests 2 / Test channel=chromium (macos-latest) (push) Waiting to run Details
tests 2 / Test channel=chromium (ubuntu-latest) (push) Waiting to run Details
tests 2 / Test channel=chromium (windows-latest) (push) Waiting to run Details
tests Video / Video Linux (chromium, ubuntu-22.04) (push) Waiting to run Details
tests Video / Video Linux (chromium, ubuntu-24.04) (push) Waiting to run Details
tests Video / Video Linux (firefox, ubuntu-22.04) (push) Waiting to run Details
tests Video / Video Linux (firefox, ubuntu-24.04) (push) Waiting to run Details
tests Video / Video Linux (webkit, ubuntu-22.04) (push) Waiting to run Details
tests Video / Video Linux (webkit, ubuntu-24.04) (push) Waiting to run Details
Internal Tests / trigger (push) Waiting to run Details

This commit is contained in:
Pavel Feldman 2025-05-08 13:25:39 -07:00 committed by GitHub
parent 7850c15e0e
commit 3196aff329
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 109 additions and 78 deletions

View File

@ -26,6 +26,7 @@ import type { Box } from './domUtils';
export type AriaNode = AriaProps & { export type AriaNode = AriaProps & {
role: AriaRole | 'fragment' | 'iframe'; role: AriaRole | 'fragment' | 'iframe';
name: string; name: string;
ref?: number;
children: (AriaNode | string)[]; children: (AriaNode | string)[];
element: Element; element: Element;
box: Box; box: Box;
@ -36,28 +37,24 @@ export type AriaNode = AriaProps & {
export type AriaSnapshot = { export type AriaSnapshot = {
root: AriaNode; root: AriaNode;
elements: Map<number, Element>; elements: Map<number, Element>;
generation: number;
ids: Map<Element, number>;
}; };
export function generateAriaTree(rootElement: Element, generation: number, options?: { forAI?: boolean }): AriaSnapshot { type AriaRef = {
role: string;
name: string;
ref: number;
};
let lastRef = 0;
export function generateAriaTree(rootElement: Element, options?: { forAI?: boolean }): AriaSnapshot {
const visited = new Set<Node>(); const visited = new Set<Node>();
const snapshot: AriaSnapshot = { const snapshot: AriaSnapshot = {
root: { role: 'fragment', name: '', children: [], element: rootElement, props: {}, box: box(rootElement), receivesPointerEvents: true }, root: { role: 'fragment', name: '', children: [], element: rootElement, props: {}, box: box(rootElement), receivesPointerEvents: true },
elements: new Map<number, Element>(), elements: new Map<number, Element>(),
generation,
ids: new Map<Element, number>(),
}; };
const addElement = (element: Element) => {
const id = snapshot.elements.size + 1;
snapshot.elements.set(id, element);
snapshot.ids.set(element, id);
};
addElement(rootElement);
const visit = (ariaNode: AriaNode, node: Node) => { const visit = (ariaNode: AriaNode, node: Node) => {
if (visited.has(node)) if (visited.has(node))
return; return;
@ -91,10 +88,12 @@ export function generateAriaTree(rootElement: Element, generation: number, optio
} }
} }
addElement(element);
const childAriaNode = toAriaNode(element, options); const childAriaNode = toAriaNode(element, options);
if (childAriaNode) if (childAriaNode) {
if (childAriaNode.ref)
snapshot.elements.set(childAriaNode.ref, element);
ariaNode.children.push(childAriaNode); ariaNode.children.push(childAriaNode);
}
processElement(childAriaNode || ariaNode, element, ariaChildren); processElement(childAriaNode || ariaNode, element, ariaChildren);
}; };
@ -150,9 +149,32 @@ export function generateAriaTree(rootElement: Element, generation: number, optio
return snapshot; return snapshot;
} }
function ariaRef(element: Element, role: string, name: string, options?: { forAI?: boolean }): number | undefined {
if (!options?.forAI)
return undefined;
let ariaRef: AriaRef | undefined;
ariaRef = (element as any)._ariaRef;
if (!ariaRef || ariaRef.role !== role || ariaRef.name !== name) {
ariaRef = { role, name, ref: ++lastRef };
(element as any)._ariaRef = ariaRef;
}
return ariaRef.ref;
}
function toAriaNode(element: Element, options?: { forAI?: boolean }): AriaNode | null { function toAriaNode(element: Element, options?: { forAI?: boolean }): AriaNode | null {
if (element.nodeName === 'IFRAME') if (element.nodeName === 'IFRAME') {
return { role: 'iframe', name: '', children: [], props: {}, element, box: box(element), receivesPointerEvents: true }; return {
role: 'iframe',
name: '',
ref: ariaRef(element, 'iframe', '', options),
children: [],
props: {},
element,
box: box(element),
receivesPointerEvents: true
};
}
const defaultRole = options?.forAI ? 'generic' : null; const defaultRole = options?.forAI ? 'generic' : null;
const role = roleUtils.getAriaRole(element) ?? defaultRole; const role = roleUtils.getAriaRole(element) ?? defaultRole;
@ -161,7 +183,17 @@ function toAriaNode(element: Element, options?: { forAI?: boolean }): AriaNode |
const name = normalizeWhiteSpace(roleUtils.getElementAccessibleName(element, false) || ''); const name = normalizeWhiteSpace(roleUtils.getElementAccessibleName(element, false) || '');
const receivesPointerEvents = roleUtils.receivesPointerEvents(element); const receivesPointerEvents = roleUtils.receivesPointerEvents(element);
const result: AriaNode = { role, name, children: [], props: {}, element, box: box(element), receivesPointerEvents };
const result: AriaNode = {
role,
name,
ref: ariaRef(element, role, name, options),
children: [],
props: {},
element,
box: box(element),
receivesPointerEvents
};
if (roleUtils.kAriaCheckedRoles.includes(role)) if (roleUtils.kAriaCheckedRoles.includes(role))
result.checked = roleUtils.getAriaChecked(element); result.checked = roleUtils.getAriaChecked(element);
@ -266,7 +298,7 @@ export type MatcherReceived = {
}; };
export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode): { matches: AriaNode[], received: MatcherReceived } { export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode): { matches: AriaNode[], received: MatcherReceived } {
const snapshot = generateAriaTree(rootElement, 0); const snapshot = generateAriaTree(rootElement);
const matches = matchesNodeDeep(snapshot.root, template, false, false); const matches = matchesNodeDeep(snapshot.root, template, false, false);
return { return {
matches, matches,
@ -278,7 +310,7 @@ export function matchesAriaTree(rootElement: Element, template: AriaTemplateNode
} }
export function getAllByAria(rootElement: Element, template: AriaTemplateNode): Element[] { export function getAllByAria(rootElement: Element, template: AriaTemplateNode): Element[] {
const root = generateAriaTree(rootElement, 0).root; const root = generateAriaTree(rootElement).root;
const matches = matchesNodeDeep(root, template, true, false); const matches = matchesNodeDeep(root, template, true, false);
return matches.map(n => n.element); return matches.map(n => n.element);
} }
@ -408,10 +440,10 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, options?: { mode?: 'r
if (ariaNode.selected === true) if (ariaNode.selected === true)
key += ` [selected]`; key += ` [selected]`;
if (options?.forAI && receivesPointerEvents(ariaNode)) { if (options?.forAI && receivesPointerEvents(ariaNode)) {
const id = ariaSnapshot.ids.get(ariaNode.element); const ref = ariaNode.ref;
const cursor = hasPointerCursor(ariaNode) ? ' [cursor=pointer]' : ''; const cursor = hasPointerCursor(ariaNode) ? ' [cursor=pointer]' : '';
if (id) if (ref)
key += ` [ref=s${ariaSnapshot.generation}e${id}]${cursor}`; key += ` [ref=e${ref}]${cursor}`;
} }
const escapedKey = indent + '- ' + yamlEscapeKeyIfNeeded(key); const escapedKey = indent + '- ' + yamlEscapeKeyIfNeeded(key);

View File

@ -227,7 +227,7 @@ export class InjectedScript {
this._engines.set('internal:attr', this._createNamedAttributeEngine()); this._engines.set('internal:attr', this._createNamedAttributeEngine());
this._engines.set('internal:testid', this._createNamedAttributeEngine()); this._engines.set('internal:testid', this._createNamedAttributeEngine());
this._engines.set('internal:role', createRoleEngine(true)); this._engines.set('internal:role', createRoleEngine(true));
this._engines.set('aria-ref', this._createAriaIdEngine()); this._engines.set('aria-ref', this._createAriaRefEngine());
for (const { name, source } of options.customEngines) for (const { name, source } of options.customEngines)
this._engines.set(name, this.eval(source)); this._engines.set(name, this.eval(source));
@ -300,15 +300,10 @@ export class InjectedScript {
ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex', forAI?: boolean }): string { ariaSnapshot(node: Node, options?: { mode?: 'raw' | 'regex', forAI?: boolean }): string {
if (node.nodeType !== Node.ELEMENT_NODE) if (node.nodeType !== Node.ELEMENT_NODE)
throw this.createStacklessError('Can only capture aria snapshot of Element nodes.'); throw this.createStacklessError('Can only capture aria snapshot of Element nodes.');
const generation = (this._lastAriaSnapshot?.generation || 0) + 1; this._lastAriaSnapshot = generateAriaTree(node as Element, options);
this._lastAriaSnapshot = generateAriaTree(node as Element, generation, options);
return renderAriaTree(this._lastAriaSnapshot, options); return renderAriaTree(this._lastAriaSnapshot, options);
} }
ariaSnapshotElement(snapshot: AriaSnapshot, elementId: number): Element | null {
return snapshot.elements.get(elementId) || null;
}
getAllByAria(document: Document, template: AriaTemplateNode): Element[] { getAllByAria(document: Document, template: AriaTemplateNode): Element[] {
return getAllByAria(document.documentElement, template); return getAllByAria(document.documentElement, template);
} }
@ -678,15 +673,12 @@ export class InjectedScript {
return result; return result;
} }
_createAriaIdEngine() { _createAriaRefEngine() {
const queryAll = (root: SelectorRoot, selector: string): Element[] => { const queryAll = (root: SelectorRoot, selector: string): Element[] => {
const match = selector.match(/^s(\d+)e(\d+)$/); if (!selector.startsWith('e'))
if (!match) throw this.createStacklessError(`Invalid aria-ref selector "${selector}"`);
throw this.createStacklessError('Invalid aria-ref selector, should be of form s<number>e<number>'); const ref = +selector.substring(1);
const [, generation, elementId] = match; const result = this._lastAriaSnapshot?.elements?.get(ref);
if (this._lastAriaSnapshot?.generation !== +generation)
throw this.createStacklessError(`Stale aria-ref, expected s${this._lastAriaSnapshot?.generation}e{number}, got ${selector}`);
const result = this._lastAriaSnapshot?.elements?.get(+elementId);
return result && result.isConnected ? [result] : []; return result && result.isConnected ? [result] : [];
}; };
return { queryAll }; return { queryAll };

View File

@ -27,20 +27,27 @@ it('should generate refs', async ({ page }) => {
`); `);
const snapshot1 = await page.locator('body').ariaSnapshot(forAI); const snapshot1 = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot1).toContain('- button "One" [ref=s1e3]'); expect(snapshot1).toContainYaml(`
expect(snapshot1).toContain('- button "Two" [ref=s1e4]'); - generic [ref=e1]:
expect(snapshot1).toContain('- button "Three" [ref=s1e5]'); - button "One" [ref=e2]
- button "Two" [ref=e3]
- button "Three" [ref=e4]
`);
await expect(page.locator('aria-ref=e2')).toHaveText('One');
await expect(page.locator('aria-ref=e3')).toHaveText('Two');
await expect(page.locator('aria-ref=e4')).toHaveText('Three');
await expect(page.locator('aria-ref=s1e3')).toHaveText('One'); await page.locator('aria-ref=e3').evaluate((e: HTMLElement) => {
await expect(page.locator('aria-ref=s1e4')).toHaveText('Two'); e.textContent = 'Not Two';
await expect(page.locator('aria-ref=s1e5')).toHaveText('Three'); });
const snapshot2 = await page.locator('body').ariaSnapshot(forAI); const snapshot2 = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot2).toContain('- button "One" [ref=s2e3]'); expect(snapshot2).toContainYaml(`
await expect(page.locator('aria-ref=s2e3')).toHaveText('One'); - generic [ref=e1]:
- button "One" [ref=e2]
const e = await expect(page.locator('aria-ref=s1e3')).toHaveText('One').catch(e => e); - button "Not Two" [ref=e5]
expect(e.message).toContain('Error: Stale aria-ref, expected s2e{number}, got s1e3'); - button "Three" [ref=e4]
`);
}); });
it('should list iframes', async ({ page }) => { it('should list iframes', async ({ page }) => {
@ -80,15 +87,15 @@ it('ref mode can be used to stitch all frame snapshots', async ({ page, server }
} }
expect(await allFrameSnapshot(page)).toContainYaml(` expect(await allFrameSnapshot(page)).toContainYaml(`
- generic [ref=s1e2]: - generic [ref=e1]:
- iframe [ref=s1e3]: - iframe [ref=e2]:
- generic [ref=s1e2]: - generic [ref=e1]:
- iframe [ref=s1e3]: - iframe [ref=e2]:
- generic [ref=s1e3]: Hi, I'm frame - generic [ref=e2]: Hi, I'm frame
- iframe [ref=s1e4]: - iframe [ref=e3]:
- generic [ref=s1e3]: Hi, I'm frame - generic [ref=e2]: Hi, I'm frame
- iframe [ref=s1e4]: - iframe [ref=e3]:
- generic [ref=s1e3]: Hi, I'm frame - generic [ref=e2]: Hi, I'm frame
`); `);
}); });
@ -101,10 +108,10 @@ it('should not generate refs for hidden elements', async ({ page }) => {
const snapshot = await page.locator('body').ariaSnapshot(forAI); const snapshot = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot).toContainYaml(` expect(snapshot).toContainYaml(`
- generic [ref=s1e2]: - generic [ref=e1]:
- button "One" [ref=s1e3] - button "One" [ref=e2]
- button "Two" - button "Two"
- button "Three" [ref=s1e5] - button "Three" [ref=e4]
`); `);
}); });
@ -133,12 +140,12 @@ it('should not generate refs for elements with pointer-events:none', async ({ pa
const snapshot = await page.locator('body').ariaSnapshot(forAI); const snapshot = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot).toContainYaml(` expect(snapshot).toContainYaml(`
- generic [ref=s1e2]: - generic [ref=e1]:
- button "no-ref" - button "no-ref"
- button "with-ref" [ref=s1e5] - button "with-ref" [ref=e4]
- button "with-ref" [ref=s1e8] - button "with-ref" [ref=e7]
- button "with-ref" [ref=s1e11] - button "with-ref" [ref=e10]
- generic [ref=s1e12]: - generic [ref=e11]:
- generic: - generic:
- button "no-ref" - button "no-ref"
`); `);
@ -178,19 +185,19 @@ it('emit generic roles for nodes w/o roles', async ({ page }) => {
const snapshot = await page.locator('body').ariaSnapshot(forAI); const snapshot = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot).toContainYaml(` expect(snapshot).toContainYaml(`
- generic [ref=s1e3]: - generic [ref=e2]:
- generic [ref=s1e4]: - generic [ref=e3]:
- generic [ref=s1e5]: - generic [ref=e4]:
- radio "Apple" [checked] - radio "Apple" [checked]
- generic [ref=s1e7]: Apple - generic [ref=e6]: Apple
- generic [ref=s1e8]: - generic [ref=e7]:
- generic [ref=s1e9]: - generic [ref=e8]:
- radio "Pear" - radio "Pear"
- generic [ref=s1e11]: Pear - generic [ref=e10]: Pear
- generic [ref=s1e12]: - generic [ref=e11]:
- generic [ref=s1e13]: - generic [ref=e12]:
- radio "Orange" - radio "Orange"
- generic [ref=s1e15]: Orange - generic [ref=e14]: Orange
`); `);
}); });
@ -207,7 +214,7 @@ it('should collapse generic nodes', async ({ page }) => {
const snapshot = await page.locator('body').ariaSnapshot(forAI); const snapshot = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot).toContainYaml(` expect(snapshot).toContainYaml(`
- button \"Button\" [ref=s1e6] - button \"Button\" [ref=e5]
`); `);
}); });
@ -218,6 +225,6 @@ it('should include cursor pointer hint', async ({ page }) => {
const snapshot = await page.locator('body').ariaSnapshot(forAI); const snapshot = await page.locator('body').ariaSnapshot(forAI);
expect(snapshot).toContainYaml(` expect(snapshot).toContainYaml(`
- button \"Button\" [ref=s1e3] [cursor=pointer] - button \"Button\" [ref=e2] [cursor=pointer]
`); `);
}); });