fix(locator.count): do not touch main workd when computing count (#11256)

This commit is contained in:
Pavel Feldman 2022-01-07 15:52:14 -08:00 committed by GitHub
parent 71a8da9c88
commit dc07fa6da6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 54 additions and 5 deletions

View File

@ -213,6 +213,10 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
return result.elements.map(e => ElementHandle.from(e) as ElementHandle<SVGElement | HTMLElement>);
}
async _queryCount(selector: string): Promise<number> {
return (await this._channel.queryCount({ selector })).value;
}
async content(): Promise<string> {
return (await this._channel.content()).value;
}

View File

@ -135,7 +135,7 @@ export class Locator implements api.Locator {
}
async count(): Promise<number> {
return this.evaluateAll(ee => ee.length);
return this._frame._queryCount(this._selector);
}
async getAttribute(name: string, options?: TimeoutOptions): Promise<string | null> {

View File

@ -100,6 +100,10 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel> im
return { elements: elements.map(e => ElementHandleDispatcher.from(this._scope, e)) };
}
async queryCount(params: channels.FrameQueryCountParams): Promise<channels.FrameQueryCountResult> {
return { value: await this._frame.queryCount(params.selector) };
}
async content(): Promise<channels.FrameContentResult> {
return { value: await this._frame.content() };
}

View File

@ -1794,6 +1794,7 @@ export interface FrameChannel extends FrameEventTarget, Channel {
press(params: FramePressParams, metadata?: Metadata): Promise<FramePressResult>;
querySelector(params: FrameQuerySelectorParams, metadata?: Metadata): Promise<FrameQuerySelectorResult>;
querySelectorAll(params: FrameQuerySelectorAllParams, metadata?: Metadata): Promise<FrameQuerySelectorAllResult>;
queryCount(params: FrameQueryCountParams, metadata?: Metadata): Promise<FrameQueryCountResult>;
selectOption(params: FrameSelectOptionParams, metadata?: Metadata): Promise<FrameSelectOptionResult>;
setContent(params: FrameSetContentParams, metadata?: Metadata): Promise<FrameSetContentResult>;
setInputFiles(params: FrameSetInputFilesParams, metadata?: Metadata): Promise<FrameSetInputFilesResult>;
@ -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,

View File

@ -1640,6 +1640,12 @@ Frame:
type: array
items: ElementHandle
queryCount:
parameters:
selector: string
returns:
value: number
selectOption:
parameters:
selector: string

View File

@ -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),

View File

@ -797,7 +797,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
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;

View File

@ -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<number> {
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<string> {
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<SelectorInFrame | null> {
async resolveFrameForSelectorNoWait(selector: string, options: types.StrictOptions & types.TimeoutOptions = {}, scope?: dom.ElementHandle): Promise<SelectorInFrame | null> {
let frame: Frame | null = this;
const frameChunks = splitSelectorByFrame(selector);

View File

@ -83,7 +83,7 @@ export class Selectors {
return this._adoptIfNeeded(elementHandle, mainContext);
}
async _queryArray(frame: frames.Frame, info: SelectorInfo, scope?: dom.ElementHandle): Promise<js.JSHandle<Element[]>> {
async _queryArrayInMainWorld(frame: frames.Frame, info: SelectorInfo, scope?: dom.ElementHandle): Promise<js.JSHandle<Element[]>> {
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<number> {
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<dom.ElementHandle<Element>[]> {
const info = typeof selector === 'string' ? frame._page.parseSelector(selector) : selector;
const context = await frame._context(info.world);

View File

@ -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);
});