816 lines
26 KiB
TypeScript
816 lines
26 KiB
TypeScript
/**
|
|
* Copyright (c) Microsoft Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
import { test as it, expect } from './pageTest';
|
|
import { unshift } from '../config/utils';
|
|
import type { Page } from 'playwright-core';
|
|
|
|
async function snapshotForAI(page: Page, options?: Omit<Parameters<Page['ariaSnapshot']>[0], 'mode'> & { _track?: string }): Promise<string> {
|
|
return await page.ariaSnapshot({ ...options, mode: 'ai' });
|
|
}
|
|
|
|
it('should generate refs', async ({ page }) => {
|
|
await page.setContent(`
|
|
<button>One</button>
|
|
<button>Two</button>
|
|
<button>Three</button>
|
|
`);
|
|
|
|
const snapshot1 = await snapshotForAI(page);
|
|
expect(snapshot1).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- 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 page.locator('aria-ref=e3').evaluate((e: HTMLElement) => {
|
|
e.textContent = 'Not Two';
|
|
});
|
|
|
|
const snapshot2 = await snapshotForAI(page);
|
|
expect(snapshot2).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- button "One" [ref=e2]
|
|
- button "Not Two" [ref=e5]
|
|
- button "Three" [ref=e4]
|
|
`);
|
|
});
|
|
|
|
it('should list iframes', async ({ page }) => {
|
|
await page.setContent(`
|
|
<h1>Hello</h1>
|
|
<iframe name="foo" src="data:text/html,<h1>World</h1>">
|
|
`);
|
|
|
|
const snapshot1 = await snapshotForAI(page);
|
|
expect(snapshot1).toContain('- iframe');
|
|
|
|
const frameSnapshot = await page.frameLocator(`iframe`).locator('body').ariaSnapshot();
|
|
expect(frameSnapshot).toEqual('- heading "World" [level=1]');
|
|
});
|
|
|
|
it('should snapshot a locator inside an iframe', async ({ page }) => {
|
|
await page.setContent(`
|
|
<h1>Main Page</h1>
|
|
<iframe srcdoc="<ul><li>Item 1</li><li>Item 2</li></ul>"></iframe>
|
|
`);
|
|
|
|
const list = page.frames()[1].locator('ul');
|
|
const snapshot = await list.ariaSnapshot({ mode: 'ai' });
|
|
expect(snapshot).toContainYaml(`
|
|
- list [ref=f1e1]:
|
|
- listitem [ref=f1e2]: Item 1
|
|
- listitem [ref=f1e3]: Item 2
|
|
`);
|
|
});
|
|
|
|
it('should limit depth across iframe boundary', async ({ page }) => {
|
|
await page.setContent(`
|
|
<nav>
|
|
<iframe srcdoc="<ul><li><button>Deep</button></li></ul>"></iframe>
|
|
</nav>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page, { depth: 3 });
|
|
expect(snapshot).toContainYaml(`
|
|
- navigation [ref=e2]:
|
|
- iframe [ref=e3]:
|
|
- list [ref=f1e2]:
|
|
- listitem [ref=f1e3]
|
|
`);
|
|
expect(snapshot).not.toContain('button');
|
|
});
|
|
|
|
it('should stitch all frame snapshots', async ({ page, server }) => {
|
|
await page.goto(server.PREFIX + '/frames/nested-frames.html');
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- iframe [ref=e2]:
|
|
- generic [active] [ref=f1e1]:
|
|
- iframe [ref=f1e2]:
|
|
- generic [ref=f3e2]: Hi, I'm frame
|
|
- iframe [ref=f1e3]:
|
|
- generic [ref=f4e2]: Hi, I'm frame
|
|
- iframe [ref=e3]:
|
|
- generic [ref=f2e2]: Hi, I'm frame
|
|
`);
|
|
|
|
const href = await page.locator('aria-ref=e1').evaluate(e => e.ownerDocument.defaultView.location.href);
|
|
expect(href).toBe(server.PREFIX + '/frames/nested-frames.html');
|
|
|
|
const href2 = await page.locator('aria-ref=f1e2').evaluate(e => e.ownerDocument.defaultView.location.href);
|
|
expect(href2).toBe(server.PREFIX + '/frames/two-frames.html');
|
|
|
|
const href3 = await page.locator('aria-ref=f4e2').evaluate(e => e.ownerDocument.defaultView.location.href);
|
|
expect(href3).toBe(server.PREFIX + '/frames/frame.html');
|
|
|
|
{
|
|
const resolved = await page.locator('aria-ref=e1').normalize();
|
|
expect(resolved.toString()).toBe(`locator('body')`);
|
|
}
|
|
{
|
|
const resolved = await page.locator('aria-ref=f4e2').normalize();
|
|
expect(resolved.toString()).toBe(`locator('iframe[name="2frames"]').contentFrame().locator('iframe[name="dos"]').contentFrame().getByText('Hi, I\\'m frame')`);
|
|
}
|
|
{
|
|
// Should tolerate .describe().
|
|
const resolved = await page.locator('aria-ref=f3e2').describe('foo bar').normalize();
|
|
expect(resolved.toString()).toBe(`locator('iframe[name=\"2frames\"]').contentFrame().locator('iframe[name=\"uno\"]').contentFrame().getByText('Hi, I\\'m frame')`);
|
|
}
|
|
{
|
|
const error = await page.locator('aria-ref=e1000').normalize().catch(e => e);
|
|
expect(error.message).toContain(`No element matching aria-ref=e1000`);
|
|
}
|
|
});
|
|
|
|
it('should persist iframe references', async ({ page }) => {
|
|
await page.setContent(`
|
|
<ul>
|
|
<li><iframe srcdoc="<button>button1</button>"></iframe></li>
|
|
<li><iframe srcdoc="<button>button2</button>"></iframe></li>
|
|
</ul>
|
|
`);
|
|
expect(await snapshotForAI(page)).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]:
|
|
- iframe [ref=e4]:
|
|
- button "button1" [ref=f1e2]
|
|
- listitem [ref=e5]:
|
|
- iframe [ref=e6]:
|
|
- button "button2" [ref=f2e2]
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('iframe').remove());
|
|
expect(await snapshotForAI(page)).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]
|
|
- listitem [ref=e5]:
|
|
- iframe [ref=e6]:
|
|
- button "button2" [ref=f2e2]
|
|
`);
|
|
await expect(page.locator('aria-ref=f2e2')).toHaveText('button2');
|
|
|
|
await page.evaluate(() => {
|
|
const frame = document.createElement('iframe');
|
|
frame.setAttribute('srcdoc', '<button>button1</button>');
|
|
document.querySelector('li').appendChild(frame);
|
|
});
|
|
expect(await snapshotForAI(page)).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]:
|
|
- iframe [ref=e7]:
|
|
- button "button1" [ref=f3e2]
|
|
- listitem [ref=e5]:
|
|
- iframe [ref=e6]:
|
|
- button "button2" [ref=f2e2]
|
|
`);
|
|
await expect(page.locator('aria-ref=f3e2')).toHaveText('button1');
|
|
await expect(page.locator('aria-ref=f2e2')).toHaveText('button2');
|
|
});
|
|
|
|
it('should not generate refs for elements with pointer-events:none', async ({ page }) => {
|
|
await page.setContent(`
|
|
<button style="pointer-events: none">no-ref</button>
|
|
<div style="pointer-events: none">
|
|
<button style="pointer-events: auto">with-ref</button>
|
|
</div>
|
|
<div style="pointer-events: none">
|
|
<div style="pointer-events: initial">
|
|
<button>with-ref</button>
|
|
</div>
|
|
</div>
|
|
<div style="pointer-events: none">
|
|
<div style="pointer-events: auto">
|
|
<button>with-ref</button>
|
|
</div>
|
|
</div>
|
|
<div style="pointer-events: auto">
|
|
<div style="pointer-events: none">
|
|
<button>no-ref</button>
|
|
</div>
|
|
</div>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- button "no-ref"
|
|
- button "with-ref" [ref=e2]
|
|
- button "with-ref" [ref=e4]
|
|
- button "with-ref" [ref=e6]
|
|
- generic [ref=e7]:
|
|
- generic:
|
|
- button "no-ref"
|
|
`);
|
|
});
|
|
|
|
it('emit generic roles for nodes w/o roles', async ({ page }) => {
|
|
await page.setContent(`
|
|
<style>
|
|
input {
|
|
width: 0;
|
|
height: 0;
|
|
opacity: 0;
|
|
}
|
|
</style>
|
|
<div>
|
|
<label>
|
|
<span>
|
|
<input type="radio" value="Apple" checked="">
|
|
</span>
|
|
<span>Apple</span>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<input type="radio" value="Pear">
|
|
</span>
|
|
<span>Pear</span>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<input type="radio" value="Orange">
|
|
</span>
|
|
<span>Orange</span>
|
|
</label>
|
|
</div>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [ref=e2]:
|
|
- generic [ref=e3]:
|
|
- generic [ref=e4]:
|
|
- radio "Apple" [checked]
|
|
- text: Apple
|
|
- generic [ref=e5]:
|
|
- generic [ref=e6]:
|
|
- radio "Pear"
|
|
- text: Pear
|
|
- generic [ref=e7]:
|
|
- generic [ref=e8]:
|
|
- radio "Orange"
|
|
- text: Orange
|
|
`);
|
|
});
|
|
|
|
it('should collapse generic nodes', async ({ page }) => {
|
|
await page.setContent(`
|
|
<div>
|
|
<div>
|
|
<div>
|
|
<button>Button</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- button \"Button\" [ref=e5]
|
|
`);
|
|
});
|
|
|
|
it('should include cursor pointer hint', async ({ page }) => {
|
|
await page.setContent(`
|
|
<button style="cursor: pointer">Button</button>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- button \"Button\" [ref=e2] [cursor=pointer]
|
|
`);
|
|
});
|
|
|
|
it('should not nest cursor pointer hints', async ({ page }) => {
|
|
await page.setContent(`
|
|
<a style="cursor: pointer" href="about:blank">
|
|
Link with a button
|
|
<button style="cursor: pointer">Button</button>
|
|
</a>
|
|
`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- link \"Link with a button Button\" [ref=e2] [cursor=pointer]:
|
|
- /url: about:blank
|
|
- text: Link with a button
|
|
- button "Button" [ref=e3]
|
|
`);
|
|
});
|
|
|
|
it('should gracefully fallback when child frame cant be captured', async ({ page, server }) => {
|
|
await page.setContent(`
|
|
<p>Test</p>
|
|
<iframe src="${server.PREFIX}/redirectloop1.html#depth=100000"></iframe>
|
|
`, { waitUntil: 'domcontentloaded' });
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- paragraph [ref=e2]: Test
|
|
- iframe [ref=e3]
|
|
`);
|
|
});
|
|
|
|
it('should auto-wait for navigation', async ({ page, server }) => {
|
|
await page.goto(server.PREFIX + '/frames/frame.html');
|
|
const [, snapshot] = await Promise.all([
|
|
page.evaluate(() => window.location.reload()),
|
|
snapshotForAI(page)
|
|
]);
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [ref=e2]: Hi, I'm frame
|
|
`);
|
|
});
|
|
|
|
it('should auto-wait for blocking CSS', async ({ page, server }) => {
|
|
server.setRoute('/css', (req, res) => {
|
|
res.setHeader('Content-Type', 'text/css');
|
|
setTimeout(() => res.end(`body { monospace }`), 1000);
|
|
});
|
|
await page.setContent(`
|
|
<script src="${server.PREFIX}/css"></script>
|
|
<p>Hello World</p>
|
|
`, { waitUntil: 'commit' });
|
|
expect(await snapshotForAI(page)).toContainYaml('Hello World');
|
|
});
|
|
|
|
it('should show visible children of hidden elements', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36296' } }, async ({ page }) => {
|
|
await page.setContent(`
|
|
<div style="visibility: hidden">
|
|
<div style="visibility: visible">
|
|
<button>Visible</button>
|
|
</div>
|
|
<div style="visibility: hidden">
|
|
<button style="visibility: visible">Visible</button>
|
|
</div>
|
|
<div>
|
|
<div style="visibility: visible">
|
|
<button style="visibility: hidden">Hidden</button>
|
|
</div>
|
|
<button>Hidden</button>
|
|
</div>
|
|
</div>
|
|
`);
|
|
|
|
expect(await snapshotForAI(page)).toEqual(unshift(`
|
|
- generic [active] [ref=e1]:
|
|
- button "Visible" [ref=e3]
|
|
- button "Visible" [ref=e4]
|
|
`));
|
|
});
|
|
|
|
it('should include active element information', async ({ page }) => {
|
|
await page.setContent(`
|
|
<button id="btn1">Button 1</button>
|
|
<button id="btn2" autofocus>Button 2</button>
|
|
<div>Not focusable</div>
|
|
`);
|
|
|
|
// Wait for autofocus to take effect
|
|
await page.waitForFunction(() => document.activeElement?.id === 'btn2');
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [ref=e1]:
|
|
- button "Button 1" [ref=e2]
|
|
- button "Button 2" [active] [ref=e3]
|
|
- generic [ref=e4]: Not focusable
|
|
`);
|
|
});
|
|
|
|
it('should update active element on focus', async ({ page }) => {
|
|
await page.setContent(`
|
|
<input id="input1" placeholder="First input">
|
|
<input id="input2" placeholder="Second input">
|
|
`);
|
|
|
|
// Initially there shouldn't be an active element on the inputs
|
|
const initialSnapshot = await snapshotForAI(page);
|
|
expect(initialSnapshot).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- textbox "First input" [ref=e2]
|
|
- textbox "Second input" [ref=e3]
|
|
`);
|
|
|
|
// Focus the second input
|
|
await page.locator('#input2').focus();
|
|
|
|
// After focus, the second input should be active
|
|
const afterFocusSnapshot = await snapshotForAI(page);
|
|
|
|
expect(afterFocusSnapshot).toContainYaml(`
|
|
- generic [ref=e1]:
|
|
- textbox "First input" [ref=e2]
|
|
- textbox "Second input" [active] [ref=e3]
|
|
`);
|
|
});
|
|
|
|
it('should mark iframe as active when it contains focused element', async ({ page }) => {
|
|
// Create a simple HTML file for the iframe
|
|
await page.setContent(`
|
|
<input id="regular-input" placeholder="Regular input">
|
|
<iframe src="data:text/html,<input id='iframe-input' placeholder='Input in iframe'>" tabindex="0"></iframe>
|
|
`);
|
|
|
|
// Test 1: Focus the input inside the iframe
|
|
await page.frameLocator('iframe').locator('#iframe-input').focus();
|
|
const inputInIframeFocusedSnapshot = await snapshotForAI(page);
|
|
|
|
// The iframe should be marked as active when it contains a focused element
|
|
expect(inputInIframeFocusedSnapshot).toContainYaml(`
|
|
- generic [ref=e1]:
|
|
- textbox "Regular input" [ref=e2]
|
|
- iframe [active] [ref=e3]:
|
|
- textbox "Input in iframe" [active] [ref=f1e2]
|
|
`);
|
|
});
|
|
|
|
it('return empty snapshot when iframe is not loaded', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/pull/36710' } }, async ({ page, server }) => {
|
|
await page.setContent(`
|
|
<div style="height: 5000px;">Test</div>
|
|
<iframe loading="lazy" src="${server.PREFIX}/frame.html"></iframe>
|
|
`);
|
|
|
|
// Wait for the iframe to load
|
|
await page.waitForSelector('iframe');
|
|
|
|
// Get the snapshot of the page
|
|
const snapshot = await snapshotForAI(page, { timeout: 3000 });
|
|
|
|
// The iframe should be present but empty
|
|
expect(snapshot).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- generic [ref=e2]: Test
|
|
- iframe [ref=e3]
|
|
`);
|
|
});
|
|
|
|
it('should support many properties on iframes', async ({ page }) => {
|
|
await page.setContent(`
|
|
<input id="regular-input" placeholder="Regular input">
|
|
<iframe style='cursor: pointer' src="data:text/html,<input id='iframe-input' placeholder='Input in iframe'/>" tabindex="0"></iframe>
|
|
`);
|
|
|
|
// Test 1: Focus the input inside the iframe
|
|
await page.frameLocator('iframe').locator('#iframe-input').focus();
|
|
const inputInIframeFocusedSnapshot = await snapshotForAI(page);
|
|
|
|
expect(inputInIframeFocusedSnapshot).toContainYaml(`
|
|
- generic [ref=e1]:
|
|
- textbox "Regular input" [ref=e2]
|
|
- iframe [active] [ref=e3] [cursor=pointer]:
|
|
- textbox "Input in iframe" [active] [ref=f1e2]
|
|
`);
|
|
});
|
|
|
|
it('should collapse inline generic nodes', async ({ page }) => {
|
|
await page.setContent(`
|
|
<ul>
|
|
<li><b>3</b> <abbr>bds</abbr></li>
|
|
<li><b>2</b> <abbr>ba</abbr></li>
|
|
<li><b>1,200</b> <abbr>sqft</abbr></li>
|
|
</ul>
|
|
<ul>
|
|
<li><div>3</div></li>
|
|
<li><div>2</div></li>
|
|
<li><div>1,200</div></li>
|
|
</ul>`);
|
|
|
|
const snapshot1 = await snapshotForAI(page);
|
|
expect(snapshot1).toContainYaml(`
|
|
- generic [active] [ref=e1]:
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: 3 bds
|
|
- listitem [ref=e4]: 2 ba
|
|
- listitem [ref=e5]: 1,200 sqft
|
|
- list [ref=e6]:
|
|
- listitem [ref=e7]:
|
|
- generic [ref=e8]: "3"
|
|
- listitem [ref=e9]:
|
|
- generic [ref=e10]: "2"
|
|
- listitem [ref=e11]:
|
|
- generic [ref=e12]: 1,200
|
|
`);
|
|
});
|
|
|
|
it('should not remove generic nodes with title', async ({ page }) => {
|
|
await page.setContent(`<div title="Element title">Element content</div>`);
|
|
|
|
const snapshot = await snapshotForAI(page);
|
|
expect(snapshot).toContainYaml(`
|
|
- generic "Element title" [ref=e2]
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshots on multiple tracks', async ({ page }) => {
|
|
await page.setContent(`<ul><li><button>a button</button></li><li><span>a span</span></li><li id=hidden-li style="display:none">some text</li></ul>`);
|
|
|
|
expect(await snapshotForAI(page, { _track: 'first' })).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]:
|
|
- button "a button" [ref=e4]
|
|
- listitem [ref=e5]: a span
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'second' })).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]:
|
|
- button "a button" [ref=e4]
|
|
- listitem [ref=e5]: a span
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'first' })).toContainYaml(`
|
|
`);
|
|
|
|
await page.evaluate(() => {
|
|
document.querySelector('span').textContent = 'changed span';
|
|
document.getElementById('hidden-li').style.display = 'inline';
|
|
});
|
|
expect(await snapshotForAI(page, { _track: 'first' })).toContainYaml(`
|
|
- <changed> list [ref=e2]:
|
|
- ref=e3 [unchanged]
|
|
- listitem [ref=e5]: changed span
|
|
- listitem [ref=e6]: some text
|
|
`);
|
|
|
|
await page.evaluate(() => {
|
|
document.querySelector('span').textContent = 'a span';
|
|
document.getElementById('hidden-li').style.display = 'none';
|
|
});
|
|
expect(await snapshotForAI(page, { _track: 'first' })).toContainYaml(`
|
|
- <changed> list [ref=e2]:
|
|
- ref=e3 [unchanged]
|
|
- listitem [ref=e5]: a span
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'second' })).toContainYaml(`
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for attribute change', async ({ page }) => {
|
|
await page.setContent(`<button>a button</button>`);
|
|
await page.evaluate(() => document.querySelector('button').focus());
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- button "a button" [active] [ref=e2]
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('button').blur());
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> button "a button" [ref=e2]
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for child removal', async ({ page }) => {
|
|
await page.setContent(`<li><button>a button</button><span>some text</span></li>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- listitem [ref=e2]:
|
|
- button "a button" [ref=e3]
|
|
- text: some text
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('span').remove());
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> listitem [ref=e2]:
|
|
- ref=e3 [unchanged]
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for child addition', async ({ page }) => {
|
|
await page.setContent(`<li><button>a button</button><span style="display:none">some text</span></li>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- listitem [ref=e2]:
|
|
- button "a button" [ref=e3]
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('span').style.display = 'inline');
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> listitem [ref=e2]:
|
|
- ref=e3 [unchanged]
|
|
- text: some text
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for prop change', async ({ page }) => {
|
|
await page.setContent(`<a href="about:blank" style="cursor:pointer">a link</a>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- link "a link" [ref=e2] [cursor=pointer]:
|
|
- /url: about:blank
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('a').setAttribute('href', 'https://playwright.dev'));
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> link "a link" [ref=e2] [cursor=pointer]:
|
|
- /url: https://playwright.dev
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for cursor change', async ({ page }) => {
|
|
await page.setContent(`<a href="about:blank" style="cursor:pointer">a link</a>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- link "a link" [ref=e2] [cursor=pointer]:
|
|
- /url: about:blank
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('a').style.cursor = 'default');
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> link "a link" [ref=e2]:
|
|
- /url: about:blank
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for name change', async ({ page }) => {
|
|
await page.setContent(`<button><span>a button</span></button>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- button "a button" [ref=e2]
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('span').textContent = 'new button');
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> button "new button" [ref=e3]
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for text change', async ({ page }) => {
|
|
await page.setContent(`<li><span>an item</span></li>`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- listitem [ref=e2]: an item
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('span').textContent = 'new text');
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> listitem [ref=e2]: new text
|
|
`);
|
|
});
|
|
|
|
it('should produce incremental snapshot for iframes', async ({ page }) => {
|
|
await page.setContent(`
|
|
<iframe srcdoc="
|
|
<li>
|
|
<span style='display:none'>outer text</span>
|
|
<button>a button</button>
|
|
<iframe src='data:text/html,<li>inner text</li>' style='display:none'></iframe>
|
|
</li>
|
|
"></iframe>
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- iframe [ref=e2]:
|
|
- listitem [ref=f1e2]:
|
|
- button "a button" [ref=f1e3]
|
|
`);
|
|
|
|
await page.frames()[1].evaluate(() => {
|
|
document.querySelector('span').style.display = 'block';
|
|
document.querySelector('iframe').style.display = 'block';
|
|
});
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> listitem [ref=f1e2]:
|
|
- generic [ref=f1e4]: outer text
|
|
- ref=f1e3 [unchanged]
|
|
- iframe [ref=f1e5]
|
|
- <changed> iframe [ref=f1e5]:
|
|
- listitem [ref=f2e2]: inner text
|
|
`);
|
|
});
|
|
|
|
it('should create multiple chunks in incremental snapshot', async ({ page }) => {
|
|
await page.setContent(`
|
|
<ul>
|
|
<li><span>item1</span></li>
|
|
<li><span>item2</span></li>
|
|
<li><div role=group><span>item3</span></div></li>
|
|
<ul>
|
|
<li id=to-remove>to be removed</li>
|
|
<li>one more</li>
|
|
</ul>
|
|
</ul>
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: item1
|
|
- listitem [ref=e4]: item2
|
|
- listitem [ref=e5]:
|
|
- group [ref=e6]: item3
|
|
- list [ref=e7]:
|
|
- listitem [ref=e8]: to be removed
|
|
- listitem [ref=e9]: one more
|
|
`);
|
|
|
|
await page.evaluate(() => {
|
|
const spans = document.querySelectorAll('span');
|
|
spans[0].textContent = 'new item1';
|
|
spans[2].textContent = 'new item3';
|
|
const button = document.createElement('button');
|
|
button.textContent = 'button';
|
|
spans[2].parentElement.appendChild(button);
|
|
document.querySelector('#to-remove').remove();
|
|
});
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> listitem [ref=e3]: new item1
|
|
- <changed> group [ref=e6]:
|
|
- text: new item3
|
|
- button "button" [ref=e10]
|
|
- <changed> list [ref=e7]:
|
|
- ref=e9 [unchanged]
|
|
`);
|
|
});
|
|
|
|
it('should create incremental snapshot for children swap', async ({ page }) => {
|
|
await page.setContent(`
|
|
<ul>
|
|
<li>item 1</li>
|
|
<li>item 2</li>
|
|
</ul>
|
|
`);
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: item 1
|
|
- listitem [ref=e4]: item 2
|
|
`);
|
|
|
|
await page.evaluate(() => document.querySelector('ul').appendChild(document.querySelector('li')));
|
|
expect(await snapshotForAI(page, { _track: 'track' })).toContainYaml(`
|
|
- <changed> list [ref=e2]:
|
|
- ref=e4 [unchanged]
|
|
- ref=e3 [unchanged]
|
|
`);
|
|
});
|
|
|
|
it('should limit depth', async ({ page }) => {
|
|
await page.setContent(`
|
|
<ul>
|
|
<li>item1</li>
|
|
<a href="about:blank" style="cursor:pointer">link</a>
|
|
<li>
|
|
<ul id=target>
|
|
<li>item2</li>
|
|
<li>
|
|
<ul>
|
|
<li>item3</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
`);
|
|
|
|
const snapshot1 = await snapshotForAI(page, { depth: 1 });
|
|
expect(snapshot1).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: item1
|
|
- link "link" [ref=e4] [cursor=pointer]:
|
|
- /url: about:blank
|
|
- listitem [ref=e5]
|
|
`);
|
|
|
|
const snapshot2 = await snapshotForAI(page, { depth: 3 });
|
|
expect(snapshot2).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: item1
|
|
- link "link" [ref=e4] [cursor=pointer]:
|
|
- /url: about:blank
|
|
- listitem [ref=e5]:
|
|
- list [ref=e6]:
|
|
- listitem [ref=e7]: item2
|
|
- listitem [ref=e8]
|
|
`);
|
|
|
|
const snapshot3 = await snapshotForAI(page, { depth: 100 });
|
|
expect(snapshot3).toContainYaml(`
|
|
- list [ref=e2]:
|
|
- listitem [ref=e3]: item1
|
|
- link "link" [ref=e4] [cursor=pointer]:
|
|
- /url: about:blank
|
|
- listitem [ref=e5]:
|
|
- list [ref=e6]:
|
|
- listitem [ref=e7]: item2
|
|
- listitem [ref=e8]:
|
|
- list [ref=e9]:
|
|
- listitem [ref=e10]: item3
|
|
`);
|
|
|
|
const snapshot4 = await page.locator('#target').ariaSnapshot({ mode: 'ai', depth: 1 });
|
|
expect(snapshot4).toContainYaml(`
|
|
- list [ref=e6]:
|
|
- listitem [ref=e7]: item2
|
|
- listitem [ref=e8]
|
|
`);
|
|
});
|