chore(trace): remove dependency on handle._previewPromise (#3906)
We now mark the target with '__playwright_target__' attribute and let the trace viewer do whatever it wants.
This commit is contained in:
parent
73db4a450e
commit
36f2420b0f
|
|
@ -99,20 +99,18 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
readonly _context: FrameExecutionContext;
|
readonly _context: FrameExecutionContext;
|
||||||
readonly _page: Page;
|
readonly _page: Page;
|
||||||
readonly _objectId: string;
|
readonly _objectId: string;
|
||||||
readonly _previewPromise: Promise<string>;
|
|
||||||
|
|
||||||
constructor(context: FrameExecutionContext, objectId: string) {
|
constructor(context: FrameExecutionContext, objectId: string) {
|
||||||
super(context, 'node', objectId);
|
super(context, 'node', objectId);
|
||||||
this._objectId = objectId;
|
this._objectId = objectId;
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._page = context.frame._page;
|
this._page = context.frame._page;
|
||||||
this._previewPromise = this._initializePreview().catch(e => 'node');
|
this._initializePreview().catch(e => {});
|
||||||
this._previewPromise.then(preview => this._setPreview('JSHandle@' + preview));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _initializePreview() {
|
async _initializePreview() {
|
||||||
const utility = await this._context.injectedScript();
|
const utility = await this._context.injectedScript();
|
||||||
return utility.evaluate((injected, e) => injected.previewNode(e), this);
|
this._setPreview(await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this));
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement(): ElementHandle<T> | null {
|
asElement(): ElementHandle<T> | null {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import * as js from '../server/javascript';
|
||||||
import * as types from '../server/types';
|
import * as types from '../server/types';
|
||||||
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
||||||
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
||||||
|
import { ElementHandle } from '../server/dom';
|
||||||
|
|
||||||
export type SnapshotterResource = {
|
export type SnapshotterResource = {
|
||||||
pageId: string,
|
pageId: string,
|
||||||
|
|
@ -113,14 +114,14 @@ export class Snapshotter {
|
||||||
this._delegate.onBlob({ sha1, buffer: body });
|
this._delegate.onBlob({ sha1, buffer: body });
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeSnapshot(page: Page, timeout: number): Promise<PageSnapshot | null> {
|
async takeSnapshot(page: Page, target: ElementHandle | undefined, timeout: number): Promise<PageSnapshot | null> {
|
||||||
assert(page.context() === this._context);
|
assert(page.context() === this._context);
|
||||||
|
|
||||||
const frames = page.frames();
|
const frames = page.frames();
|
||||||
const frameSnapshotPromises = frames.map(async frame => {
|
const frameSnapshotPromises = frames.map(async frame => {
|
||||||
// TODO: use different timeout depending on the frame depth/origin
|
// TODO: use different timeout depending on the frame depth/origin
|
||||||
// to avoid waiting for too long for some useless frame.
|
// to avoid waiting for too long for some useless frame.
|
||||||
const frameResult = await runAbortableTask(progress => this._snapshotFrame(progress, frame), timeout).catch(e => null);
|
const frameResult = await runAbortableTask(progress => this._snapshotFrame(progress, target, frame), timeout).catch(e => null);
|
||||||
if (frameResult)
|
if (frameResult)
|
||||||
return frameResult;
|
return frameResult;
|
||||||
const frameSnapshot = {
|
const frameSnapshot = {
|
||||||
|
|
@ -180,14 +181,16 @@ export class Snapshotter {
|
||||||
}, timeout).catch(e => null);
|
}, timeout).catch(e => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _snapshotFrame(progress: Progress, frame: Frame): Promise<FrameSnapshotAndMapping | null> {
|
private async _snapshotFrame(progress: Progress, target: ElementHandle | undefined, frame: Frame): Promise<FrameSnapshotAndMapping | null> {
|
||||||
if (!progress.isRunning())
|
if (!progress.isRunning())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
if (target && (await target.ownerFrame()) !== frame)
|
||||||
|
target = undefined;
|
||||||
const context = await frame._utilityContext();
|
const context = await frame._utilityContext();
|
||||||
const guid = createGuid();
|
const guid = createGuid();
|
||||||
const removeNoScript = !frame._page.context()._options.javaScriptEnabled;
|
const removeNoScript = !frame._page.context()._options.javaScriptEnabled;
|
||||||
const result = await js.evaluate(context, false /* returnByValue */, takeSnapshotInFrame, guid, removeNoScript) as js.JSHandle;
|
const result = await js.evaluate(context, false /* returnByValue */, takeSnapshotInFrame, guid, removeNoScript, target) as js.JSHandle;
|
||||||
if (!progress.isRunning())
|
if (!progress.isRunning())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ type SnapshotResult = {
|
||||||
frameElements: Element[],
|
frameElements: Element[],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function takeSnapshotInFrame(guid: string, removeNoScript: boolean): SnapshotResult {
|
export function takeSnapshotInFrame(guid: string, removeNoScript: boolean, target: Node | undefined): SnapshotResult {
|
||||||
const shadowAttribute = 'playwright-shadow-root';
|
const shadowAttribute = 'playwright-shadow-root';
|
||||||
const win = window;
|
const win = window;
|
||||||
const doc = win.document;
|
const doc = win.document;
|
||||||
|
|
@ -161,6 +161,8 @@ export function takeSnapshotInFrame(guid: string, removeNoScript: boolean): Snap
|
||||||
const element = node as Element;
|
const element = node as Element;
|
||||||
builder.push('<');
|
builder.push('<');
|
||||||
builder.push(nodeName);
|
builder.push(nodeName);
|
||||||
|
if (node === target)
|
||||||
|
builder.push(' __playwright_target__="true"');
|
||||||
for (let i = 0; i < element.attributes.length; i++) {
|
for (let i = 0; i < element.attributes.length; i++) {
|
||||||
const name = element.attributes[i].name;
|
const name = element.attributes[i].name;
|
||||||
let value = element.attributes[i].value;
|
let value = element.attributes[i].value;
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export type ActionTraceEvent = {
|
||||||
contextId: string,
|
contextId: string,
|
||||||
action: string,
|
action: string,
|
||||||
pageId?: string,
|
pageId?: string,
|
||||||
target?: string,
|
selector?: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
value?: string,
|
value?: string,
|
||||||
startTime?: number,
|
startTime?: number,
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class ContextTracer implements SnapshotterDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
||||||
const snapshot = await this._takeSnapshot(page, options.timeout);
|
const snapshot = await this._takeSnapshot(page, undefined, options.timeout);
|
||||||
if (!snapshot)
|
if (!snapshot)
|
||||||
return;
|
return;
|
||||||
const event: ActionTraceEvent = {
|
const event: ActionTraceEvent = {
|
||||||
|
|
@ -152,13 +152,13 @@ class ContextTracer implements SnapshotterDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async recordAction(result: ActionResult, metadata: ActionMetadata) {
|
async recordAction(result: ActionResult, metadata: ActionMetadata) {
|
||||||
const snapshot = await this._takeSnapshot(metadata.page);
|
const snapshot = await this._takeSnapshot(metadata.page, typeof metadata.target === 'string' ? undefined : metadata.target);
|
||||||
const event: ActionTraceEvent = {
|
const event: ActionTraceEvent = {
|
||||||
type: 'action',
|
type: 'action',
|
||||||
contextId: this._contextId,
|
contextId: this._contextId,
|
||||||
pageId: this._pageToId.get(metadata.page),
|
pageId: this._pageToId.get(metadata.page),
|
||||||
action: metadata.type,
|
action: metadata.type,
|
||||||
target: metadata.target instanceof ElementHandle ? await metadata.target._previewPromise : metadata.target,
|
selector: typeof metadata.target === 'string' ? metadata.target : undefined,
|
||||||
value: metadata.value,
|
value: metadata.value,
|
||||||
snapshot,
|
snapshot,
|
||||||
startTime: result.startTime,
|
startTime: result.startTime,
|
||||||
|
|
@ -194,14 +194,14 @@ class ContextTracer implements SnapshotterDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _takeSnapshot(page: Page, timeout: number = 0): Promise<{ sha1: string, duration: number } | undefined> {
|
private async _takeSnapshot(page: Page, target: ElementHandle | undefined, timeout: number = 0): Promise<{ sha1: string, duration: number } | undefined> {
|
||||||
if (!timeout) {
|
if (!timeout) {
|
||||||
// Never use zero timeout to avoid stalling because of snapshot.
|
// Never use zero timeout to avoid stalling because of snapshot.
|
||||||
// Use 20% of the default timeout.
|
// Use 20% of the default timeout.
|
||||||
timeout = (page._timeoutSettings.timeout({}) || DEFAULT_TIMEOUT) / 5;
|
timeout = (page._timeoutSettings.timeout({}) || DEFAULT_TIMEOUT) / 5;
|
||||||
}
|
}
|
||||||
const startTime = monotonicTime();
|
const startTime = monotonicTime();
|
||||||
const snapshot = await this._snapshotter.takeSnapshot(page, timeout);
|
const snapshot = await this._snapshotter.takeSnapshot(page, target, timeout);
|
||||||
if (!snapshot)
|
if (!snapshot)
|
||||||
return;
|
return;
|
||||||
const buffer = Buffer.from(JSON.stringify(snapshot));
|
const buffer = Buffer.from(JSON.stringify(snapshot));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue