mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: Add BiDi Page.evaluate (#9609)
This commit is contained in:
parent
45b7197a73
commit
abcc1756dd
11
package-lock.json
generated
11
package-lock.json
generated
@ -40,7 +40,6 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||||
"@typescript-eslint/parser": "5.46.1",
|
"@typescript-eslint/parser": "5.46.1",
|
||||||
"c8": "7.12.0",
|
"c8": "7.12.0",
|
||||||
"chromium-bidi": "0.4.3",
|
|
||||||
"commonmark": "0.30.0",
|
"commonmark": "0.30.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"diff": "5.1.0",
|
"diff": "5.1.0",
|
||||||
@ -2663,7 +2662,6 @@
|
|||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
||||||
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
||||||
"dev": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"devtools-protocol": "*",
|
"devtools-protocol": "*",
|
||||||
"mitt": "*"
|
"mitt": "*"
|
||||||
@ -6036,8 +6034,7 @@
|
|||||||
"node_modules/mitt": {
|
"node_modules/mitt": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
||||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==",
|
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp-classic": {
|
"node_modules/mkdirp-classic": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
@ -8677,6 +8674,7 @@
|
|||||||
"version": "19.6.3",
|
"version": "19.6.3",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chromium-bidi": "0.4.3",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1082910",
|
"devtools-protocol": "0.0.1082910",
|
||||||
@ -11003,7 +11001,6 @@
|
|||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.3.tgz",
|
||||||
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
"integrity": "sha512-A40H1rdpJqkTdnGhnYDzMhtDdIbkXNFj2wgIfivMXL7LyHFDmBtv1hdyycDhnxtYunbPLDZtTs/n+ZT5j7Vnew==",
|
||||||
"dev": true,
|
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"cli-cursor": {
|
"cli-cursor": {
|
||||||
@ -13404,8 +13401,7 @@
|
|||||||
"mitt": {
|
"mitt": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
|
||||||
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==",
|
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"mkdirp-classic": {
|
"mkdirp-classic": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
@ -14106,6 +14102,7 @@
|
|||||||
"puppeteer-core": {
|
"puppeteer-core": {
|
||||||
"version": "file:packages/puppeteer-core",
|
"version": "file:packages/puppeteer-core",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"chromium-bidi": "0.4.3",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1082910",
|
"devtools-protocol": "0.0.1082910",
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||||
"@typescript-eslint/parser": "5.46.1",
|
"@typescript-eslint/parser": "5.46.1",
|
||||||
"c8": "7.12.0",
|
"c8": "7.12.0",
|
||||||
"chromium-bidi": "0.4.3",
|
|
||||||
"commonmark": "0.30.0",
|
"commonmark": "0.30.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"diff": "5.1.0",
|
"diff": "5.1.0",
|
||||||
|
@ -163,6 +163,7 @@
|
|||||||
"author": "The Chromium Authors",
|
"author": "The Chromium Authors",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chromium-bidi": "0.4.3",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"devtools-protocol": "0.0.1082910",
|
"devtools-protocol": "0.0.1082910",
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
createJSHandle,
|
createJSHandle,
|
||||||
getExceptionMessage,
|
getExceptionMessage,
|
||||||
isString,
|
isString,
|
||||||
|
stringifyFunction,
|
||||||
valueFromRemoteObject,
|
valueFromRemoteObject,
|
||||||
} from './util.js';
|
} from './util.js';
|
||||||
|
|
||||||
@ -266,29 +267,11 @@ export class ExecutionContext {
|
|||||||
: createJSHandle(this, remoteObject);
|
: createJSHandle(this, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
let functionText = pageFunction.toString();
|
|
||||||
try {
|
|
||||||
new Function('(' + functionText + ')');
|
|
||||||
} catch (error) {
|
|
||||||
// This means we might have a function shorthand. Try another
|
|
||||||
// time prefixing 'function '.
|
|
||||||
if (functionText.startsWith('async ')) {
|
|
||||||
functionText =
|
|
||||||
'async function ' + functionText.substring('async '.length);
|
|
||||||
} else {
|
|
||||||
functionText = 'function ' + functionText;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
new Function('(' + functionText + ')');
|
|
||||||
} catch (error) {
|
|
||||||
// We tried hard to serialize, but there's a weird beast here.
|
|
||||||
throw new Error('Passed function is not well-serializable!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let callFunctionOnPromise;
|
let callFunctionOnPromise;
|
||||||
try {
|
try {
|
||||||
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
|
callFunctionOnPromise = this._client.send('Runtime.callFunctionOn', {
|
||||||
functionDeclaration: functionText + '\n' + suffix + '\n',
|
functionDeclaration:
|
||||||
|
stringifyFunction(pageFunction) + '\n' + suffix + '\n',
|
||||||
executionContextId: this._contextId,
|
executionContextId: this._contextId,
|
||||||
arguments: await Promise.all(args.map(convertArgument.bind(this))),
|
arguments: await Promise.all(args.map(convertArgument.bind(this))),
|
||||||
returnByValue,
|
returnByValue,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js';
|
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js';
|
||||||
import {Connection as BidiPPtrConnection} from './Connection.js';
|
import {Connection as BidiPPtrConnection} from './Connection.js';
|
||||||
import {Bidi, BidiMapper} from '../../../third_party/chromium-bidi/index.js';
|
import * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
|
||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
|
||||||
import {Handler} from '../EventEmitter.js';
|
import {Handler} from '../EventEmitter.js';
|
||||||
|
|
||||||
|
@ -34,7 +34,10 @@ export class Browser extends BrowserBase {
|
|||||||
static async create(opts: Options): Promise<Browser> {
|
static async create(opts: Options): Promise<Browser> {
|
||||||
// TODO: await until the connection is established.
|
// TODO: await until the connection is established.
|
||||||
try {
|
try {
|
||||||
(await opts.connection.send('session.new', {})) as {sessionId: string};
|
// TODO: Add 'session.new' to BiDi types
|
||||||
|
(await opts.connection.send('session.new' as any, {})) as unknown as {
|
||||||
|
sessionId: string;
|
||||||
|
};
|
||||||
} catch {}
|
} catch {}
|
||||||
return new Browser(opts);
|
return new Browser(opts);
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,10 @@ export class BrowserContext extends BrowserContextBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async newPage(): Promise<PageBase> {
|
override async newPage(): Promise<PageBase> {
|
||||||
const result = (await this.#connection.send('browsingContext.create', {
|
const response = await this.#connection.send('browsingContext.create', {
|
||||||
type: 'tab',
|
type: 'tab',
|
||||||
})) as {context: string};
|
});
|
||||||
return new Page(this.#connection, result.context);
|
return new Page(this.#connection, response.result.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async close(): Promise<void> {}
|
override async close(): Promise<void> {}
|
||||||
|
@ -22,28 +22,32 @@ import {ConnectionTransport} from '../ConnectionTransport.js';
|
|||||||
import {EventEmitter} from '../EventEmitter.js';
|
import {EventEmitter} from '../EventEmitter.js';
|
||||||
import {ProtocolError} from '../Errors.js';
|
import {ProtocolError} from '../Errors.js';
|
||||||
import {ConnectionCallback} from '../Connection.js';
|
import {ConnectionCallback} from '../Connection.js';
|
||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
interface Command {
|
/**
|
||||||
id: number;
|
* @internal
|
||||||
method: string;
|
*/
|
||||||
params: object;
|
interface Commands {
|
||||||
}
|
'script.evaluate': {
|
||||||
|
params: Bidi.Script.EvaluateParameters;
|
||||||
interface CommandResponse {
|
returnType: Bidi.Script.EvaluateResult;
|
||||||
id: number;
|
};
|
||||||
result: object;
|
'script.callFunction': {
|
||||||
}
|
params: Bidi.Script.CallFunctionParameters;
|
||||||
|
returnType: Bidi.Script.CallFunctionResult;
|
||||||
interface ErrorResponse {
|
};
|
||||||
id: number;
|
'browsingContext.create': {
|
||||||
error: string;
|
params: Bidi.BrowsingContext.CreateParameters;
|
||||||
message: string;
|
returnType: Bidi.BrowsingContext.CreateResult;
|
||||||
stacktrace?: string;
|
};
|
||||||
}
|
'browsingContext.close': {
|
||||||
|
params: Bidi.BrowsingContext.CloseParameters;
|
||||||
interface Event {
|
returnType: Bidi.BrowsingContext.CloseResult;
|
||||||
method: string;
|
};
|
||||||
params: object;
|
'session.status': {
|
||||||
|
params: {context: string}; // TODO: Update Types in chromium bidi
|
||||||
|
returnType: Bidi.Session.StatusResult;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,13 +73,16 @@ export class Connection extends EventEmitter {
|
|||||||
return this.#closed;
|
return this.#closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(method: string, params: object): Promise<CommandResponse['result']> {
|
send<T extends keyof Commands>(
|
||||||
|
method: T,
|
||||||
|
params: Commands[T]['params']
|
||||||
|
): Promise<Commands[T]['returnType']> {
|
||||||
const id = ++this.#lastId;
|
const id = ++this.#lastId;
|
||||||
const stringifiedMessage = JSON.stringify({
|
const stringifiedMessage = JSON.stringify({
|
||||||
id,
|
id,
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
} as Command);
|
} as Bidi.Message.CommandRequest);
|
||||||
debugProtocolSend(stringifiedMessage);
|
debugProtocolSend(stringifiedMessage);
|
||||||
this.#transport.send(stringifiedMessage);
|
this.#transport.send(stringifiedMessage);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -99,9 +106,8 @@ export class Connection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
debugProtocolReceive(message);
|
debugProtocolReceive(message);
|
||||||
const object = JSON.parse(message) as
|
const object = JSON.parse(message) as
|
||||||
| Event
|
| Bidi.Message.CommandResponse
|
||||||
| ErrorResponse
|
| Bidi.EventResponse<string, unknown>;
|
||||||
| CommandResponse;
|
|
||||||
if ('id' in object) {
|
if ('id' in object) {
|
||||||
const callback = this.#callbacks.get(object.id);
|
const callback = this.#callbacks.get(object.id);
|
||||||
// Callbacks could be all rejected if someone has called `.dispose()`.
|
// Callbacks could be all rejected if someone has called `.dispose()`.
|
||||||
@ -112,7 +118,7 @@ export class Connection extends EventEmitter {
|
|||||||
createProtocolError(callback.error, callback.method, object)
|
createProtocolError(callback.error, callback.method, object)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
callback.resolve(object.result);
|
callback.resolve(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -154,10 +160,13 @@ function rewriteError(
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
function createProtocolError(
|
function createProtocolError(
|
||||||
error: ProtocolError,
|
error: ProtocolError,
|
||||||
method: string,
|
method: string,
|
||||||
object: ErrorResponse
|
object: Bidi.Message.ErrorResult
|
||||||
): Error {
|
): Error {
|
||||||
let message = `Protocol error (${method}): ${object.error} ${object.message}`;
|
let message = `Protocol error (${method}): ${object.error} ${object.message}`;
|
||||||
if (object.stacktrace) {
|
if (object.stacktrace) {
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
import {Page as PageBase} from '../../api/Page.js';
|
import {Page as PageBase} from '../../api/Page.js';
|
||||||
import {Connection} from './Connection.js';
|
import {Connection} from './Connection.js';
|
||||||
import type {EvaluateFunc} from '..//types.js';
|
import type {EvaluateFunc} from '../types.js';
|
||||||
|
import {isString, stringifyFunction} from '../util.js';
|
||||||
|
import {BidiSerializer} from './Serializer.js';
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -42,15 +43,32 @@ export class Page extends PageBase {
|
|||||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||||
>(
|
>(
|
||||||
pageFunction: Func | string,
|
pageFunction: Func | string,
|
||||||
..._args: Params
|
...args: Params
|
||||||
): Promise<Awaited<ReturnType<Func>>> {
|
): Promise<Awaited<ReturnType<Func>>> {
|
||||||
// TODO: re-use evaluate logic from Execution context.
|
let responsePromise;
|
||||||
const str = `(${pageFunction.toString()})()`;
|
if (isString(pageFunction)) {
|
||||||
const result = (await this.#connection.send('script.evaluate', {
|
responsePromise = this.#connection.send('script.evaluate', {
|
||||||
expression: str,
|
expression: pageFunction,
|
||||||
target: {context: this.#contextId},
|
target: {context: this.#contextId},
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
})) as {result: {type: string; value: any}};
|
});
|
||||||
return result.result.value;
|
} else {
|
||||||
|
responsePromise = this.#connection.send('script.callFunction', {
|
||||||
|
functionDeclaration: stringifyFunction(pageFunction),
|
||||||
|
arguments: await Promise.all(args.map(BidiSerializer.serialize)),
|
||||||
|
target: {context: this.#contextId},
|
||||||
|
awaitPromise: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const {result} = await responsePromise;
|
||||||
|
|
||||||
|
if ('type' in result && result.type === 'exception') {
|
||||||
|
throw new Error(result.exceptionDetails.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BidiSerializer.deserialize(result.result) as Awaited<
|
||||||
|
ReturnType<Func>
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
203
packages/puppeteer-core/src/common/bidi/Serializer.ts
Normal file
203
packages/puppeteer-core/src/common/bidi/Serializer.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
import {debugError, isPlainObject} from '../util.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class UnserializableError extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export class BidiSerializer {
|
||||||
|
static serializeNumber(arg: number): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||||
|
let value: Bidi.CommonDataTypes.SpecialNumber | number;
|
||||||
|
if (Object.is(arg, -0)) {
|
||||||
|
value = '-0';
|
||||||
|
} else if (Object.is(arg, Infinity)) {
|
||||||
|
value = 'Infinity';
|
||||||
|
} else if (Object.is(arg, -Infinity)) {
|
||||||
|
value = '-Infinity';
|
||||||
|
} else if (Object.is(arg, NaN)) {
|
||||||
|
value = 'NaN';
|
||||||
|
} else {
|
||||||
|
value = arg;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'number',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static serializeObject(
|
||||||
|
arg: object | null
|
||||||
|
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||||
|
if (arg === null) {
|
||||||
|
return {
|
||||||
|
type: 'null',
|
||||||
|
};
|
||||||
|
} else if (Array.isArray(arg)) {
|
||||||
|
const parsedArray = arg.map(subArg => {
|
||||||
|
return BidiSerializer.serializeRemoveValue(subArg);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
value: parsedArray,
|
||||||
|
};
|
||||||
|
} else if (isPlainObject(arg)) {
|
||||||
|
const parsedObject: Bidi.CommonDataTypes.MappingLocalValue = [];
|
||||||
|
for (const key in arg) {
|
||||||
|
parsedObject.push([
|
||||||
|
BidiSerializer.serializeRemoveValue(key),
|
||||||
|
BidiSerializer.serializeRemoveValue(arg[key]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
value: parsedObject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnserializableError(
|
||||||
|
'Custom object sterilization not possible. Use plain objects instead.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static serializeRemoveValue(
|
||||||
|
arg: unknown
|
||||||
|
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||||
|
switch (typeof arg) {
|
||||||
|
case 'symbol':
|
||||||
|
case 'function':
|
||||||
|
throw new UnserializableError(`Unable to serializable ${typeof arg}`);
|
||||||
|
case 'object':
|
||||||
|
return BidiSerializer.serializeObject(arg);
|
||||||
|
|
||||||
|
case 'undefined':
|
||||||
|
return {
|
||||||
|
type: 'undefined',
|
||||||
|
};
|
||||||
|
case 'number':
|
||||||
|
return BidiSerializer.serializeNumber(arg);
|
||||||
|
case 'bigint':
|
||||||
|
return {
|
||||||
|
type: 'bigint',
|
||||||
|
value: arg.toString(),
|
||||||
|
};
|
||||||
|
case 'string':
|
||||||
|
return {
|
||||||
|
type: 'string',
|
||||||
|
value: arg,
|
||||||
|
};
|
||||||
|
case 'boolean':
|
||||||
|
return {
|
||||||
|
type: 'boolean',
|
||||||
|
value: arg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static serialize(arg: unknown): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||||
|
// TODO: See use case of LazyArgs
|
||||||
|
return BidiSerializer.serializeRemoveValue(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserializeNumber(
|
||||||
|
value: Bidi.CommonDataTypes.SpecialNumber | number
|
||||||
|
): number {
|
||||||
|
switch (value) {
|
||||||
|
case '-0':
|
||||||
|
return -0;
|
||||||
|
case 'NaN':
|
||||||
|
return NaN;
|
||||||
|
case 'Infinity':
|
||||||
|
case '+Infinity':
|
||||||
|
return Infinity;
|
||||||
|
case '-Infinity':
|
||||||
|
return -Infinity;
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserializeLocalValue(
|
||||||
|
result: Bidi.CommonDataTypes.RemoteValue
|
||||||
|
): unknown {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'array':
|
||||||
|
// TODO: Check expected output when value is undefined
|
||||||
|
return result.value?.map(value => {
|
||||||
|
return BidiSerializer.deserializeLocalValue(value);
|
||||||
|
});
|
||||||
|
case 'set':
|
||||||
|
// TODO: Check expected output when value is undefined
|
||||||
|
return result.value.reduce((acc: Set<unknown>, value: unknown) => {
|
||||||
|
return acc.add(value);
|
||||||
|
}, new Set());
|
||||||
|
case 'object':
|
||||||
|
if (result.value) {
|
||||||
|
return result.value.reduce((acc: Record<any, unknown>, tuple) => {
|
||||||
|
const {key, value} = BidiSerializer.deserializeTuple(tuple);
|
||||||
|
acc[key as any] = value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'map':
|
||||||
|
return result.value.reduce((acc: Map<unknown, unknown>, tuple) => {
|
||||||
|
const {key, value} = BidiSerializer.deserializeTuple(tuple);
|
||||||
|
return acc.set(key, value);
|
||||||
|
}, new Map());
|
||||||
|
case 'promise':
|
||||||
|
return {};
|
||||||
|
case 'undefined':
|
||||||
|
return undefined;
|
||||||
|
case 'null':
|
||||||
|
return null;
|
||||||
|
case 'number':
|
||||||
|
return BidiSerializer.deserializeNumber(result.value);
|
||||||
|
case 'bigint':
|
||||||
|
return BigInt(result.value);
|
||||||
|
case 'boolean':
|
||||||
|
return Boolean(result.value);
|
||||||
|
case 'string':
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnserializableError(
|
||||||
|
`Deserialization of type ${result.type} not supported.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserializeTuple([serializedKey, serializedValue]: [
|
||||||
|
Bidi.CommonDataTypes.RemoteValue | string,
|
||||||
|
Bidi.CommonDataTypes.RemoteValue
|
||||||
|
]): {key: unknown; value: unknown} {
|
||||||
|
const key =
|
||||||
|
typeof serializedKey === 'string'
|
||||||
|
? serializedKey
|
||||||
|
: BidiSerializer.deserializeLocalValue(serializedKey);
|
||||||
|
const value = BidiSerializer.deserializeLocalValue(serializedValue);
|
||||||
|
|
||||||
|
return {key, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
static deserialize(result: Bidi.CommonDataTypes.RemoteValue): unknown {
|
||||||
|
if (!result) {
|
||||||
|
debugError('Service did not produce a result.');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return BidiSerializer.deserializeLocalValue(result);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof UnserializableError) {
|
||||||
|
debugError(error.message);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -159,6 +159,13 @@ export const isNumber = (obj: unknown): obj is number => {
|
|||||||
return typeof obj === 'number' || obj instanceof Number;
|
return typeof obj === 'number' || obj instanceof Number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const isPlainObject = (obj: unknown): obj is Record<any, unknown> => {
|
||||||
|
return typeof obj === 'object' && obj?.constructor === Object;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -440,3 +447,29 @@ export async function getReadableFromProtocolStream(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function stringifyFunction(expression: Function): string {
|
||||||
|
let functionText = expression.toString();
|
||||||
|
try {
|
||||||
|
new Function('(' + functionText + ')');
|
||||||
|
} catch (error) {
|
||||||
|
// This means we might have a function shorthand. Try another
|
||||||
|
// time prefixing 'function '.
|
||||||
|
if (functionText.startsWith('async ')) {
|
||||||
|
functionText =
|
||||||
|
'async function ' + functionText.substring('async '.length);
|
||||||
|
} else {
|
||||||
|
functionText = 'function ' + functionText;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Function('(' + functionText + ')');
|
||||||
|
} catch (error) {
|
||||||
|
// We tried hard to serialize, but there's a weird beast here.
|
||||||
|
throw new Error('Passed function is not well-serializable!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return functionText;
|
||||||
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2022 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
|
|
||||||
export * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|
@ -379,7 +379,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
|
||||||
"platforms": ["darwin", "win32"],
|
"platforms": ["darwin", "win32", "linux"],
|
||||||
"parameters": ["firefox"],
|
"parameters": ["firefox"],
|
||||||
"expectations": ["SKIP"]
|
"expectations": ["SKIP"]
|
||||||
},
|
},
|
||||||
@ -3174,9 +3174,111 @@
|
|||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work",
|
"testIdPattern": "[evaluation.spec]",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
"parameters": ["webDriverBiDi"],
|
"parameters": ["webDriverBiDi"],
|
||||||
"expectations": ["PASS"]
|
"expectations": ["PASS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should evaluate in the page context",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work right after framenavigated",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should work from-inside an exposed function",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should be able to throw a tricky error",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should accept element handle as an argument",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if underlying element was disposed",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw if elementHandles are from other frames",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw when evaluation triggers reload",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should simulate a user gesture",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should not throw an error when evaluation does a navigation",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument should evaluate before anything else on the page",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluateOnNewDocument should work with CSP",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have correct execution contexts",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should execute after cross-site navigation",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["webDriverBiDi"],
|
||||||
|
"expectations": ["SKIP"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -51,7 +51,7 @@ describe('WebDriver BiDi', () => {
|
|||||||
JSON.stringify(rawResponse)
|
JSON.stringify(rawResponse)
|
||||||
);
|
);
|
||||||
const response = await responsePromise;
|
const response = await responsePromise;
|
||||||
expect(response).toEqual(rawResponse.result);
|
expect(response).toEqual(rawResponse);
|
||||||
connection.dispose();
|
connection.dispose();
|
||||||
expect(transport.closed).toBeTruthy();
|
expect(transport.closed).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -328,6 +328,20 @@ describe('Evaluation specs', function () {
|
|||||||
})
|
})
|
||||||
).toBe(undefined);
|
).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
it('should return promise as empty object', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const result = await page.evaluate(() => {
|
||||||
|
return {
|
||||||
|
promise: new Promise(resolve => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
promise: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
it('should fail for circular object', async () => {
|
it('should fail for circular object', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user