diff --git a/packages/injected/src/injectedScript.ts b/packages/injected/src/injectedScript.ts index 3d15105369070..ed1e49518ee1e 100644 --- a/packages/injected/src/injectedScript.ts +++ b/packages/injected/src/injectedScript.ts @@ -49,6 +49,7 @@ export type FrameExpectParams = Omit }; + private _markedElements?: { markTargets: string, elements: Set }; readonly window: Window & typeof globalThis; readonly document: Document; readonly consoleApi: ConsoleAPI; @@ -1379,15 +1380,15 @@ export class InjectedScript { } } - markTargetElements(markedElements: Set, callId: string) { - if (this._markedElements?.callId !== callId) + markTargetElements(markedElements: Set, markTargets: string) { + if (this._markedElements?.markTargets !== markTargets) this._markedElements = undefined; const previous = this._markedElements?.elements || new Set(); const unmarkEvent = new CustomEvent('__playwright_unmark_target__', { bubbles: true, cancelable: true, - detail: callId, + detail: markTargets, composed: true, }); for (const element of previous) { @@ -1398,7 +1399,7 @@ export class InjectedScript { const markEvent = new CustomEvent('__playwright_mark_target__', { bubbles: true, cancelable: true, - detail: callId, + detail: markTargets, composed: true, }); for (const element of markedElements) { @@ -1406,7 +1407,7 @@ export class InjectedScript { element.dispatchEvent(markEvent); } - this._markedElements = { callId, elements: markedElements }; + this._markedElements = { markTargets, elements: markedElements }; } private _setupGlobalListenersRemovalDetection() { diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index 899c71a0db103..5a0e4a0b47b96 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -66,54 +66,54 @@ export class ElementHandleDispatcher extends JSHandleDispatcher } async getAttribute(params: channels.ElementHandleGetAttributeParams, progress: Progress): Promise { - const value = await this._elementHandle.getAttribute(progress, params.name); + const value = await this._elementHandle.getAttribute(progress, params.name, { markTargets: progress.metadata.id }); return { value: value === null ? undefined : value }; } async inputValue(params: channels.ElementHandleInputValueParams, progress: Progress): Promise { - const value = await this._elementHandle.inputValue(progress); + const value = await this._elementHandle.inputValue(progress, { markTargets: progress.metadata.id }); return { value }; } async textContent(params: channels.ElementHandleTextContentParams, progress: Progress): Promise { - const value = await this._elementHandle.textContent(progress); + const value = await this._elementHandle.textContent(progress, { markTargets: progress.metadata.id }); return { value: value === null ? undefined : value }; } async innerText(params: channels.ElementHandleInnerTextParams, progress: Progress): Promise { - return { value: await this._elementHandle.innerText(progress) }; + return { value: await this._elementHandle.innerText(progress, { markTargets: progress.metadata.id }) }; } async innerHTML(params: channels.ElementHandleInnerHTMLParams, progress: Progress): Promise { - return { value: await this._elementHandle.innerHTML(progress) }; + return { value: await this._elementHandle.innerHTML(progress, { markTargets: progress.metadata.id }) }; } async isChecked(params: channels.ElementHandleIsCheckedParams, progress: Progress): Promise { - return { value: await this._elementHandle.isChecked(progress) }; + return { value: await this._elementHandle.isChecked(progress, { markTargets: progress.metadata.id }) }; } async isDisabled(params: channels.ElementHandleIsDisabledParams, progress: Progress): Promise { - return { value: await this._elementHandle.isDisabled(progress) }; + return { value: await this._elementHandle.isDisabled(progress, { markTargets: progress.metadata.id }) }; } async isEditable(params: channels.ElementHandleIsEditableParams, progress: Progress): Promise { - return { value: await this._elementHandle.isEditable(progress) }; + return { value: await this._elementHandle.isEditable(progress, { markTargets: progress.metadata.id }) }; } async isEnabled(params: channels.ElementHandleIsEnabledParams, progress: Progress): Promise { - return { value: await this._elementHandle.isEnabled(progress) }; + return { value: await this._elementHandle.isEnabled(progress, { markTargets: progress.metadata.id }) }; } async isHidden(params: channels.ElementHandleIsHiddenParams, progress: Progress): Promise { - return { value: await this._elementHandle.isHidden(progress) }; + return { value: await this._elementHandle.isHidden(progress, { markTargets: progress.metadata.id }) }; } async isVisible(params: channels.ElementHandleIsVisibleParams, progress: Progress): Promise { - return { value: await this._elementHandle.isVisible(progress) }; + return { value: await this._elementHandle.isVisible(progress, { markTargets: progress.metadata.id }) }; } async dispatchEvent(params: channels.ElementHandleDispatchEventParams, progress: Progress): Promise { - await this._elementHandle.dispatchEvent(progress, params.type, parseArgument(params.eventInit)); + await this._elementHandle.dispatchEvent(progress, params.type, parseArgument(params.eventInit), { markTargets: progress.metadata.id }); } async scrollIntoViewIfNeeded(params: channels.ElementHandleScrollIntoViewIfNeededParams, progress: Progress): Promise { @@ -121,28 +121,28 @@ export class ElementHandleDispatcher extends JSHandleDispatcher } async hover(params: channels.ElementHandleHoverParams, progress: Progress): Promise { - return await this._elementHandle.hover(progress, params); + return await this._elementHandle.hover(progress, { ...params, markTargets: progress.metadata.id }); } async click(params: channels.ElementHandleClickParams, progress: Progress): Promise { - return await this._elementHandle.click(progress, params); + return await this._elementHandle.click(progress, { ...params, markTargets: progress.metadata.id }); } async dblclick(params: channels.ElementHandleDblclickParams, progress: Progress): Promise { - return await this._elementHandle.dblclick(progress, params); + return await this._elementHandle.dblclick(progress, { ...params, markTargets: progress.metadata.id }); } async tap(params: channels.ElementHandleTapParams, progress: Progress): Promise { - return await this._elementHandle.tap(progress, params); + return await this._elementHandle.tap(progress, { ...params, markTargets: progress.metadata.id }); } async selectOption(params: channels.ElementHandleSelectOptionParams, progress: Progress): Promise { const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle); - return { values: await this._elementHandle.selectOption(progress, elements, params.options || [], params) }; + return { values: await this._elementHandle.selectOption(progress, elements, params.options || [], { ...params, markTargets: progress.metadata.id }) }; } async fill(params: channels.ElementHandleFillParams, progress: Progress): Promise { - return await this._elementHandle.fill(progress, params.value, params); + return await this._elementHandle.fill(progress, params.value, { ...params, markTargets: progress.metadata.id }); } async selectText(params: channels.ElementHandleSelectTextParams, progress: Progress): Promise { @@ -150,27 +150,27 @@ export class ElementHandleDispatcher extends JSHandleDispatcher } async setInputFiles(params: channels.ElementHandleSetInputFilesParams, progress: Progress): Promise { - return await this._elementHandle.setInputFiles(progress, params); + return await this._elementHandle.setInputFiles(progress, { ...params, markTargets: progress.metadata.id }); } async focus(params: channels.ElementHandleFocusParams, progress: Progress): Promise { - await this._elementHandle.focus(progress); + await this._elementHandle.focus(progress, { markTargets: progress.metadata.id }); } async type(params: channels.ElementHandleTypeParams, progress: Progress): Promise { - return await this._elementHandle.type(progress, params.text, params); + return await this._elementHandle.type(progress, params.text, { ...params, markTargets: progress.metadata.id }); } async press(params: channels.ElementHandlePressParams, progress: Progress): Promise { - return await this._elementHandle.press(progress, params.key, params); + return await this._elementHandle.press(progress, params.key, { ...params, markTargets: progress.metadata.id }); } async check(params: channels.ElementHandleCheckParams, progress: Progress): Promise { - return await this._elementHandle.check(progress, params); + return await this._elementHandle.check(progress, { ...params, markTargets: progress.metadata.id }); } async uncheck(params: channels.ElementHandleUncheckParams, progress: Progress): Promise { - return await this._elementHandle.uncheck(progress, params); + return await this._elementHandle.uncheck(progress, { ...params, markTargets: progress.metadata.id }); } async boundingBox(params: channels.ElementHandleBoundingBoxParams, progress: Progress): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 33b88d7633c03..d156b48671ff7 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -95,7 +95,7 @@ export class FrameDispatcher extends Dispatcher { - return this._frame.dispatchEvent(progress, params.selector, params.type, parseArgument(params.eventInit), params); + return this._frame.dispatchEvent(progress, params.selector, params.type, parseArgument(params.eventInit), { ...params, markTargets: progress.metadata.id }); } async evalOnSelector(params: channels.FrameEvalOnSelectorParams, progress: Progress): Promise { @@ -141,48 +141,48 @@ export class FrameDispatcher extends Dispatcher { progress.metadata.potentiallyClosesScope = true; - return await this._frame.click(progress, params.selector, params); + return await this._frame.click(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async dblclick(params: channels.FrameDblclickParams, progress: Progress): Promise { - return await this._frame.dblclick(progress, params.selector, params); + return await this._frame.dblclick(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async dragAndDrop(params: channels.FrameDragAndDropParams, progress: Progress): Promise { - return await this._frame.dragAndDrop(progress, params.source, params.target, params); + return await this._frame.dragAndDrop(progress, params.source, params.target, { ...params, markTargets: progress.metadata.id }); } async drop(params: channels.FrameDropParams, progress: Progress): Promise { - return await this._frame.drop(progress, params.selector, params, params); + return await this._frame.drop(progress, params.selector, params, { ...params, markTargets: progress.metadata.id }); } async tap(params: channels.FrameTapParams, progress: Progress): Promise { - return await this._frame.tap(progress, params.selector, params); + return await this._frame.tap(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async fill(params: channels.FrameFillParams, progress: Progress): Promise { - return await this._frame.fill(progress, params.selector, params.value, params); + return await this._frame.fill(progress, params.selector, params.value, { ...params, markTargets: progress.metadata.id }); } async focus(params: channels.FrameFocusParams, progress: Progress): Promise { - await this._frame.focus(progress, params.selector, params); + await this._frame.focus(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async blur(params: channels.FrameBlurParams, progress: Progress): Promise { - await this._frame.blur(progress, params.selector, params); + await this._frame.blur(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async textContent(params: channels.FrameTextContentParams, progress: Progress): Promise { - const value = await this._frame.textContent(progress, params.selector, params); + const value = await this._frame.textContent(progress, params.selector, { ...params, markTargets: progress.metadata.id }); return { value: value === null ? undefined : value }; } async innerText(params: channels.FrameInnerTextParams, progress: Progress): Promise { - return { value: await this._frame.innerText(progress, params.selector, params) }; + return { value: await this._frame.innerText(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async innerHTML(params: channels.FrameInnerHTMLParams, progress: Progress): Promise { - return { value: await this._frame.innerHTML(progress, params.selector, params) }; + return { value: await this._frame.innerHTML(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async resolveSelector(params: channels.FrameResolveSelectorParams, progress: Progress): Promise { @@ -190,66 +190,66 @@ export class FrameDispatcher extends Dispatcher { - const value = await this._frame.getAttribute(progress, params.selector, params.name, params); + const value = await this._frame.getAttribute(progress, params.selector, params.name, { ...params, markTargets: progress.metadata.id }); return { value: value === null ? undefined : value }; } async inputValue(params: channels.FrameInputValueParams, progress: Progress): Promise { - const value = await this._frame.inputValue(progress, params.selector, params); + const value = await this._frame.inputValue(progress, params.selector, { ...params, markTargets: progress.metadata.id }); return { value }; } async isChecked(params: channels.FrameIsCheckedParams, progress: Progress): Promise { - return { value: await this._frame.isChecked(progress, params.selector, params) }; + return { value: await this._frame.isChecked(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async isDisabled(params: channels.FrameIsDisabledParams, progress: Progress): Promise { - return { value: await this._frame.isDisabled(progress, params.selector, params) }; + return { value: await this._frame.isDisabled(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async isEditable(params: channels.FrameIsEditableParams, progress: Progress): Promise { - return { value: await this._frame.isEditable(progress, params.selector, params) }; + return { value: await this._frame.isEditable(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async isEnabled(params: channels.FrameIsEnabledParams, progress: Progress): Promise { - return { value: await this._frame.isEnabled(progress, params.selector, params) }; + return { value: await this._frame.isEnabled(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async isHidden(params: channels.FrameIsHiddenParams, progress: Progress): Promise { - return { value: await this._frame.isHidden(progress, params.selector, params) }; + return { value: await this._frame.isHidden(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async isVisible(params: channels.FrameIsVisibleParams, progress: Progress): Promise { - return { value: await this._frame.isVisible(progress, params.selector, params) }; + return { value: await this._frame.isVisible(progress, params.selector, { ...params, markTargets: progress.metadata.id }) }; } async hover(params: channels.FrameHoverParams, progress: Progress): Promise { - return await this._frame.hover(progress, params.selector, params); + return await this._frame.hover(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async selectOption(params: channels.FrameSelectOptionParams, progress: Progress): Promise { const elements = (params.elements || []).map(e => (e as ElementHandleDispatcher)._elementHandle); - return { values: await this._frame.selectOption(progress, params.selector, elements, params.options || [], params) }; + return { values: await this._frame.selectOption(progress, params.selector, elements, params.options || [], { ...params, markTargets: progress.metadata.id }) }; } async setInputFiles(params: channels.FrameSetInputFilesParams, progress: Progress): Promise { - return await this._frame.setInputFiles(progress, params.selector, params); + return await this._frame.setInputFiles(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async type(params: channels.FrameTypeParams, progress: Progress): Promise { - return await this._frame.type(progress, params.selector, params.text, params); + return await this._frame.type(progress, params.selector, params.text, { ...params, markTargets: progress.metadata.id }); } async press(params: channels.FramePressParams, progress: Progress): Promise { - return await this._frame.press(progress, params.selector, params.key, params); + return await this._frame.press(progress, params.selector, params.key, { ...params, markTargets: progress.metadata.id }); } async check(params: channels.FrameCheckParams, progress: Progress): Promise { - return await this._frame.check(progress, params.selector, params); + return await this._frame.check(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async uncheck(params: channels.FrameUncheckParams, progress: Progress): Promise { - return await this._frame.uncheck(progress, params.selector, params); + return await this._frame.uncheck(progress, params.selector, { ...params, markTargets: progress.metadata.id }); } async waitForTimeout(params: channels.FrameWaitForTimeoutParams, progress: Progress): Promise { @@ -277,7 +277,7 @@ export class FrameDispatcher extends Dispatcher extends js.JSHandle { return this._page.delegate.getContentFrame(this); } - async getAttribute(progress: Progress, name: string): Promise { - return this._frame.getAttribute(progress, ':scope', name, {}, this); + async getAttribute(progress: Progress, name: string, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.getAttribute(progress, ':scope', name, options, this); } - async inputValue(progress: Progress): Promise { - return this._frame.inputValue(progress, ':scope', {}, this); + async inputValue(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.inputValue(progress, ':scope', options, this); } - async textContent(progress: Progress): Promise { - return this._frame.textContent(progress, ':scope', {}, this); + async textContent(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.textContent(progress, ':scope', options, this); } - async innerText(progress: Progress): Promise { - return this._frame.innerText(progress, ':scope', {}, this); + async innerText(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.innerText(progress, ':scope', options, this); } - async innerHTML(progress: Progress): Promise { - return this._frame.innerHTML(progress, ':scope', {}, this); + async innerHTML(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.innerHTML(progress, ':scope', options, this); } - async dispatchEvent(progress: Progress, type: string, eventInit: Object = {}) { - return this._frame.dispatchEvent(progress, ':scope', type, eventInit, {}, this); + async dispatchEvent(progress: Progress, type: string, eventInit: Object = {}, options: types.MarkTargetsOptions = {}) { + return this._frame.dispatchEvent(progress, ':scope', type, eventInit, options, this); } async _scrollRectIntoViewIfNeeded(progress: Progress, rect?: types.Rect): Promise<'error:notvisible' | 'error:notconnected' | 'done'> { @@ -518,17 +518,17 @@ export class ElementHandle extends js.JSHandle { return 'done'; } - private async _markAsTargetElement(progress: Progress) { - if (!progress.metadata.id) + private async _markAsTargetElement(progress: Progress, markTargets: string | undefined) { + if (!markTargets) return; - await progress.race(this.evaluateInUtility(([injected, node, callId]) => { + await progress.race(this.evaluateInUtility(([injected, node, markTargets]) => { if (node.nodeType === 1 /* Node.ELEMENT_NODE */) - injected.markTargetElements(new Set([node as Node as Element]), callId); - }, progress.metadata.id)); + injected.markTargetElements(new Set([node as Node as Element]), markTargets); + }, markTargets)); } async hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._hover(progress, options); return assertDone(throwRetargetableDOMError(result)); } @@ -538,7 +538,7 @@ export class ElementHandle extends js.JSHandle { } async click(progress: Progress, options: { noWaitAfter?: boolean } & types.MouseClickOptions & types.PointerActionWaitOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._click(progress, { ...options, waitAfter: !options.noWaitAfter }); return assertDone(throwRetargetableDOMError(result)); } @@ -548,7 +548,7 @@ export class ElementHandle extends js.JSHandle { } async dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._dblclick(progress, options); return assertDone(throwRetargetableDOMError(result)); } @@ -558,7 +558,7 @@ export class ElementHandle extends js.JSHandle { } async tap(progress: Progress, options: types.PointerActionWaitOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._tap(progress, options); return assertDone(throwRetargetableDOMError(result)); } @@ -568,7 +568,7 @@ export class ElementHandle extends js.JSHandle { } async selectOption(progress: Progress, elements: ElementHandle[], values: types.SelectOption[], options: types.CommonActionOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._selectOption(progress, elements, values, options); return throwRetargetableDOMError(result); } @@ -607,7 +607,7 @@ export class ElementHandle extends js.JSHandle { } async fill(progress: Progress, value: string, options: types.CommonActionOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._fill(progress, value, options); assertDone(throwRetargetableDOMError(result)); } @@ -654,9 +654,9 @@ export class ElementHandle extends js.JSHandle { assertDone(throwRetargetableDOMError(result)); } - async setInputFiles(progress: Progress, params: Omit) { + async setInputFiles(progress: Progress, params: Omit & types.MarkTargetsOptions) { const inputFileItems = await progress.race(prepareFilesForUpload(this._frame, params)); - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, params.markTargets); const result = await this._setInputFiles(progress, inputFileItems); return assertDone(throwRetargetableDOMError(result)); } @@ -768,8 +768,8 @@ export class ElementHandle extends js.JSHandle { return 'done'; } - async focus(progress: Progress): Promise { - await this._markAsTargetElement(progress); + async focus(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + await this._markAsTargetElement(progress, options.markTargets); const result = await this._focus(progress); return assertDone(throwRetargetableDOMError(result)); } @@ -783,7 +783,7 @@ export class ElementHandle extends js.JSHandle { } async type(progress: Progress, text: string, options: { delay?: number } & types.StrictOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._type(progress, text, options); return assertDone(throwRetargetableDOMError(result)); } @@ -799,7 +799,7 @@ export class ElementHandle extends js.JSHandle { } async press(progress: Progress, key: string, options: { delay?: number, noWaitAfter?: boolean } & types.StrictOptions): Promise { - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const result = await this._press(progress, key, options); return assertDone(throwRetargetableDOMError(result)); } @@ -833,7 +833,7 @@ export class ElementHandle extends js.JSHandle { throwElementIsNotAttached(); return { matches: result.matches, isRadio: result.isRadio }; }; - await this._markAsTargetElement(progress); + await this._markAsTargetElement(progress, options.markTargets); const checkedState = await isChecked(progress); if (checkedState.matches === state) return 'done'; @@ -882,28 +882,28 @@ export class ElementHandle extends js.JSHandle { return this._frame.evalOnSelectorAll(progress, selector, expression, isFunction, arg, this); } - async isVisible(progress: Progress): Promise { - return this._frame.isVisible(progress, ':scope', {}, this); + async isVisible(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isVisible(progress, ':scope', options, this); } - async isHidden(progress: Progress): Promise { - return this._frame.isHidden(progress, ':scope', {}, this); + async isHidden(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isHidden(progress, ':scope', options, this); } - async isEnabled(progress: Progress): Promise { - return this._frame.isEnabled(progress, ':scope', {}, this); + async isEnabled(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isEnabled(progress, ':scope', options, this); } - async isDisabled(progress: Progress): Promise { - return this._frame.isDisabled(progress, ':scope', {}, this); + async isDisabled(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isDisabled(progress, ':scope', options, this); } - async isEditable(progress: Progress): Promise { - return this._frame.isEditable(progress, ':scope', {}, this); + async isEditable(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isEditable(progress, ':scope', options, this); } - async isChecked(progress: Progress): Promise { - return this._frame.isChecked(progress, ':scope', {}, this); + async isChecked(progress: Progress, options: types.MarkTargetsOptions = {}): Promise { + return this._frame.isChecked(progress, ':scope', options, this); } async waitForElementState(progress: Progress, state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled' | 'editable'): Promise { diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index c1c556e0fef73..9ac61801002a7 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1157,7 +1157,7 @@ export class Frame extends SdkObject { private async _retryWithProgressIfNotConnected( progress: Progress, selector: string, - options: { strict?: boolean, noAutoWaiting?: boolean, force?: boolean, performActionPreChecks?: boolean }, + options: { strict?: boolean, noAutoWaiting?: boolean, force?: boolean, performActionPreChecks?: boolean, markTargets?: string }, action: (progress: Progress, handle: dom.ElementHandle) => Promise): Promise { progress.log(`waiting for ${this._asLocator(selector)}`); const noAutoWaiting = (options as any).__testHookNoAutoWaiting ?? options.noAutoWaiting; @@ -1172,10 +1172,10 @@ export class Frame extends SdkObject { throw new dom.NonRecoverableDOMError('Element(s) not found'); return continuePolling; } - const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, callId }) => { + const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, markTargets }) => { const elements = injected.querySelectorAll(info.parsed, document); - if (callId) - injected.markTargetElements(new Set(elements), callId); + if (markTargets) + injected.markTargetElements(new Set(elements), markTargets); const element = elements[0] as Element | undefined; let log = ''; if (elements.length > 1) { @@ -1187,7 +1187,7 @@ export class Frame extends SdkObject { } injected.checkDeprecatedSelectorUsage(info.parsed, elements); return { log, success: !!element, element }; - }, { info: resolved.info, callId: progress.metadata.id })); + }, { info: resolved.info, markTargets: options.markTargets })); const { log, success } = await progress.race(result.evaluate(r => ({ log: r.log, success: r.success }))); if (log) progress.log(log); @@ -1417,7 +1417,7 @@ export class Frame extends SdkObject { return await this._retryWithProgressIfNotConnected(progress, selector, options, (progress, handle) => handle._selectOption(progress, elements, values, options)); } - async setInputFiles(progress: Progress, selector: string, params: Omit & { noAutoWaiting?: boolean }): Promise { + async setInputFiles(progress: Progress, selector: string, params: Omit & { noAutoWaiting?: boolean } & types.MarkTargetsOptions): Promise { const inputFileItems = await progress.race(prepareFilesForUpload(this, params)); return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params, (progress, handle) => handle._setInputFiles(progress, inputFileItems))); } @@ -1516,7 +1516,7 @@ export class Frame extends SdkObject { private async _expectInternal(progress: Progress, selector: string | undefined, options: FrameExpectParams, lastIntermediateResult: { received?: ExpectReceived, isSet: boolean, errorMessage?: string }, noAbort: boolean) { const progressLog = (text: string) => progress.log(text); - const callId = progress.metadata.id; + const markTargets = options.markTargets; // The first expect check, a.k.a. one-shot, always finishes - even when progress is aborted. if (noAbort) progress = nullProgress; @@ -1527,10 +1527,10 @@ export class Frame extends SdkObject { const context = await progress.race(frame.context(world)); const injected = await progress.race(context.injectedScript()); - const { log, matches, received, missingReceived } = await progress.race(injected.evaluate(async (injected, { info, options, callId }) => { + const { log, matches, received, missingReceived } = await progress.race(injected.evaluate(async (injected, { info, options, markTargets }) => { const elements = info ? injected.querySelectorAll(info.parsed, document) : []; - if (callId) - injected.markTargetElements(new Set(elements), callId); + if (markTargets) + injected.markTargetElements(new Set(elements), markTargets); const isArray = options.expression === 'to.have.count' || options.expression.endsWith('.array'); let log = ''; if (isArray) @@ -1542,7 +1542,7 @@ export class Frame extends SdkObject { if (info) injected.checkDeprecatedSelectorUsage(info.parsed, elements); return { log, ...await injected.expect(elements[0], options, elements) }; - }, { info, options, callId })); + }, { info, options, markTargets })); if (log) progressLog(log); @@ -1692,16 +1692,16 @@ export class Frame extends SdkObject { const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, options, scope)); if (!resolved) return continuePolling; - const { log, success, value } = await progress.race(resolved.injected.evaluate((injected, { info, callbackText, taskData, callId, root }) => { + const { log, success, value } = await progress.race(resolved.injected.evaluate((injected, { info, callbackText, taskData, markTargets, root }) => { const callback = injected.eval(callbackText) as ElementCallback; const element = injected.querySelector(info.parsed, root || document, info.strict); if (!element) return { success: false }; const log = ` locator resolved to ${injected.previewNode(element)}`; - if (callId) - injected.markTargetElements(new Set([element]), callId); + if (markTargets) + injected.markTargetElements(new Set([element]), markTargets); return { log, success: true, value: callback(injected, element, taskData as T) }; - }, { info: resolved.info, callbackText, taskData, callId: progress.metadata.id, root: resolved.frame === this ? scope : undefined })); + }, { info: resolved.info, callbackText, taskData, markTargets: options.markTargets, root: resolved.frame === this ? scope : undefined })); if (log) progress.log(log); if (!success) diff --git a/packages/playwright-core/src/server/types.ts b/packages/playwright-core/src/server/types.ts index 25c0f81206b75..c7d00b9c2dd81 100644 --- a/packages/playwright-core/src/server/types.ts +++ b/packages/playwright-core/src/server/types.ts @@ -20,7 +20,13 @@ export type { HeadersArray, Point, Quad, Rect, Size } from '@isomorphic/types'; import type * as channels from '@protocol/channels'; import type { ProxySettings } from '@utils/network'; -export type StrictOptions = { +// When set, the action marks resolved DOM elements with this token via +// injected.markTargetElements, so trace snapshots can highlight them. +export type MarkTargetsOptions = { + markTargets?: string, +}; + +export type StrictOptions = MarkTargetsOptions & { strict?: boolean, };