chore: fix cross browser leak tests (#32843)
This commit is contained in:
parent
728b4814b4
commit
bcb6860ef5
|
|
@ -78,13 +78,6 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel> im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _objectCount() {
|
|
||||||
return await this._wrapApiCall(async () => {
|
|
||||||
const { count } = await this._channel.objectCount();
|
|
||||||
return count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
override toString(): string {
|
override toString(): string {
|
||||||
return this._preview;
|
return this._preview;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1843,12 +1843,6 @@ scheme.JSHandleJsonValueResult = tObject({
|
||||||
value: tType('SerializedValue'),
|
value: tType('SerializedValue'),
|
||||||
});
|
});
|
||||||
scheme.ElementHandleJsonValueResult = tType('JSHandleJsonValueResult');
|
scheme.ElementHandleJsonValueResult = tType('JSHandleJsonValueResult');
|
||||||
scheme.JSHandleObjectCountParams = tOptional(tObject({}));
|
|
||||||
scheme.ElementHandleObjectCountParams = tType('JSHandleObjectCountParams');
|
|
||||||
scheme.JSHandleObjectCountResult = tObject({
|
|
||||||
count: tNumber,
|
|
||||||
});
|
|
||||||
scheme.ElementHandleObjectCountResult = tType('JSHandleObjectCountResult');
|
|
||||||
scheme.ElementHandleInitializer = tObject({
|
scheme.ElementHandleInitializer = tObject({
|
||||||
preview: tString,
|
preview: tString,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -122,10 +122,6 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
objectCount(objectId: js.ObjectId): Promise<number> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise<bidi.Script.RemoteValue> {
|
async rawCallFunction(functionDeclaration: string, arg: bidi.Script.LocalValue): Promise<bidi.Script.RemoteValue> {
|
||||||
const response = await this._session.send('script.callFunction', {
|
const response = await this._session.send('script.callFunction', {
|
||||||
functionDeclaration,
|
functionDeclaration,
|
||||||
|
|
|
||||||
|
|
@ -102,14 +102,6 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
||||||
await releaseObject(this._client, objectId);
|
await releaseObject(this._client, objectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async objectCount(objectId: js.ObjectId): Promise<number> {
|
|
||||||
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 {
|
function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue {
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,6 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, channels.JSHandl
|
||||||
return { value: serializeResult(await this._object.jsonValue()) };
|
return { value: serializeResult(await this._object.jsonValue()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async objectCount(params?: channels.JSHandleObjectCountParams | undefined): Promise<channels.JSHandleObjectCountResult> {
|
|
||||||
return { count: await this._object.objectCount() };
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose(_: any, metadata: CallMetadata) {
|
async dispose(_: any, metadata: CallMetadata) {
|
||||||
metadata.potentiallyClosesScope = true;
|
metadata.potentiallyClosesScope = true;
|
||||||
this._object.dispose();
|
this._object.dispose();
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,6 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
||||||
objectId
|
objectId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
objectCount(objectId: js.ObjectId): Promise<number> {
|
|
||||||
throw new Error('Method not implemented in Firefox.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkException(exceptionDetails?: Protocol.Runtime.ExceptionDetails) {
|
function checkException(exceptionDetails?: Protocol.Runtime.ExceptionDetails) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ export interface ExecutionContextDelegate {
|
||||||
getProperties(context: ExecutionContext, objectId: ObjectId): Promise<Map<string, JSHandle>>;
|
getProperties(context: ExecutionContext, objectId: ObjectId): Promise<Map<string, JSHandle>>;
|
||||||
createHandle(context: ExecutionContext, remoteObject: RemoteObject): JSHandle;
|
createHandle(context: ExecutionContext, remoteObject: RemoteObject): JSHandle;
|
||||||
releaseHandle(objectId: ObjectId): Promise<void>;
|
releaseHandle(objectId: ObjectId): Promise<void>;
|
||||||
objectCount(objectId: ObjectId): Promise<number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionContext extends SdkObject {
|
export class ExecutionContext extends SdkObject {
|
||||||
|
|
@ -126,10 +125,6 @@ export class ExecutionContext extends SdkObject {
|
||||||
return this._utilityScriptPromise;
|
return this._utilityScriptPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async objectCount(objectId: ObjectId): Promise<number> {
|
|
||||||
return this._delegate.objectCount(objectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async doSlowMo() {
|
async doSlowMo() {
|
||||||
// overridden in FrameExecutionContext
|
// overridden in FrameExecutionContext
|
||||||
}
|
}
|
||||||
|
|
@ -246,12 +241,6 @@ export class JSHandle<T = any> extends SdkObject {
|
||||||
if (this._previewCallback)
|
if (this._previewCallback)
|
||||||
this._previewCallback(preview);
|
this._previewCallback(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
async objectCount(): Promise<number> {
|
|
||||||
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<any> {
|
export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
|
||||||
|
|
|
||||||
|
|
@ -116,10 +116,6 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
||||||
await this._session.send('Runtime.releaseObject', { objectId });
|
await this._session.send('Runtime.releaseObject', { objectId });
|
||||||
}
|
}
|
||||||
|
|
||||||
objectCount(objectId: js.ObjectId): Promise<number> {
|
|
||||||
throw new Error('Method not implemented in WebKit.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function potentiallyUnserializableValue(remoteObject: Protocol.Runtime.RemoteObject): any {
|
function potentiallyUnserializableValue(remoteObject: Protocol.Runtime.RemoteObject): any {
|
||||||
|
|
|
||||||
|
|
@ -3226,7 +3226,6 @@ export interface JSHandleChannel extends JSHandleEventTarget, Channel {
|
||||||
getPropertyList(params?: JSHandleGetPropertyListParams, metadata?: CallMetadata): Promise<JSHandleGetPropertyListResult>;
|
getPropertyList(params?: JSHandleGetPropertyListParams, metadata?: CallMetadata): Promise<JSHandleGetPropertyListResult>;
|
||||||
getProperty(params: JSHandleGetPropertyParams, metadata?: CallMetadata): Promise<JSHandleGetPropertyResult>;
|
getProperty(params: JSHandleGetPropertyParams, metadata?: CallMetadata): Promise<JSHandleGetPropertyResult>;
|
||||||
jsonValue(params?: JSHandleJsonValueParams, metadata?: CallMetadata): Promise<JSHandleJsonValueResult>;
|
jsonValue(params?: JSHandleJsonValueParams, metadata?: CallMetadata): Promise<JSHandleJsonValueResult>;
|
||||||
objectCount(params?: JSHandleObjectCountParams, metadata?: CallMetadata): Promise<JSHandleObjectCountResult>;
|
|
||||||
}
|
}
|
||||||
export type JSHandlePreviewUpdatedEvent = {
|
export type JSHandlePreviewUpdatedEvent = {
|
||||||
preview: string,
|
preview: string,
|
||||||
|
|
@ -3278,11 +3277,6 @@ export type JSHandleJsonValueOptions = {};
|
||||||
export type JSHandleJsonValueResult = {
|
export type JSHandleJsonValueResult = {
|
||||||
value: SerializedValue,
|
value: SerializedValue,
|
||||||
};
|
};
|
||||||
export type JSHandleObjectCountParams = {};
|
|
||||||
export type JSHandleObjectCountOptions = {};
|
|
||||||
export type JSHandleObjectCountResult = {
|
|
||||||
count: number,
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface JSHandleEvents {
|
export interface JSHandleEvents {
|
||||||
'previewUpdated': JSHandlePreviewUpdatedEvent;
|
'previewUpdated': JSHandlePreviewUpdatedEvent;
|
||||||
|
|
|
||||||
|
|
@ -2488,10 +2488,6 @@ JSHandle:
|
||||||
returns:
|
returns:
|
||||||
value: SerializedValue
|
value: SerializedValue
|
||||||
|
|
||||||
objectCount:
|
|
||||||
returns:
|
|
||||||
count: number
|
|
||||||
|
|
||||||
events:
|
events:
|
||||||
|
|
||||||
previewUpdated:
|
previewUpdated:
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,36 @@ function leakedJSHandles(): string {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function objectCounts(pageImpl, constructorName: string): Promise<{ main: number, utility: number }> {
|
async function weakRefObjects(pageImpl: any, selector: string) {
|
||||||
const result = { main: 0, utility: 0 };
|
|
||||||
for (const world of ['main', 'utility']) {
|
for (const world of ['main', 'utility']) {
|
||||||
const context = await pageImpl.mainFrame()._context(world);
|
const context = await pageImpl.mainFrame()._context(world);
|
||||||
const prototype = await context.evaluateHandle(name => (window as any)[name].prototype, constructorName);
|
await context.evaluate(selector => {
|
||||||
result[world] = await prototype.objectCount();
|
const elements = document.querySelectorAll(selector);
|
||||||
|
globalThis.weakRefs = globalThis.weakRefs || [];
|
||||||
|
for (const element of elements)
|
||||||
|
globalThis.weakRefs.push(new WeakRef(element));
|
||||||
|
}, selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function weakRefCount(pageImpl): Promise<{ main: number, utility: number }> {
|
||||||
|
const result = { main: 0, utility: 0 };
|
||||||
|
for (const world of ['main', 'utility']) {
|
||||||
|
await pageImpl.requestGC();
|
||||||
|
const context = await pageImpl.mainFrame()._context(world);
|
||||||
|
result[world] = await context.evaluate(() => globalThis.weakRefs.filter(r => !!r.deref()).length);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkWeakRefs(pageImpl, from: number, to: number) {
|
||||||
|
await expect(async () => {
|
||||||
|
const counts = await weakRefCount(pageImpl);
|
||||||
|
expect(counts.main + counts.utility).toBeGreaterThanOrEqual(from);
|
||||||
|
expect(counts.main + counts.utility).toBeLessThan(to);
|
||||||
|
}).toPass();
|
||||||
|
}
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
(globalThis as any).leakedJSHandles = new Map();
|
(globalThis as any).leakedJSHandles = new Map();
|
||||||
});
|
});
|
||||||
|
|
@ -59,14 +79,12 @@ test.afterEach(() => {
|
||||||
(globalThis as any).leakedJSHandles = null;
|
(globalThis as any).leakedJSHandles = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('click should not leak', async ({ page, browserName, toImpl }) => {
|
test('click should not leak', async ({ page, toImpl }) => {
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
<button>static button 1</button>
|
<button>static button 1</button>
|
||||||
<button>static button 2</button>
|
<button>static button 2</button>
|
||||||
<div id="buttons"></div>
|
<div id="buttons"></div>
|
||||||
`);
|
`);
|
||||||
// Create JS wrappers for static elements.
|
|
||||||
await page.evaluate(() => document.querySelectorAll('button'));
|
|
||||||
|
|
||||||
for (let i = 0; i < 25; ++i) {
|
for (let i = 0; i < 25; ++i) {
|
||||||
await page.evaluate(i => {
|
await page.evaluate(i => {
|
||||||
|
|
@ -74,24 +92,19 @@ test('click should not leak', async ({ page, browserName, toImpl }) => {
|
||||||
element.textContent = 'dynamic ' + i;
|
element.textContent = 'dynamic ' + i;
|
||||||
document.getElementById('buttons').appendChild(element);
|
document.getElementById('buttons').appendChild(element);
|
||||||
}, i);
|
}, i);
|
||||||
await page.locator('#buttons > button').click();
|
await page.locator('#buttons > button').last().click();
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById('buttons').textContent = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await weakRefObjects(toImpl(page), 'button');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById('buttons').textContent = '';
|
||||||
|
});
|
||||||
|
|
||||||
expect(leakedJSHandles()).toBeFalsy();
|
expect(leakedJSHandles()).toBeFalsy();
|
||||||
|
await checkWeakRefs(toImpl(page), 2, 25);
|
||||||
if (browserName === 'chromium') {
|
|
||||||
await expect(async () => {
|
|
||||||
const counts = await objectCounts(toImpl(page), 'HTMLButtonElement');
|
|
||||||
expect(counts.main + counts.utility).toBeGreaterThanOrEqual(2);
|
|
||||||
expect(counts.main + counts.utility).toBeLessThan(25);
|
|
||||||
}).toPass();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fill should not leak', async ({ page, mode, browserName, toImpl }) => {
|
test('fill should not leak', async ({ page, mode, toImpl }) => {
|
||||||
test.skip(mode !== 'default');
|
test.skip(mode !== 'default');
|
||||||
|
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
@ -99,32 +112,53 @@ test('fill should not leak', async ({ page, mode, browserName, toImpl }) => {
|
||||||
<input value="static input 2"</input>
|
<input value="static input 2"</input>
|
||||||
<div id="inputs"></div>
|
<div id="inputs"></div>
|
||||||
`);
|
`);
|
||||||
// Create JS wrappers for static elements.
|
|
||||||
await page.evaluate(() => document.querySelectorAll('input'));
|
|
||||||
|
|
||||||
for (let i = 0; i < 25; ++i) {
|
for (let i = 0; i < 25; ++i) {
|
||||||
await page.evaluate(i => {
|
await page.evaluate(() => {
|
||||||
const element = document.createElement('input');
|
const element = document.createElement('input');
|
||||||
document.getElementById('inputs').appendChild(element);
|
document.getElementById('inputs').appendChild(element);
|
||||||
}, i);
|
|
||||||
await page.locator('#inputs > input').fill('input ' + i);
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById('inputs').textContent = '';
|
|
||||||
});
|
});
|
||||||
|
await page.locator('#inputs > input').last().fill('input ' + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await weakRefObjects(toImpl(page), 'input');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById('inputs').textContent = '';
|
||||||
|
});
|
||||||
|
|
||||||
expect(leakedJSHandles()).toBeFalsy();
|
expect(leakedJSHandles()).toBeFalsy();
|
||||||
|
await checkWeakRefs(toImpl(page), 2, 25);
|
||||||
if (browserName === 'chromium') {
|
|
||||||
await expect(async () => {
|
|
||||||
const counts = await objectCounts(toImpl(page), 'HTMLInputElement');
|
|
||||||
expect(counts.main + counts.utility).toBeGreaterThanOrEqual(2);
|
|
||||||
expect(counts.main + counts.utility).toBeLessThan(25);
|
|
||||||
}).toPass();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('expect should not leak', async ({ page, mode, browserName, toImpl }) => {
|
test('expect should not leak', async ({ page, mode, toImpl }) => {
|
||||||
|
test.skip(mode !== 'default');
|
||||||
|
|
||||||
|
await page.setContent(`
|
||||||
|
<button>static button 1</button>
|
||||||
|
<button>static button 2</button>
|
||||||
|
<div id="buttons"></div>
|
||||||
|
`);
|
||||||
|
await weakRefObjects(toImpl(page), 'button');
|
||||||
|
|
||||||
|
for (let i = 0; i < 25; ++i) {
|
||||||
|
await page.evaluate(i => {
|
||||||
|
const element = document.createElement('button');
|
||||||
|
element.textContent = 'dynamic ' + i;
|
||||||
|
document.getElementById('buttons').appendChild(element);
|
||||||
|
}, i);
|
||||||
|
await expect(page.locator('#buttons > button').last()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await weakRefObjects(toImpl(page), 'button');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById('buttons').textContent = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(leakedJSHandles()).toBeFalsy();
|
||||||
|
await checkWeakRefs(toImpl(page), 2, 25);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('waitFor should not leak', async ({ page, mode, toImpl }) => {
|
||||||
test.skip(mode !== 'default');
|
test.skip(mode !== 'default');
|
||||||
|
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
@ -139,51 +173,14 @@ test('expect should not leak', async ({ page, mode, browserName, toImpl }) => {
|
||||||
element.textContent = 'dynamic ' + i;
|
element.textContent = 'dynamic ' + i;
|
||||||
document.getElementById('buttons').appendChild(element);
|
document.getElementById('buttons').appendChild(element);
|
||||||
}, i);
|
}, i);
|
||||||
await expect(page.locator('#buttons > button')).toBeVisible();
|
await page.locator('#buttons > button').last().waitFor();
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById('buttons').textContent = '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await weakRefObjects(toImpl(page), 'button');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById('buttons').textContent = '';
|
||||||
|
});
|
||||||
|
|
||||||
expect(leakedJSHandles()).toBeFalsy();
|
expect(leakedJSHandles()).toBeFalsy();
|
||||||
|
await checkWeakRefs(toImpl(page), 2, 25);
|
||||||
if (browserName === 'chromium') {
|
|
||||||
await expect(async () => {
|
|
||||||
const counts = await objectCounts(toImpl(page), 'HTMLButtonElement');
|
|
||||||
expect(counts.main + counts.utility).toBeGreaterThanOrEqual(2);
|
|
||||||
expect(counts.main + counts.utility).toBeLessThan(25);
|
|
||||||
}).toPass();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('waitFor should not leak', async ({ page, mode, browserName, toImpl }) => {
|
|
||||||
test.skip(mode !== 'default');
|
|
||||||
|
|
||||||
await page.setContent(`
|
|
||||||
<button>static button 1</button>
|
|
||||||
<button>static button 2</button>
|
|
||||||
<div id="buttons"></div>
|
|
||||||
`);
|
|
||||||
|
|
||||||
for (let i = 0; i < 25; ++i) {
|
|
||||||
await page.evaluate(i => {
|
|
||||||
const element = document.createElement('button');
|
|
||||||
element.textContent = 'dynamic ' + i;
|
|
||||||
document.getElementById('buttons').appendChild(element);
|
|
||||||
}, i);
|
|
||||||
await page.locator('#buttons > button').waitFor();
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById('buttons').textContent = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(leakedJSHandles()).toBeFalsy();
|
|
||||||
|
|
||||||
if (browserName === 'chromium') {
|
|
||||||
await expect(async () => {
|
|
||||||
const counts = await objectCounts(toImpl(page), 'HTMLButtonElement');
|
|
||||||
expect(counts.main + counts.utility).toBeGreaterThanOrEqual(2);
|
|
||||||
expect(counts.main + counts.utility).toBeLessThan(25);
|
|
||||||
}).toPass();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* 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('<button>Submit</button>');
|
|
||||||
await page.evaluate(() => document.querySelectorAll('button'));
|
|
||||||
const proto = await page.evaluateHandle(() => HTMLButtonElement.prototype);
|
|
||||||
expect(await (proto as any)._objectCount()).toBe(1);
|
|
||||||
});
|
|
||||||
Loading…
Reference in a new issue