feat: include iframes in aria snapshots with `ref` (#35396)
This commit is contained in:
parent
2c0e1e5e3a
commit
e3bb687cfc
|
@ -24,7 +24,7 @@ import type { AriaProps, AriaRegex, AriaRole, AriaTemplateNode, AriaTemplateRole
|
|||
import type { Builtins } from '../isomorphic/builtins';
|
||||
|
||||
export type AriaNode = AriaProps & {
|
||||
role: AriaRole | 'fragment';
|
||||
role: AriaRole | 'fragment' | 'iframe';
|
||||
name: string;
|
||||
children: (AriaNode | string)[];
|
||||
element: Element;
|
||||
|
@ -38,7 +38,7 @@ export type AriaSnapshot = {
|
|||
ids: Builtins.Map<Element, number>;
|
||||
};
|
||||
|
||||
export function generateAriaTree(builtins: Builtins, rootElement: Element, generation: number): AriaSnapshot {
|
||||
export function generateAriaTree(builtins: Builtins, rootElement: Element, generation: number, includeIframe: boolean): AriaSnapshot {
|
||||
const visited = new builtins.Set<Node>();
|
||||
|
||||
const snapshot: AriaSnapshot = {
|
||||
|
@ -87,7 +87,7 @@ export function generateAriaTree(builtins: Builtins, rootElement: Element, gener
|
|||
}
|
||||
|
||||
addElement(element);
|
||||
const childAriaNode = toAriaNode(builtins, element);
|
||||
const childAriaNode = toAriaNode(builtins, element, includeIframe);
|
||||
if (childAriaNode)
|
||||
ariaNode.children.push(childAriaNode);
|
||||
processElement(childAriaNode || ariaNode, element, ariaChildren);
|
||||
|
@ -144,7 +144,10 @@ export function generateAriaTree(builtins: Builtins, rootElement: Element, gener
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
function toAriaNode(builtins: Builtins, element: Element): AriaNode | null {
|
||||
function toAriaNode(builtins: Builtins, element: Element, includeIframe: boolean): AriaNode | null {
|
||||
if (includeIframe && element.nodeName === 'IFRAME')
|
||||
return { role: 'iframe', name: '', children: [], props: {}, element };
|
||||
|
||||
const role = roleUtils.getAriaRole(element);
|
||||
if (!role || role === 'presentation' || role === 'none')
|
||||
return null;
|
||||
|
@ -232,7 +235,7 @@ export type MatcherReceived = {
|
|||
};
|
||||
|
||||
export function matchesAriaTree(builtins: Builtins, rootElement: Element, template: AriaTemplateNode): { matches: AriaNode[], received: MatcherReceived } {
|
||||
const snapshot = generateAriaTree(builtins, rootElement, 0);
|
||||
const snapshot = generateAriaTree(builtins, rootElement, 0, false);
|
||||
const matches = matchesNodeDeep(snapshot.root, template, false);
|
||||
return {
|
||||
matches,
|
||||
|
@ -244,7 +247,7 @@ export function matchesAriaTree(builtins: Builtins, rootElement: Element, templa
|
|||
}
|
||||
|
||||
export function getAllByAria(builtins: Builtins, rootElement: Element, template: AriaTemplateNode): Element[] {
|
||||
const root = generateAriaTree(builtins, rootElement, 0).root;
|
||||
const root = generateAriaTree(builtins, rootElement, 0, false).root;
|
||||
const matches = matchesNodeDeep(root, template, true);
|
||||
return matches.map(n => n.element);
|
||||
}
|
||||
|
|
|
@ -285,7 +285,7 @@ export class InjectedScript {
|
|||
if (node.nodeType !== Node.ELEMENT_NODE)
|
||||
throw this.createStacklessError('Can only capture aria snapshot of Element nodes.');
|
||||
const generation = (this._lastAriaSnapshot?.generation || 0) + 1;
|
||||
this._lastAriaSnapshot = generateAriaTree(this.builtins, node as Element, generation);
|
||||
this._lastAriaSnapshot = generateAriaTree(this.builtins, node as Element, generation, options?.ref ?? false);
|
||||
return renderAriaTree(this._lastAriaSnapshot, options);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Locator } from '@playwright/test';
|
||||
import type { Locator, FrameLocator, Page } from '@playwright/test';
|
||||
import { test as it, expect } from './pageTest';
|
||||
|
||||
function unshift(snapshot: string): string {
|
||||
|
@ -677,3 +677,50 @@ it('should generate refs', async ({ page }) => {
|
|||
const e = await expect(page.locator('aria-ref=s1e3')).toHaveText('One').catch(e => e);
|
||||
expect(e.message).toContain('Error: Stale aria-ref, expected s2e{number}, got s1e3');
|
||||
});
|
||||
|
||||
it('ref mode should list iframes', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<h1>Hello</h1>
|
||||
<iframe name="foo" src="data:text/html,<h1>World</h1>">
|
||||
`);
|
||||
|
||||
const snapshot1 = await page.locator('body').ariaSnapshot({ ref: true });
|
||||
expect(snapshot1).toContain('- iframe [ref=s1e4]');
|
||||
|
||||
const frameSnapshot = await page.frameLocator(`aria-ref=s1e4`).locator('body').ariaSnapshot({ ref: true });
|
||||
expect(frameSnapshot).toEqual('- heading "World" [level=1] [ref=s1e3]');
|
||||
});
|
||||
|
||||
it('ref mode can be used to stitch all frame snapshots', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
||||
|
||||
async function allFrameSnapshot(frame: Page | FrameLocator): Promise<string> {
|
||||
const snapshot = await frame.locator('body').ariaSnapshot({ ref: true });
|
||||
const lines = snapshot.split('\n');
|
||||
const result = [];
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(\s*)- iframe \[ref=(.*)\]/);
|
||||
if (!match) {
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const leadingSpace = match[1];
|
||||
const ref = match[2];
|
||||
const childFrame = frame.frameLocator(`aria-ref=${ref}`);
|
||||
const childSnapshot = await allFrameSnapshot(childFrame);
|
||||
result.push(line + ':', childSnapshot.split('\n').map(l => leadingSpace + ' ' + l).join('\n'));
|
||||
}
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
expect(await allFrameSnapshot(page)).toEqual(`
|
||||
- iframe [ref=s1e3]:
|
||||
- iframe [ref=s1e3]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
- iframe [ref=s1e4]:
|
||||
- text: Hi, I'm frame
|
||||
`.trim());
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue