feat(trace): highlight strict mode violation elements in the snapshot
This commit is contained in:
parent
011034050b
commit
ad3d9dd345
|
|
@ -77,10 +77,6 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
|||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||
}
|
||||
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
const response = await this._session.send('script.callFunction', {
|
||||
functionDeclaration,
|
||||
|
|
|
|||
|
|
@ -53,16 +53,6 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
|||
return remoteObject.objectId!;
|
||||
}
|
||||
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]) {
|
||||
this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: func.toString(),
|
||||
arguments: args.map(a => a instanceof js.JSHandle ? { objectId: a._objectId } : { value: a }),
|
||||
returnByValue: true,
|
||||
executionContextId: this._contextId,
|
||||
userGesture: true
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: expression,
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return maybePoint;
|
||||
const point = roundPoint(maybePoint);
|
||||
progress.metadata.point = point;
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
|
||||
let hitTargetInterceptionHandle: js.JSHandle<HitTargetInterceptionResult> | undefined;
|
||||
if (force) {
|
||||
|
|
@ -490,9 +490,19 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
return 'done';
|
||||
}
|
||||
|
||||
private async _markAsTargetElement(metadata: CallMetadata) {
|
||||
if (!metadata.id)
|
||||
return;
|
||||
await this.evaluateInUtility(([injected, node, callId]) => {
|
||||
if (node.nodeType === 1 /* Node.ELEMENT_NODE */)
|
||||
injected.markTargetElements(new Set([node as Node as Element]), callId);
|
||||
}, metadata.id);
|
||||
}
|
||||
|
||||
async hover(metadata: CallMetadata, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._hover(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -505,6 +515,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async click(metadata: CallMetadata, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._click(progress, { ...options, waitAfter: !options.noWaitAfter });
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -517,6 +528,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async dblclick(metadata: CallMetadata, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._dblclick(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -529,6 +541,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async tap(metadata: CallMetadata, options: types.PointerActionWaitOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._tap(progress, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -541,6 +554,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async selectOption(metadata: CallMetadata, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise<string[]> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._selectOption(progress, elements, values, options);
|
||||
return throwRetargetableDOMError(result);
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -549,7 +563,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async _selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise<string[] | 'error:notconnected'> {
|
||||
let resultingOptions: string[] = [];
|
||||
await this._retryAction(progress, 'select option', async () => {
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
if (!options.force)
|
||||
progress.log(` waiting for element to be visible and enabled`);
|
||||
const optionsToSelect = [...elements, ...values];
|
||||
|
|
@ -574,6 +588,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async fill(metadata: CallMetadata, value: string, options: types.CommonActionOptions = {}): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._fill(progress, value, options);
|
||||
assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -582,7 +597,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async _fill(progress: Progress, value: string, options: types.CommonActionOptions): Promise<'error:notconnected' | 'done'> {
|
||||
progress.log(` fill("${value}")`);
|
||||
return await this._retryAction(progress, 'fill', async () => {
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
if (!options.force)
|
||||
progress.log(' waiting for element to be visible, enabled and editable');
|
||||
const result = await this.evaluateInUtility(async ([injected, node, { value, force }]) => {
|
||||
|
|
@ -629,6 +644,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
const inputFileItems = await prepareFilesForUpload(this._frame, params);
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._setInputFiles(progress, inputFileItems);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(params));
|
||||
|
|
@ -655,7 +671,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
if (result === 'error:notconnected' || !result.asElement())
|
||||
return 'error:notconnected';
|
||||
const retargeted = result.asElement() as ElementHandle<HTMLInputElement>;
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
progress.throwIfAborted(); // Avoid action that has side-effects.
|
||||
if (localPaths || localDirectory) {
|
||||
const localPathsOrDirectory = localDirectory ? [localDirectory] : localPaths!;
|
||||
|
|
@ -677,6 +693,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async focus(metadata: CallMetadata): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
await controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._focus(progress);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, 0);
|
||||
|
|
@ -695,6 +712,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async type(metadata: CallMetadata, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._type(progress, text, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -702,7 +720,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
|
||||
async _type(progress: Progress, text: string, options: { delay?: number } & types.TimeoutOptions & types.StrictOptions): Promise<'error:notconnected' | 'done'> {
|
||||
progress.log(`elementHandle.type("${text}")`);
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
|
||||
if (result !== 'done')
|
||||
return result;
|
||||
|
|
@ -714,6 +732,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
async press(metadata: CallMetadata, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions): Promise<void> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
return controller.run(async progress => {
|
||||
await this._markAsTargetElement(metadata);
|
||||
const result = await this._press(progress, key, options);
|
||||
return assertDone(throwRetargetableDOMError(result));
|
||||
}, this._page._timeoutSettings.timeout(options));
|
||||
|
|
@ -721,7 +740,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
|
||||
async _press(progress: Progress, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & types.StrictOptions): Promise<'error:notconnected' | 'done'> {
|
||||
progress.log(`elementHandle.press("${key}")`);
|
||||
await progress.beforeInputAction(this);
|
||||
await this.instrumentation.onBeforeInputAction(this, progress.metadata);
|
||||
return this._page._frameManager.waitForSignalsCreatedBy(progress, !options.noWaitAfter, async () => {
|
||||
const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
|
||||
if (result !== 'done')
|
||||
|
|
@ -753,6 +772,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {});
|
||||
return throwRetargetableDOMError(result);
|
||||
};
|
||||
await this._markAsTargetElement(progress.metadata);
|
||||
if (await isChecked() === state)
|
||||
return 'done';
|
||||
const result = await this._click(progress, { ...options, waitAfter: 'disabled' });
|
||||
|
|
|
|||
|
|
@ -51,15 +51,6 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
|||
return payload.result!.objectId!;
|
||||
}
|
||||
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]) {
|
||||
this._session.send('Runtime.callFunction', {
|
||||
functionDeclaration: func.toString(),
|
||||
args: args.map(a => a instanceof js.JSHandle ? { objectId: a._objectId } : { value: a }) as any,
|
||||
returnByValue: true,
|
||||
executionContextId: this._executionContextId
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
const payload = await this._session.send('Runtime.callFunction', {
|
||||
functionDeclaration: expression,
|
||||
|
|
|
|||
|
|
@ -1124,8 +1124,10 @@ export class Frame extends SdkObject {
|
|||
progress.throwIfAborted();
|
||||
if (!resolved)
|
||||
return continuePolling;
|
||||
const result = await resolved.injected.evaluateHandle((injected, { info }) => {
|
||||
const result = await resolved.injected.evaluateHandle((injected, { info, callId }) => {
|
||||
const elements = injected.querySelectorAll(info.parsed, document);
|
||||
if (callId)
|
||||
injected.markTargetElements(new Set(elements), callId);
|
||||
const element = elements[0] as Element | undefined;
|
||||
let log = '';
|
||||
if (elements.length > 1) {
|
||||
|
|
@ -1136,7 +1138,7 @@ export class Frame extends SdkObject {
|
|||
log = ` locator resolved to ${injected.previewNode(element)}`;
|
||||
}
|
||||
return { log, success: !!element, element };
|
||||
}, { info: resolved.info });
|
||||
}, { info: resolved.info, callId: progress.metadata.id });
|
||||
const { log, success } = await result.evaluate(r => ({ log: r.log, success: r.success }));
|
||||
if (log)
|
||||
progress.log(log);
|
||||
|
|
@ -1478,6 +1480,8 @@ export class Frame extends SdkObject {
|
|||
|
||||
const { log, matches, received, missingReceived } = await injected.evaluate(async (injected, { info, options, callId }) => {
|
||||
const elements = info ? injected.querySelectorAll(info.parsed, document) : [];
|
||||
if (callId)
|
||||
injected.markTargetElements(new Set(elements), callId);
|
||||
const isArray = options.expression === 'to.have.count' || options.expression.endsWith('.array');
|
||||
let log = '';
|
||||
if (isArray)
|
||||
|
|
@ -1486,8 +1490,6 @@ export class Frame extends SdkObject {
|
|||
throw injected.strictModeViolationError(info!.parsed, elements);
|
||||
else if (elements.length)
|
||||
log = ` locator resolved to ${injected.previewNode(elements[0])}`;
|
||||
if (callId)
|
||||
injected.markTargetElements(new Set(elements), callId);
|
||||
return { log, ...await injected.expect(elements[0], options, elements) };
|
||||
}, { info, options, callId: progress.metadata.id });
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import type { APIRequestContext } from './fetch';
|
|||
import type { Browser } from './browser';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { BrowserType } from './browserType';
|
||||
import type { ElementHandle } from './dom';
|
||||
import type { Frame } from './frames';
|
||||
import type { Page } from './page';
|
||||
import type { Playwright } from './playwright';
|
||||
|
|
@ -57,7 +56,7 @@ export interface Instrumentation {
|
|||
addListener(listener: InstrumentationListener, context: BrowserContext | APIRequestContext | null): void;
|
||||
removeListener(listener: InstrumentationListener): void;
|
||||
onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string): void;
|
||||
onAfterCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onPageOpen(page: Page): void;
|
||||
|
|
@ -70,7 +69,7 @@ export interface Instrumentation {
|
|||
|
||||
export interface InstrumentationListener {
|
||||
onBeforeCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onBeforeInputAction?(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle): Promise<void>;
|
||||
onBeforeInputAction?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onCallLog?(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string): void;
|
||||
onAfterCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||
onPageOpen?(page: Page): void;
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ export type SmartHandle<T> = T extends Node ? dom.ElementHandle<T> : JSHandle<T>
|
|||
export interface ExecutionContextDelegate {
|
||||
rawEvaluateJSON(expression: string): Promise<any>;
|
||||
rawEvaluateHandle(expression: string): Promise<ObjectId>;
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]): void;
|
||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any>;
|
||||
getProperties(context: ExecutionContext, objectId: ObjectId): Promise<Map<string, JSHandle>>;
|
||||
createHandle(context: ExecutionContext, remoteObject: RemoteObject): JSHandle;
|
||||
|
|
@ -88,10 +87,6 @@ export class ExecutionContext extends SdkObject {
|
|||
return this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(expression));
|
||||
}
|
||||
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]): void {
|
||||
this._delegate.rawCallFunctionNoReply(func, ...args);
|
||||
}
|
||||
|
||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any> {
|
||||
return this._raceAgainstContextDestroyed(this._delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds));
|
||||
}
|
||||
|
|
@ -151,10 +146,6 @@ export class JSHandle<T = any> extends SdkObject {
|
|||
(globalThis as any).leakedJSHandles.set(this, new Error('Leaked JSHandle'));
|
||||
}
|
||||
|
||||
callFunctionNoReply(func: Function, arg: any) {
|
||||
this._context.rawCallFunctionNoReply(func, this, arg);
|
||||
}
|
||||
|
||||
async evaluate<R, Arg>(pageFunction: FuncOn<T, Arg, R>, arg?: Arg): Promise<R> {
|
||||
return evaluate(this._context, true /* returnByValue */, pageFunction, this, arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import { TimeoutError } from './errors';
|
|||
import { assert, monotonicTime } from '../utils';
|
||||
import type { LogName } from '../utils/debugLogger';
|
||||
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||
import type { ElementHandle } from './dom';
|
||||
import { ManualPromise } from '../utils/manualPromise';
|
||||
|
||||
export interface Progress {
|
||||
|
|
@ -27,7 +26,6 @@ export interface Progress {
|
|||
isRunning(): boolean;
|
||||
cleanupWhenAborted(cleanup: () => any): void;
|
||||
throwIfAborted(): void;
|
||||
beforeInputAction(element: ElementHandle): Promise<void>;
|
||||
metadata: CallMetadata;
|
||||
}
|
||||
|
||||
|
|
@ -89,9 +87,6 @@ export class ProgressController {
|
|||
if (this._state === 'aborted')
|
||||
throw new AbortedError();
|
||||
},
|
||||
beforeInputAction: async (element: ElementHandle) => {
|
||||
await this.instrumentation.onBeforeInputAction(this.sdkObject, this.metadata, element);
|
||||
},
|
||||
metadata: this.metadata
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import type { SnapshotData } from './snapshotterInjected';
|
|||
import { frameSnapshotStreamer } from './snapshotterInjected';
|
||||
import { calculateSha1, createGuid, monotonicTime } from '../../../utils';
|
||||
import type { FrameSnapshot } from '@trace/snapshot';
|
||||
import type { ElementHandle } from '../../dom';
|
||||
import { mime } from '../../../utilsBundle';
|
||||
|
||||
export type SnapshotterBlob = {
|
||||
|
|
@ -105,21 +104,10 @@ export class Snapshotter {
|
|||
eventsHelper.removeEventListeners(this._eventListeners);
|
||||
}
|
||||
|
||||
async captureSnapshot(page: Page, callId: string, snapshotName: string, element?: ElementHandle): Promise<void> {
|
||||
async captureSnapshot(page: Page, callId: string, snapshotName: string): Promise<void> {
|
||||
// Prepare expression synchronously.
|
||||
const expression = `window["${this._snapshotStreamer}"].captureSnapshot(${JSON.stringify(snapshotName)})`;
|
||||
|
||||
// In a best-effort manner, without waiting for it, mark target element.
|
||||
element?.callFunctionNoReply((element: Element, callId: string) => {
|
||||
const customEvent = new CustomEvent('__playwright_target__', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: callId,
|
||||
composed: true,
|
||||
});
|
||||
element.dispatchEvent(customEvent);
|
||||
}, callId);
|
||||
|
||||
// In each frame, in a non-stalling manner, capture the snapshots.
|
||||
const snapshots = page.frames().map(async frame => {
|
||||
const data = await frame.nonStallingRawEvaluateInExistingMainContext(expression).catch(e => debugLogger.log('error', e)) as SnapshotData;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import { commandsWithTracingSnapshots } from '../../../protocol/debug';
|
|||
import { assert, createGuid, monotonicTime, SerializedFS, removeFolders, eventsHelper, type RegisteredListener } from '../../../utils';
|
||||
import { Artifact } from '../../artifact';
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import type { ElementHandle } from '../../dom';
|
||||
import type { APIRequestContext } from '../../fetch';
|
||||
import type { CallMetadata, InstrumentationListener } from '../../instrumentation';
|
||||
import { SdkObject } from '../../instrumentation';
|
||||
|
|
@ -341,7 +340,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
|||
return { artifact };
|
||||
}
|
||||
|
||||
async _captureSnapshot(snapshotName: string, sdkObject: SdkObject, metadata: CallMetadata, element?: ElementHandle): Promise<void> {
|
||||
async _captureSnapshot(snapshotName: string, sdkObject: SdkObject, metadata: CallMetadata): Promise<void> {
|
||||
if (!this._snapshotter)
|
||||
return;
|
||||
if (!sdkObject.attribution.page)
|
||||
|
|
@ -350,7 +349,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
|||
return;
|
||||
if (!shouldCaptureSnapshot(metadata))
|
||||
return;
|
||||
await this._snapshotter.captureSnapshot(sdkObject.attribution.page, metadata.id, snapshotName, element).catch(() => {});
|
||||
await this._snapshotter.captureSnapshot(sdkObject.attribution.page, metadata.id, snapshotName).catch(() => {});
|
||||
}
|
||||
|
||||
onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||
|
|
@ -365,7 +364,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
|||
return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata);
|
||||
}
|
||||
|
||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle) {
|
||||
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||
if (!this._state?.callIds.has(metadata.id))
|
||||
return Promise.resolve();
|
||||
// IMPORTANT: no awaits before this._appendTraceEvent in this method.
|
||||
|
|
@ -375,7 +374,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
|||
sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
|
||||
event.inputSnapshot = `input@${metadata.id}`;
|
||||
this._appendTraceEvent(event);
|
||||
return this._captureSnapshot(event.inputSnapshot, sdkObject, metadata, element);
|
||||
return this._captureSnapshot(event.inputSnapshot, sdkObject, metadata);
|
||||
}
|
||||
|
||||
onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import type { SnapshotRenderer } from '../../../../../trace-viewer/src/sw/snapsh
|
|||
import { SnapshotStorage } from '../../../../../trace-viewer/src/sw/snapshotStorage';
|
||||
import type { SnapshotterBlob, SnapshotterDelegate } from '../recorder/snapshotter';
|
||||
import { Snapshotter } from '../recorder/snapshotter';
|
||||
import type { ElementHandle } from '../../dom';
|
||||
import type { HarTracerDelegate } from '../../har/harTracer';
|
||||
import { HarTracer } from '../../har/harTracer';
|
||||
import type * as har from '@trace/har';
|
||||
|
|
@ -59,11 +58,11 @@ export class InMemorySnapshotter implements SnapshotterDelegate, HarTracerDelega
|
|||
this._harTracer.stop();
|
||||
}
|
||||
|
||||
async captureSnapshot(page: Page, callId: string, snapshotName: string, element?: ElementHandle): Promise<SnapshotRenderer> {
|
||||
async captureSnapshot(page: Page, callId: string, snapshotName: string): Promise<SnapshotRenderer> {
|
||||
if (this._snapshotReadyPromises.has(snapshotName))
|
||||
throw new Error('Duplicate snapshot name: ' + snapshotName);
|
||||
|
||||
this._snapshotter.captureSnapshot(page, callId, snapshotName, element).catch(() => {});
|
||||
this._snapshotter.captureSnapshot(page, callId, snapshotName).catch(() => {});
|
||||
const promise = new ManualPromise<SnapshotRenderer>();
|
||||
this._snapshotReadyPromises.set(snapshotName, promise);
|
||||
return promise;
|
||||
|
|
|
|||
|
|
@ -60,16 +60,6 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
rawCallFunctionNoReply(func: Function, ...args: any[]) {
|
||||
this._session.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: func.toString(),
|
||||
objectId: args.find(a => a instanceof js.JSHandle)!._objectId!,
|
||||
arguments: args.map(a => a instanceof js.JSHandle ? { objectId: a._objectId } : { value: a }),
|
||||
returnByValue: true,
|
||||
emulateUserGesture: true
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
||||
try {
|
||||
const response = await this._session.send('Runtime.callFunctionOn', {
|
||||
|
|
|
|||
|
|
@ -215,20 +215,6 @@ it.describe('snapshots', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should capture snapshot target', async ({ page, toImpl, snapshotter }) => {
|
||||
await page.setContent('<button>Hello</button><button>World</button>');
|
||||
{
|
||||
const handle = await page.$('text=Hello');
|
||||
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1', toImpl(handle));
|
||||
expect(distillSnapshot(snapshot, false /* distillTarget */)).toBe('<BUTTON __playwright_target__=\"call@1\">Hello</BUTTON><BUTTON>World</BUTTON>');
|
||||
}
|
||||
{
|
||||
const handle = await page.$('text=World');
|
||||
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'call@2', 'snapshot@call@2', toImpl(handle));
|
||||
expect(distillSnapshot(snapshot, false /* distillTarget */)).toBe('<BUTTON __playwright_target__=\"call@1\">Hello</BUTTON><BUTTON __playwright_target__=\"call@2\">World</BUTTON>');
|
||||
}
|
||||
});
|
||||
|
||||
it('should collect on attribute change', async ({ page, toImpl, snapshotter }) => {
|
||||
await page.setContent('<button>Hello</button>');
|
||||
{
|
||||
|
|
|
|||
|
|
@ -776,6 +776,8 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName
|
|||
await expect(page.locator('text=t6')).toHaveText(/t6/i);
|
||||
await expect(page.locator('text=multi')).toHaveText(['a', 'b'], { timeout: 1000 }).catch(() => {});
|
||||
await page.mouse.move(123, 234);
|
||||
await page.getByText(/^t\d$/).click().catch(() => {});
|
||||
await expect(page.getByText(/t3|t4/)).toBeVisible().catch(() => {});
|
||||
});
|
||||
|
||||
async function highlightedDivs(frameLocator: FrameLocator) {
|
||||
|
|
@ -817,6 +819,12 @@ test('should highlight target elements', async ({ page, runAndTrace, browserName
|
|||
|
||||
const frameMouseMove = await traceViewer.snapshotFrame('mouse.move');
|
||||
await expect(frameMouseMove.locator('x-pw-pointer')).toBeVisible();
|
||||
|
||||
const frameClickStrictViolation = await traceViewer.snapshotFrame('locator.click');
|
||||
await expect.poll(() => highlightedDivs(frameClickStrictViolation)).toEqual(['t1', 't2', 't3', 't4', 't5', 't6']);
|
||||
|
||||
const frameExpectStrictViolation = await traceViewer.snapshotFrame('expect.toBeVisible');
|
||||
await expect.poll(() => highlightedDivs(frameExpectStrictViolation)).toEqual(['t3', 't4']);
|
||||
});
|
||||
|
||||
test('should highlight target element in shadow dom', async ({ page, server, runAndTrace }) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue