Implement basic element handles (#248)
This patch implements basic element handles which a backed with remote objects. Fixes #111
This commit is contained in:
parent
a424f5613a
commit
af89e893e7
102
docs/api.md
102
docs/api.md
@ -26,6 +26,7 @@
|
||||
+ [event: 'requestfailed'](#event-requestfailed)
|
||||
+ [event: 'requestfinished'](#event-requestfinished)
|
||||
+ [event: 'response'](#event-response)
|
||||
+ [page.$(selector)](#pageselector)
|
||||
+ [page.addBinding(name, puppeteerFunction)](#pageaddbindingname-puppeteerfunction)
|
||||
+ [page.addScriptTag(url)](#pageaddscripttagurl)
|
||||
+ [page.click(selector[, options])](#pageclickselector-options)
|
||||
@ -82,12 +83,10 @@
|
||||
+ [dialog.message()](#dialogmessage)
|
||||
+ [dialog.type](#dialogtype)
|
||||
* [class: Frame](#class-frame)
|
||||
+ [frame.$(selector)](#frameselector)
|
||||
+ [frame.addScriptTag(url)](#frameaddscripttagurl)
|
||||
+ [frame.childFrames()](#framechildframes)
|
||||
+ [frame.click(selector[, options])](#frameclickselector-options)
|
||||
+ [frame.evaluate(pageFunction, ...args)](#frameevaluatepagefunction-args)
|
||||
+ [frame.focus(selector)](#framefocusselector)
|
||||
+ [frame.hover(selector)](#framehoverselector)
|
||||
+ [frame.injectFile(filePath)](#frameinjectfilefilepath)
|
||||
+ [frame.isDetached()](#frameisdetached)
|
||||
+ [frame.name()](#framename)
|
||||
@ -98,6 +97,11 @@
|
||||
+ [frame.waitFor(selectorOrFunctionOrTimeout[, options])](#framewaitforselectororfunctionortimeout-options)
|
||||
+ [frame.waitForFunction(pageFunction[, options, ...args])](#framewaitforfunctionpagefunction-options-args)
|
||||
+ [frame.waitForSelector(selector[, options])](#framewaitforselectorselector-options)
|
||||
* [class: ElementHandle](#class-elementhandle)
|
||||
+ [elementHandle.click([options])](#elementhandleclickoptions)
|
||||
+ [elementHandle.evaluate(pageFunction, ...args)](#elementhandleevaluatepagefunction-args)
|
||||
+ [elementHandle.hover()](#elementhandlehover)
|
||||
+ [elementHandle.release()](#elementhandlerelease)
|
||||
* [class: Request](#class-request)
|
||||
+ [request.abort()](#requestabort)
|
||||
+ [request.continue([overrides])](#requestcontinueoverrides)
|
||||
@ -283,6 +287,13 @@ Emitted when a request is successfully finished.
|
||||
|
||||
Emitted when a [response] is received.
|
||||
|
||||
#### page.$(selector)
|
||||
- `selector` <[string]> Selector to query page for
|
||||
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
|
||||
|
||||
The method queries page for the selector. If there's no such element on the page, the method will resolve to `null`.
|
||||
|
||||
Shortcut for [page.mainFrame().$(selector)](#frameselector).
|
||||
|
||||
#### page.addBinding(name, puppeteerFunction)
|
||||
- `name` <[string]> Name of the binding on window object
|
||||
@ -345,7 +356,6 @@ puppeteer.launch().then(async browser => {
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### page.addScriptTag(url)
|
||||
- `url` <[string]> Url of a script to be added
|
||||
- returns: <[Promise]> Promise which resolves as the script gets added and loads.
|
||||
@ -932,6 +942,12 @@ puppeteer.launch().then(async browser => {
|
||||
});
|
||||
```
|
||||
|
||||
#### frame.$(selector)
|
||||
- `selector` <[string]> Selector to query page for
|
||||
- returns: <[Promise]<[ElementHandle]>> Promise which resolves to ElementHandle pointing to the page element.
|
||||
|
||||
The method queries page for the selector. If there's no such element on the page, the method will resolve to `null`.
|
||||
|
||||
|
||||
#### frame.addScriptTag(url)
|
||||
- `url` <[string]> Url of a script to be added
|
||||
@ -942,14 +958,6 @@ Adds a `<script>` tag to the frame with the desired url. Alternatively, JavaScri
|
||||
#### frame.childFrames()
|
||||
- returns: <[Array]<[Frame]>>
|
||||
|
||||
#### frame.click(selector[, options])
|
||||
- `selector` <[string]> A query [selector] to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
|
||||
- `options` <[Object]>
|
||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||
- `clickCount` <[number]> defaults to 1
|
||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully clicked. Promise gets rejected if there's no element matching `selector`.
|
||||
|
||||
#### frame.evaluate(pageFunction, ...args)
|
||||
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
@ -976,15 +984,6 @@ A string can also be passed in instead of a function.
|
||||
console.log(await page.evaluate('1 + 2')); // prints "3"
|
||||
```
|
||||
|
||||
|
||||
#### frame.focus(selector)
|
||||
- `selector` <[string]> A query [selector] of element to focus. If there are multiple elements satisfying the selector, the first will be focused.
|
||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully focused. Promise gets rejected if there's no element matching `selector`.
|
||||
|
||||
#### frame.hover(selector)
|
||||
- `selector` <[string]> A query [selector] to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.
|
||||
- returns: <[Promise]> Promise which resolves when the element matching `selector` is successfully hovered. Promise gets rejected if there's no element matching `selector`.
|
||||
|
||||
#### frame.injectFile(filePath)
|
||||
- `filePath` <[string]> Path to the JavaScript file to be injected into frame. If `filePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
|
||||
- returns: <[Promise]> Promise which resolves when file gets successfully evaluated in frame.
|
||||
@ -1079,6 +1078,66 @@ puppeteer.launch().then(async browser => {
|
||||
});
|
||||
```
|
||||
|
||||
### class: ElementHandle
|
||||
|
||||
ElementHandle represents an in-page DOM element. ElementHandles could be created with the [page.$](#pageselector) method.
|
||||
|
||||
```js
|
||||
const puppeteer = require('puppeteer');
|
||||
puppeteer.launch().then(async browser => {
|
||||
let page = await browser.newPage();
|
||||
await page.goto('https://google.com');
|
||||
let inputElement = await page.$('input[type=submit]');
|
||||
await inputElement.click();
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
ElementHandle prevents DOM element from garbage collection unless the handle is [released](#elementhandlerelease). ElementHandles are auto-released when their origin frame gets navigated.
|
||||
|
||||
#### elementHandle.click([options])
|
||||
- `options` <[Object]>
|
||||
- `button` <[string]> `left`, `right`, or `middle`, defaults to `left`.
|
||||
- `clickCount` <[number]> defaults to 1
|
||||
- `delay` <[number]> Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully clicked. Promise gets rejected if the element is detached from DOM.
|
||||
|
||||
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to click in the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
#### elementHandle.evaluate(pageFunction, ...args)
|
||||
- `pageFunction` <[function]> Function to be evaluated in browser context
|
||||
- `...args` <...[string]> Arguments to pass to `pageFunction`
|
||||
- returns: <[Promise]<[Object]>> Promise which resolves to function return value
|
||||
|
||||
If the function, passed to the `elementHandle.evaluate`, returns a [Promise], then `elementHandle.evaluate` would wait for the promise to resolve and return it's value.
|
||||
The function will be passed in the element ifself as a first argument.
|
||||
|
||||
#### elementHandle.hover()
|
||||
- returns: <[Promise]> Promise which resolves when the element is successfully hovered.
|
||||
|
||||
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to hover over the center of the element.
|
||||
If the element is detached from DOM, the method throws an error.
|
||||
|
||||
|
||||
#### elementHandle.release()
|
||||
- returns: <[Promise]> Promise which resolves when the element handle is successfully released.
|
||||
|
||||
The `elementHandle.release` method stops referencing the element handle.
|
||||
|
||||
```js
|
||||
const {Browser} = require('puppeteer');
|
||||
const browser = new Browser();
|
||||
browser.newPage().then(async page =>
|
||||
await page.setContent('<div>hello</div>');
|
||||
let element = await page.$('div');
|
||||
let text = element.evaluate((e, suffix) => e.textContent + ' ' + suffix, 'world!');
|
||||
console.log(text); // "hello world!"
|
||||
browser.close();
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### class: Request
|
||||
|
||||
Whenever the page sends a request, the following events are emitted by puppeteer's page:
|
||||
@ -1189,3 +1248,4 @@ Contains the URL of the response.
|
||||
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"
|
||||
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
|
||||
[Tracing]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-tracing "Tracing"
|
||||
[ElementHandle]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-element "ElementHandle"
|
||||
|
@ -166,6 +166,8 @@ class Session extends EventEmitter {
|
||||
* @return {!Promise<?Object>}
|
||||
*/
|
||||
send(method, params = {}) {
|
||||
if (!this._connection)
|
||||
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the page has been closed.`));
|
||||
let id = ++this._lastId;
|
||||
let message = JSON.stringify({id, method, params});
|
||||
debugSession('SEND ► ' + message);
|
||||
@ -212,6 +214,7 @@ class Session extends EventEmitter {
|
||||
for (let callback of this._callbacks.values())
|
||||
callback.reject(new Error(`Protocol error (${callback.method}): Target closed.`));
|
||||
this._callbacks.clear();
|
||||
this._connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
98
lib/ElementHandle.js
Normal file
98
lib/ElementHandle.js
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright 2017 Google Inc. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
const helper = require('./helper');
|
||||
|
||||
class ElementHandle {
|
||||
/**
|
||||
* @param {!Connection} client
|
||||
* @param {!Object} remoteObject
|
||||
* @param {!Mouse} mouse
|
||||
*/
|
||||
constructor(client, remoteObject, mouse) {
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
this._mouse = mouse;
|
||||
this._released = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async release() {
|
||||
if (this._released)
|
||||
return;
|
||||
this._released = true;
|
||||
await helper.releaseObject(this._client, this._remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
console.assert(!this._released, 'ElementHandle is released!');
|
||||
console.assert(typeof pageFunction === 'function', 'First argument to ElementHandle.evaluate must be a function!');
|
||||
|
||||
let stringifiedArgs = ['this'];
|
||||
stringifiedArgs.push(...args.map(x => JSON.stringify(x)));
|
||||
let functionDeclaration = `function() { return (${pageFunction})(${stringifiedArgs.join(',')}) }`;
|
||||
const objectId = this._remoteObject.objectId;
|
||||
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', { objectId, functionDeclaration, returnByValue: false});
|
||||
if (exceptionDetails)
|
||||
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<{x: number, y: number}>}
|
||||
*/
|
||||
async _visibleCenter() {
|
||||
let center = await this.evaluate(element => {
|
||||
if (!element.ownerDocument.contains(element))
|
||||
return null;
|
||||
element.scrollIntoViewIfNeeded();
|
||||
let rect = element.getBoundingClientRect();
|
||||
return {
|
||||
x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
|
||||
y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
|
||||
};
|
||||
});
|
||||
if (!center)
|
||||
throw new Error('No node found for selector: ' + selector);
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async hover() {
|
||||
let {x, y} = await this._visibleCenter();
|
||||
await this._mouse.move(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async click(options) {
|
||||
let {x, y} = await this._visibleCenter();
|
||||
await this._mouse.click(x, y, options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ElementHandle;
|
||||
helper.tracePublicAPI(ElementHandle);
|
@ -18,6 +18,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const EventEmitter = require('events');
|
||||
const helper = require('./helper');
|
||||
const ElementHandle = require('./ElementHandle');
|
||||
|
||||
class FrameManager extends EventEmitter {
|
||||
/**
|
||||
@ -171,12 +172,34 @@ class Frame {
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async evaluate(pageFunction, ...args) {
|
||||
let remoteObject = await this._rawEvaluate(pageFunction, ...args);
|
||||
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @return {!Promise<?ElementHandle>}
|
||||
*/
|
||||
async $(selector) {
|
||||
let remoteObject = await this._rawEvaluate(selector => document.querySelector(selector), selector);
|
||||
if (remoteObject.subtype === 'node')
|
||||
return new ElementHandle(this._client, remoteObject, this._mouse);
|
||||
helper.releaseObject(this._client, remoteObject);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()|string} pageFunction
|
||||
* @param {!Array<*>} args
|
||||
* @return {!Promise<(!Object|undefined)>}
|
||||
*/
|
||||
async _rawEvaluate(pageFunction, ...args) {
|
||||
let expression = helper.evaluationString(pageFunction, ...args);
|
||||
const contextId = this._defaultContextId;
|
||||
let { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false});
|
||||
if (exceptionDetails)
|
||||
throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
|
||||
return await helper.serializeRemoteObject(this._client, remoteObject);
|
||||
return remoteObject;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,62 +347,6 @@ class Frame {
|
||||
return this.evaluate(() => document.title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @return {!Promise<{x: number, y: number}>}
|
||||
*/
|
||||
async _centerOfElement(selector) {
|
||||
let center = await this.evaluate(selector => {
|
||||
let element = document.querySelector(selector);
|
||||
if (!element)
|
||||
return null;
|
||||
element.scrollIntoViewIfNeeded();
|
||||
let rect = element.getBoundingClientRect();
|
||||
return {
|
||||
x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
|
||||
y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
|
||||
};
|
||||
}, selector);
|
||||
if (!center)
|
||||
throw new Error('No node found for selector: ' + selector);
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async hover(selector) {
|
||||
let {x, y} = await this._centerOfElement(selector);
|
||||
await this._mouse.move(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {!Object=} options
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async click(selector, options) {
|
||||
let {x, y} = await this._centerOfElement(selector);
|
||||
await this._mouse.click(x, y, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async focus(selector) {
|
||||
let success = await this.evaluate(selector => {
|
||||
let node = document.querySelector(selector);
|
||||
if (!node)
|
||||
return false;
|
||||
node.focus();
|
||||
return true;
|
||||
}, selector);
|
||||
if (!success)
|
||||
throw new Error('No node found for selector: ' + selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} framePayload
|
||||
*/
|
||||
|
23
lib/Page.js
23
lib/Page.js
@ -138,6 +138,14 @@ class Page extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @return {!Promise<?ElementHandle>}
|
||||
*/
|
||||
async $(selector) {
|
||||
return this.mainFrame().$(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @return {!Promise}
|
||||
@ -555,7 +563,10 @@ class Page extends EventEmitter {
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async click(selector, options) {
|
||||
await this.mainFrame().click(selector, options);
|
||||
let handle = await this.$(selector);
|
||||
console.assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.click(options);
|
||||
await handle.release();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -563,7 +574,10 @@ class Page extends EventEmitter {
|
||||
* @param {!Promise}
|
||||
*/
|
||||
async hover(selector) {
|
||||
await this.mainFrame().hover(selector);
|
||||
let handle = await this.$(selector);
|
||||
console.assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.hover();
|
||||
await handle.release();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -571,7 +585,10 @@ class Page extends EventEmitter {
|
||||
* @return {!Promise}
|
||||
*/
|
||||
async focus(selector) {
|
||||
return this.mainFrame().focus(selector);
|
||||
let handle = await this.$(selector);
|
||||
console.assert(handle, 'No node found for selector: ' + selector);
|
||||
await handle.evaluate(element => element.focus());
|
||||
await handle.release();
|
||||
}
|
||||
|
||||
/**
|
||||
|
72
test/test.js
72
test/test.js
@ -1059,6 +1059,78 @@ describe('Page', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Page.$', function() {
|
||||
it('should query existing element', SX(async function() {
|
||||
await page.setContent('<section>test</section>');
|
||||
let element = await page.$('section');
|
||||
expect(element).toBeTruthy();
|
||||
}));
|
||||
it('should return null for non-existing element', SX(async function() {
|
||||
let element = await page.$('non-existing-element');
|
||||
expect(element).toBe(null);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ElementHandle.evaluate', function() {
|
||||
it('should work', SX(async function() {
|
||||
await page.setContent('<section>42</section>');
|
||||
let element = await page.$('section');
|
||||
let text = await element.evaluate(e => e.textContent);
|
||||
expect(text).toBe('42');
|
||||
}));
|
||||
it('should await promise if any', SX(async function() {
|
||||
await page.setContent('<section>39</section>');
|
||||
let element = await page.$('section');
|
||||
let text = await element.evaluate(e => Promise.resolve(e.textContent));
|
||||
expect(text).toBe('39');
|
||||
}));
|
||||
it('should throw if underlying page got closed', SX(async function() {
|
||||
let otherPage = await browser.newPage();
|
||||
await otherPage.setContent('<section>88</section>');
|
||||
let element = await otherPage.$('section');
|
||||
expect(element).toBeTruthy();
|
||||
await otherPage.close();
|
||||
let error = null;
|
||||
try {
|
||||
await element.evaluate(e => e.textContent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toContain('Session closed');
|
||||
}));
|
||||
it('should throw if underlying element was released', SX(async function() {
|
||||
await page.setContent('<section>39</section>');
|
||||
let element = await page.$('section');
|
||||
expect(element).toBeTruthy();
|
||||
await element.release();
|
||||
let error = null;
|
||||
try {
|
||||
await element.evaluate(e => e.textContent);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toContain('ElementHandle is released');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ElementHandle.click', function() {
|
||||
it('should work', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
let button = await page.$('button');
|
||||
await button.click('button');
|
||||
expect(await page.evaluate(() => result)).toBe('Clicked');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('ElementHandle.hover', function() {
|
||||
it('should work', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/scrollable.html');
|
||||
let button = await page.$('#button-6');
|
||||
await button.hover();
|
||||
expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('input', function() {
|
||||
it('should click the button', SX(async function() {
|
||||
await page.goto(PREFIX + '/input/button.html');
|
||||
|
@ -38,6 +38,7 @@ const EXCLUDE_METHODS = new Set([
|
||||
'Body.constructor',
|
||||
'Browser.constructor',
|
||||
'Dialog.constructor',
|
||||
'ElementHandle.constructor',
|
||||
'Frame.constructor',
|
||||
'Headers.constructor',
|
||||
'Headers.fromPayload',
|
||||
|
Loading…
Reference in New Issue
Block a user