From 0d9ec60dc7e52bc3f77e4f032da3cd30216b2ef7 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 31 Mar 2023 15:52:01 -0700 Subject: [PATCH] chore: implement jsHandle._objectCount in chromium (#22127) --- .../playwright-core/src/client/jsHandle.ts | 7 ++++++ .../playwright-core/src/protocol/validator.ts | 6 +++++ .../src/server/chromium/crExecutionContext.ts | 8 ++++++ .../server/dispatchers/jsHandleDispatcher.ts | 4 +++ .../src/server/firefox/ffExecutionContext.ts | 4 +++ .../playwright-core/src/server/javascript.ts | 12 +++++++++ .../src/server/webkit/wkExecutionContext.ts | 4 +++ packages/protocol/src/channels.ts | 6 +++++ packages/protocol/src/protocol.yml | 4 +++ tests/page/page-object-count.spec.ts | 25 +++++++++++++++++++ 10 files changed, 80 insertions(+) create mode 100644 tests/page/page-object-count.spec.ts diff --git a/packages/playwright-core/src/client/jsHandle.ts b/packages/playwright-core/src/client/jsHandle.ts index 596f2acba0..c51c6694f3 100644 --- a/packages/playwright-core/src/client/jsHandle.ts +++ b/packages/playwright-core/src/client/jsHandle.ts @@ -67,6 +67,13 @@ export class JSHandle extends ChannelOwner im return await this._channel.dispose(); } + async _objectCount() { + return this._wrapApiCall(async () => { + const { count } = await this._channel.objectCount(); + return count; + }); + } + override toString(): string { return this._preview; } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 0d8935a809..1c87acb9ee 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1703,6 +1703,12 @@ scheme.JSHandleJsonValueResult = tObject({ value: tType('SerializedValue'), }); scheme.ElementHandleJsonValueResult = tType('JSHandleJsonValueResult'); +scheme.JSHandleObjectCountParams = tOptional(tObject({})); +scheme.ElementHandleObjectCountParams = tType('JSHandleObjectCountParams'); +scheme.JSHandleObjectCountResult = tObject({ + count: tNumber, +}); +scheme.ElementHandleObjectCountResult = tType('JSHandleObjectCountResult'); scheme.ElementHandleInitializer = tObject({ preview: tString, }); diff --git a/packages/playwright-core/src/server/chromium/crExecutionContext.ts b/packages/playwright-core/src/server/chromium/crExecutionContext.ts index d54505d283..c6952f5c82 100644 --- a/packages/playwright-core/src/server/chromium/crExecutionContext.ts +++ b/packages/playwright-core/src/server/chromium/crExecutionContext.ts @@ -102,6 +102,14 @@ export class CRExecutionContext implements js.ExecutionContextDelegate { async releaseHandle(objectId: js.ObjectId): Promise { await releaseObject(this._client, objectId); } + + async objectCount(objectId: js.ObjectId): Promise { + const result = await this._client.send('Runtime.queryObjects', { + prototypeObjectId: objectId + }); + const match = result.objects.description!.match(/Array\((\d+)\)/)!; + return +match[1]; + } } function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue { diff --git a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts index 88b623bb7b..869d766a25 100644 --- a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts @@ -61,6 +61,10 @@ export class JSHandleDispatcher extends Dispatcher { + return { count: await this._object.objectCount() }; + } + async dispose() { await this._object.dispose(); } diff --git a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts index ba981da942..3a6b931a47 100644 --- a/packages/playwright-core/src/server/firefox/ffExecutionContext.ts +++ b/packages/playwright-core/src/server/firefox/ffExecutionContext.ts @@ -98,6 +98,10 @@ export class FFExecutionContext implements js.ExecutionContextDelegate { objectId }); } + + objectCount(objectId: js.ObjectId): Promise { + throw new Error('Method not implemented in Firefox.'); + } } function checkException(exceptionDetails?: Protocol.Runtime.ExceptionDetails) { diff --git a/packages/playwright-core/src/server/javascript.ts b/packages/playwright-core/src/server/javascript.ts index db2c64e84e..0ceac99f2b 100644 --- a/packages/playwright-core/src/server/javascript.ts +++ b/packages/playwright-core/src/server/javascript.ts @@ -57,6 +57,7 @@ export interface ExecutionContextDelegate { getProperties(context: ExecutionContext, objectId: ObjectId): Promise>; createHandle(context: ExecutionContext, remoteObject: RemoteObject): JSHandle; releaseHandle(objectId: ObjectId): Promise; + objectCount(objectId: ObjectId): Promise; } export class ExecutionContext extends SdkObject { @@ -126,6 +127,10 @@ export class ExecutionContext extends SdkObject { return this._utilityScriptPromise; } + async objectCount(objectId: ObjectId): Promise { + return this._delegate.objectCount(objectId); + } + async doSlowMo() { // overridden in FrameExecutionContext } @@ -227,6 +232,13 @@ export class JSHandle extends SdkObject { if (this._previewCallback) this._previewCallback(preview); } + + + async objectCount(): Promise { + if (!this._objectId) + throw new Error('Can only count objects for a handle that points to the constructor prototype'); + return this._context.objectCount(this._objectId); + } } export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise { diff --git a/packages/playwright-core/src/server/webkit/wkExecutionContext.ts b/packages/playwright-core/src/server/webkit/wkExecutionContext.ts index 85c9847c13..666331a6f1 100644 --- a/packages/playwright-core/src/server/webkit/wkExecutionContext.ts +++ b/packages/playwright-core/src/server/webkit/wkExecutionContext.ts @@ -116,6 +116,10 @@ export class WKExecutionContext implements js.ExecutionContextDelegate { async releaseHandle(objectId: js.ObjectId): Promise { await this._session.send('Runtime.releaseObject', { objectId }); } + + objectCount(objectId: js.ObjectId): Promise { + throw new Error('Method not implemented in WebKit.'); + } } function potentiallyUnserializableValue(remoteObject: Protocol.Runtime.RemoteObject): any { diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 09e70f5e2d..b353c9a6ec 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -2976,6 +2976,7 @@ export interface JSHandleChannel extends JSHandleEventTarget, Channel { getPropertyList(params?: JSHandleGetPropertyListParams, metadata?: CallMetadata): Promise; getProperty(params: JSHandleGetPropertyParams, metadata?: CallMetadata): Promise; jsonValue(params?: JSHandleJsonValueParams, metadata?: CallMetadata): Promise; + objectCount(params?: JSHandleObjectCountParams, metadata?: CallMetadata): Promise; } export type JSHandlePreviewUpdatedEvent = { preview: string, @@ -3027,6 +3028,11 @@ export type JSHandleJsonValueOptions = {}; export type JSHandleJsonValueResult = { value: SerializedValue, }; +export type JSHandleObjectCountParams = {}; +export type JSHandleObjectCountOptions = {}; +export type JSHandleObjectCountResult = { + count: number, +}; export interface JSHandleEvents { 'previewUpdated': JSHandlePreviewUpdatedEvent; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 32a4833861..514b6ca783 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -2295,6 +2295,10 @@ JSHandle: returns: value: SerializedValue + objectCount: + returns: + count: number + events: previewUpdated: diff --git a/tests/page/page-object-count.spec.ts b/tests/page/page-object-count.spec.ts new file mode 100644 index 0000000000..57377b76e7 --- /dev/null +++ b/tests/page/page-object-count.spec.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from './pageTest'; + +test('should count objects', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium'); + await page.setContent(''); + await page.evaluate(() => document.querySelectorAll('button')); + const proto = await page.evaluateHandle(() => HTMLButtonElement.prototype); + expect(await (proto as any)._objectCount()).toBe(1); +});