From dc07fa6da6df9a97274060ede7aed51c92391339 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 7 Jan 2022 15:52:14 -0800 Subject: [PATCH] fix(locator.count): do not touch main workd when computing count (#11256) --- packages/playwright-core/src/client/frame.ts | 4 ++++ packages/playwright-core/src/client/locator.ts | 2 +- .../src/dispatchers/frameDispatcher.ts | 4 ++++ packages/playwright-core/src/protocol/channels.ts | 10 ++++++++++ packages/playwright-core/src/protocol/protocol.yml | 6 ++++++ packages/playwright-core/src/protocol/validator.ts | 3 +++ packages/playwright-core/src/server/dom.ts | 2 +- packages/playwright-core/src/server/frames.ts | 11 +++++++++-- packages/playwright-core/src/server/selectors.ts | 10 +++++++++- tests/page/locator-misc-2.spec.ts | 7 +++++++ 10 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/playwright-core/src/client/frame.ts b/packages/playwright-core/src/client/frame.ts index ed2769a5ee..b3497d321c 100644 --- a/packages/playwright-core/src/client/frame.ts +++ b/packages/playwright-core/src/client/frame.ts @@ -213,6 +213,10 @@ export class Frame extends ChannelOwner implements api.Fr return result.elements.map(e => ElementHandle.from(e) as ElementHandle); } + async _queryCount(selector: string): Promise { + return (await this._channel.queryCount({ selector })).value; + } + async content(): Promise { return (await this._channel.content()).value; } diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index 272f5787f1..d048385ebc 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -135,7 +135,7 @@ export class Locator implements api.Locator { } async count(): Promise { - return this.evaluateAll(ee => ee.length); + return this._frame._queryCount(this._selector); } async getAttribute(name: string, options?: TimeoutOptions): Promise { diff --git a/packages/playwright-core/src/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/dispatchers/frameDispatcher.ts index b90c3dbf04..e2cbd7c51d 100644 --- a/packages/playwright-core/src/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/dispatchers/frameDispatcher.ts @@ -100,6 +100,10 @@ export class FrameDispatcher extends Dispatcher im return { elements: elements.map(e => ElementHandleDispatcher.from(this._scope, e)) }; } + async queryCount(params: channels.FrameQueryCountParams): Promise { + return { value: await this._frame.queryCount(params.selector) }; + } + async content(): Promise { return { value: await this._frame.content() }; } diff --git a/packages/playwright-core/src/protocol/channels.ts b/packages/playwright-core/src/protocol/channels.ts index 11eff87d3f..a966f6bc60 100644 --- a/packages/playwright-core/src/protocol/channels.ts +++ b/packages/playwright-core/src/protocol/channels.ts @@ -1794,6 +1794,7 @@ export interface FrameChannel extends FrameEventTarget, Channel { press(params: FramePressParams, metadata?: Metadata): Promise; querySelector(params: FrameQuerySelectorParams, metadata?: Metadata): Promise; querySelectorAll(params: FrameQuerySelectorAllParams, metadata?: Metadata): Promise; + queryCount(params: FrameQueryCountParams, metadata?: Metadata): Promise; selectOption(params: FrameSelectOptionParams, metadata?: Metadata): Promise; setContent(params: FrameSetContentParams, metadata?: Metadata): Promise; setInputFiles(params: FrameSetInputFilesParams, metadata?: Metadata): Promise; @@ -2210,6 +2211,15 @@ export type FrameQuerySelectorAllOptions = { export type FrameQuerySelectorAllResult = { elements: ElementHandleChannel[], }; +export type FrameQueryCountParams = { + selector: string, +}; +export type FrameQueryCountOptions = { + +}; +export type FrameQueryCountResult = { + value: number, +}; export type FrameSelectOptionParams = { selector: string, strict?: boolean, diff --git a/packages/playwright-core/src/protocol/protocol.yml b/packages/playwright-core/src/protocol/protocol.yml index 648263f926..0bd3467ea4 100644 --- a/packages/playwright-core/src/protocol/protocol.yml +++ b/packages/playwright-core/src/protocol/protocol.yml @@ -1640,6 +1640,12 @@ Frame: type: array items: ElementHandle + queryCount: + parameters: + selector: string + returns: + value: number + selectOption: parameters: selector: string diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 1d5e59bf92..9bd8d2a48f 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -826,6 +826,9 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { scheme.FrameQuerySelectorAllParams = tObject({ selector: tString, }); + scheme.FrameQueryCountParams = tObject({ + selector: tString, + }); scheme.FrameSelectOptionParams = tObject({ selector: tString, strict: tOptional(tBoolean), diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 4663478c3a..50b2a52df7 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -797,7 +797,7 @@ export class ElementHandle extends js.JSHandle { throw new Error(`Error: failed to find frame for selector "${selector}"`); const { frame, info } = pair; // If we end up in the same frame => use the scope again, line above was noop. - const arrayHandle = await this._page.selectors._queryArray(frame, info, this._frame === frame ? this : undefined); + const arrayHandle = await this._page.selectors._queryArrayInMainWorld(frame, info, this._frame === frame ? this : undefined); const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); arrayHandle.dispose(); return result; diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 9016f033d0..ba7933302b 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -780,7 +780,7 @@ export class Frame extends SdkObject { const pair = await this.resolveFrameForSelectorNoWait(selector, {}); if (!pair) throw new Error(`Error: failed to find frame for selector "${selector}"`); - const arrayHandle = await this._page.selectors._queryArray(pair.frame, pair.info); + const arrayHandle = await this._page.selectors._queryArrayInMainWorld(pair.frame, pair.info); const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); arrayHandle.dispose(); return result; @@ -793,6 +793,13 @@ export class Frame extends SdkObject { return this._page.selectors._queryAll(pair.frame, pair.info, undefined, true /* adoptToMain */); } + async queryCount(selector: string): Promise { + const pair = await this.resolveFrameForSelectorNoWait(selector); + if (!pair) + throw new Error(`Error: failed to find frame for selector "${selector}"`); + return await this._page.selectors._queryCount(pair.frame, pair.info); + } + async content(): Promise { try { const context = await this._utilityContext(); @@ -1536,7 +1543,7 @@ export class Frame extends SdkObject { return { frame, info: this._page.parseSelector(frameChunks[frameChunks.length - 1], options) }; } - async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions & types.TimeoutOptions, scope?: dom.ElementHandle): Promise { + async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions & types.TimeoutOptions = {}, scope?: dom.ElementHandle): Promise { let frame: Frame | null = this; const frameChunks = splitSelectorByFrame(selector); diff --git a/packages/playwright-core/src/server/selectors.ts b/packages/playwright-core/src/server/selectors.ts index d452666e41..44c9829254 100644 --- a/packages/playwright-core/src/server/selectors.ts +++ b/packages/playwright-core/src/server/selectors.ts @@ -83,7 +83,7 @@ export class Selectors { return this._adoptIfNeeded(elementHandle, mainContext); } - async _queryArray(frame: frames.Frame, info: SelectorInfo, scope?: dom.ElementHandle): Promise> { + async _queryArrayInMainWorld(frame: frames.Frame, info: SelectorInfo, scope?: dom.ElementHandle): Promise> { const context = await frame._mainContext(); const injectedScript = await context.injectedScript(); const arrayHandle = await injectedScript.evaluateHandle((injected, { parsed, scope }) => { @@ -92,6 +92,14 @@ export class Selectors { return arrayHandle; } + async _queryCount(frame: frames.Frame, info: SelectorInfo, scope?: dom.ElementHandle): Promise { + const context = await frame._utilityContext(); + const injectedScript = await context.injectedScript(); + return await injectedScript.evaluate((injected, { parsed, scope }) => { + return injected.querySelectorAll(parsed, scope || document).length; + }, { parsed: info.parsed, scope }); + } + async _queryAll(frame: frames.Frame, selector: SelectorInfo, scope?: dom.ElementHandle, adoptToMain?: boolean): Promise[]> { const info = typeof selector === 'string' ? frame._page.parseSelector(selector) : selector; const context = await frame._context(info.world); diff --git a/tests/page/locator-misc-2.spec.ts b/tests/page/locator-misc-2.spec.ts index 1b33940556..923fb62ffb 100644 --- a/tests/page/locator-misc-2.spec.ts +++ b/tests/page/locator-misc-2.spec.ts @@ -113,3 +113,10 @@ it('should combine visible with other selectors', async ({ page }) => { await expect(locator).toHaveText('visible data2'); await expect(page.locator('.item >> visible=true >> text=data3')).toHaveText('visible data3'); }); + +it('locator.count should work with deleted Map in main world', async ({ page }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11254' }); + await page.evaluate('Map = 1'); + await page.locator('#searchResultTableDiv .x-grid3-row').count(); + await expect(page.locator('#searchResultTableDiv .x-grid3-row')).toHaveCount(0); +});