diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index f49389eee1..059fe535bb 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -952,7 +952,7 @@ export function waitForSelectorTask(selector: SelectorInfo, state: 'attached' | return injectedScript => injectedScript.evaluateHandle((injected, { parsed, strict, state, omitReturnValue, root }) => { let lastElement: Element | undefined; - return injected.pollRaf((progress, continuePolling) => { + return injected.pollRaf(progress => { const elements = injected.querySelectorAll(parsed, root || document); let element: Element | undefined = elements[0]; const visible = element ? injected.isVisible(element) : false; @@ -977,13 +977,13 @@ export function waitForSelectorTask(selector: SelectorInfo, state: 'attached' | switch (state) { case 'attached': - return hasElement ? element : continuePolling; + return hasElement ? element : progress.continuePolling; case 'detached': - return !hasElement ? undefined : continuePolling; + return !hasElement ? undefined : progress.continuePolling; case 'visible': - return visible ? element : continuePolling; + return visible ? element : progress.continuePolling; case 'hidden': - return !visible ? undefined : continuePolling; + return !visible ? undefined : progress.continuePolling; } }); }, { parsed: selector.parsed, strict: selector.strict, state, omitReturnValue, root }); diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index fa9572337d..065f622559 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -70,7 +70,7 @@ export type NavigationEvent = { }; export type SchedulableTask = (injectedScript: js.JSHandle) => Promise>>; -export type DomTaskBody = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[], continuePolling: symbol) => R | symbol; +export type DomTaskBody = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[]) => R | symbol; export class FrameManager { private _page: Page; @@ -1158,7 +1158,7 @@ export class Frame extends SdkObject { const controller = new ProgressController(metadata, this); const querySelectorAll = options.expression === 'to.have.count' || options.expression.endsWith('.array'); const mainWorld = options.expression === 'to.have.property'; - return await this._scheduleRerunnableTaskWithController(controller, selector, (progress, element, options, elements, continuePolling) => { + return await this._scheduleRerunnableTaskWithController(controller, selector, (progress, element, options, elements) => { if (!element) { // expect(locator).toBeHidden() passes when there is no element. if (!options.isNot && options.expression === 'to.be.hidden') @@ -1181,7 +1181,7 @@ export class Frame extends SdkObject { return { matches: expectsEmptyCount, received: 0 }; // When none of the above applies, keep waiting for the element. - return continuePolling; + return progress.continuePolling; } const { matches, received } = progress.injectedScript.expect(progress, element, options, elements); @@ -1192,7 +1192,7 @@ export class Frame extends SdkObject { progress.setIntermediateResult(received); if (!Array.isArray(received)) progress.log(` unexpected value "${received}"`); - return continuePolling; + return progress.continuePolling; } // Reached the expected state! @@ -1226,8 +1226,8 @@ export class Frame extends SdkObject { return result; }; if (typeof polling !== 'number') - return injectedScript.pollRaf((progress, continuePolling) => predicate(arg) || continuePolling); - return injectedScript.pollInterval(polling, (progress, continuePolling) => predicate(arg) || continuePolling); + return injectedScript.pollRaf(progress => predicate(arg) || progress.continuePolling); + return injectedScript.pollInterval(polling, progress => predicate(arg) || progress.continuePolling); }, { expression, isFunction, polling: options.pollingInterval, arg }); return controller.run( progress => this._scheduleRerunnableHandleTask(progress, world, task), @@ -1286,7 +1286,7 @@ export class Frame extends SdkObject { const callback = injected.eval(callbackText) as DomTaskBody; const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected); let markedElements = new Set(); - return poller((progress, continuePolling) => { + return poller(progress => { let element: Element | undefined; let elements: Element[] = []; if (querySelectorAll) { @@ -1301,7 +1301,7 @@ export class Frame extends SdkObject { } if (!element && !omitAttached) - return continuePolling; + return progress.continuePolling; if (snapshotName) { const previouslyMarkedElements = markedElements; @@ -1316,7 +1316,7 @@ export class Frame extends SdkObject { } } - return callback(progress, element, taskData as T, elements, continuePolling); + return callback(progress, element, taskData as T, elements); }); }, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached, snapshotName: progress.metadata.afterSnapshot }); }, true); diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index e5b7bb4f8b..077257a972 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -24,10 +24,11 @@ import { CSSComplexSelectorList } from '../common/cssParser'; import { generateSelector } from './selectorGenerator'; import type * as channels from '../../protocol/channels'; -type Predicate = (progress: InjectedScriptProgress, continuePolling: symbol) => T | symbol; +type Predicate = (progress: InjectedScriptProgress) => T | symbol; export type InjectedScriptProgress = { injectedScript: InjectedScript; + continuePolling: symbol; aborted: boolean; log: (message: string) => void; logRepeating: (message: string) => void; @@ -293,9 +294,8 @@ export class InjectedScript { if (progress.aborted) return; try { - const continuePolling = Symbol('continuePolling'); - const success = predicate(progress, continuePolling); - if (success !== continuePolling) + const success = predicate(progress); + if (success !== progress.continuePolling) fulfill(success as T); else scheduleNext(next); @@ -333,6 +333,7 @@ export class InjectedScript { const progress: InjectedScriptProgress = { injectedScript: this, aborted: false, + continuePolling: Symbol('continuePolling'), log: (message: string) => { lastMessage = message; unsentLog.push({ message }); @@ -399,16 +400,16 @@ export class InjectedScript { } waitForElementStatesAndPerformAction(node: Node, states: ElementState[], force: boolean | undefined, - callback: (node: Node, progress: InjectedScriptProgress, continuePolling: symbol) => T | symbol): InjectedScriptPoll { + callback: (node: Node, progress: InjectedScriptProgress) => T | symbol): InjectedScriptPoll { let lastRect: { x: number, y: number, width: number, height: number } | undefined; let counter = 0; let samePositionCounter = 0; let lastTime = 0; - return this.pollRaf((progress, continuePolling) => { + return this.pollRaf(progress => { if (force) { progress.log(` forcing action`); - return callback(node, progress, continuePolling); + return callback(node, progress); } for (const state of states) { @@ -418,7 +419,7 @@ export class InjectedScript { return result; if (!result) { progress.logRepeating(` element is not ${state} - waiting...`); - return continuePolling; + return progress.continuePolling; } continue; } @@ -431,12 +432,12 @@ export class InjectedScript { // any client rect difference compared to synchronous call. We skip the synchronous call // and only force layout during actual rafs as a small optimisation. if (++counter === 1) - return continuePolling; + return progress.continuePolling; // Drop frames that are shorter than 16ms - WebKit Win bug. const time = performance.now(); if (this._stableRafCount > 1 && time - lastTime < 15) - return continuePolling; + return progress.continuePolling; lastTime = time; const clientRect = element.getBoundingClientRect(); @@ -452,10 +453,10 @@ export class InjectedScript { if (!isStableForLogs) progress.logRepeating(` element is not stable - waiting...`); if (!isStable) - return continuePolling; + return progress.continuePolling; } - return callback(node, progress, continuePolling); + return callback(node, progress); }); } @@ -495,7 +496,7 @@ export class InjectedScript { } selectOptions(optionsToSelect: (Node | { value?: string, label?: string, index?: number })[], - node: Node, progress: InjectedScriptProgress, continuePolling: symbol): string[] | 'error:notconnected' | symbol { + node: Node, progress: InjectedScriptProgress): string[] | 'error:notconnected' | symbol { const element = this.retarget(node, 'follow-label'); if (!element) return 'error:notconnected'; @@ -531,7 +532,7 @@ export class InjectedScript { } if (remainingOptionsToSelect.length) { progress.logRepeating(' did not find some options - waiting... '); - return continuePolling; + return progress.continuePolling; } select.value = undefined as any; selectedOptions.forEach(option => option.selected = true);