mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 20:03:03 +03:00
feat(trace): highlight targets for accessors and expects (#9527)
This commit is contained in:
parent
cd7dfc8448
commit
4ce765c3ae
@ -28,6 +28,7 @@ export type CallMetadata = {
|
||||
apiName?: string;
|
||||
stack?: StackFrame[];
|
||||
log: string[];
|
||||
afterSnapshot?: string;
|
||||
snapshots: { title: string, snapshotName: string }[];
|
||||
error?: SerializedError;
|
||||
result?: any;
|
||||
|
@ -1281,9 +1281,10 @@ export class Frame extends SdkObject {
|
||||
return controller.run(async progress => {
|
||||
progress.log(`waiting for selector "${selector}"`);
|
||||
const rerunnableTask = new RerunnableTask(data, progress, injectedScript => {
|
||||
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale, omitAttached }) => {
|
||||
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale, omitAttached, snapshotName }) => {
|
||||
const callback = injected.eval(callbackText) as DomTaskBody<T, R, Element | undefined>;
|
||||
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
|
||||
let markedElements = new Set<Element>();
|
||||
return poller((progress, continuePolling) => {
|
||||
let element: Element | undefined;
|
||||
let elements: Element[] = [];
|
||||
@ -1293,16 +1294,30 @@ export class Frame extends SdkObject {
|
||||
progress.logRepeating(` selector resolved to ${elements.length} element${elements.length === 1 ? '' : 's'}`);
|
||||
} else {
|
||||
element = injected.querySelector(info.parsed, document, info.strict);
|
||||
elements = [];
|
||||
elements = element ? [element] : [];
|
||||
if (element)
|
||||
progress.logRepeating(` selector resolved to ${injected.previewNode(element)}`);
|
||||
}
|
||||
|
||||
if (!element && !omitAttached)
|
||||
return continuePolling;
|
||||
|
||||
if (snapshotName) {
|
||||
const previouslyMarkedElements = markedElements;
|
||||
markedElements = new Set(elements);
|
||||
for (const e of previouslyMarkedElements) {
|
||||
if (!markedElements.has(e))
|
||||
e.removeAttribute('__playwright_target__');
|
||||
}
|
||||
for (const e of markedElements) {
|
||||
if (!previouslyMarkedElements.has(e))
|
||||
e.setAttribute('__playwright_target__', snapshotName);
|
||||
}
|
||||
}
|
||||
|
||||
return callback(progress, element, taskData as T, elements, continuePolling);
|
||||
});
|
||||
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached });
|
||||
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached, snapshotName: progress.metadata.afterSnapshot });
|
||||
}, true);
|
||||
|
||||
if (this._detached)
|
||||
|
@ -19,7 +19,7 @@ import { createGuid } from '../utils/utils';
|
||||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { BrowserType } from './browserType';
|
||||
import { ElementHandle } from './dom';
|
||||
import type { ElementHandle } from './dom';
|
||||
import type { Frame } from './frames';
|
||||
import type { Page } from './page';
|
||||
|
||||
|
@ -234,10 +234,17 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
||||
return;
|
||||
const snapshotName = `${name}@${metadata.id}`;
|
||||
metadata.snapshots.push({ title: name, snapshotName });
|
||||
// We have |element| for input actions (page.click and handle.click)
|
||||
// and |sdkObject| element for accessors like handle.textContent.
|
||||
if (!element && sdkObject instanceof ElementHandle)
|
||||
element = sdkObject;
|
||||
await this._snapshotter.captureSnapshot(sdkObject.attribution.page, snapshotName, element).catch(() => {});
|
||||
}
|
||||
|
||||
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||
// Set afterSnapshot name for all the actions that operate selectors.
|
||||
// Elements resolved from selectors will be marked on the snapshot.
|
||||
metadata.afterSnapshot = `after@${metadata.id}`;
|
||||
const beforeSnapshot = this._captureSnapshot('before', sdkObject, metadata);
|
||||
this._pendingCalls.set(metadata.id, { sdkObject, metadata, beforeSnapshot });
|
||||
await beforeSnapshot;
|
||||
|
@ -83,7 +83,11 @@ class TraceViewerPage {
|
||||
}
|
||||
|
||||
async snapshotFrame(actionName: string, ordinal: number = 0, hasSubframe: boolean = false): Promise<Frame> {
|
||||
await this.selectAction(actionName, ordinal);
|
||||
const existing = this.page.mainFrame().childFrames()[0];
|
||||
await Promise.all([
|
||||
existing ? existing.waitForNavigation() as any : Promise.resolve(),
|
||||
this.selectAction(actionName, ordinal),
|
||||
]);
|
||||
while (this.page.frames().length < (hasSubframe ? 3 : 2))
|
||||
await this.page.waitForEvent('frameattached');
|
||||
return this.page.mainFrame().childFrames()[0];
|
||||
@ -497,3 +501,43 @@ test('should handle src=blob', async ({ page, server, runAndTrace, browserName }
|
||||
const size = await img.evaluate(e => (e as HTMLImageElement).naturalWidth);
|
||||
expect(size).toBe(10);
|
||||
});
|
||||
|
||||
test('should highlight target elements', async ({ page, runAndTrace, browserName }) => {
|
||||
test.skip(browserName === 'firefox');
|
||||
|
||||
const traceViewer = await runAndTrace(async () => {
|
||||
await page.setContent(`
|
||||
<div>hello</div>
|
||||
<div>world</div>
|
||||
`);
|
||||
await page.click('text=hello');
|
||||
await page.innerText('text=hello');
|
||||
const handle = await page.$('text=hello');
|
||||
await handle.click();
|
||||
await handle.innerText();
|
||||
await page.locator('text=hello').innerText();
|
||||
await expect(page.locator('text=hello')).toHaveText(/hello/i);
|
||||
await expect(page.locator('div')).toHaveText(['a', 'b'], { timeout: 1000 }).catch(() => {});
|
||||
});
|
||||
|
||||
const framePageClick = await traceViewer.snapshotFrame('page.click');
|
||||
await expect(framePageClick.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const framePageInnerText = await traceViewer.snapshotFrame('page.innerText');
|
||||
await expect(framePageInnerText.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const frameHandleClick = await traceViewer.snapshotFrame('elementHandle.click');
|
||||
await expect(frameHandleClick.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const frameHandleInnerText = await traceViewer.snapshotFrame('elementHandle.innerText');
|
||||
await expect(frameHandleInnerText.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const frameLocatorInnerText = await traceViewer.snapshotFrame('locator.innerText');
|
||||
await expect(frameLocatorInnerText.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const frameExpect1 = await traceViewer.snapshotFrame('expect.toHaveText', 0);
|
||||
await expect(frameExpect1.locator('[__playwright_target__]')).toHaveText(['hello']);
|
||||
|
||||
const frameExpect2 = await traceViewer.snapshotFrame('expect.toHaveText', 1);
|
||||
await expect(frameExpect2.locator('[__playwright_target__]')).toHaveText(['hello', 'world']);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user