playwright project

This commit is contained in:
Lukáš Kaňka
2023-08-24 19:31:32 +02:00
parent 187d46c64c
commit e31b2bd477
421 changed files with 103876 additions and 0 deletions

View File

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AndroidServerLauncherImpl = void 0;
var _utilsBundle = require("./utilsBundle");
var _utils = require("./utils");
var _playwright = require("./server/playwright");
var _playwrightServer = require("./remote/playwrightServer");
/**
* 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.
*/
class AndroidServerLauncherImpl {
async launchServer(options = {}) {
const playwright = (0, _playwright.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
// 1. Pre-connect to the device
let devices = await playwright.android.devices({
host: options.adbHost,
port: options.adbPort,
omitDriverInstall: options.omitDriverInstall
});
if (devices.length === 0) throw new Error('No devices found');
if (options.deviceSerialNumber) {
devices = devices.filter(d => d.serial === options.deviceSerialNumber);
if (devices.length === 0) throw new Error(`No device with serial number '${options.deviceSerialNumber}' not found`);
}
if (devices.length > 1) throw new Error(`More than one device found. Please specify deviceSerialNumber`);
const device = devices[0];
const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
// 2. Start the server
const server = new _playwrightServer.PlaywrightServer({
mode: 'launchServer',
path,
maxConnections: 1,
preLaunchedAndroidDevice: device
});
const wsEndpoint = await server.listen(options.port);
// 3. Return the BrowserServer interface
const browserServer = new _utilsBundle.ws.EventEmitter();
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
device.on('close', () => {
server.close();
browserServer.emit('close');
});
return browserServer;
}
}
exports.AndroidServerLauncherImpl = AndroidServerLauncherImpl;

View File

@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserServerLauncherImpl = void 0;
var _utilsBundle = require("./utilsBundle");
var _clientHelper = require("./client/clientHelper");
var _utils = require("./utils");
var _instrumentation = require("./server/instrumentation");
var _playwright = require("./server/playwright");
var _playwrightServer = require("./remote/playwrightServer");
var _helper = require("./server/helper");
var _stackTrace = require("./utils/stackTrace");
var _socksProxy = require("./common/socksProxy");
/**
* 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.
*/
class BrowserServerLauncherImpl {
constructor(browserName) {
this._browserName = void 0;
this._browserName = browserName;
}
async launchServer(options = {}) {
const playwright = (0, _playwright.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
// TODO: enable socks proxy once ipv6 is supported.
const socksProxy = false ? new _socksProxy.SocksProxy() : undefined;
playwright.options.socksProxyPort = await (socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.listen(0));
// 1. Pre-launch the browser
const metadata = (0, _instrumentation.serverSideCallMetadata)();
const browser = await playwright[this._browserName].launch(metadata, {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
}, toProtocolLogger(options.logger)).catch(e => {
const log = _helper.helper.formatBrowserLogs(metadata.log);
(0, _stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
throw e;
});
const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
// 2. Start the server
const server = new _playwrightServer.PlaywrightServer({
mode: 'launchServer',
path,
maxConnections: Infinity,
preLaunchedBrowser: browser,
preLaunchedSocksProxy: socksProxy
});
const wsEndpoint = await server.listen(options.port);
// 3. Return the BrowserServer interface
const browserServer = new _utilsBundle.ws.EventEmitter();
browserServer.process = () => browser.options.browserProcess.process;
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => browser.options.browserProcess.close();
browserServer.kill = () => browser.options.browserProcess.kill();
browserServer._disconnectForTest = () => server.close();
browserServer._userDataDirForTest = browser._userDataDirForTest;
browser.options.browserProcess.onclose = (exitCode, signal) => {
socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.close().catch(() => {});
server.close();
browserServer.emit('close', exitCode, signal);
};
return browserServer;
}
}
exports.BrowserServerLauncherImpl = BrowserServerLauncherImpl;
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
if (logger.isEnabled('protocol', 'verbose')) logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
} : undefined;
}

View File

@ -0,0 +1,65 @@
#!/usr/bin/env node
/**
* 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.
*/
/* eslint-disable no-console */
"use strict";
var _utils = require("../utils");
var _program = _interopRequireDefault(require("./program"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function printPlaywrightTestError(command) {
const packages = [];
for (const pkg of ['playwright', 'playwright-chromium', 'playwright-firefox', 'playwright-webkit']) {
try {
require.resolve(pkg);
packages.push(pkg);
} catch (e) {}
}
if (!packages.length) packages.push('playwright');
const packageManager = (0, _utils.getPackageManager)();
if (packageManager === 'yarn') {
console.error(`Please install @playwright/test package before running "yarn playwright ${command}"`);
console.error(` yarn remove ${packages.join(' ')}`);
console.error(' yarn add -D @playwright/test');
} else if (packageManager === 'pnpm') {
console.error(`Please install @playwright/test package before running "pnpm exec playwright ${command}"`);
console.error(` pnpm remove ${packages.join(' ')}`);
console.error(' pnpm add -D @playwright/test');
} else {
console.error(`Please install @playwright/test package before running "npx playwright ${command}"`);
console.error(` npm uninstall ${packages.join(' ')}`);
console.error(' npm install -D @playwright/test');
}
}
{
const command = _program.default.command('test').allowUnknownOption(true);
command.description('Run tests with Playwright Test. Available in @playwright/test package.');
command.action(async () => {
printPlaywrightTestError('test');
(0, _utils.gracefullyProcessExitDoNotHang)(1);
});
}
{
const command = _program.default.command('show-report').allowUnknownOption(true);
command.description('Show Playwright Test HTML report. Available in @playwright/test package.');
command.action(async () => {
printPlaywrightTestError('show-report');
(0, _utils.gracefullyProcessExitDoNotHang)(1);
});
}
_program.default.parse(process.argv);

View File

@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.launchBrowserServer = launchBrowserServer;
exports.printApiJson = printApiJson;
exports.runDriver = runDriver;
exports.runServer = runServer;
var _fs = _interopRequireDefault(require("fs"));
var playwright = _interopRequireWildcard(require("../.."));
var _server = require("../server");
var _transport = require("../protocol/transport");
var _playwrightServer = require("../remote/playwrightServer");
var _processLauncher = require("../utils/processLauncher");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
/* eslint-disable no-console */
function printApiJson() {
// Note: this file is generated by build-playwright-driver.sh
console.log(JSON.stringify(require('../../api.json')));
}
function runDriver() {
const dispatcherConnection = new _server.DispatcherConnection();
new _server.RootDispatcher(dispatcherConnection, async (rootScope, {
sdkLanguage
}) => {
const playwright = (0, _server.createPlaywright)({
sdkLanguage
});
return new _server.PlaywrightDispatcher(rootScope, playwright);
});
const transport = new _transport.PipeTransport(process.stdout, process.stdin);
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message));
transport.onclose = () => {
// Drop any messages during shutdown on the floor.
dispatcherConnection.onmessage = () => {};
(0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
};
// Ignore the SIGINT signal in the driver process so the parent can gracefully close the connection.
// We still will destruct everything (close browsers and exit) when the transport pipe closes.
process.on('SIGINT', () => {
// Keep the process running.
});
}
async function runServer(options) {
const {
port,
path = '/',
maxConnections = Infinity,
extension
} = options;
const server = new _playwrightServer.PlaywrightServer({
mode: extension ? 'extension' : 'default',
path,
maxConnections
});
const wsEndpoint = await server.listen(port);
process.on('exit', () => server.close().catch(console.error));
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
process.stdin.on('close', () => (0, _processLauncher.gracefullyProcessExitDoNotHang)(0));
}
async function launchBrowserServer(browserName, configFile) {
let options = {};
if (configFile) options = JSON.parse(_fs.default.readFileSync(configFile).toString());
const browserType = playwright[browserName];
const server = await browserType.launchServer(options);
console.log(server.wsEndpoint());
}

View File

@ -0,0 +1,569 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _utilsBundle = require("../utilsBundle");
var _driver = require("./driver");
var _traceViewer = require("../server/trace/viewer/traceViewer");
var playwright = _interopRequireWildcard(require("../.."));
var _child_process = require("child_process");
var _utils = require("../utils");
var _server = require("../server");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
/* eslint-disable no-console */
const packageJSON = require('../../package.json');
_utilsBundle.program.version('Version ' + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
_utilsBundle.program.command('mark-docker-image [dockerImageNameTemplate]', {
hidden: true
}).description('mark docker image').allowUnknownOption(true).action(function (dockerImageNameTemplate) {
(0, _utils.assert)(dockerImageNameTemplate, 'dockerImageNameTemplate is required');
(0, _server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
});
commandWithOpenOptions('open [url]', 'open page in browser specified via -b, --browser', []).action(function (url, options) {
open(options, url, codegenId()).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Examples:
$ open
$ open -b webkit https://example.com`);
commandWithOpenOptions('codegen [url]', 'open page and generate code for user actions', [['-o, --output <file name>', 'saves the generated script to a file'], ['--target <language>', `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java`, codegenId()], ['--save-trace <filename>', 'record a trace for the session and save it to a file'], ['--test-id-attribute <attributeName>', 'use the specified attribute to generate data test ID selectors']]).action(function (url, options) {
codegen(options, url).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Examples:
$ codegen
$ codegen --target=python
$ codegen -b webkit https://example.com`);
_utilsBundle.program.command('debug <app> [args...]', {
hidden: true
}).description('run command in debug mode: disable timeout, open inspector').allowUnknownOption(true).action(function (app, options) {
(0, _child_process.spawn)(app, options, {
env: {
...process.env,
PWDEBUG: '1'
},
stdio: 'inherit'
});
}).addHelpText('afterAll', `
Examples:
$ debug node test.js
$ debug npm run test`);
function suggestedBrowsersToInstall() {
return _server.registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', ');
}
function checkBrowsersToInstall(args) {
const faultyArguments = [];
const executables = [];
for (const arg of args) {
const executable = _server.registry.findExecutable(arg);
if (!executable || executable.installType === 'none') faultyArguments.push(arg);else executables.push(executable);
}
if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`);
return executables;
}
_utilsBundle.program.command('install [browser...]').description('ensure browsers necessary for this version of Playwright are installed').option('--with-deps', 'install system dependencies for browsers').option('--dry-run', 'do not execute installation, only print information').option('--force', 'force reinstall of stable browser channels').action(async function (args, options) {
if ((0, _utils.isLikelyNpxGlobal)()) {
console.error((0, _utils.wrapInASCIIBox)([`WARNING: It looks like you are running 'npx playwright install' without first`, `installing your project's dependencies.`, ``, `To avoid unexpected behavior, please install your dependencies first, and`, `then run Playwright's install command:`, ``, ` npm install`, ` npx playwright install`, ``, `If your project does not yet depend on Playwright, first install the`, `applicable npm package (most commonly @playwright/test), and`, `then run Playwright's install command to download the browsers:`, ``, ` npm install @playwright/test`, ` npx playwright install`, ``].join('\n'), 1));
}
try {
const hasNoArguments = !args.length;
const executables = hasNoArguments ? _server.registry.defaultExecutables() : checkBrowsersToInstall(args);
if (options.withDeps) await _server.registry.installDeps(executables, !!options.dryRun);
if (options.dryRun) {
for (const executable of executables) {
var _executable$directory, _executable$downloadU;
const version = executable.browserVersion ? `version ` + executable.browserVersion : '';
console.log(`browser: ${executable.name}${version ? ' ' + version : ''}`);
console.log(` Install location: ${(_executable$directory = executable.directory) !== null && _executable$directory !== void 0 ? _executable$directory : '<system>'}`);
if ((_executable$downloadU = executable.downloadURLs) !== null && _executable$downloadU !== void 0 && _executable$downloadU.length) {
const [url, ...fallbacks] = executable.downloadURLs;
console.log(` Download url: ${url}`);
for (let i = 0; i < fallbacks.length; ++i) console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
}
console.log(``);
}
} else {
const forceReinstall = hasNoArguments ? false : !!options.force;
await _server.registry.install(executables, forceReinstall);
}
} catch (e) {
console.log(`Failed to install browsers\n${e}`);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText('afterAll', `
Examples:
- $ install
Install default browsers.
- $ install chrome firefox
Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
_utilsBundle.program.command('uninstall').description('Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.').option('--all', 'Removes all browsers used by any Playwright installation from the system.').action(async options => {
await _server.registry.uninstall(!!options.all).then(({
numberOfBrowsersLeft
}) => {
if (!options.all && numberOfBrowsersLeft > 0) {
console.log('Successfully uninstalled Playwright browsers for the current Playwright installation.');
console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.\nTo uninstall Playwright browsers for all installations, re-run with --all flag.`);
}
}).catch(logErrorAndExit);
});
_utilsBundle.program.command('install-deps [browser...]').description('install dependencies necessary to run browsers (will ask for sudo permissions)').option('--dry-run', 'Do not execute installation commands, only print them').action(async function (args, options) {
try {
if (!args.length) await _server.registry.installDeps(_server.registry.defaultExecutables(), !!options.dryRun);else await _server.registry.installDeps(checkBrowsersToInstall(args), !!options.dryRun);
} catch (e) {
console.log(`Failed to install browser dependencies\n${e}`);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText('afterAll', `
Examples:
- $ install-deps
Install dependencies for default browsers.
- $ install-deps chrome firefox
Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
const browsers = [{
alias: 'cr',
name: 'Chromium',
type: 'chromium'
}, {
alias: 'ff',
name: 'Firefox',
type: 'firefox'
}, {
alias: 'wk',
name: 'WebKit',
type: 'webkit'
}];
for (const {
alias,
name,
type
} of browsers) {
commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function (url, options) {
open({
...options,
browser: type
}, url, options.target).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Examples:
$ ${alias} https://example.com`);
}
commandWithOpenOptions('screenshot <url> <filename>', 'capture a page screenshot', [['--wait-for-selector <selector>', 'wait for selector before taking a screenshot'], ['--wait-for-timeout <timeout>', 'wait for timeout in milliseconds before taking a screenshot'], ['--full-page', 'whether to take a full page screenshot (entire scrollable area)']]).action(function (url, filename, command) {
screenshot(command, command, url, filename).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Examples:
$ screenshot -b webkit https://example.com example.png`);
commandWithOpenOptions('pdf <url> <filename>', 'save page as pdf', [['--wait-for-selector <selector>', 'wait for given selector before saving as pdf'], ['--wait-for-timeout <timeout>', 'wait for given timeout in milliseconds before saving as pdf']]).action(function (url, filename, options) {
pdf(options, options, url, filename).catch(logErrorAndExit);
}).addHelpText('afterAll', `
Examples:
$ pdf https://example.com example.pdf`);
_utilsBundle.program.command('run-driver', {
hidden: true
}).action(function (options) {
(0, _driver.runDriver)();
});
_utilsBundle.program.command('run-server', {
hidden: true
}).option('--port <port>', 'Server port').option('--path <path>', 'Endpoint Path', '/').option('--max-clients <maxClients>', 'Maximum clients').option('--mode <mode>', 'Server mode, either "default" or "extension"').action(function (options) {
(0, _driver.runServer)({
port: options.port ? +options.port : undefined,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
extension: options.mode === 'extension' || !!process.env.PW_EXTENSION_MODE
}).catch(logErrorAndExit);
});
_utilsBundle.program.command('print-api-json', {
hidden: true
}).action(function (options) {
(0, _driver.printApiJson)();
});
_utilsBundle.program.command('launch-server', {
hidden: true
}).requiredOption('--browser <browserName>', 'Browser name, one of "chromium", "firefox" or "webkit"').option('--config <path-to-config-file>', 'JSON file with launchServer options').action(function (options) {
(0, _driver.launchBrowserServer)(options.browser, options.config);
});
_utilsBundle.program.command('show-trace [trace...]').option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('-h, --host <host>', 'Host to serve trace on; specifying this option opens trace in a browser tab').option('-p, --port <port>', 'Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab').option('--stdin', 'Accept trace URLs over stdin to update the viewer').description('show trace viewer').action(function (traces, options) {
if (options.browser === 'cr') options.browser = 'chromium';
if (options.browser === 'ff') options.browser = 'firefox';
if (options.browser === 'wk') options.browser = 'webkit';
const openOptions = {
headless: false,
host: options.host,
port: +options.port,
isServer: !!options.stdin
};
if (options.port !== undefined || options.host !== undefined) {
(0, _traceViewer.openTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);
} else {
(0, _traceViewer.openTraceViewerApp)(traces, options.browser, openOptions).then(page => {
page.on('close', () => (0, _utils.gracefullyProcessExitDoNotHang)(0));
}).catch(logErrorAndExit);
}
}).addHelpText('afterAll', `
Examples:
$ show-trace https://example.com/trace.zip`);
async function launchContext(options, headless, executablePath) {
validateOptions(options);
const browserType = lookupBrowserType(options);
const launchOptions = {
headless,
executablePath
};
if (options.channel) launchOptions.channel = options.channel;
launchOptions.handleSIGINT = false;
const contextOptions =
// Copy the device descriptor since we have to compare and modify the options.
options.device ? {
...playwright.devices[options.device]
} : {};
// In headful mode, use host device scale factor for things to look nice.
// In headless, keep things the way it works in Playwright by default.
// Assume high-dpi on MacOS. TODO: this is not perfect.
if (!headless) contextOptions.deviceScaleFactor = _os.default.platform() === 'darwin' ? 2 : 1;
// Work around the WebKit GTK scrolling issue.
if (browserType.name() === 'webkit' && process.platform === 'linux') {
delete contextOptions.hasTouch;
delete contextOptions.isMobile;
}
if (contextOptions.isMobile && browserType.name() === 'firefox') contextOptions.isMobile = undefined;
if (options.blockServiceWorkers) contextOptions.serviceWorkers = 'block';
// Proxy
if (options.proxyServer) {
launchOptions.proxy = {
server: options.proxyServer
};
if (options.proxyBypass) launchOptions.proxy.bypass = options.proxyBypass;
}
const browser = await browserType.launch(launchOptions);
if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
process._didSetSourcesForTest = text => {
process.stdout.write('\n-------------8<-------------\n');
process.stdout.write(text);
process.stdout.write('\n-------------8<-------------\n');
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
if (autoExitCondition && text.includes(autoExitCondition)) Promise.all(context.pages().map(async p => p.close()));
};
// Make sure we exit abnormally when browser crashes.
const logs = [];
require('playwright-core/lib/utilsBundle').debug.log = (...args) => {
const line = require('util').format(...args) + '\n';
logs.push(line);
process.stderr.write(line);
};
browser.on('disconnected', () => {
const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
if (hasCrashLine) {
process.stderr.write('Detected browser crash.\n');
(0, _utils.gracefullyProcessExitDoNotHang)(1);
}
});
}
// Viewport size
if (options.viewportSize) {
try {
const [width, height] = options.viewportSize.split(',').map(n => parseInt(n, 10));
contextOptions.viewport = {
width,
height
};
} catch (e) {
throw new Error('Invalid viewport size format: use "width, height", for example --viewport-size=800,600');
}
}
// Geolocation
if (options.geolocation) {
try {
const [latitude, longitude] = options.geolocation.split(',').map(n => parseFloat(n.trim()));
contextOptions.geolocation = {
latitude,
longitude
};
} catch (e) {
throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
}
contextOptions.permissions = ['geolocation'];
}
// User agent
if (options.userAgent) contextOptions.userAgent = options.userAgent;
// Lang
if (options.lang) contextOptions.locale = options.lang;
// Color scheme
if (options.colorScheme) contextOptions.colorScheme = options.colorScheme;
// Timezone
if (options.timezone) contextOptions.timezoneId = options.timezone;
// Storage
if (options.loadStorage) contextOptions.storageState = options.loadStorage;
if (options.ignoreHttpsErrors) contextOptions.ignoreHTTPSErrors = true;
// HAR
if (options.saveHar) {
contextOptions.recordHar = {
path: _path.default.resolve(process.cwd(), options.saveHar),
mode: 'minimal'
};
if (options.saveHarGlob) contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = 'block';
}
// Close app when the last window closes.
const context = await browser.newContext(contextOptions);
let closingBrowser = false;
async function closeBrowser() {
// We can come here multiple times. For example, saving storage creates
// a temporary page and we call closeBrowser again when that page closes.
if (closingBrowser) return;
closingBrowser = true;
if (options.saveTrace) await context.tracing.stop({
path: options.saveTrace
});
if (options.saveStorage) await context.storageState({
path: options.saveStorage
}).catch(e => null);
if (options.saveHar) await context.close();
await browser.close();
}
context.on('page', page => {
page.on('dialog', () => {}); // Prevent dialogs from being automatically dismissed.
page.on('close', () => {
const hasPage = browser.contexts().some(context => context.pages().length > 0);
if (hasPage) return;
// Avoid the error when the last page is closed because the browser has been closed.
closeBrowser().catch(e => null);
});
});
process.on('SIGINT', async () => {
await closeBrowser();
(0, _utils.gracefullyProcessExitDoNotHang)(130);
});
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
context.setDefaultTimeout(timeout);
context.setDefaultNavigationTimeout(timeout);
if (options.saveTrace) await context.tracing.start({
screenshots: true,
snapshots: true
});
// Omit options that we add automatically for presentation purpose.
delete launchOptions.headless;
delete launchOptions.executablePath;
delete launchOptions.handleSIGINT;
delete contextOptions.deviceScaleFactor;
return {
browser,
browserName: browserType.name(),
context,
contextOptions,
launchOptions
};
}
async function openPage(context, url) {
const page = await context.newPage();
if (url) {
if (_fs.default.existsSync(url)) url = 'file://' + _path.default.resolve(url);else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:')) url = 'http://' + url;
await page.goto(url).catch(error => {
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && error.message.includes('Navigation failed because page was closed')) {
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
// in a stray navigation aborted error. We should ignore it.
} else {
throw error;
}
});
}
return page;
}
async function open(options, url, language) {
const {
context,
launchOptions,
contextOptions
} = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH);
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage
});
await openPage(context, url);
}
async function codegen(options, url) {
const {
target: language,
output: outputFile,
testIdAttribute: testIdAttributeName
} = options;
const {
context,
launchOptions,
contextOptions
} = await launchContext(options, !!process.env.PWTEST_CLI_HEADLESS, process.env.PWTEST_CLI_EXECUTABLE_PATH);
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
mode: 'recording',
testIdAttributeName,
outputFile: outputFile ? _path.default.resolve(outputFile) : undefined,
handleSIGINT: false
});
await openPage(context, url);
}
async function waitForPage(page, captureOptions) {
if (captureOptions.waitForSelector) {
console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
await page.waitForSelector(captureOptions.waitForSelector);
}
if (captureOptions.waitForTimeout) {
console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
}
}
async function screenshot(options, captureOptions, url, path) {
const {
context
} = await launchContext(options, true);
console.log('Navigating to ' + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log('Capturing screenshot into ' + path);
await page.screenshot({
path,
fullPage: !!captureOptions.fullPage
});
// launchContext takes care of closing the browser.
await page.close();
}
async function pdf(options, captureOptions, url, path) {
if (options.browser !== 'chromium') throw new Error('PDF creation is only working with Chromium');
const {
context
} = await launchContext({
...options,
browser: 'chromium'
}, true);
console.log('Navigating to ' + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log('Saving as pdf into ' + path);
await page.pdf({
path
});
// launchContext takes care of closing the browser.
await page.close();
}
function lookupBrowserType(options) {
let name = options.browser;
if (options.device) {
const device = playwright.devices[options.device];
name = device.defaultBrowserType;
}
let browserType;
switch (name) {
case 'chromium':
browserType = playwright.chromium;
break;
case 'webkit':
browserType = playwright.webkit;
break;
case 'firefox':
browserType = playwright.firefox;
break;
case 'cr':
browserType = playwright.chromium;
break;
case 'wk':
browserType = playwright.webkit;
break;
case 'ff':
browserType = playwright.firefox;
break;
}
if (browserType) return browserType;
_utilsBundle.program.help();
}
function validateOptions(options) {
if (options.device && !(options.device in playwright.devices)) {
const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
for (const name in playwright.devices) lines.push(` "${name}"`);
throw new Error(lines.join('\n'));
}
if (options.colorScheme && !['light', 'dark'].includes(options.colorScheme)) throw new Error('Invalid color scheme, should be one of "light", "dark"');
}
function logErrorAndExit(e) {
if (process.env.PWDEBUGIMPL) console.error(e);else console.error(e.name + ': ' + e.message);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
}
function codegenId() {
return process.env.PW_LANG_NAME || 'playwright-test';
}
function commandWithOpenOptions(command, description, options) {
let result = _utilsBundle.program.command(command).description(description);
for (const option of options) result = result.option(option[0], ...option.slice(1));
return result.option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('--block-service-workers', 'block service workers').option('--channel <channel>', 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option('--color-scheme <scheme>', 'emulate preferred color scheme, "light" or "dark"').option('--device <deviceName>', 'emulate device, for example "iPhone 11"').option('--geolocation <coordinates>', 'specify geolocation coordinates, for example "37.819722,-122.478611"').option('--ignore-https-errors', 'ignore https errors').option('--load-storage <filename>', 'load context storage state from the file, previously saved with --save-storage').option('--lang <language>', 'specify language / locale, for example "en-GB"').option('--proxy-server <proxy>', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option('--proxy-bypass <bypass>', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option('--save-har <filename>', 'save HAR file with all network activity at the end').option('--save-har-glob <glob pattern>', 'filter entries in the HAR by matching url against this glob pattern').option('--save-storage <filename>', 'save context storage state at the end, for later use with --load-storage').option('--timezone <time zone>', 'time zone to emulate, for example "Europe/Rome"').option('--timeout <timeout>', 'timeout for Playwright actions in milliseconds, no timeout by default').option('--user-agent <ua string>', 'specify user agent string').option('--viewport-size <size>', 'specify browser viewport size in pixels, for example "1280, 720"');
}
function buildBasePlaywrightCLICommand(cliTargetLang) {
switch (cliTargetLang) {
case 'python':
return `playwright`;
case 'java':
return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
case 'csharp':
return `pwsh bin/Debug/netX/playwright.ps1`;
default:
return `npx playwright`;
}
}
var _default = _utilsBundle.program;
exports.default = _default;

View File

@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Accessibility = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
function axNodeFromProtocol(axNode) {
const result = {
...axNode,
value: axNode.valueNumber !== undefined ? axNode.valueNumber : axNode.valueString,
checked: axNode.checked === 'checked' ? true : axNode.checked === 'unchecked' ? false : axNode.checked,
pressed: axNode.pressed === 'pressed' ? true : axNode.pressed === 'released' ? false : axNode.pressed,
children: axNode.children ? axNode.children.map(axNodeFromProtocol) : undefined
};
delete result.valueNumber;
delete result.valueString;
return result;
}
class Accessibility {
constructor(channel) {
this._channel = void 0;
this._channel = channel;
}
async snapshot(options = {}) {
const root = options.root ? options.root._elementChannel : undefined;
const result = await this._channel.accessibilitySnapshot({
interestingOnly: options.interestingOnly,
root
});
return result.rootAXNode ? axNodeFromProtocol(result.rootAXNode) : null;
}
}
exports.Accessibility = Accessibility;

View File

@ -0,0 +1,466 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AndroidWebView = exports.AndroidSocket = exports.AndroidInput = exports.AndroidDevice = exports.Android = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _utils = require("../utils");
var _events = require("./events");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _timeoutSettings = require("../common/timeoutSettings");
var _waiter = require("./waiter");
var _events2 = require("events");
var _connection = require("./connection");
var _errors = require("../common/errors");
var _timeoutRunner = require("../utils/timeoutRunner");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
class Android extends _channelOwner.ChannelOwner {
static from(android) {
return android._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._timeoutSettings = void 0;
this._serverLauncher = void 0;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({
timeout
});
}
async devices(options = {}) {
const {
devices
} = await this._channel.devices(options);
return devices.map(d => AndroidDevice.from(d));
}
async launchServer(options = {}) {
if (!this._serverLauncher) throw new Error('Launching server is not supported');
return this._serverLauncher.launchServer(options);
}
async connect(wsEndpoint, options = {}) {
return await this._wrapApiCall(async () => {
const deadline = options.timeout ? (0, _utils.monotonicTime)() + options.timeout : 0;
const headers = {
'x-playwright-browser': 'android',
...options.headers
};
const localUtils = this._connection.localUtils();
const connectParams = {
wsEndpoint,
headers,
slowMo: options.slowMo,
timeout: options.timeout
};
const {
pipe
} = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new _connection.Connection(localUtils, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);
let device;
let closeError;
const onPipeClosed = () => {
var _device;
(_device = device) === null || _device === void 0 ? void 0 : _device._didClose();
connection.close(closeError || _errors.kBrowserClosedError);
};
pipe.on('closed', onPipeClosed);
connection.onmessage = message => pipe.send({
message
}).catch(onPipeClosed);
pipe.on('message', ({
message
}) => {
try {
connection.dispatch(message);
} catch (e) {
closeError = e.toString();
closePipe();
}
});
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preConnectedAndroidDevice) {
closePipe();
throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
}
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
device._shouldCloseConnectionOnClose = true;
device.on(_events.Events.AndroidDevice.Close, closePipe);
return device;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
closePipe();
throw new Error(`Timeout ${options.timeout}ms exceeded`);
}
});
}
}
exports.Android = Android;
class AndroidDevice extends _channelOwner.ChannelOwner {
static from(androidDevice) {
return androidDevice._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._timeoutSettings = void 0;
this._webViews = new Map();
this._shouldCloseConnectionOnClose = false;
this.input = void 0;
this.input = new AndroidInput(this);
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(parent._timeoutSettings);
this._channel.on('webViewAdded', ({
webView
}) => this._onWebViewAdded(webView));
this._channel.on('webViewRemoved', ({
socketName
}) => this._onWebViewRemoved(socketName));
this._channel.on('close', () => this._didClose());
}
_onWebViewAdded(webView) {
const view = new AndroidWebView(this, webView);
this._webViews.set(webView.socketName, view);
this.emit(_events.Events.AndroidDevice.WebView, view);
}
_onWebViewRemoved(socketName) {
const view = this._webViews.get(socketName);
this._webViews.delete(socketName);
if (view) view.emit(_events.Events.AndroidWebView.Close);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({
timeout
});
}
serial() {
return this._initializer.serial;
}
model() {
return this._initializer.model;
}
webViews() {
return [...this._webViews.values()];
}
async webView(selector, options) {
const predicate = v => {
if (selector.pkg) return v.pkg() === selector.pkg;
if (selector.socketName) return v._socketName() === selector.socketName;
return false;
};
const webView = [...this._webViews.values()].find(predicate);
if (webView) return webView;
return this.waitForEvent('webview', {
...options,
predicate
});
}
async wait(selector, options) {
await this._channel.wait({
selector: toSelectorChannel(selector),
...options
});
}
async fill(selector, text, options) {
await this._channel.fill({
selector: toSelectorChannel(selector),
text,
...options
});
}
async press(selector, key, options) {
await this.tap(selector, options);
await this.input.press(key);
}
async tap(selector, options) {
await this._channel.tap({
selector: toSelectorChannel(selector),
...options
});
}
async drag(selector, dest, options) {
await this._channel.drag({
selector: toSelectorChannel(selector),
dest,
...options
});
}
async fling(selector, direction, options) {
await this._channel.fling({
selector: toSelectorChannel(selector),
direction,
...options
});
}
async longTap(selector, options) {
await this._channel.longTap({
selector: toSelectorChannel(selector),
...options
});
}
async pinchClose(selector, percent, options) {
await this._channel.pinchClose({
selector: toSelectorChannel(selector),
percent,
...options
});
}
async pinchOpen(selector, percent, options) {
await this._channel.pinchOpen({
selector: toSelectorChannel(selector),
percent,
...options
});
}
async scroll(selector, direction, percent, options) {
await this._channel.scroll({
selector: toSelectorChannel(selector),
direction,
percent,
...options
});
}
async swipe(selector, direction, percent, options) {
await this._channel.swipe({
selector: toSelectorChannel(selector),
direction,
percent,
...options
});
}
async info(selector) {
return (await this._channel.info({
selector: toSelectorChannel(selector)
})).info;
}
async screenshot(options = {}) {
const {
binary
} = await this._channel.screenshot();
if (options.path) await _fs.default.promises.writeFile(options.path, binary);
return binary;
}
async close() {
try {
if (this._shouldCloseConnectionOnClose) this._connection.close(_errors.kBrowserClosedError);else await this._channel.close();
} catch (e) {
if ((0, _errors.isSafeCloseError)(e)) return;
throw e;
}
}
_didClose() {
this.emit(_events.Events.AndroidDevice.Close, this);
}
async shell(command) {
const {
result
} = await this._channel.shell({
command
});
return result;
}
async open(command) {
return AndroidSocket.from((await this._channel.open({
command
})).socket);
}
async installApk(file, options) {
await this._channel.installApk({
file: await loadFile(file),
args: options && options.args
});
}
async push(file, path, options) {
await this._channel.push({
file: await loadFile(file),
path,
mode: options ? options.mode : undefined
});
}
async launchBrowser(options = {}) {
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
const {
context
} = await this._channel.launchBrowser(contextOptions);
return _browserContext.BrowserContext.from(context);
}
async waitForEvent(event, optionsOrPredicate = {}) {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== _events.Events.AndroidDevice.Close) waiter.rejectOnEvent(this, _events.Events.AndroidDevice.Close, new Error('Device closed'));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
exports.AndroidDevice = AndroidDevice;
class AndroidSocket extends _channelOwner.ChannelOwner {
static from(androidDevice) {
return androidDevice._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on('data', ({
data
}) => this.emit(_events.Events.AndroidSocket.Data, data));
this._channel.on('close', () => this.emit(_events.Events.AndroidSocket.Close));
}
async write(data) {
await this._channel.write({
data
});
}
async close() {
await this._channel.close();
}
}
exports.AndroidSocket = AndroidSocket;
async function loadFile(file) {
if ((0, _utils.isString)(file)) return _fs.default.promises.readFile(file);
return file;
}
class AndroidInput {
constructor(device) {
this._device = void 0;
this._device = device;
}
async type(text) {
await this._device._channel.inputType({
text
});
}
async press(key) {
await this._device._channel.inputPress({
key
});
}
async tap(point) {
await this._device._channel.inputTap({
point
});
}
async swipe(from, segments, steps) {
await this._device._channel.inputSwipe({
segments,
steps
});
}
async drag(from, to, steps) {
await this._device._channel.inputDrag({
from,
to,
steps
});
}
}
exports.AndroidInput = AndroidInput;
function toSelectorChannel(selector) {
const {
checkable,
checked,
clazz,
clickable,
depth,
desc,
enabled,
focusable,
focused,
hasChild,
hasDescendant,
longClickable,
pkg,
res,
scrollable,
selected,
text
} = selector;
const toRegex = value => {
if (value === undefined) return undefined;
if ((0, _utils.isRegExp)(value)) return value.source;
return '^' + value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') + '$';
};
return {
checkable,
checked,
clazz: toRegex(clazz),
pkg: toRegex(pkg),
desc: toRegex(desc),
res: toRegex(res),
text: toRegex(text),
clickable,
depth,
enabled,
focusable,
focused,
hasChild: hasChild ? {
selector: toSelectorChannel(hasChild.selector)
} : undefined,
hasDescendant: hasDescendant ? {
selector: toSelectorChannel(hasDescendant.selector),
maxDepth: hasDescendant.maxDepth
} : undefined,
longClickable,
scrollable,
selected
};
}
class AndroidWebView extends _events2.EventEmitter {
constructor(device, data) {
super();
this._device = void 0;
this._data = void 0;
this._pagePromise = void 0;
this._device = device;
this._data = data;
}
pid() {
return this._data.pid;
}
pkg() {
return this._data.pkg;
}
_socketName() {
return this._data.socketName;
}
async page() {
if (!this._pagePromise) this._pagePromise = this._fetchPage();
return this._pagePromise;
}
async _fetchPage() {
const {
context
} = await this._device._channel.connectToWebView({
socketName: this._data.socketName
});
return _browserContext.BrowserContext.from(context).pages()[0];
}
}
exports.AndroidWebView = AndroidWebView;

View File

@ -0,0 +1,265 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "APIRequest", {
enumerable: true,
get: function () {
return _fetch.APIRequest;
}
});
Object.defineProperty(exports, "APIRequestContext", {
enumerable: true,
get: function () {
return _fetch.APIRequestContext;
}
});
Object.defineProperty(exports, "APIResponse", {
enumerable: true,
get: function () {
return _fetch.APIResponse;
}
});
Object.defineProperty(exports, "Accessibility", {
enumerable: true,
get: function () {
return _accessibility.Accessibility;
}
});
Object.defineProperty(exports, "Android", {
enumerable: true,
get: function () {
return _android.Android;
}
});
Object.defineProperty(exports, "AndroidDevice", {
enumerable: true,
get: function () {
return _android.AndroidDevice;
}
});
Object.defineProperty(exports, "AndroidInput", {
enumerable: true,
get: function () {
return _android.AndroidInput;
}
});
Object.defineProperty(exports, "AndroidSocket", {
enumerable: true,
get: function () {
return _android.AndroidSocket;
}
});
Object.defineProperty(exports, "AndroidWebView", {
enumerable: true,
get: function () {
return _android.AndroidWebView;
}
});
Object.defineProperty(exports, "Browser", {
enumerable: true,
get: function () {
return _browser.Browser;
}
});
Object.defineProperty(exports, "BrowserContext", {
enumerable: true,
get: function () {
return _browserContext.BrowserContext;
}
});
Object.defineProperty(exports, "BrowserType", {
enumerable: true,
get: function () {
return _browserType.BrowserType;
}
});
Object.defineProperty(exports, "CDPSession", {
enumerable: true,
get: function () {
return _cdpSession.CDPSession;
}
});
Object.defineProperty(exports, "ConsoleMessage", {
enumerable: true,
get: function () {
return _consoleMessage.ConsoleMessage;
}
});
Object.defineProperty(exports, "Coverage", {
enumerable: true,
get: function () {
return _coverage.Coverage;
}
});
Object.defineProperty(exports, "Dialog", {
enumerable: true,
get: function () {
return _dialog.Dialog;
}
});
Object.defineProperty(exports, "Download", {
enumerable: true,
get: function () {
return _download.Download;
}
});
Object.defineProperty(exports, "Electron", {
enumerable: true,
get: function () {
return _electron.Electron;
}
});
Object.defineProperty(exports, "ElectronApplication", {
enumerable: true,
get: function () {
return _electron.ElectronApplication;
}
});
Object.defineProperty(exports, "ElementHandle", {
enumerable: true,
get: function () {
return _elementHandle.ElementHandle;
}
});
Object.defineProperty(exports, "FileChooser", {
enumerable: true,
get: function () {
return _fileChooser.FileChooser;
}
});
Object.defineProperty(exports, "Frame", {
enumerable: true,
get: function () {
return _frame.Frame;
}
});
Object.defineProperty(exports, "FrameLocator", {
enumerable: true,
get: function () {
return _locator.FrameLocator;
}
});
Object.defineProperty(exports, "JSHandle", {
enumerable: true,
get: function () {
return _jsHandle.JSHandle;
}
});
Object.defineProperty(exports, "Keyboard", {
enumerable: true,
get: function () {
return _input.Keyboard;
}
});
Object.defineProperty(exports, "Locator", {
enumerable: true,
get: function () {
return _locator.Locator;
}
});
Object.defineProperty(exports, "Mouse", {
enumerable: true,
get: function () {
return _input.Mouse;
}
});
Object.defineProperty(exports, "Page", {
enumerable: true,
get: function () {
return _page.Page;
}
});
Object.defineProperty(exports, "Playwright", {
enumerable: true,
get: function () {
return _playwright.Playwright;
}
});
Object.defineProperty(exports, "Request", {
enumerable: true,
get: function () {
return _network.Request;
}
});
Object.defineProperty(exports, "Response", {
enumerable: true,
get: function () {
return _network.Response;
}
});
Object.defineProperty(exports, "Route", {
enumerable: true,
get: function () {
return _network.Route;
}
});
Object.defineProperty(exports, "Selectors", {
enumerable: true,
get: function () {
return _selectors.Selectors;
}
});
Object.defineProperty(exports, "TimeoutError", {
enumerable: true,
get: function () {
return _errors.TimeoutError;
}
});
Object.defineProperty(exports, "Touchscreen", {
enumerable: true,
get: function () {
return _input.Touchscreen;
}
});
Object.defineProperty(exports, "Tracing", {
enumerable: true,
get: function () {
return _tracing.Tracing;
}
});
Object.defineProperty(exports, "Video", {
enumerable: true,
get: function () {
return _video.Video;
}
});
Object.defineProperty(exports, "WebSocket", {
enumerable: true,
get: function () {
return _network.WebSocket;
}
});
Object.defineProperty(exports, "Worker", {
enumerable: true,
get: function () {
return _worker.Worker;
}
});
var _accessibility = require("./accessibility");
var _android = require("./android");
var _browser = require("./browser");
var _browserContext = require("./browserContext");
var _browserType = require("./browserType");
var _consoleMessage = require("./consoleMessage");
var _coverage = require("./coverage");
var _dialog = require("./dialog");
var _download = require("./download");
var _electron = require("./electron");
var _locator = require("./locator");
var _elementHandle = require("./elementHandle");
var _fileChooser = require("./fileChooser");
var _errors = require("../common/errors");
var _frame = require("./frame");
var _input = require("./input");
var _jsHandle = require("./jsHandle");
var _network = require("./network");
var _fetch = require("./fetch");
var _page = require("./page");
var _selectors = require("./selectors");
var _tracing = require("./tracing");
var _video = require("./video");
var _worker = require("./worker");
var _cdpSession = require("./cdpSession");
var _playwright = require("./playwright");

View File

@ -0,0 +1,80 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Artifact = void 0;
var fs = _interopRequireWildcard(require("fs"));
var _stream = require("./stream");
var _fileUtils = require("../utils/fileUtils");
var _channelOwner = require("./channelOwner");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* 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.
*/
class Artifact extends _channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
async pathAfterFinished() {
if (this._connection.isRemote()) throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
return (await this._channel.pathAfterFinished()).value || null;
}
async saveAs(path) {
if (!this._connection.isRemote()) {
await this._channel.saveAs({
path
});
return;
}
const result = await this._channel.saveAsStream();
const stream = _stream.Stream.from(result.stream);
await (0, _fileUtils.mkdirIfNeeded)(path);
await new Promise((resolve, reject) => {
stream.stream().pipe(fs.createWriteStream(path)).on('finish', resolve).on('error', reject);
});
}
async failure() {
return (await this._channel.failure()).error || null;
}
async createReadStream() {
const result = await this._channel.stream();
if (!result.stream) return null;
const stream = _stream.Stream.from(result.stream);
return stream.stream();
}
async readIntoBuffer() {
const stream = await this.createReadStream();
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => {
chunks.push(chunk);
});
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
stream.on('error', reject);
});
}
async cancel() {
return this._channel.cancel();
}
async delete() {
return this._channel.delete();
}
}
exports.Artifact = Artifact;

View File

@ -0,0 +1,133 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Browser = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _events = require("./events");
var _errors = require("../common/errors");
var _cdpSession = require("./cdpSession");
var _artifact = require("./artifact");
var _utils = require("../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
class Browser extends _channelOwner.ChannelOwner {
// Used from @playwright/test fixtures.
static from(browser) {
return browser._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._contexts = new Set();
this._isConnected = true;
this._closedPromise = void 0;
this._shouldCloseConnectionOnClose = false;
this._browserType = void 0;
this._options = {};
this._name = void 0;
this._path = void 0;
this._connectHeaders = void 0;
this._name = initializer.name;
this._channel.on('close', () => this._didClose());
this._closedPromise = new Promise(f => this.once(_events.Events.Browser.Disconnected, f));
}
browserType() {
return this._browserType;
}
async newContext(options = {}) {
return await this._innerNewContext(options, false);
}
async _newContextForReuse(options = {}) {
return await this._wrapApiCall(async () => {
for (const context of this._contexts) {
await this._browserType._willCloseContext(context);
for (const page of context.pages()) page._onClose();
context._onClose();
}
return await this._innerNewContext(options, true);
}, true);
}
async _innerNewContext(options = {}, forReuse) {
options = {
...this._browserType._defaultContextOptions,
...options
};
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = _browserContext.BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
return context;
}
contexts() {
return [...this._contexts];
}
version() {
return this._initializer.version;
}
async newPage(options = {}) {
return await this._wrapApiCall(async () => {
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
context._ownerPage = page;
return page;
});
}
isConnected() {
return this._isConnected;
}
async newBrowserCDPSession() {
return _cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
}
async startTracing(page, options = {}) {
this._path = options.path;
await this._channel.startTracing({
...options,
page: page ? page._channel : undefined
});
}
async stopTracing() {
const artifact = _artifact.Artifact.from((await this._channel.stopTracing()).artifact);
const buffer = await artifact.readIntoBuffer();
await artifact.delete();
if (this._path) {
await (0, _utils.mkdirIfNeeded)(this._path);
await _fs.default.promises.writeFile(this._path, buffer);
this._path = undefined;
}
return buffer;
}
async close() {
try {
if (this._shouldCloseConnectionOnClose) this._connection.close(_errors.kBrowserClosedError);else await this._channel.close();
await this._closedPromise;
} catch (e) {
if ((0, _errors.isSafeCloseError)(e)) return;
throw e;
}
}
_didClose() {
this._isConnected = false;
this.emit(_events.Events.Browser.Disconnected, this);
}
}
exports.Browser = Browser;

View File

@ -0,0 +1,452 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserContext = void 0;
exports.prepareBrowserContextParams = prepareBrowserContextParams;
var _page = require("./page");
var _frame = require("./frame");
var network = _interopRequireWildcard(require("./network"));
var _fs = _interopRequireDefault(require("fs"));
var _channelOwner = require("./channelOwner");
var _clientHelper = require("./clientHelper");
var _browser = require("./browser");
var _worker = require("./worker");
var _events = require("./events");
var _timeoutSettings = require("../common/timeoutSettings");
var _waiter = require("./waiter");
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _cdpSession = require("./cdpSession");
var _tracing = require("./tracing");
var _artifact = require("./artifact");
var _fetch = require("./fetch");
var _stackTrace = require("../utils/stackTrace");
var _harRouter = require("./harRouter");
var _consoleMessage = require("./consoleMessage");
var _dialog = require("./dialog");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class BrowserContext extends _channelOwner.ChannelOwner {
static from(context) {
return context._object;
}
static fromNullable(context) {
return context ? BrowserContext.from(context) : null;
}
constructor(parent, type, guid, initializer) {
var _this$_browser, _this$_browser2;
super(parent, type, guid, initializer);
this._pages = new Set();
this._routes = [];
this._browser = null;
this._browserType = void 0;
this._bindings = new Map();
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._ownerPage = void 0;
this._closedPromise = void 0;
this._options = {};
this.request = void 0;
this.tracing = void 0;
this._backgroundPages = new Set();
this._serviceWorkers = new Set();
this._isChromium = void 0;
this._harRecorders = new Map();
this._closeWasCalled = false;
if (parent instanceof _browser.Browser) this._browser = parent;
(_this$_browser = this._browser) === null || _this$_browser === void 0 ? void 0 : _this$_browser._contexts.add(this);
this._isChromium = ((_this$_browser2 = this._browser) === null || _this$_browser2 === void 0 ? void 0 : _this$_browser2._name) === 'chromium';
this.tracing = _tracing.Tracing.from(initializer.tracing);
this.request = _fetch.APIRequestContext.from(initializer.requestContext);
this._channel.on('bindingCall', ({
binding
}) => this._onBinding(_page.BindingCall.from(binding)));
this._channel.on('close', () => this._onClose());
this._channel.on('page', ({
page
}) => this._onPage(_page.Page.from(page)));
this._channel.on('route', ({
route
}) => this._onRoute(network.Route.from(route)));
this._channel.on('backgroundPage', ({
page
}) => {
const backgroundPage = _page.Page.from(page);
this._backgroundPages.add(backgroundPage);
this.emit(_events.Events.BrowserContext.BackgroundPage, backgroundPage);
});
this._channel.on('serviceWorker', ({
worker
}) => {
const serviceWorker = _worker.Worker.from(worker);
serviceWorker._context = this;
this._serviceWorkers.add(serviceWorker);
this.emit(_events.Events.BrowserContext.ServiceWorker, serviceWorker);
});
this._channel.on('console', ({
message
}) => {
const consoleMessage = _consoleMessage.ConsoleMessage.from(message);
this.emit(_events.Events.BrowserContext.Console, consoleMessage);
const page = consoleMessage.page();
if (page) page.emit(_events.Events.Page.Console, consoleMessage);
});
this._channel.on('dialog', ({
dialog
}) => {
const dialogObject = _dialog.Dialog.from(dialog);
let hasListeners = this.emit(_events.Events.BrowserContext.Dialog, dialogObject);
const page = dialogObject.page();
if (page) hasListeners = page.emit(_events.Events.Page.Dialog, dialogObject) || hasListeners;
if (!hasListeners) {
// Although we do similar handling on the server side, we still need this logic
// on the client side due to a possible race condition between two async calls:
// a) removing "dialog" listener subscription (client->server)
// b) actual "dialog" event (server->client)
if (dialogObject.type() === 'beforeunload') dialog.accept({}).catch(() => {});else dialog.dismiss().catch(() => {});
}
});
this._channel.on('request', ({
request,
page
}) => this._onRequest(network.Request.from(request), _page.Page.fromNullable(page)));
this._channel.on('requestFailed', ({
request,
failureText,
responseEndTiming,
page
}) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, _page.Page.fromNullable(page)));
this._channel.on('requestFinished', params => this._onRequestFinished(params));
this._channel.on('response', ({
response,
page
}) => this._onResponse(network.Response.from(response), _page.Page.fromNullable(page)));
this._closedPromise = new Promise(f => this.once(_events.Events.BrowserContext.Close, f));
this._setEventToSubscriptionMapping(new Map([[_events.Events.BrowserContext.Console, 'console'], [_events.Events.BrowserContext.Dialog, 'dialog'], [_events.Events.BrowserContext.Request, 'request'], [_events.Events.BrowserContext.Response, 'response'], [_events.Events.BrowserContext.RequestFinished, 'requestFinished'], [_events.Events.BrowserContext.RequestFailed, 'requestFailed']]));
}
_setOptions(contextOptions, browserOptions) {
this._options = contextOptions;
if (this._options.recordHar) this._harRecorders.set('', {
path: this._options.recordHar.path,
content: this._options.recordHar.content
});
this.tracing._tracesDir = browserOptions.tracesDir;
}
_onPage(page) {
this._pages.add(page);
this.emit(_events.Events.BrowserContext.Page, page);
if (page._opener && !page._opener.isClosed()) page._opener.emit(_events.Events.Page.Popup, page);
}
_onRequest(request, page) {
this.emit(_events.Events.BrowserContext.Request, request);
if (page) page.emit(_events.Events.Page.Request, request);
}
_onResponse(response, page) {
this.emit(_events.Events.BrowserContext.Response, response);
if (page) page.emit(_events.Events.Page.Response, response);
}
_onRequestFailed(request, responseEndTiming, failureText, page) {
request._failureText = failureText || null;
request._setResponseEndTiming(responseEndTiming);
this.emit(_events.Events.BrowserContext.RequestFailed, request);
if (page) page.emit(_events.Events.Page.RequestFailed, request);
}
_onRequestFinished(params) {
const {
responseEndTiming
} = params;
const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response);
const page = _page.Page.fromNullable(params.page);
request._setResponseEndTiming(responseEndTiming);
this.emit(_events.Events.BrowserContext.RequestFinished, request);
if (page) page.emit(_events.Events.Page.RequestFinished, request);
if (response) response._finishedPromise.resolve(null);
}
async _onRoute(route) {
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url())) continue;
if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1);
const handled = await routeHandler.handle(route);
if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {});
if (handled) return;
}
await route._innerContinue(true);
}
async _onBinding(bindingCall) {
const func = this._bindings.get(bindingCall._initializer.name);
if (!func) return;
await bindingCall.call(func);
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultNavigationTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
}
browser() {
return this._browser;
}
pages() {
return [...this._pages];
}
async newPage() {
if (this._ownerPage) throw new Error('Please use browser.newContext()');
return _page.Page.from((await this._channel.newPage()).page);
}
async cookies(urls) {
if (!urls) urls = [];
if (urls && typeof urls === 'string') urls = [urls];
return (await this._channel.cookies({
urls: urls
})).cookies;
}
async addCookies(cookies) {
await this._channel.addCookies({
cookies
});
}
async clearCookies() {
await this._channel.clearCookies();
}
async grantPermissions(permissions, options) {
await this._channel.grantPermissions({
permissions,
...options
});
}
async clearPermissions() {
await this._channel.clearPermissions();
}
async setGeolocation(geolocation) {
await this._channel.setGeolocation({
geolocation: geolocation || undefined
});
}
async setExtraHTTPHeaders(headers) {
network.validateHeaders(headers);
await this._channel.setExtraHTTPHeaders({
headers: (0, _utils.headersObjectToArray)(headers)
});
}
async setOffline(offline) {
await this._channel.setOffline({
offline
});
}
async setHTTPCredentials(httpCredentials) {
await this._channel.setHTTPCredentials({
httpCredentials: httpCredentials || undefined
});
}
async addInitScript(script, arg) {
const source = await (0, _clientHelper.evaluationScript)(script, arg);
await this._channel.addInitScript({
source
});
}
async exposeBinding(name, callback, options = {}) {
await this._channel.exposeBinding({
name,
needsHandle: options.handle
});
this._bindings.set(name, callback);
}
async exposeFunction(name, callback) {
await this._channel.exposeBinding({
name
});
const binding = (source, ...args) => callback(...args);
this._bindings.set(name, binding);
}
async route(url, handler, options = {}) {
this._routes.unshift(new network.RouteHandler(this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns();
}
async _recordIntoHAR(har, page, options = {}) {
var _options$updateConten, _options$updateMode, _options$updateConten2;
const {
harId
} = await this._channel.harStart({
page: page === null || page === void 0 ? void 0 : page._channel,
options: prepareRecordHarOptions({
path: har,
content: (_options$updateConten = options.updateContent) !== null && _options$updateConten !== void 0 ? _options$updateConten : 'attach',
mode: (_options$updateMode = options.updateMode) !== null && _options$updateMode !== void 0 ? _options$updateMode : 'minimal',
urlFilter: options.url
})
});
this._harRecorders.set(harId, {
path: har,
content: (_options$updateConten2 = options.updateContent) !== null && _options$updateConten2 !== void 0 ? _options$updateConten2 : 'attach'
});
}
async routeFromHAR(har, options = {}) {
if (options.update) {
await this._recordIntoHAR(har, null, options);
return;
}
const harRouter = await _harRouter.HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', {
urlMatch: options.url
});
harRouter.addContextRoute(this);
}
async unroute(url, handler) {
this._routes = this._routes.filter(route => !(0, _utils.urlMatchesEqual)(route.url, url) || handler && route.handler !== handler);
await this._updateInterceptionPatterns();
}
async _updateInterceptionPatterns() {
const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._channel.setNetworkInterceptionPatterns({
patterns
});
}
async waitForEvent(event, optionsOrPredicate = {}) {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== _events.Events.BrowserContext.Close) waiter.rejectOnEvent(this, _events.Events.BrowserContext.Close, new Error('Context closed'));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async storageState(options = {}) {
const state = await this._channel.storageState();
if (options.path) {
await (0, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
}
return state;
}
backgroundPages() {
return [...this._backgroundPages];
}
serviceWorkers() {
return [...this._serviceWorkers];
}
async newCDPSession(page) {
// channelOwner.ts's validation messages don't handle the pseudo-union type, so we're explicit here
if (!(page instanceof _page.Page) && !(page instanceof _frame.Frame)) throw new Error('page: expected Page or Frame');
const result = await this._channel.newCDPSession(page instanceof _page.Page ? {
page: page._channel
} : {
frame: page._channel
});
return _cdpSession.CDPSession.from(result.session);
}
_onClose() {
var _this$_browserType, _this$_browserType$_c;
if (this._browser) this._browser._contexts.delete(this);
(_this$_browserType = this._browserType) === null || _this$_browserType === void 0 ? void 0 : (_this$_browserType$_c = _this$_browserType._contexts) === null || _this$_browserType$_c === void 0 ? void 0 : _this$_browserType$_c.delete(this);
this.emit(_events.Events.BrowserContext.Close, this);
}
async close() {
if (this._closeWasCalled) return;
this._closeWasCalled = true;
await this._wrapApiCall(async () => {
var _this$_browserType2;
await ((_this$_browserType2 = this._browserType) === null || _this$_browserType2 === void 0 ? void 0 : _this$_browserType2._willCloseContext(this));
for (const [harId, harParams] of this._harRecorders) {
const har = await this._channel.harExport({
harId
});
const artifact = _artifact.Artifact.from(har.artifact);
// Server side will compress artifact if content is attach or if file is .zip.
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
const needCompressed = harParams.path.endsWith('.zip');
if (isCompressed && !needCompressed) {
await artifact.saveAs(harParams.path + '.tmp');
await this._connection.localUtils()._channel.harUnzip({
zipFile: harParams.path + '.tmp',
harFile: harParams.path
});
} else {
await artifact.saveAs(harParams.path);
}
await artifact.delete();
}
}, true);
await this._channel.close();
await this._closedPromise;
}
async _enableRecorder(params) {
await this._channel.recorderSupplementEnable(params);
}
}
exports.BrowserContext = BrowserContext;
async function prepareStorageState(options) {
if (typeof options.storageState !== 'string') return options.storageState;
try {
return JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8'));
} catch (e) {
(0, _stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
throw e;
}
}
function prepareRecordHarOptions(options) {
if (!options) return;
return {
path: options.path,
content: options.content || (options.omitContent ? 'omit' : undefined),
urlGlob: (0, _utils.isString)(options.urlFilter) ? options.urlFilter : undefined,
urlRegexSource: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.source : undefined,
urlRegexFlags: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.flags : undefined,
mode: options.mode
};
}
async function prepareBrowserContextParams(options) {
if (options.videoSize && !options.videosPath) throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders) network.validateHeaders(options.extraHTTPHeaders);
const contextParams = {
...options,
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
storageState: await prepareStorageState(options),
serviceWorkers: options.serviceWorkers,
recordHar: prepareRecordHarOptions(options.recordHar),
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
dir: options.videosPath,
size: options.videoSize
};
}
return contextParams;
}

View File

@ -0,0 +1,254 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserType = void 0;
var _browser3 = require("./browser");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _connection = require("./connection");
var _events = require("./events");
var _clientHelper = require("./clientHelper");
var _utils = require("../utils");
var _errors = require("../common/errors");
var _timeoutRunner = require("../utils/timeoutRunner");
/**
* 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.
*/
// This is here just for api generation and checking.
class BrowserType extends _channelOwner.ChannelOwner {
constructor(...args) {
super(...args);
this._serverLauncher = void 0;
this._contexts = new Set();
this._playwright = void 0;
// Instrumentation.
this._defaultContextOptions = void 0;
this._defaultContextTimeout = void 0;
this._defaultContextNavigationTimeout = void 0;
this._defaultLaunchOptions = void 0;
this._defaultConnectOptions = void 0;
}
static from(browserType) {
return browserType._object;
}
executablePath() {
if (!this._initializer.executablePath) throw new Error('Browser is not supported on current platform');
return this._initializer.executablePath;
}
name() {
return this._initializer.name;
}
async launch(options = {}) {
var _this$_defaultLaunchO;
(0, _utils.assert)(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
if (this._defaultConnectOptions) return await this._connectInsteadOfLaunching(this._defaultConnectOptions, options);
const logger = options.logger || ((_this$_defaultLaunchO = this._defaultLaunchOptions) === null || _this$_defaultLaunchO === void 0 ? void 0 : _this$_defaultLaunchO.logger);
options = {
...this._defaultLaunchOptions,
...options
};
const launchOptions = {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
};
return await this._wrapApiCall(async () => {
const browser = _browser3.Browser.from((await this._channel.launch(launchOptions)).browser);
this._didLaunchBrowser(browser, options, logger);
return browser;
});
}
async _connectInsteadOfLaunching(connectOptions, launchOptions) {
var _connectOptions$expos, _connectOptions$timeo;
return this._connect({
wsEndpoint: connectOptions.wsEndpoint,
headers: {
// HTTP headers are ASCII only (not UTF-8).
'x-playwright-launch-options': (0, _utils.jsonStringifyForceASCII)({
...this._defaultLaunchOptions,
...launchOptions
}),
...connectOptions.headers
},
exposeNetwork: (_connectOptions$expos = connectOptions.exposeNetwork) !== null && _connectOptions$expos !== void 0 ? _connectOptions$expos : connectOptions._exposeNetwork,
slowMo: connectOptions.slowMo,
timeout: (_connectOptions$timeo = connectOptions.timeout) !== null && _connectOptions$timeo !== void 0 ? _connectOptions$timeo : 3 * 60 * 1000 // 3 minutes
});
}
async launchServer(options = {}) {
if (!this._serverLauncher) throw new Error('Launching server is not supported');
options = {
...this._defaultLaunchOptions,
...options
};
return this._serverLauncher.launchServer(options);
}
async launchPersistentContext(userDataDir, options = {}) {
var _this$_defaultLaunchO2;
const logger = options.logger || ((_this$_defaultLaunchO2 = this._defaultLaunchOptions) === null || _this$_defaultLaunchO2 === void 0 ? void 0 : _this$_defaultLaunchO2.logger);
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
options = {
...this._defaultLaunchOptions,
...this._defaultContextOptions,
...options
};
const contextParams = await (0, _browserContext.prepareBrowserContextParams)(options);
const persistentParams = {
...contextParams,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined,
channel: options.channel,
userDataDir
};
return await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams);
const context = _browserContext.BrowserContext.from(result.context);
await this._didCreateContext(context, contextParams, options, logger);
return context;
});
}
async connect(optionsOrWsEndpoint, options) {
if (typeof optionsOrWsEndpoint === 'string') return this._connect({
...options,
wsEndpoint: optionsOrWsEndpoint
});
(0, _utils.assert)(optionsOrWsEndpoint.wsEndpoint, 'options.wsEndpoint is required');
return this._connect(optionsOrWsEndpoint);
}
async _connect(params) {
const logger = params.logger;
return await this._wrapApiCall(async () => {
var _params$exposeNetwork;
const deadline = params.timeout ? (0, _utils.monotonicTime)() + params.timeout : 0;
const headers = {
'x-playwright-browser': this.name(),
...params.headers
};
const localUtils = this._connection.localUtils();
const connectParams = {
wsEndpoint: params.wsEndpoint,
headers,
exposeNetwork: (_params$exposeNetwork = params.exposeNetwork) !== null && _params$exposeNetwork !== void 0 ? _params$exposeNetwork : params._exposeNetwork,
slowMo: params.slowMo,
timeout: params.timeout
};
if (params.__testHookRedirectPortForwarding) connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
const {
pipe,
headers: connectHeaders
} = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new _connection.Connection(localUtils, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);
let browser;
let closeError;
const onPipeClosed = () => {
var _browser2;
// Emulate all pages, contexts and the browser closing upon disconnect.
for (const context of ((_browser = browser) === null || _browser === void 0 ? void 0 : _browser.contexts()) || []) {
var _browser;
for (const page of context.pages()) page._onClose();
context._onClose();
}
(_browser2 = browser) === null || _browser2 === void 0 ? void 0 : _browser2._didClose();
connection.close(closeError || _errors.kBrowserClosedError);
};
pipe.on('closed', onPipeClosed);
connection.onmessage = message => pipe.send({
message
}).catch(onPipeClosed);
pipe.on('message', ({
message
}) => {
try {
connection.dispatch(message);
} catch (e) {
closeError = e.toString();
closePipe();
}
});
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
// For tests.
if (params.__testHookBeforeCreateBrowser) await params.__testHookBeforeCreateBrowser();
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preLaunchedBrowser) {
closePipe();
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
}
playwright._setSelectors(this._playwright.selectors);
browser = _browser3.Browser.from(playwright._initializer.preLaunchedBrowser);
this._didLaunchBrowser(browser, {}, logger);
browser._shouldCloseConnectionOnClose = true;
browser._connectHeaders = connectHeaders;
browser.on(_events.Events.Browser.Disconnected, closePipe);
return browser;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
closePipe();
throw new Error(`Timeout ${params.timeout}ms exceeded`);
}
});
}
connectOverCDP(endpointURLOrOptions, options) {
if (typeof endpointURLOrOptions === 'string') return this._connectOverCDP(endpointURLOrOptions, options);
const endpointURL = 'endpointURL' in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
(0, _utils.assert)(endpointURL, 'Cannot connect over CDP without wsEndpoint.');
return this.connectOverCDP(endpointURL, endpointURLOrOptions);
}
async _connectOverCDP(endpointURL, params = {}) {
if (this.name() !== 'chromium') throw new Error('Connecting over CDP is only supported in Chromium.');
const headers = params.headers ? (0, _utils.headersObjectToArray)(params.headers) : undefined;
const result = await this._channel.connectOverCDP({
endpointURL,
headers,
slowMo: params.slowMo,
timeout: params.timeout
});
const browser = _browser3.Browser.from(result.browser);
this._didLaunchBrowser(browser, {}, params.logger);
if (result.defaultContext) await this._didCreateContext(_browserContext.BrowserContext.from(result.defaultContext), {}, {}, undefined);
return browser;
}
_didLaunchBrowser(browser, browserOptions, logger) {
browser._browserType = this;
browser._options = browserOptions;
browser._logger = logger;
}
async _didCreateContext(context, contextOptions, browserOptions, logger) {
context._logger = logger;
context._browserType = this;
this._contexts.add(context);
context._setOptions(contextOptions, browserOptions);
if (this._defaultContextTimeout !== undefined) context.setDefaultTimeout(this._defaultContextTimeout);
if (this._defaultContextNavigationTimeout !== undefined) context.setDefaultNavigationTimeout(this._defaultContextNavigationTimeout);
await this._instrumentation.onDidCreateBrowserContext(context);
}
async _willCloseContext(context) {
this._contexts.delete(context);
await this._instrumentation.onWillCloseBrowserContext(context);
}
}
exports.BrowserType = BrowserType;

View File

@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CDPSession = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class CDPSession extends _channelOwner.ChannelOwner {
static from(cdpSession) {
return cdpSession._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on('event', ({
method,
params
}) => {
this.emit(method, params);
});
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
async send(method, params) {
const result = await this._channel.send({
method,
params
});
return result.result;
}
async detach() {
return this._channel.detach();
}
}
exports.CDPSession = CDPSession;

View File

@ -0,0 +1,223 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ChannelOwner = void 0;
var _events = require("events");
var _validator = require("../protocol/validator");
var _debugLogger = require("../common/debugLogger");
var _stackTrace = require("../utils/stackTrace");
var _utils = require("../utils");
var _zones = require("../utils/zones");
/**
* 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.
*/
class ChannelOwner extends _events.EventEmitter {
constructor(parent, type, guid, initializer) {
super();
this._connection = void 0;
this._parent = void 0;
this._objects = new Map();
this._type = void 0;
this._guid = void 0;
this._channel = void 0;
this._initializer = void 0;
this._logger = void 0;
this._instrumentation = void 0;
this._eventToSubscriptionMapping = new Map();
this.setMaxListeners(0);
this._connection = parent instanceof ChannelOwner ? parent._connection : parent;
this._type = type;
this._guid = guid;
this._parent = parent instanceof ChannelOwner ? parent : undefined;
this._instrumentation = this._connection._instrumentation;
this._connection._objects.set(guid, this);
if (this._parent) {
this._parent._objects.set(guid, this);
this._logger = this._parent._logger;
}
this._channel = this._createChannel(new _events.EventEmitter());
this._initializer = initializer;
}
_setEventToSubscriptionMapping(mapping) {
this._eventToSubscriptionMapping = mapping;
}
_updateSubscription(event, enabled) {
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
if (protocolEvent) {
this._wrapApiCall(async () => {
await this._channel.updateSubscription({
event: protocolEvent,
enabled
});
}, true).catch(() => {});
}
}
on(event, listener) {
if (!this.listenerCount(event)) this._updateSubscription(event, true);
super.on(event, listener);
return this;
}
addListener(event, listener) {
if (!this.listenerCount(event)) this._updateSubscription(event, true);
super.addListener(event, listener);
return this;
}
prependListener(event, listener) {
if (!this.listenerCount(event)) this._updateSubscription(event, true);
super.prependListener(event, listener);
return this;
}
off(event, listener) {
super.off(event, listener);
if (!this.listenerCount(event)) this._updateSubscription(event, false);
return this;
}
removeListener(event, listener) {
super.removeListener(event, listener);
if (!this.listenerCount(event)) this._updateSubscription(event, false);
return this;
}
_adopt(child) {
child._parent._objects.delete(child._guid);
this._objects.set(child._guid, child);
child._parent = this;
}
_dispose() {
// Clean up from parent and connection.
if (this._parent) this._parent._objects.delete(this._guid);
this._connection._objects.delete(this._guid);
// Dispose all children.
for (const object of [...this._objects.values()]) object._dispose();
this._objects.clear();
}
_debugScopeState() {
return {
_guid: this._guid,
objects: Array.from(this._objects.values()).map(o => o._debugScopeState())
};
}
_createChannel(base) {
const channel = new Proxy(base, {
get: (obj, prop) => {
if (typeof prop === 'string') {
const validator = (0, _validator.maybeFindValidator)(this._type, prop, 'Params');
if (validator) {
return params => {
return this._wrapApiCall(apiZone => {
const {
stackTrace,
csi,
callCookie,
wallTime
} = apiZone.reported ? {
csi: undefined,
callCookie: undefined,
stackTrace: null,
wallTime: undefined
} : apiZone;
apiZone.reported = true;
if (csi && stackTrace && stackTrace.apiName) csi.onApiCallBegin(stackTrace.apiName, params, stackTrace, wallTime, callCookie);
return this._connection.sendMessageToServer(this, this._type, prop, validator(params, '', {
tChannelImpl: tChannelImplToWire,
binary: this._connection.isRemote() ? 'toBase64' : 'buffer'
}), stackTrace, wallTime);
});
};
}
}
return obj[prop];
}
});
channel._object = this;
return channel;
}
async _wrapApiCall(func, isInternal = false) {
const logger = this._logger;
const stack = (0, _stackTrace.captureRawStack)();
const apiZone = _zones.zones.zoneData('apiZone', stack);
if (apiZone) return func(apiZone);
const stackTrace = (0, _stackTrace.captureLibraryStackTrace)(stack);
isInternal = isInternal || this._type === 'LocalUtils';
if (isInternal) delete stackTrace.apiName;
// Enclosing zone could have provided the apiName and wallTime.
const expectZone = _zones.zones.zoneData('expectZone', stack);
const wallTime = expectZone ? expectZone.wallTime : Date.now();
if (!isInternal && expectZone) stackTrace.apiName = expectZone.title;
const csi = isInternal ? undefined : this._instrumentation;
const callCookie = {};
const {
apiName,
frameTexts
} = stackTrace;
try {
logApiCall(logger, `=> ${apiName} started`, isInternal);
const apiZone = {
stackTrace,
isInternal,
reported: false,
csi,
callCookie,
wallTime
};
const result = await _zones.zones.run('apiZone', apiZone, async () => {
return await func(apiZone);
});
csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie);
logApiCall(logger, `<= ${apiName} succeeded`, isInternal);
return result;
} catch (e) {
const innerError = (process.env.PWDEBUGIMPL || (0, _utils.isUnderTest)()) && e.stack ? '\n<inner error>\n' + e.stack : '';
if (apiName && !apiName.includes('<anonymous>')) e.message = apiName + ': ' + e.message;
const stackFrames = '\n' + frameTexts.join('\n') + innerError;
if (stackFrames.trim()) e.stack = e.message + stackFrames;else e.stack = '';
csi === null || csi === void 0 ? void 0 : csi.onApiCallEnd(callCookie, e);
logApiCall(logger, `<= ${apiName} failed`, isInternal);
throw e;
}
}
_toImpl() {
var _this$_connection$toI, _this$_connection;
return (_this$_connection$toI = (_this$_connection = this._connection).toImpl) === null || _this$_connection$toI === void 0 ? void 0 : _this$_connection$toI.call(_this$_connection, this);
}
toJSON() {
// Jest's expect library tries to print objects sometimes.
// RPC objects can contain links to lots of other objects,
// which can cause jest to crash. Let's help it out
// by just returning the important values.
return {
_type: this._type,
_guid: this._guid
};
}
}
exports.ChannelOwner = ChannelOwner;
function logApiCall(logger, message, isNested) {
if (isNested) return;
if (logger && logger.isEnabled('api', 'info')) logger.log('api', 'info', message, [], {
color: 'cyan'
});
_debugLogger.debugLogger.log('api', message);
}
function tChannelImplToWire(names, arg, path, context) {
if (arg._object instanceof ChannelOwner && (names === '*' || names.includes(arg._object._type))) return {
guid: arg._object._guid
};
throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}

View File

@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.envObjectToArray = envObjectToArray;
exports.evaluationScript = evaluationScript;
var _fs = _interopRequireDefault(require("fs"));
var _utils = require("../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
function envObjectToArray(env) {
const result = [];
for (const name in env) {
if (!Object.is(env[name], undefined)) result.push({
name,
value: String(env[name])
});
}
return result;
}
async function evaluationScript(fun, arg, addSourceUrl = true) {
if (typeof fun === 'function') {
const source = fun.toString();
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
return `(${source})(${argString})`;
}
if (arg !== undefined) throw new Error('Cannot evaluate a string with arguments');
if ((0, _utils.isString)(fun)) return fun;
if (fun.content !== undefined) return fun.content;
if (fun.path !== undefined) {
let source = await _fs.default.promises.readFile(fun.path, 'utf8');
if (addSourceUrl) source += '\n//# sourceURL=' + fun.path.replace(/\n/g, '');
return source;
}
throw new Error('Either path or content property must be present');
}

View File

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createInstrumentation = createInstrumentation;
/**
* Copyright (c) Microsoft Corporation. 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.
*/
function createInstrumentation() {
const listeners = [];
return new Proxy({}, {
get: (obj, prop) => {
if (typeof prop !== 'string') return obj[prop];
if (prop === 'addListener') return listener => listeners.push(listener);
if (prop === 'removeListener') return listener => listeners.splice(listeners.indexOf(listener), 1);
if (prop === 'removeAllListeners') return () => listeners.splice(0, listeners.length);
if (!prop.startsWith('on')) return obj[prop];
return async (...params) => {
for (const listener of listeners) {
var _prop, _ref;
await ((_prop = (_ref = listener)[prop]) === null || _prop === void 0 ? void 0 : _prop.call(_ref, ...params));
}
};
}
});
}

View File

@ -0,0 +1,330 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Connection = void 0;
var _browser = require("./browser");
var _browserContext = require("./browserContext");
var _browserType = require("./browserType");
var _channelOwner = require("./channelOwner");
var _elementHandle = require("./elementHandle");
var _frame = require("./frame");
var _jsHandle = require("./jsHandle");
var _network = require("./network");
var _page = require("./page");
var _worker = require("./worker");
var _consoleMessage = require("./consoleMessage");
var _dialog = require("./dialog");
var _serializers = require("../protocol/serializers");
var _cdpSession = require("./cdpSession");
var _playwright = require("./playwright");
var _electron = require("./electron");
var _stream = require("./stream");
var _writableStream = require("./writableStream");
var _debugLogger = require("../common/debugLogger");
var _selectors = require("./selectors");
var _android = require("./android");
var _stackTrace = require("../utils/stackTrace");
var _artifact = require("./artifact");
var _events = require("events");
var _jsonPipe = require("./jsonPipe");
var _fetch = require("./fetch");
var _localUtils = require("./localUtils");
var _tracing = require("./tracing");
var _validator = require("../protocol/validator");
var _clientInstrumentation = require("./clientInstrumentation");
/**
* 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.
*/
class Root extends _channelOwner.ChannelOwner {
constructor(connection) {
super(connection, 'Root', '', {});
}
async initialize() {
return _playwright.Playwright.from((await this._channel.initialize({
sdkLanguage: 'javascript'
})).playwright);
}
}
class DummyChannelOwner extends _channelOwner.ChannelOwner {}
class Connection extends _events.EventEmitter {
// Some connections allow resolving in-process dispatchers.
constructor(localUtils, instrumentation) {
super();
this._objects = new Map();
this.onmessage = message => {};
this._lastId = 0;
this._callbacks = new Map();
this._rootObject = void 0;
this._closedErrorMessage = void 0;
this._isRemote = false;
this._localUtils = void 0;
this.toImpl = void 0;
this._tracingCount = 0;
this._instrumentation = void 0;
this._rootObject = new Root(this);
this._localUtils = localUtils;
this._instrumentation = instrumentation || (0, _clientInstrumentation.createInstrumentation)();
}
markAsRemote() {
this._isRemote = true;
}
isRemote() {
return this._isRemote;
}
localUtils() {
return this._localUtils;
}
async initializePlaywright() {
return await this._rootObject.initialize();
}
pendingProtocolCalls() {
return Array.from(this._callbacks.values()).map(callback => callback.stackTrace).filter(Boolean);
}
getObjectWithKnownName(guid) {
return this._objects.get(guid);
}
async setIsTracing(isTracing) {
if (isTracing) this._tracingCount++;else this._tracingCount--;
}
async sendMessageToServer(object, type, method, params, stackTrace, wallTime) {
var _this$_localUtils;
if (this._closedErrorMessage) throw new Error(this._closedErrorMessage);
const {
apiName,
frames
} = stackTrace || {
apiName: '',
frames: []
};
const guid = object._guid;
const id = ++this._lastId;
const converted = {
id,
guid,
method,
params
};
// Do not include metadata in debug logs to avoid noise.
_debugLogger.debugLogger.log('channel:command', converted);
const location = frames[0] ? {
file: frames[0].file,
line: frames[0].line,
column: frames[0].column
} : undefined;
const metadata = {
wallTime,
apiName,
location,
internal: !apiName
};
this.onmessage({
...converted,
metadata
});
if (this._tracingCount && frames && type !== 'LocalUtils') (_this$_localUtils = this._localUtils) === null || _this$_localUtils === void 0 ? void 0 : _this$_localUtils._channel.addStackToTracingNoReply({
callData: {
stack: frames,
id
}
}).catch(() => {});
return await new Promise((resolve, reject) => this._callbacks.set(id, {
resolve,
reject,
stackTrace,
type,
method
}));
}
dispatch(message) {
if (this._closedErrorMessage) return;
const {
id,
guid,
method,
params,
result,
error
} = message;
if (id) {
_debugLogger.debugLogger.log('channel:response', message);
const callback = this._callbacks.get(id);
if (!callback) throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id);
if (error && !result) {
callback.reject((0, _serializers.parseError)(error));
} else {
const validator = (0, _validator.findValidator)(callback.type, callback.method, 'Result');
callback.resolve(validator(result, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this.isRemote() ? 'fromBase64' : 'buffer'
}));
}
return;
}
_debugLogger.debugLogger.log('channel:event', message);
if (method === '__create__') {
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return;
}
const object = this._objects.get(guid);
if (!object) throw new Error(`Cannot find object to "${method}": ${guid}`);
if (method === '__adopt__') {
const child = this._objects.get(params.guid);
if (!child) throw new Error(`Unknown new child: ${params.guid}`);
object._adopt(child);
return;
}
if (method === '__dispose__') {
object._dispose();
return;
}
const validator = (0, _validator.findValidator)(object._type, method, 'Event');
object._channel.emit(method, validator(params, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this.isRemote() ? 'fromBase64' : 'buffer'
}));
}
close(errorMessage = 'Connection closed') {
const stack = (0, _stackTrace.captureLibraryStackTrace)().frameTexts.join('\n');
if (stack) errorMessage += '\n ==== Closed by ====\n' + stack + '\n';
this._closedErrorMessage = errorMessage;
for (const callback of this._callbacks.values()) callback.reject(new Error(errorMessage));
this._callbacks.clear();
this.emit('close');
}
_tChannelImplFromWire(names, arg, path, context) {
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
const object = this._objects.get(arg.guid);
if (!object) throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
if (names !== '*' && !names.includes(object._type)) throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
return object._channel;
}
throw new _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}
_createRemoteObject(parentGuid, type, guid, initializer) {
const parent = this._objects.get(parentGuid);
if (!parent) throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
let result;
const validator = (0, _validator.findValidator)(type, '', 'Initializer');
initializer = validator(initializer, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this.isRemote() ? 'fromBase64' : 'buffer'
});
switch (type) {
case 'Android':
result = new _android.Android(parent, type, guid, initializer);
break;
case 'AndroidSocket':
result = new _android.AndroidSocket(parent, type, guid, initializer);
break;
case 'AndroidDevice':
result = new _android.AndroidDevice(parent, type, guid, initializer);
break;
case 'APIRequestContext':
result = new _fetch.APIRequestContext(parent, type, guid, initializer);
break;
case 'Artifact':
result = new _artifact.Artifact(parent, type, guid, initializer);
break;
case 'BindingCall':
result = new _page.BindingCall(parent, type, guid, initializer);
break;
case 'Browser':
result = new _browser.Browser(parent, type, guid, initializer);
break;
case 'BrowserContext':
result = new _browserContext.BrowserContext(parent, type, guid, initializer);
break;
case 'BrowserType':
result = new _browserType.BrowserType(parent, type, guid, initializer);
break;
case 'CDPSession':
result = new _cdpSession.CDPSession(parent, type, guid, initializer);
break;
case 'ConsoleMessage':
result = new _consoleMessage.ConsoleMessage(parent, type, guid, initializer);
break;
case 'Dialog':
result = new _dialog.Dialog(parent, type, guid, initializer);
break;
case 'Electron':
result = new _electron.Electron(parent, type, guid, initializer);
break;
case 'ElectronApplication':
result = new _electron.ElectronApplication(parent, type, guid, initializer);
break;
case 'ElementHandle':
result = new _elementHandle.ElementHandle(parent, type, guid, initializer);
break;
case 'Frame':
result = new _frame.Frame(parent, type, guid, initializer);
break;
case 'JSHandle':
result = new _jsHandle.JSHandle(parent, type, guid, initializer);
break;
case 'JsonPipe':
result = new _jsonPipe.JsonPipe(parent, type, guid, initializer);
break;
case 'LocalUtils':
result = new _localUtils.LocalUtils(parent, type, guid, initializer);
if (!this._localUtils) this._localUtils = result;
break;
case 'Page':
result = new _page.Page(parent, type, guid, initializer);
break;
case 'Playwright':
result = new _playwright.Playwright(parent, type, guid, initializer);
break;
case 'Request':
result = new _network.Request(parent, type, guid, initializer);
break;
case 'Response':
result = new _network.Response(parent, type, guid, initializer);
break;
case 'Route':
result = new _network.Route(parent, type, guid, initializer);
break;
case 'Stream':
result = new _stream.Stream(parent, type, guid, initializer);
break;
case 'Selectors':
result = new _selectors.SelectorsOwner(parent, type, guid, initializer);
break;
case 'SocksSupport':
result = new DummyChannelOwner(parent, type, guid, initializer);
break;
case 'Tracing':
result = new _tracing.Tracing(parent, type, guid, initializer);
break;
case 'WebSocket':
result = new _network.WebSocket(parent, type, guid, initializer);
break;
case 'Worker':
result = new _worker.Worker(parent, type, guid, initializer);
break;
case 'WritableStream':
result = new _writableStream.WritableStream(parent, type, guid, initializer);
break;
default:
throw new Error('Missing type ' + type);
}
return result;
}
}
exports.Connection = Connection;

View File

@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ConsoleMessage = void 0;
var util = _interopRequireWildcard(require("util"));
var _jsHandle = require("./jsHandle");
var _channelOwner = require("./channelOwner");
var _page = require("./page");
let _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
_util$inspect$custom = util.inspect.custom;
class ConsoleMessage extends _channelOwner.ChannelOwner {
static from(message) {
return message._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
// Note: currently, we only report console messages for pages and they always have a page.
// However, in the future we might report console messages for service workers or something else,
// where page() would be null.
this._page = void 0;
this._page = _page.Page.fromNullable(initializer.page);
}
page() {
return this._page;
}
type() {
return this._initializer.type;
}
text() {
return this._initializer.text;
}
args() {
return this._initializer.args.map(_jsHandle.JSHandle.from);
}
location() {
return this._initializer.location;
}
[_util$inspect$custom]() {
return this.text();
}
}
exports.ConsoleMessage = ConsoleMessage;

View File

@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Coverage = void 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.
*/
class Coverage {
constructor(channel) {
this._channel = void 0;
this._channel = channel;
}
async startJSCoverage(options = {}) {
await this._channel.startJSCoverage(options);
}
async stopJSCoverage() {
return (await this._channel.stopJSCoverage()).entries;
}
async startCSSCoverage(options = {}) {
await this._channel.startCSSCoverage(options);
}
async stopCSSCoverage() {
return (await this._channel.stopCSSCoverage()).entries;
}
}
exports.Coverage = Coverage;

View File

@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Dialog = void 0;
var _channelOwner = require("./channelOwner");
var _page = require("./page");
/**
* 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.
*/
class Dialog extends _channelOwner.ChannelOwner {
static from(dialog) {
return dialog._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
// Note: dialogs that open early during page initialization block it.
// Therefore, we must report the dialog without a page to be able to handle it.
this._page = void 0;
this._page = _page.Page.fromNullable(initializer.page);
}
page() {
return this._page;
}
type() {
return this._initializer.type;
}
message() {
return this._initializer.message;
}
defaultValue() {
return this._initializer.defaultValue;
}
async accept(promptText) {
await this._channel.accept({
promptText
});
}
async dismiss() {
await this._channel.dismiss();
}
}
exports.Dialog = Dialog;

View File

@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Download = void 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.
*/
class Download {
constructor(page, url, suggestedFilename, artifact) {
this._page = void 0;
this._url = void 0;
this._suggestedFilename = void 0;
this._artifact = void 0;
this._page = page;
this._url = url;
this._suggestedFilename = suggestedFilename;
this._artifact = artifact;
}
page() {
return this._page;
}
url() {
return this._url;
}
suggestedFilename() {
return this._suggestedFilename;
}
async path() {
return this._artifact.pathAfterFinished();
}
async saveAs(path) {
return this._artifact.saveAs(path);
}
async failure() {
return this._artifact.failure();
}
async createReadStream() {
return this._artifact.createReadStream();
}
async cancel() {
return this._artifact.cancel();
}
async delete() {
return this._artifact.delete();
}
}
exports.Download = Download;

View File

@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ElectronApplication = exports.Electron = void 0;
var _timeoutSettings = require("../common/timeoutSettings");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _clientHelper = require("./clientHelper");
var _events = require("./events");
var _jsHandle = require("./jsHandle");
var _waiter = require("./waiter");
/**
* 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.
*/
class Electron extends _channelOwner.ChannelOwner {
static from(electron) {
return electron._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
async launch(options = {}) {
const params = {
...(await (0, _browserContext.prepareBrowserContextParams)(options)),
env: (0, _clientHelper.envObjectToArray)(options.env ? options.env : process.env),
tracesDir: options.tracesDir
};
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
app._context._options = params;
return app;
}
}
exports.Electron = Electron;
class ElectronApplication extends _channelOwner.ChannelOwner {
static from(electronApplication) {
return electronApplication._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._context = void 0;
this._windows = new Set();
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._isClosed = false;
this._context = _browserContext.BrowserContext.from(initializer.context);
for (const page of this._context._pages) this._onPage(page);
this._context.on(_events.Events.BrowserContext.Page, page => this._onPage(page));
this._channel.on('close', () => {
this._isClosed = true;
this.emit(_events.Events.ElectronApplication.Close);
});
}
process() {
return this._toImpl().process();
}
_onPage(page) {
this._windows.add(page);
this.emit(_events.Events.ElectronApplication.Window, page);
page.once(_events.Events.Page.Close, () => this._windows.delete(page));
}
windows() {
// TODO: add ElectronPage class inherting from Page.
return [...this._windows];
}
async firstWindow(options) {
if (this._windows.size) return this._windows.values().next().value;
return this.waitForEvent('window', options);
}
context() {
return this._context;
}
async close() {
if (this._isClosed) return;
await this._channel.close().catch(() => {});
}
async waitForEvent(event, optionsOrPredicate = {}) {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== _events.Events.ElectronApplication.Close) waiter.rejectOnEvent(this, _events.Events.ElectronApplication.Close, new Error('Electron application closed'));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async browserWindow(page) {
const result = await this._channel.browserWindow({
page: page._channel
});
return _jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
}
}
exports.ElectronApplication = ElectronApplication;

View File

@ -0,0 +1,312 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ElementHandle = void 0;
exports.convertInputFiles = convertInputFiles;
exports.convertSelectOptionValues = convertSelectOptionValues;
exports.determineScreenshotType = determineScreenshotType;
var _frame = require("./frame");
var _jsHandle = require("./jsHandle");
var _fs = _interopRequireDefault(require("fs"));
var _utilsBundle = require("../utilsBundle");
var _path = _interopRequireDefault(require("path"));
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _writableStream = require("./writableStream");
var _stream = require("stream");
var _util = require("util");
var _debugLogger = require("../common/debugLogger");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
const pipelineAsync = (0, _util.promisify)(_stream.pipeline);
class ElementHandle extends _jsHandle.JSHandle {
static from(handle) {
return handle._object;
}
static fromNullable(handle) {
return handle ? ElementHandle.from(handle) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._elementChannel = void 0;
this._elementChannel = this._channel;
}
asElement() {
return this;
}
async ownerFrame() {
return _frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
}
async contentFrame() {
return _frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
}
async getAttribute(name) {
const value = (await this._elementChannel.getAttribute({
name
})).value;
return value === undefined ? null : value;
}
async inputValue() {
return (await this._elementChannel.inputValue()).value;
}
async textContent() {
const value = (await this._elementChannel.textContent()).value;
return value === undefined ? null : value;
}
async innerText() {
return (await this._elementChannel.innerText()).value;
}
async innerHTML() {
return (await this._elementChannel.innerHTML()).value;
}
async isChecked() {
return (await this._elementChannel.isChecked()).value;
}
async isDisabled() {
return (await this._elementChannel.isDisabled()).value;
}
async isEditable() {
return (await this._elementChannel.isEditable()).value;
}
async isEnabled() {
return (await this._elementChannel.isEnabled()).value;
}
async isHidden() {
return (await this._elementChannel.isHidden()).value;
}
async isVisible() {
return (await this._elementChannel.isVisible()).value;
}
async dispatchEvent(type, eventInit = {}) {
await this._elementChannel.dispatchEvent({
type,
eventInit: (0, _jsHandle.serializeArgument)(eventInit)
});
}
async scrollIntoViewIfNeeded(options = {}) {
await this._elementChannel.scrollIntoViewIfNeeded(options);
}
async hover(options = {}) {
await this._elementChannel.hover(options);
}
async click(options = {}) {
return await this._elementChannel.click(options);
}
async dblclick(options = {}) {
return await this._elementChannel.dblclick(options);
}
async tap(options = {}) {
return await this._elementChannel.tap(options);
}
async selectOption(values, options = {}) {
const result = await this._elementChannel.selectOption({
...convertSelectOptionValues(values),
...options
});
return result.values;
}
async fill(value, options = {}) {
return await this._elementChannel.fill({
value,
...options
});
}
async selectText(options = {}) {
await this._elementChannel.selectText(options);
}
async setInputFiles(files, options = {}) {
const frame = await this.ownerFrame();
if (!frame) throw new Error('Cannot set input files to detached element');
const converted = await convertInputFiles(files, frame.page().context());
if (converted.files) {
await this._elementChannel.setInputFiles({
files: converted.files,
...options
});
} else {
_debugLogger.debugLogger.log('api', 'switching to large files mode');
await this._elementChannel.setInputFilePaths({
...converted,
...options
});
}
}
async focus() {
await this._elementChannel.focus();
}
async type(text, options = {}) {
await this._elementChannel.type({
text,
...options
});
}
async press(key, options = {}) {
await this._elementChannel.press({
key,
...options
});
}
async check(options = {}) {
return await this._elementChannel.check(options);
}
async uncheck(options = {}) {
return await this._elementChannel.uncheck(options);
}
async setChecked(checked, options) {
if (checked) await this.check(options);else await this.uncheck(options);
}
async boundingBox() {
const value = (await this._elementChannel.boundingBox()).value;
return value === undefined ? null : value;
}
async screenshot(options = {}) {
const copy = {
...options,
mask: undefined
};
if (!copy.type) copy.type = determineScreenshotType(options);
if (options.mask) {
copy.mask = options.mask.map(locator => ({
frame: locator._frame._channel,
selector: locator._selector
}));
}
const result = await this._elementChannel.screenshot(copy);
if (options.path) {
await (0, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, result.binary);
}
return result.binary;
}
async $(selector) {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({
selector
})).element);
}
async $$(selector) {
const result = await this._elementChannel.querySelectorAll({
selector
});
return result.elements.map(h => ElementHandle.from(h));
}
async $eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelector({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelectorAll({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async waitForElementState(state, options = {}) {
return await this._elementChannel.waitForElementState({
state,
...options
});
}
async waitForSelector(selector, options = {}) {
const result = await this._elementChannel.waitForSelector({
selector,
...options
});
return ElementHandle.fromNullable(result.element);
}
}
exports.ElementHandle = ElementHandle;
function convertSelectOptionValues(values) {
if (values === null) return {};
if (!Array.isArray(values)) values = [values];
if (!values.length) return {};
for (let i = 0; i < values.length; i++) (0, _utils.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
if (values[0] instanceof ElementHandle) return {
elements: values.map(v => v._elementChannel)
};
if ((0, _utils.isString)(values[0])) return {
options: values.map(valueOrLabel => ({
valueOrLabel
}))
};
return {
options: values
};
}
async function convertInputFiles(files, context) {
const items = Array.isArray(files) ? files.slice() : [files];
const sizeLimit = 50 * 1024 * 1024;
const totalBufferSizeExceedsLimit = items.reduce((size, item) => size + (typeof item === 'object' && item.buffer ? item.buffer.byteLength : 0), 0) > sizeLimit;
if (totalBufferSizeExceedsLimit) throw new Error('Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.');
const stats = await Promise.all(items.filter(_utils.isString).map(item => _fs.default.promises.stat(item)));
const totalFileSizeExceedsLimit = stats.reduce((acc, stat) => acc + stat.size, 0) > sizeLimit;
if (totalFileSizeExceedsLimit) {
if (context._connection.isRemote()) {
const streams = await Promise.all(items.map(async item => {
(0, _utils.assert)((0, _utils.isString)(item));
const {
writableStream: stream
} = await context._channel.createTempFile({
name: _path.default.basename(item)
});
const writable = _writableStream.WritableStream.from(stream);
await pipelineAsync(_fs.default.createReadStream(item), writable.stream());
return stream;
}));
return {
streams
};
}
return {
localPaths: items.map(f => _path.default.resolve(f))
};
}
const filePayloads = await Promise.all(items.map(async item => {
if (typeof item === 'string') {
return {
name: _path.default.basename(item),
buffer: await _fs.default.promises.readFile(item)
};
} else {
return {
name: item.name,
mimeType: item.mimeType,
buffer: item.buffer
};
}
}));
return {
files: filePayloads
};
}
function determineScreenshotType(options) {
if (options.path) {
const mimeType = _utilsBundle.mime.getType(options.path);
if (mimeType === 'image/png') return 'png';else if (mimeType === 'image/jpeg') return 'jpeg';
throw new Error(`path: unsupported mime type "${mimeType}"`);
}
return options.type;
}

View File

@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Events = void 0;
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications 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.
*/
const Events = {
AndroidDevice: {
WebView: 'webview',
Close: 'close'
},
AndroidSocket: {
Data: 'data',
Close: 'close'
},
AndroidWebView: {
Close: 'close'
},
Browser: {
Disconnected: 'disconnected'
},
BrowserContext: {
Console: 'console',
Close: 'close',
Dialog: 'dialog',
Page: 'page',
BackgroundPage: 'backgroundpage',
ServiceWorker: 'serviceworker',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished'
},
BrowserServer: {
Close: 'close'
},
Page: {
Close: 'close',
Crash: 'crash',
Console: 'console',
Dialog: 'dialog',
Download: 'download',
FileChooser: 'filechooser',
DOMContentLoaded: 'domcontentloaded',
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: 'pageerror',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
FrameNavigated: 'framenavigated',
Load: 'load',
Popup: 'popup',
WebSocket: 'websocket',
Worker: 'worker'
},
WebSocket: {
Close: 'close',
Error: 'socketerror',
FrameReceived: 'framereceived',
FrameSent: 'framesent'
},
Worker: {
Close: 'close'
},
ElectronApplication: {
Close: 'close',
Window: 'window'
}
};
exports.Events = Events;

View File

@ -0,0 +1,327 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.APIResponse = exports.APIRequestContext = exports.APIRequest = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var util = _interopRequireWildcard(require("util"));
var _errors = require("../common/errors");
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _channelOwner = require("./channelOwner");
var _network = require("./network");
var _tracing = require("./tracing");
let _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class APIRequest {
// Instrumentation.
constructor(playwright) {
this._playwright = void 0;
this._contexts = new Set();
this._defaultContextOptions = void 0;
this._playwright = playwright;
}
async newContext(options = {}) {
var _this$_defaultContext;
options = {
...this._defaultContextOptions,
...options
};
const storageState = typeof options.storageState === 'string' ? JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8')) : options.storageState;
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
const tracesDir = (_this$_defaultContext = this._defaultContextOptions) === null || _this$_defaultContext === void 0 ? void 0 : _this$_defaultContext.tracesDir;
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
...options,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
storageState,
tracesDir
})).request);
this._contexts.add(context);
context._request = this;
context._tracing._tracesDir = tracesDir;
await context._instrumentation.onDidCreateRequestContext(context);
return context;
}
}
exports.APIRequest = APIRequest;
class APIRequestContext extends _channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._request = void 0;
this._tracing = void 0;
this._tracing = _tracing.Tracing.from(initializer.tracing);
}
async dispose() {
var _this$_request;
await this._instrumentation.onWillCloseRequestContext(this);
await this._channel.dispose();
(_this$_request = this._request) === null || _this$_request === void 0 ? void 0 : _this$_request._contexts.delete(this);
}
async delete(url, options) {
return this.fetch(url, {
...options,
method: 'DELETE'
});
}
async head(url, options) {
return this.fetch(url, {
...options,
method: 'HEAD'
});
}
async get(url, options) {
return this.fetch(url, {
...options,
method: 'GET'
});
}
async patch(url, options) {
return this.fetch(url, {
...options,
method: 'PATCH'
});
}
async post(url, options) {
return this.fetch(url, {
...options,
method: 'POST'
});
}
async put(url, options) {
return this.fetch(url, {
...options,
method: 'PUT'
});
}
async fetch(urlOrRequest, options = {}) {
const url = (0, _utils.isString)(urlOrRequest) ? urlOrRequest : undefined;
const request = (0, _utils.isString)(urlOrRequest) ? undefined : urlOrRequest;
return this._innerFetch({
url,
request,
...options
});
}
async _innerFetch(options = {}) {
return this._wrapApiCall(async () => {
var _options$request, _options$request2, _options$request3;
(0, _utils.assert)(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
(0, _utils.assert)((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
(0, _utils.assert)(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' should be greater than or equal to '0'`);
const url = options.url !== undefined ? options.url : options.request.url();
const params = objectToArray(options.params);
const method = options.method || ((_options$request = options.request) === null || _options$request === void 0 ? void 0 : _options$request.method());
const maxRedirects = options.maxRedirects;
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || ((_options$request2 = options.request) === null || _options$request2 === void 0 ? void 0 : _options$request2.headers());
const headers = headersObj ? (0, _utils.headersObjectToArray)(headersObj) : undefined;
let jsonData;
let formData;
let multipartData;
let postDataBuffer;
if (options.data !== undefined) {
if ((0, _utils.isString)(options.data)) {
if (isJsonContentType(headers)) jsonData = options.data;else postDataBuffer = Buffer.from(options.data, 'utf8');
} else if (Buffer.isBuffer(options.data)) {
postDataBuffer = options.data;
} else if (typeof options.data === 'object' || typeof options.data === 'number' || typeof options.data === 'boolean') {
jsonData = options.data;
} else {
throw new Error(`Unexpected 'data' type`);
}
} else if (options.form) {
formData = objectToArray(options.form);
} else if (options.multipart) {
multipartData = [];
// Convert file-like values to ServerFilePayload structs.
for (const [name, value] of Object.entries(options.multipart)) {
if (isFilePayload(value)) {
const payload = value;
if (!Buffer.isBuffer(payload.buffer)) throw new Error(`Unexpected buffer type of 'data.${name}'`);
multipartData.push({
name,
file: filePayloadToJson(payload)
});
} else if (value instanceof _fs.default.ReadStream) {
multipartData.push({
name,
file: await readStreamToJson(value)
});
} else {
multipartData.push({
name,
value: String(value)
});
}
}
}
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined) postDataBuffer = ((_options$request3 = options.request) === null || _options$request3 === void 0 ? void 0 : _options$request3.postDataBuffer()) || undefined;
const fixtures = {
__testHookLookup: options.__testHookLookup
};
const result = await this._channel.fetch({
url,
params,
method,
headers,
postData: postDataBuffer,
jsonData,
formData,
multipartData,
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
maxRedirects: maxRedirects,
...fixtures
});
return new APIResponse(this, result.response);
});
}
async storageState(options = {}) {
const state = await this._channel.storageState();
if (options.path) {
await (0, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
}
return state;
}
}
exports.APIRequestContext = APIRequestContext;
_util$inspect$custom = util.inspect.custom;
class APIResponse {
constructor(context, initializer) {
this._initializer = void 0;
this._headers = void 0;
this._request = void 0;
this._request = context;
this._initializer = initializer;
this._headers = new _network.RawHeaders(this._initializer.headers);
}
ok() {
return this._initializer.status >= 200 && this._initializer.status <= 299;
}
url() {
return this._initializer.url;
}
status() {
return this._initializer.status;
}
statusText() {
return this._initializer.statusText;
}
headers() {
return this._headers.headers();
}
headersArray() {
return this._headers.headersArray();
}
async body() {
try {
const result = await this._request._channel.fetchResponseBody({
fetchUid: this._fetchUid()
});
if (result.binary === undefined) throw new Error('Response has been disposed');
return result.binary;
} catch (e) {
if (e.message.includes(_errors.kBrowserOrContextClosedError)) throw new Error('Response has been disposed');
throw e;
}
}
async text() {
const content = await this.body();
return content.toString('utf8');
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
async dispose() {
await this._request._channel.disposeAPIResponse({
fetchUid: this._fetchUid()
});
}
[_util$inspect$custom]() {
const headers = this.headersArray().map(({
name,
value
}) => ` ${name}: ${value}`);
return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
}
_fetchUid() {
return this._initializer.fetchUid;
}
async _fetchLog() {
const {
log
} = await this._request._channel.fetchLog({
fetchUid: this._fetchUid()
});
return log;
}
}
exports.APIResponse = APIResponse;
function filePayloadToJson(payload) {
return {
name: payload.name,
mimeType: payload.mimeType,
buffer: payload.buffer
};
}
async function readStreamToJson(stream) {
const buffer = await new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks)));
stream.on('error', err => reject(err));
});
const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path;
return {
name: _path.default.basename(streamPath),
buffer
};
}
function isJsonContentType(headers) {
if (!headers) return false;
for (const {
name,
value
} of headers) {
if (name.toLocaleLowerCase() === 'content-type') return value === 'application/json';
}
return false;
}
function objectToArray(map) {
if (!map) return undefined;
const result = [];
for (const [name, value] of Object.entries(map)) result.push({
name,
value: String(value)
});
return result;
}
function isFilePayload(value) {
return typeof value === 'object' && value['name'] && value['mimeType'] && value['buffer'];
}

View File

@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FileChooser = void 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.
*/
class FileChooser {
constructor(page, elementHandle, isMultiple) {
this._page = void 0;
this._elementHandle = void 0;
this._isMultiple = void 0;
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
}
element() {
return this._elementHandle;
}
isMultiple() {
return this._isMultiple;
}
page() {
return this._page;
}
async setFiles(files, options) {
return this._elementHandle.setInputFiles(files, options);
}
}
exports.FileChooser = FileChooser;

View File

@ -0,0 +1,515 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Frame = void 0;
exports.verifyLoadState = verifyLoadState;
var _utils = require("../utils");
var _channelOwner = require("./channelOwner");
var _locator = require("./locator");
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
var _elementHandle = require("./elementHandle");
var _jsHandle = require("./jsHandle");
var _fs = _interopRequireDefault(require("fs"));
var network = _interopRequireWildcard(require("./network"));
var _events = require("events");
var _waiter = require("./waiter");
var _events2 = require("./events");
var _types = require("./types");
var _network2 = require("../utils/network");
var _debugLogger = require("../common/debugLogger");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class Frame extends _channelOwner.ChannelOwner {
static from(frame) {
return frame._object;
}
static fromNullable(frame) {
return frame ? Frame.from(frame) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._eventEmitter = void 0;
this._loadStates = void 0;
this._parentFrame = null;
this._url = '';
this._name = '';
this._detached = false;
this._childFrames = new Set();
this._page = void 0;
this._eventEmitter = new _events.EventEmitter();
this._eventEmitter.setMaxListeners(0);
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
if (this._parentFrame) this._parentFrame._childFrames.add(this);
this._name = initializer.name;
this._url = initializer.url;
this._loadStates = new Set(initializer.loadStates);
this._channel.on('loadstate', event => {
if (event.add) {
this._loadStates.add(event.add);
this._eventEmitter.emit('loadstate', event.add);
}
if (event.remove) this._loadStates.delete(event.remove);
if (!this._parentFrame && event.add === 'load' && this._page) this._page.emit(_events2.Events.Page.Load, this._page);
if (!this._parentFrame && event.add === 'domcontentloaded' && this._page) this._page.emit(_events2.Events.Page.DOMContentLoaded, this._page);
});
this._channel.on('navigated', event => {
this._url = event.url;
this._name = event.name;
this._eventEmitter.emit('navigated', event);
if (!event.error && this._page) this._page.emit(_events2.Events.Page.FrameNavigated, this);
});
}
page() {
return this._page;
}
async goto(url, options = {}) {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return network.Response.fromNullable((await this._channel.goto({
url,
...options,
waitUntil
})).response);
}
_setupNavigationWaiter(options) {
const waiter = new _waiter.Waiter(this._page, '');
if (this._page.isClosed()) waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
waiter.rejectOnEvent(this._page, _events2.Events.Page.Close, new Error('Navigation failed because page was closed!'));
waiter.rejectOnEvent(this._page, _events2.Events.Page.Crash, new Error('Navigation failed because page crashed!'));
waiter.rejectOnEvent(this._page, _events2.Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
const timeout = this._page._timeoutSettings.navigationTimeout(options);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded.`);
return waiter;
}
async waitForNavigation(options = {}) {
return this._page._wrapApiCall(async () => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
const waiter = this._setupNavigationWaiter(options);
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, 'navigated', event => {
var _this$_page;
// Any failed navigation results in a rejection.
if (event.error) return true;
waiter.log(` navigated to "${event.url}"`);
return (0, _network2.urlMatches)((_this$_page = this._page) === null || _this$_page === void 0 ? void 0 : _this$_page.context()._options.baseURL, event.url, options.url);
});
if (navigatedEvent.error) {
const e = new Error(navigatedEvent.error);
e.stack = '';
await waiter.waitForPromise(Promise.reject(e));
}
if (!this._loadStates.has(waitUntil)) {
await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => {
waiter.log(` "${s}" event fired`);
return s === waitUntil;
});
}
const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
const response = request ? await waiter.waitForPromise(request._finalRequest()._internalResponse()) : null;
waiter.dispose();
return response;
});
}
async waitForLoadState(state = 'load', options = {}) {
state = verifyLoadState('state', state);
return this._page._wrapApiCall(async () => {
const waiter = this._setupNavigationWaiter(options);
if (this._loadStates.has(state)) {
waiter.log(` not waiting, "${state}" event already fired`);
} else {
await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => {
waiter.log(` "${s}" event fired`);
return s === state;
});
}
waiter.dispose();
});
}
async waitForURL(url, options = {}) {
var _this$_page2;
if ((0, _network2.urlMatches)((_this$_page2 = this._page) === null || _this$_page2 === void 0 ? void 0 : _this$_page2.context()._options.baseURL, this.url(), url)) return await this.waitForLoadState(options.waitUntil, options);
await this.waitForNavigation({
url,
...options
});
}
async frameElement() {
return _elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
}
async evaluateHandle(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async _evaluateExposeUtilityScript(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
exposeUtilityScript: true,
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async $(selector, options) {
const result = await this._channel.querySelector({
selector,
...options
});
return _elementHandle.ElementHandle.fromNullable(result.element);
}
async waitForSelector(selector, options = {}) {
if (options.visibility) throw new Error('options.visibility is not supported, did you mean options.state?');
if (options.waitFor && options.waitFor !== 'visible') throw new Error('options.waitFor is not supported, did you mean options.state?');
const result = await this._channel.waitForSelector({
selector,
...options
});
return _elementHandle.ElementHandle.fromNullable(result.element);
}
async dispatchEvent(selector, type, eventInit, options = {}) {
await this._channel.dispatchEvent({
selector,
type,
eventInit: (0, _jsHandle.serializeArgument)(eventInit),
...options
});
}
async $eval(selector, pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelector({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelectorAll({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async $$(selector) {
const result = await this._channel.querySelectorAll({
selector
});
return result.elements.map(e => _elementHandle.ElementHandle.from(e));
}
async _queryCount(selector) {
return (await this._channel.queryCount({
selector
})).value;
}
async content() {
return (await this._channel.content()).value;
}
async setContent(html, options = {}) {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
await this._channel.setContent({
html,
...options,
waitUntil
});
}
name() {
return this._name || '';
}
url() {
return this._url;
}
parentFrame() {
return this._parentFrame;
}
childFrames() {
return Array.from(this._childFrames);
}
isDetached() {
return this._detached;
}
async addScriptTag(options = {}) {
const copy = {
...options
};
if (copy.path) {
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
copy.content += '//# sourceURL=' + copy.path.replace(/\n/g, '');
}
return _elementHandle.ElementHandle.from((await this._channel.addScriptTag({
...copy
})).element);
}
async addStyleTag(options = {}) {
const copy = {
...options
};
if (copy.path) {
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/';
}
return _elementHandle.ElementHandle.from((await this._channel.addStyleTag({
...copy
})).element);
}
async click(selector, options = {}) {
return await this._channel.click({
selector,
...options
});
}
async dblclick(selector, options = {}) {
return await this._channel.dblclick({
selector,
...options
});
}
async dragAndDrop(source, target, options = {}) {
return await this._channel.dragAndDrop({
source,
target,
...options
});
}
async tap(selector, options = {}) {
return await this._channel.tap({
selector,
...options
});
}
async fill(selector, value, options = {}) {
return await this._channel.fill({
selector,
value,
...options
});
}
async _highlight(selector) {
return await this._channel.highlight({
selector
});
}
locator(selector, options) {
return new _locator.Locator(this, selector, options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)((0, _locator.testIdAttributeName)(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new _locator.FrameLocator(this, selector);
}
async focus(selector, options = {}) {
await this._channel.focus({
selector,
...options
});
}
async textContent(selector, options = {}) {
const value = (await this._channel.textContent({
selector,
...options
})).value;
return value === undefined ? null : value;
}
async innerText(selector, options = {}) {
return (await this._channel.innerText({
selector,
...options
})).value;
}
async innerHTML(selector, options = {}) {
return (await this._channel.innerHTML({
selector,
...options
})).value;
}
async getAttribute(selector, name, options = {}) {
const value = (await this._channel.getAttribute({
selector,
name,
...options
})).value;
return value === undefined ? null : value;
}
async inputValue(selector, options = {}) {
return (await this._channel.inputValue({
selector,
...options
})).value;
}
async isChecked(selector, options = {}) {
return (await this._channel.isChecked({
selector,
...options
})).value;
}
async isDisabled(selector, options = {}) {
return (await this._channel.isDisabled({
selector,
...options
})).value;
}
async isEditable(selector, options = {}) {
return (await this._channel.isEditable({
selector,
...options
})).value;
}
async isEnabled(selector, options = {}) {
return (await this._channel.isEnabled({
selector,
...options
})).value;
}
async isHidden(selector, options = {}) {
return (await this._channel.isHidden({
selector,
...options
})).value;
}
async isVisible(selector, options = {}) {
return (await this._channel.isVisible({
selector,
...options
})).value;
}
async hover(selector, options = {}) {
await this._channel.hover({
selector,
...options
});
}
async selectOption(selector, values, options = {}) {
return (await this._channel.selectOption({
selector,
...(0, _elementHandle.convertSelectOptionValues)(values),
...options
})).values;
}
async setInputFiles(selector, files, options = {}) {
const converted = await (0, _elementHandle.convertInputFiles)(files, this.page().context());
if (converted.files) {
await this._channel.setInputFiles({
selector,
files: converted.files,
...options
});
} else {
_debugLogger.debugLogger.log('api', 'switching to large files mode');
await this._channel.setInputFilePaths({
selector,
...converted,
...options
});
}
}
async type(selector, text, options = {}) {
await this._channel.type({
selector,
text,
...options
});
}
async press(selector, key, options = {}) {
await this._channel.press({
selector,
key,
...options
});
}
async check(selector, options = {}) {
await this._channel.check({
selector,
...options
});
}
async uncheck(selector, options = {}) {
await this._channel.uncheck({
selector,
...options
});
}
async setChecked(selector, checked, options) {
if (checked) await this.check(selector, options);else await this.uncheck(selector, options);
}
async waitForTimeout(timeout) {
await this._channel.waitForTimeout({
timeout
});
}
async waitForFunction(pageFunction, arg, options = {}) {
if (typeof options.polling === 'string') (0, _utils.assert)(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
const result = await this._channel.waitForFunction({
...options,
pollingInterval: options.polling === 'raf' ? undefined : options.polling,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
}
async title() {
return (await this._channel.title()).value;
}
}
exports.Frame = Frame;
function verifyLoadState(name, waitUntil) {
if (waitUntil === 'networkidle0') waitUntil = 'networkidle';
if (!_types.kLifecycleEvents.has(waitUntil)) throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
return waitUntil;
}

View File

@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HarRouter = void 0;
var _debugLogger = require("../common/debugLogger");
var _events = require("./events");
/**
* 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.
*/
class HarRouter {
static async create(localUtils, file, notFoundAction, options) {
const {
harId,
error
} = await localUtils._channel.harOpen({
file
});
if (error) throw new Error(error);
return new HarRouter(localUtils, harId, notFoundAction, options);
}
constructor(localUtils, harId, notFoundAction, options) {
this._localUtils = void 0;
this._harId = void 0;
this._notFoundAction = void 0;
this._options = void 0;
this._localUtils = localUtils;
this._harId = harId;
this._options = options;
this._notFoundAction = notFoundAction;
}
async _handle(route) {
const request = route.request();
const response = await this._localUtils._channel.harLookup({
harId: this._harId,
url: request.url(),
method: request.method(),
headers: await request.headersArray(),
postData: request.postDataBuffer() || undefined,
isNavigationRequest: request.isNavigationRequest()
});
if (response.action === 'redirect') {
_debugLogger.debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
await route._redirectNavigationRequest(response.redirectURL);
return;
}
if (response.action === 'fulfill') {
await route.fulfill({
status: response.status,
headers: Object.fromEntries(response.headers.map(h => [h.name, h.value])),
body: response.body
});
return;
}
if (response.action === 'error') _debugLogger.debugLogger.log('api', 'HAR: ' + response.message);
// Report the error, but fall through to the default handler.
if (this._notFoundAction === 'abort') {
await route.abort();
return;
}
await route.fallback();
}
async addContextRoute(context) {
await context.route(this._options.urlMatch || '**/*', route => this._handle(route));
context.once(_events.Events.BrowserContext.Close, () => this.dispose());
}
async addPageRoute(page) {
await page.route(this._options.urlMatch || '**/*', route => this._handle(route));
page.once(_events.Events.Page.Close, () => this.dispose());
}
dispose() {
this._localUtils._channel.harClose({
harId: this._harId
}).catch(() => {});
}
}
exports.HarRouter = HarRouter;

View File

@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Touchscreen = exports.Mouse = exports.Keyboard = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class Keyboard {
constructor(page) {
this._page = void 0;
this._page = page;
}
async down(key) {
await this._page._channel.keyboardDown({
key
});
}
async up(key) {
await this._page._channel.keyboardUp({
key
});
}
async insertText(text) {
await this._page._channel.keyboardInsertText({
text
});
}
async type(text, options = {}) {
await this._page._channel.keyboardType({
text,
...options
});
}
async press(key, options = {}) {
await this._page._channel.keyboardPress({
key,
...options
});
}
}
exports.Keyboard = Keyboard;
class Mouse {
constructor(page) {
this._page = void 0;
this._page = page;
}
async move(x, y, options = {}) {
await this._page._channel.mouseMove({
x,
y,
...options
});
}
async down(options = {}) {
await this._page._channel.mouseDown({
...options
});
}
async up(options = {}) {
await this._page._channel.mouseUp(options);
}
async click(x, y, options = {}) {
await this._page._channel.mouseClick({
x,
y,
...options
});
}
async dblclick(x, y, options = {}) {
await this.click(x, y, {
...options,
clickCount: 2
});
}
async wheel(deltaX, deltaY) {
await this._page._channel.mouseWheel({
deltaX,
deltaY
});
}
}
exports.Mouse = Mouse;
class Touchscreen {
constructor(page) {
this._page = void 0;
this._page = page;
}
async tap(x, y) {
await this._page._channel.touchscreenTap({
x,
y
});
}
}
exports.Touchscreen = Touchscreen;

View File

@ -0,0 +1,119 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JSHandle = void 0;
exports.assertMaxArguments = assertMaxArguments;
exports.parseResult = parseResult;
exports.serializeArgument = serializeArgument;
var _channelOwner = require("./channelOwner");
var _serializers = require("../protocol/serializers");
/**
* 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.
*/
class JSHandle extends _channelOwner.ChannelOwner {
static from(handle) {
return handle._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._preview = void 0;
this._preview = this._initializer.preview;
this._channel.on('previewUpdated', ({
preview
}) => this._preview = preview);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: serializeArgument(arg)
});
return parseResult(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: serializeArgument(arg)
});
return JSHandle.from(result.handle);
}
async getProperty(propertyName) {
const result = await this._channel.getProperty({
name: propertyName
});
return JSHandle.from(result.handle);
}
async getProperties() {
const map = new Map();
for (const {
name,
value
} of (await this._channel.getPropertyList()).properties) map.set(name, JSHandle.from(value));
return map;
}
async jsonValue() {
return parseResult((await this._channel.jsonValue()).value);
}
asElement() {
return null;
}
async dispose() {
return await this._channel.dispose();
}
async _objectCount() {
return this._wrapApiCall(async () => {
const {
count
} = await this._channel.objectCount();
return count;
});
}
toString() {
return this._preview;
}
}
// This function takes care of converting all JSHandles to their channels,
// so that generic channel serializer converts them to guids.
exports.JSHandle = JSHandle;
function serializeArgument(arg) {
const handles = [];
const pushHandle = channel => {
handles.push(channel);
return handles.length - 1;
};
const value = (0, _serializers.serializeValue)(arg, value => {
if (value instanceof JSHandle) return {
h: pushHandle(value._channel)
};
return {
fallThrough: value
};
});
return {
value,
handles
};
}
function parseResult(value) {
return (0, _serializers.parseSerializedValue)(value, undefined);
}
function assertMaxArguments(count, max) {
if (count > max) throw new Error('Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.');
}

View File

@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JsonPipe = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class JsonPipe extends _channelOwner.ChannelOwner {
static from(jsonPipe) {
return jsonPipe._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
channel() {
return this._channel;
}
}
exports.JsonPipe = JsonPipe;

View File

@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LocalUtils = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class LocalUtils extends _channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
}
exports.LocalUtils = LocalUtils;

View File

@ -0,0 +1,429 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Locator = exports.FrameLocator = void 0;
exports.setTestIdAttribute = setTestIdAttribute;
exports.testIdAttributeName = testIdAttributeName;
var util = _interopRequireWildcard(require("util"));
var _utils = require("../utils");
var _elementHandle = require("./elementHandle");
var _jsHandle = require("./jsHandle");
var _stringUtils = require("../utils/isomorphic/stringUtils");
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
let _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
_util$inspect$custom = util.inspect.custom;
class Locator {
constructor(frame, selector, options) {
this._frame = void 0;
this._selector = void 0;
this._frame = frame;
this._selector = selector;
if (options !== null && options !== void 0 && options.hasText) this._selector += ` >> internal:has-text=${(0, _stringUtils.escapeForTextSelector)(options.hasText, false)}`;
if (options !== null && options !== void 0 && options.hasNotText) this._selector += ` >> internal:has-not-text=${(0, _stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
if (options !== null && options !== void 0 && options.has) {
const locator = options.has;
if (locator._frame !== frame) throw new Error(`Inner "has" locator must belong to the same frame.`);
this._selector += ` >> internal:has=` + JSON.stringify(locator._selector);
}
if (options !== null && options !== void 0 && options.hasNot) {
const locator = options.hasNot;
if (locator._frame !== frame) throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
}
}
async _withElement(task, timeout) {
timeout = this._frame.page()._timeoutSettings.timeout({
timeout
});
const deadline = timeout ? (0, _utils.monotonicTime)() + timeout : 0;
return this._frame._wrapApiCall(async () => {
const result = await this._frame._channel.waitForSelector({
selector: this._selector,
strict: true,
state: 'attached',
timeout
});
const handle = _elementHandle.ElementHandle.fromNullable(result.element);
if (!handle) throw new Error(`Could not resolve ${this._selector} to DOM Element`);
try {
return await task(handle, deadline ? deadline - (0, _utils.monotonicTime)() : 0);
} finally {
await handle.dispose();
}
});
}
page() {
return this._frame.page();
}
async boundingBox(options) {
return this._withElement(h => h.boundingBox(), options === null || options === void 0 ? void 0 : options.timeout);
}
async check(options = {}) {
return this._frame.check(this._selector, {
strict: true,
...options
});
}
async click(options = {}) {
return this._frame.click(this._selector, {
strict: true,
...options
});
}
async dblclick(options = {}) {
return this._frame.dblclick(this._selector, {
strict: true,
...options
});
}
async dispatchEvent(type, eventInit = {}, options) {
return this._frame.dispatchEvent(this._selector, type, eventInit, {
strict: true,
...options
});
}
async dragTo(target, options = {}) {
return this._frame.dragAndDrop(this._selector, target._selector, {
strict: true,
...options
});
}
async evaluate(pageFunction, arg, options) {
return this._withElement(h => h.evaluate(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
}
async evaluateAll(pageFunction, arg) {
return this._frame.$$eval(this._selector, pageFunction, arg);
}
async evaluateHandle(pageFunction, arg, options) {
return this._withElement(h => h.evaluateHandle(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
}
async fill(value, options = {}) {
return this._frame.fill(this._selector, value, {
strict: true,
...options
});
}
async clear(options = {}) {
return this.fill('', options);
}
async _highlight() {
// VS Code extension uses this one, keep it for now.
return this._frame._highlight(this._selector);
}
async highlight() {
return this._frame._highlight(this._selector);
}
locator(selectorOrLocator, options) {
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._selector + ' >> ' + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ' >> internal:chain=' + JSON.stringify(selectorOrLocator._selector), options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
}
filter(options) {
return new Locator(this._frame, this._selector, options);
}
async elementHandle(options) {
return await this._frame.waitForSelector(this._selector, {
strict: true,
state: 'attached',
...options
});
}
async elementHandles() {
return this._frame.$$(this._selector);
}
first() {
return new Locator(this._frame, this._selector + ' >> nth=0');
}
last() {
return new Locator(this._frame, this._selector + ` >> nth=-1`);
}
nth(index) {
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
}
and(locator) {
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
}
or(locator) {
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:or=` + JSON.stringify(locator._selector));
}
async focus(options) {
return this._frame.focus(this._selector, {
strict: true,
...options
});
}
async blur(options) {
await this._frame._channel.blur({
selector: this._selector,
strict: true,
...options
});
}
async count() {
return this._frame._queryCount(this._selector);
}
async getAttribute(name, options) {
return this._frame.getAttribute(this._selector, name, {
strict: true,
...options
});
}
async hover(options = {}) {
return this._frame.hover(this._selector, {
strict: true,
...options
});
}
async innerHTML(options) {
return this._frame.innerHTML(this._selector, {
strict: true,
...options
});
}
async innerText(options) {
return this._frame.innerText(this._selector, {
strict: true,
...options
});
}
async inputValue(options) {
return this._frame.inputValue(this._selector, {
strict: true,
...options
});
}
async isChecked(options) {
return this._frame.isChecked(this._selector, {
strict: true,
...options
});
}
async isDisabled(options) {
return this._frame.isDisabled(this._selector, {
strict: true,
...options
});
}
async isEditable(options) {
return this._frame.isEditable(this._selector, {
strict: true,
...options
});
}
async isEnabled(options) {
return this._frame.isEnabled(this._selector, {
strict: true,
...options
});
}
async isHidden(options) {
return this._frame.isHidden(this._selector, {
strict: true,
...options
});
}
async isVisible(options) {
return this._frame.isVisible(this._selector, {
strict: true,
...options
});
}
async press(key, options = {}) {
return this._frame.press(this._selector, key, {
strict: true,
...options
});
}
async screenshot(options = {}) {
return this._withElement((h, timeout) => h.screenshot({
...options,
timeout
}), options.timeout);
}
async scrollIntoViewIfNeeded(options = {}) {
return this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({
...options,
timeout
}), options.timeout);
}
async selectOption(values, options = {}) {
return this._frame.selectOption(this._selector, values, {
strict: true,
...options
});
}
async selectText(options = {}) {
return this._withElement((h, timeout) => h.selectText({
...options,
timeout
}), options.timeout);
}
async setChecked(checked, options) {
if (checked) await this.check(options);else await this.uncheck(options);
}
async setInputFiles(files, options = {}) {
return this._frame.setInputFiles(this._selector, files, {
strict: true,
...options
});
}
async tap(options = {}) {
return this._frame.tap(this._selector, {
strict: true,
...options
});
}
async textContent(options) {
return this._frame.textContent(this._selector, {
strict: true,
...options
});
}
async type(text, options = {}) {
return this._frame.type(this._selector, text, {
strict: true,
...options
});
}
async uncheck(options = {}) {
return this._frame.uncheck(this._selector, {
strict: true,
...options
});
}
async all() {
return new Array(await this.count()).fill(0).map((e, i) => this.nth(i));
}
async allInnerTexts() {
return this._frame.$$eval(this._selector, ee => ee.map(e => e.innerText));
}
async allTextContents() {
return this._frame.$$eval(this._selector, ee => ee.map(e => e.textContent || ''));
}
async waitFor(options) {
await this._frame._channel.waitForSelector({
selector: this._selector,
strict: true,
omitReturnValue: true,
...options
});
}
async _expect(expression, options) {
const params = {
selector: this._selector,
expression,
...options,
isNot: !!options.isNot
};
params.expectedValue = (0, _jsHandle.serializeArgument)(options.expectedValue);
const result = await this._frame._channel.expect(params);
if (result.received !== undefined) result.received = (0, _jsHandle.parseResult)(result.received);
return result;
}
[_util$inspect$custom]() {
return this.toString();
}
toString() {
return `Locator@${this._selector}`;
}
}
exports.Locator = Locator;
class FrameLocator {
constructor(frame, selector) {
this._frame = void 0;
this._frameSelector = void 0;
this._frame = frame;
this._frameSelector = selector;
}
locator(selectorOrLocator, options) {
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator._selector, options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector);
}
first() {
return new FrameLocator(this._frame, this._frameSelector + ' >> nth=0');
}
last() {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
}
nth(index) {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
}
}
exports.FrameLocator = FrameLocator;
let _testIdAttributeName = 'data-testid';
function testIdAttributeName() {
return _testIdAttributeName;
}
function setTestIdAttribute(attributeName) {
_testIdAttributeName = attributeName;
}

View File

@ -0,0 +1,552 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WebSocket = exports.RouteHandler = exports.Route = exports.Response = exports.Request = exports.RawHeaders = void 0;
exports.validateHeaders = validateHeaders;
var _url = require("url");
var _channelOwner = require("./channelOwner");
var _frame = require("./frame");
var _worker = require("./worker");
var _fs = _interopRequireDefault(require("fs"));
var _utilsBundle = require("../utilsBundle");
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
var _events = require("./events");
var _waiter = require("./waiter");
var _network = require("../utils/network");
var _multimap = require("../utils/multimap");
var _fetch = require("./fetch");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
class Request extends _channelOwner.ChannelOwner {
static from(request) {
return request._object;
}
static fromNullable(request) {
return request ? Request.from(request) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._redirectedFrom = null;
this._redirectedTo = null;
this._failureText = null;
this._provisionalHeaders = void 0;
this._actualHeadersPromise = void 0;
this._timing = void 0;
this._fallbackOverrides = {};
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
if (this._redirectedFrom) this._redirectedFrom._redirectedTo = this;
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._fallbackOverrides.postDataBuffer = initializer.postData;
this._timing = {
startTime: 0,
domainLookupStart: -1,
domainLookupEnd: -1,
connectStart: -1,
secureConnectionStart: -1,
connectEnd: -1,
requestStart: -1,
responseStart: -1,
responseEnd: -1
};
}
url() {
return this._fallbackOverrides.url || this._initializer.url;
}
resourceType() {
return this._initializer.resourceType;
}
method() {
return this._fallbackOverrides.method || this._initializer.method;
}
postData() {
var _this$_fallbackOverri;
return ((_this$_fallbackOverri = this._fallbackOverrides.postDataBuffer) === null || _this$_fallbackOverri === void 0 ? void 0 : _this$_fallbackOverri.toString('utf-8')) || null;
}
postDataBuffer() {
return this._fallbackOverrides.postDataBuffer || null;
}
postDataJSON() {
const postData = this.postData();
if (!postData) return null;
const contentType = this.headers()['content-type'];
if (contentType === 'application/x-www-form-urlencoded') {
const entries = {};
const parsed = new _url.URLSearchParams(postData);
for (const [k, v] of parsed.entries()) entries[k] = v;
return entries;
}
try {
return JSON.parse(postData);
} catch (e) {
throw new Error('POST data is not a valid JSON object: ' + postData);
}
}
/**
* @deprecated
*/
headers() {
if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
return this._provisionalHeaders.headers();
}
_context() {
// TODO: make sure this works for service worker requests.
return this.frame().page().context();
}
_actualHeaders() {
if (this._fallbackOverrides.headers) return Promise.resolve(RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers));
if (!this._actualHeadersPromise) {
this._actualHeadersPromise = this._wrapApiCall(async () => {
return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
});
}
return this._actualHeadersPromise;
}
async allHeaders() {
return (await this._actualHeaders()).headers();
}
async headersArray() {
return (await this._actualHeaders()).headersArray();
}
async headerValue(name) {
return (await this._actualHeaders()).get(name);
}
async response() {
return Response.fromNullable((await this._channel.response()).response);
}
async _internalResponse() {
return this._wrapApiCall(async () => {
return Response.fromNullable((await this._channel.response()).response);
}, true);
}
frame() {
if (!this._initializer.frame) {
(0, _utils.assert)(this.serviceWorker());
throw new Error('Service Worker requests do not have an associated frame.');
}
return _frame.Frame.from(this._initializer.frame);
}
serviceWorker() {
return this._initializer.serviceWorker ? _worker.Worker.from(this._initializer.serviceWorker) : null;
}
isNavigationRequest() {
return this._initializer.isNavigationRequest;
}
redirectedFrom() {
return this._redirectedFrom;
}
redirectedTo() {
return this._redirectedTo;
}
failure() {
if (this._failureText === null) return null;
return {
errorText: this._failureText
};
}
timing() {
return this._timing;
}
async sizes() {
const response = await this.response();
if (!response) throw new Error('Unable to fetch sizes for failed request');
return (await response._channel.sizes()).sizes;
}
_setResponseEndTiming(responseEndTiming) {
this._timing.responseEnd = responseEndTiming;
if (this._timing.responseStart === -1) this._timing.responseStart = responseEndTiming;
}
_finalRequest() {
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
}
_applyFallbackOverrides(overrides) {
if (overrides.url) this._fallbackOverrides.url = overrides.url;
if (overrides.method) this._fallbackOverrides.method = overrides.method;
if (overrides.headers) this._fallbackOverrides.headers = overrides.headers;
if ((0, _utils.isString)(overrides.postData)) this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, 'utf-8');else if (overrides.postData instanceof Buffer) this._fallbackOverrides.postDataBuffer = overrides.postData;else if (overrides.postData) this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), 'utf-8');
}
_fallbackOverridesForContinue() {
return this._fallbackOverrides;
}
_targetClosedScope() {
var _this$serviceWorker, _this$frame$_page;
return ((_this$serviceWorker = this.serviceWorker()) === null || _this$serviceWorker === void 0 ? void 0 : _this$serviceWorker._closedScope) || ((_this$frame$_page = this.frame()._page) === null || _this$frame$_page === void 0 ? void 0 : _this$frame$_page._closedOrCrashedScope) || new _manualPromise.LongStandingScope();
}
}
exports.Request = Request;
class Route extends _channelOwner.ChannelOwner {
static from(route) {
return route._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._handlingPromise = null;
}
request() {
return Request.from(this._initializer.request);
}
_raceWithTargetClose(promise) {
// When page closes or crashes, we catch any potential rejects from this Route.
// Note that page could be missing when routing popup's initial request that
// does not have a Page initialized just yet.
return this.request()._targetClosedScope().safeRace(promise);
}
_startHandling() {
this._handlingPromise = new _manualPromise.ManualPromise();
return this._handlingPromise;
}
async fallback(options = {}) {
this._checkNotHandled();
this.request()._applyFallbackOverrides(options);
this._reportHandled(false);
}
async abort(errorCode) {
this._checkNotHandled();
await this._raceWithTargetClose(this._channel.abort({
requestUrl: this.request()._initializer.url,
errorCode
}));
this._reportHandled(true);
}
async _redirectNavigationRequest(url) {
this._checkNotHandled();
await this._raceWithTargetClose(this._channel.redirectNavigationRequest({
url
}));
this._reportHandled(true);
}
async fetch(options = {}) {
return await this._wrapApiCall(async () => {
const context = this.request()._context();
return context.request._innerFetch({
request: this.request(),
data: options.postData,
...options
});
});
}
async fulfill(options = {}) {
this._checkNotHandled();
await this._wrapApiCall(async () => {
await this._innerFulfill(options);
this._reportHandled(true);
});
}
async _innerFulfill(options = {}) {
let fetchResponseUid;
let {
status: statusOption,
headers: headersOption,
body
} = options;
if (options.json !== undefined) {
(0, _utils.assert)(options.body === undefined, 'Can specify either body or json parameters');
body = JSON.stringify(options.json);
}
if (options.response instanceof _fetch.APIResponse) {
var _statusOption, _headersOption;
(_statusOption = statusOption) !== null && _statusOption !== void 0 ? _statusOption : statusOption = options.response.status();
(_headersOption = headersOption) !== null && _headersOption !== void 0 ? _headersOption : headersOption = options.response.headers();
if (body === undefined && options.path === undefined) {
if (options.response._request._connection === this._connection) fetchResponseUid = options.response._fetchUid();else body = await options.response.body();
}
}
let isBase64 = false;
let length = 0;
if (options.path) {
const buffer = await _fs.default.promises.readFile(options.path);
body = buffer.toString('base64');
isBase64 = true;
length = buffer.length;
} else if ((0, _utils.isString)(body)) {
isBase64 = false;
length = Buffer.byteLength(body);
} else if (body) {
length = body.length;
body = body.toString('base64');
isBase64 = true;
}
const headers = {};
for (const header of Object.keys(headersOption || {})) headers[header.toLowerCase()] = String(headersOption[header]);
if (options.contentType) headers['content-type'] = String(options.contentType);else if (options.json) headers['content-type'] = 'application/json';else if (options.path) headers['content-type'] = _utilsBundle.mime.getType(options.path) || 'application/octet-stream';
if (length && !('content-length' in headers)) headers['content-length'] = String(length);
await this._raceWithTargetClose(this._channel.fulfill({
requestUrl: this.request()._initializer.url,
status: statusOption || 200,
headers: (0, _utils.headersObjectToArray)(headers),
body,
isBase64,
fetchResponseUid
}));
}
async continue(options = {}) {
this._checkNotHandled();
this.request()._applyFallbackOverrides(options);
await this._innerContinue();
this._reportHandled(true);
}
_checkNotHandled() {
if (!this._handlingPromise) throw new Error('Route is already handled!');
}
_reportHandled(done) {
const chain = this._handlingPromise;
this._handlingPromise = null;
chain.resolve(done);
}
async _innerContinue(internal = false) {
const options = this.request()._fallbackOverridesForContinue();
return await this._wrapApiCall(async () => {
await this._raceWithTargetClose(this._channel.continue({
requestUrl: this.request()._initializer.url,
url: options.url,
method: options.method,
headers: options.headers ? (0, _utils.headersObjectToArray)(options.headers) : undefined,
postData: options.postDataBuffer,
isFallback: internal
}));
}, !!internal);
}
}
exports.Route = Route;
class Response extends _channelOwner.ChannelOwner {
static from(response) {
return response._object;
}
static fromNullable(response) {
return response ? Response.from(response) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._provisionalHeaders = void 0;
this._actualHeadersPromise = void 0;
this._request = void 0;
this._finishedPromise = new _manualPromise.ManualPromise();
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._request = Request.from(this._initializer.request);
Object.assign(this._request._timing, this._initializer.timing);
}
url() {
return this._initializer.url;
}
ok() {
// Status 0 is for file:// URLs
return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
}
status() {
return this._initializer.status;
}
statusText() {
return this._initializer.statusText;
}
fromServiceWorker() {
return this._initializer.fromServiceWorker;
}
/**
* @deprecated
*/
headers() {
return this._provisionalHeaders.headers();
}
async _actualHeaders() {
if (!this._actualHeadersPromise) {
this._actualHeadersPromise = (async () => {
return new RawHeaders((await this._channel.rawResponseHeaders()).headers);
})();
}
return this._actualHeadersPromise;
}
async allHeaders() {
return (await this._actualHeaders()).headers();
}
async headersArray() {
return (await this._actualHeaders()).headersArray().slice();
}
async headerValue(name) {
return (await this._actualHeaders()).get(name);
}
async headerValues(name) {
return (await this._actualHeaders()).getAll(name);
}
async finished() {
return this.request()._targetClosedScope().race(this._finishedPromise);
}
async body() {
return (await this._channel.body()).binary;
}
async text() {
const content = await this.body();
return content.toString('utf8');
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
request() {
return this._request;
}
frame() {
return this._request.frame();
}
async serverAddr() {
return (await this._channel.serverAddr()).value || null;
}
async securityDetails() {
return (await this._channel.securityDetails()).value || null;
}
}
exports.Response = Response;
class WebSocket extends _channelOwner.ChannelOwner {
static from(webSocket) {
return webSocket._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = void 0;
this._isClosed = void 0;
this._isClosed = false;
this._page = parent;
this._channel.on('frameSent', event => {
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameSent, {
payload: event.data
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameSent, {
payload: Buffer.from(event.data, 'base64')
});
});
this._channel.on('frameReceived', event => {
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameReceived, {
payload: event.data
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameReceived, {
payload: Buffer.from(event.data, 'base64')
});
});
this._channel.on('socketError', ({
error
}) => this.emit(_events.Events.WebSocket.Error, error));
this._channel.on('close', () => {
this._isClosed = true;
this.emit(_events.Events.WebSocket.Close, this);
});
}
url() {
return this._initializer.url;
}
isClosed() {
return this._isClosed;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return this._wrapApiCall(async () => {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== _events.Events.WebSocket.Error) waiter.rejectOnEvent(this, _events.Events.WebSocket.Error, new Error('Socket error'));
if (event !== _events.Events.WebSocket.Close) waiter.rejectOnEvent(this, _events.Events.WebSocket.Close, new Error('Socket closed'));
waiter.rejectOnEvent(this._page, _events.Events.Page.Close, new Error('Page closed'));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
exports.WebSocket = WebSocket;
function validateHeaders(headers) {
for (const key of Object.keys(headers)) {
const value = headers[key];
if (!Object.is(value, undefined) && !(0, _utils.isString)(value)) throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
}
}
class RouteHandler {
constructor(baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
this.handledCount = 0;
this._baseURL = void 0;
this._times = void 0;
this.url = void 0;
this.handler = void 0;
this._baseURL = baseURL;
this._times = times;
this.url = url;
this.handler = handler;
}
static prepareInterceptionPatterns(handlers) {
const patterns = [];
let all = false;
for (const handler of handlers) {
if ((0, _utils.isString)(handler.url)) patterns.push({
glob: handler.url
});else if ((0, _utils.isRegExp)(handler.url)) patterns.push({
regexSource: handler.url.source,
regexFlags: handler.url.flags
});else all = true;
}
if (all) return [{
glob: '**/*'
}];
return patterns;
}
matches(requestURL) {
return (0, _network.urlMatches)(this._baseURL, requestURL, this.url);
}
async handle(route) {
++this.handledCount;
const handledPromise = route._startHandling();
// Extract handler into a variable to avoid [RouteHandler.handler] in the stack.
const handler = this.handler;
const [handled] = await Promise.all([handledPromise, handler(route, route.request())]);
return handled;
}
willExpire() {
return this.handledCount + 1 >= this._times;
}
}
exports.RouteHandler = RouteHandler;
class RawHeaders {
static _fromHeadersObjectLossy(headers) {
const headersArray = Object.entries(headers).map(([name, value]) => ({
name,
value
})).filter(header => header.value !== undefined);
return new RawHeaders(headersArray);
}
constructor(headers) {
this._headersArray = void 0;
this._headersMap = new _multimap.MultiMap();
this._headersArray = headers;
for (const header of headers) this._headersMap.set(header.name.toLowerCase(), header.value);
}
get(name) {
const values = this.getAll(name);
if (!values || !values.length) return null;
return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');
}
getAll(name) {
return [...this._headersMap.get(name.toLowerCase())];
}
headers() {
const result = {};
for (const name of this._headersMap.keys()) result[name] = this.get(name);
return result;
}
headersArray() {
return this._headersArray;
}
}
exports.RawHeaders = RawHeaders;

View File

@ -0,0 +1,655 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Page = exports.BindingCall = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _errors = require("../common/errors");
var _network = require("../utils/network");
var _timeoutSettings = require("../common/timeoutSettings");
var _serializers = require("../protocol/serializers");
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _accessibility = require("./accessibility");
var _artifact = require("./artifact");
var _channelOwner = require("./channelOwner");
var _clientHelper = require("./clientHelper");
var _coverage = require("./coverage");
var _download = require("./download");
var _elementHandle = require("./elementHandle");
var _events = require("./events");
var _fileChooser = require("./fileChooser");
var _frame = require("./frame");
var _input = require("./input");
var _jsHandle = require("./jsHandle");
var _network2 = require("./network");
var _video = require("./video");
var _waiter = require("./waiter");
var _worker = require("./worker");
var _harRouter = require("./harRouter");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class Page extends _channelOwner.ChannelOwner {
static from(page) {
return page._object;
}
static fromNullable(page) {
return page ? Page.from(page) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._browserContext = void 0;
this._ownedContext = void 0;
this._mainFrame = void 0;
this._frames = new Set();
this._workers = new Set();
this._closed = false;
this._closedOrCrashedScope = new _utils.LongStandingScope();
this._viewportSize = void 0;
this._routes = [];
this.accessibility = void 0;
this.coverage = void 0;
this.keyboard = void 0;
this.mouse = void 0;
this.request = void 0;
this.touchscreen = void 0;
this._bindings = new Map();
this._timeoutSettings = void 0;
this._video = null;
this._opener = void 0;
this._browserContext = parent;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(this._browserContext._timeoutSettings);
this.accessibility = new _accessibility.Accessibility(this._channel);
this.keyboard = new _input.Keyboard(this);
this.mouse = new _input.Mouse(this);
this.request = this._browserContext.request;
this.touchscreen = new _input.Touchscreen(this);
this._mainFrame = _frame.Frame.from(initializer.mainFrame);
this._mainFrame._page = this;
this._frames.add(this._mainFrame);
this._viewportSize = initializer.viewportSize || null;
this._closed = initializer.isClosed;
this._opener = Page.fromNullable(initializer.opener);
this._channel.on('bindingCall', ({
binding
}) => this._onBinding(BindingCall.from(binding)));
this._channel.on('close', () => this._onClose());
this._channel.on('crash', () => this._onCrash());
this._channel.on('download', ({
url,
suggestedFilename,
artifact
}) => {
const artifactObject = _artifact.Artifact.from(artifact);
this.emit(_events.Events.Page.Download, new _download.Download(this, url, suggestedFilename, artifactObject));
});
this._channel.on('fileChooser', ({
element,
isMultiple
}) => this.emit(_events.Events.Page.FileChooser, new _fileChooser.FileChooser(this, _elementHandle.ElementHandle.from(element), isMultiple)));
this._channel.on('frameAttached', ({
frame
}) => this._onFrameAttached(_frame.Frame.from(frame)));
this._channel.on('frameDetached', ({
frame
}) => this._onFrameDetached(_frame.Frame.from(frame)));
this._channel.on('pageError', ({
error
}) => this.emit(_events.Events.Page.PageError, (0, _serializers.parseError)(error)));
this._channel.on('route', ({
route
}) => this._onRoute(_network2.Route.from(route)));
this._channel.on('video', ({
artifact
}) => {
const artifactObject = _artifact.Artifact.from(artifact);
this._forceVideo()._artifactReady(artifactObject);
});
this._channel.on('webSocket', ({
webSocket
}) => this.emit(_events.Events.Page.WebSocket, _network2.WebSocket.from(webSocket)));
this._channel.on('worker', ({
worker
}) => this._onWorker(_worker.Worker.from(worker)));
this.coverage = new _coverage.Coverage(this._channel);
this.once(_events.Events.Page.Close, () => this._closedOrCrashedScope.close(_errors.kBrowserOrContextClosedError));
this.once(_events.Events.Page.Crash, () => this._closedOrCrashedScope.close(_errors.kBrowserOrContextClosedError));
this._setEventToSubscriptionMapping(new Map([[_events.Events.Page.Console, 'console'], [_events.Events.Page.Dialog, 'dialog'], [_events.Events.Page.Request, 'request'], [_events.Events.Page.Response, 'response'], [_events.Events.Page.RequestFinished, 'requestFinished'], [_events.Events.Page.RequestFailed, 'requestFailed'], [_events.Events.Page.FileChooser, 'fileChooser']]));
}
_onFrameAttached(frame) {
frame._page = this;
this._frames.add(frame);
if (frame._parentFrame) frame._parentFrame._childFrames.add(frame);
this.emit(_events.Events.Page.FrameAttached, frame);
}
_onFrameDetached(frame) {
this._frames.delete(frame);
frame._detached = true;
if (frame._parentFrame) frame._parentFrame._childFrames.delete(frame);
this.emit(_events.Events.Page.FrameDetached, frame);
}
async _onRoute(route) {
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url())) continue;
if (routeHandler.willExpire()) this._routes.splice(this._routes.indexOf(routeHandler), 1);
const handled = await routeHandler.handle(route);
if (!this._routes.length) this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {});
if (handled) return;
}
await this._browserContext._onRoute(route);
}
async _onBinding(bindingCall) {
const func = this._bindings.get(bindingCall._initializer.name);
if (func) {
await bindingCall.call(func);
return;
}
await this._browserContext._onBinding(bindingCall);
}
_onWorker(worker) {
this._workers.add(worker);
worker._page = this;
this.emit(_events.Events.Page.Worker, worker);
}
_onClose() {
this._closed = true;
this._browserContext._pages.delete(this);
this._browserContext._backgroundPages.delete(this);
this.emit(_events.Events.Page.Close, this);
}
_onCrash() {
this.emit(_events.Events.Page.Crash, this);
}
context() {
return this._browserContext;
}
async opener() {
if (!this._opener || this._opener.isClosed()) return null;
return this._opener;
}
mainFrame() {
return this._mainFrame;
}
frame(frameSelector) {
const name = (0, _utils.isString)(frameSelector) ? frameSelector : frameSelector.name;
const url = (0, _utils.isObject)(frameSelector) ? frameSelector.url : undefined;
(0, _utils.assert)(name || url, 'Either name or url matcher should be specified');
return this.frames().find(f => {
if (name) return f.name() === name;
return (0, _network.urlMatches)(this._browserContext._options.baseURL, f.url(), url);
}) || null;
}
frames() {
return [...this._frames];
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultNavigationTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
}
_forceVideo() {
if (!this._video) this._video = new _video.Video(this, this._connection);
return this._video;
}
video() {
// Note: we are creating Video object lazily, because we do not know
// BrowserContextOptions when constructing the page - it is assigned
// too late during launchPersistentContext.
if (!this._browserContext._options.recordVideo) return null;
return this._forceVideo();
}
async $(selector, options) {
return this._mainFrame.$(selector, options);
}
async waitForSelector(selector, options) {
return this._mainFrame.waitForSelector(selector, options);
}
async dispatchEvent(selector, type, eventInit, options) {
return this._mainFrame.dispatchEvent(selector, type, eventInit, options);
}
async evaluateHandle(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
return this._mainFrame.evaluateHandle(pageFunction, arg);
}
async $eval(selector, pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
return this._mainFrame.$eval(selector, pageFunction, arg);
}
async $$eval(selector, pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
return this._mainFrame.$$eval(selector, pageFunction, arg);
}
async $$(selector) {
return this._mainFrame.$$(selector);
}
async addScriptTag(options = {}) {
return this._mainFrame.addScriptTag(options);
}
async addStyleTag(options = {}) {
return this._mainFrame.addStyleTag(options);
}
async exposeFunction(name, callback) {
await this._channel.exposeBinding({
name
});
const binding = (source, ...args) => callback(...args);
this._bindings.set(name, binding);
}
async exposeBinding(name, callback, options = {}) {
await this._channel.exposeBinding({
name,
needsHandle: options.handle
});
this._bindings.set(name, callback);
}
async setExtraHTTPHeaders(headers) {
(0, _network2.validateHeaders)(headers);
await this._channel.setExtraHTTPHeaders({
headers: (0, _utils.headersObjectToArray)(headers)
});
}
url() {
return this._mainFrame.url();
}
async content() {
return this._mainFrame.content();
}
async setContent(html, options) {
return this._mainFrame.setContent(html, options);
}
async goto(url, options) {
return this._mainFrame.goto(url, options);
}
async reload(options = {}) {
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return _network2.Response.fromNullable((await this._channel.reload({
...options,
waitUntil
})).response);
}
async waitForLoadState(state, options) {
return this._mainFrame.waitForLoadState(state, options);
}
async waitForNavigation(options) {
return this._mainFrame.waitForNavigation(options);
}
async waitForURL(url, options) {
return this._mainFrame.waitForURL(url, options);
}
async waitForRequest(urlOrPredicate, options = {}) {
const predicate = request => {
if ((0, _utils.isString)(urlOrPredicate) || (0, _utils.isRegExp)(urlOrPredicate)) return (0, _network.urlMatches)(this._browserContext._options.baseURL, request.url(), urlOrPredicate);
return urlOrPredicate(request);
};
const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined;
return this._waitForEvent(_events.Events.Page.Request, {
predicate,
timeout: options.timeout
}, logLine);
}
async waitForResponse(urlOrPredicate, options = {}) {
const predicate = response => {
if ((0, _utils.isString)(urlOrPredicate) || (0, _utils.isRegExp)(urlOrPredicate)) return (0, _network.urlMatches)(this._browserContext._options.baseURL, response.url(), urlOrPredicate);
return urlOrPredicate(response);
};
const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined;
return this._waitForEvent(_events.Events.Page.Response, {
predicate,
timeout: options.timeout
}, logLine);
}
async waitForEvent(event, optionsOrPredicate = {}) {
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
}
async _waitForEvent(event, optionsOrPredicate, logLine) {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
if (logLine) waiter.log(logLine);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== _events.Events.Page.Crash) waiter.rejectOnEvent(this, _events.Events.Page.Crash, new Error('Page crashed'));
if (event !== _events.Events.Page.Close) waiter.rejectOnEvent(this, _events.Events.Page.Close, new Error('Page closed'));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async goBack(options = {}) {
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return _network2.Response.fromNullable((await this._channel.goBack({
...options,
waitUntil
})).response);
}
async goForward(options = {}) {
const waitUntil = (0, _frame.verifyLoadState)('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return _network2.Response.fromNullable((await this._channel.goForward({
...options,
waitUntil
})).response);
}
async emulateMedia(options = {}) {
await this._channel.emulateMedia({
media: options.media === null ? 'no-override' : options.media,
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors
});
}
async setViewportSize(viewportSize) {
this._viewportSize = viewportSize;
await this._channel.setViewportSize({
viewportSize
});
}
viewportSize() {
return this._viewportSize;
}
async evaluate(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
return this._mainFrame.evaluate(pageFunction, arg);
}
async addInitScript(script, arg) {
const source = await (0, _clientHelper.evaluationScript)(script, arg);
await this._channel.addInitScript({
source
});
}
async route(url, handler, options = {}) {
this._routes.unshift(new _network2.RouteHandler(this._browserContext._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns();
}
async routeFromHAR(har, options = {}) {
if (options.update) {
await this._browserContext._recordIntoHAR(har, this, options);
return;
}
const harRouter = await _harRouter.HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', {
urlMatch: options.url
});
harRouter.addPageRoute(this);
}
async unroute(url, handler) {
this._routes = this._routes.filter(route => !(0, _utils.urlMatchesEqual)(route.url, url) || handler && route.handler !== handler);
await this._updateInterceptionPatterns();
}
async _updateInterceptionPatterns() {
const patterns = _network2.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._channel.setNetworkInterceptionPatterns({
patterns
});
}
async screenshot(options = {}) {
const copy = {
...options,
mask: undefined
};
if (!copy.type) copy.type = (0, _elementHandle.determineScreenshotType)(options);
if (options.mask) {
copy.mask = options.mask.map(locator => ({
frame: locator._frame._channel,
selector: locator._selector
}));
}
const result = await this._channel.screenshot(copy);
if (options.path) {
await (0, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, result.binary);
}
return result.binary;
}
async _expectScreenshot(options) {
var _options$screenshotOp, _options$screenshotOp2;
const mask = (_options$screenshotOp = options.screenshotOptions) !== null && _options$screenshotOp !== void 0 && _options$screenshotOp.mask ? (_options$screenshotOp2 = options.screenshotOptions) === null || _options$screenshotOp2 === void 0 ? void 0 : _options$screenshotOp2.mask.map(locator => ({
frame: locator._frame._channel,
selector: locator._selector
})) : undefined;
const locator = options.locator ? {
frame: options.locator._frame._channel,
selector: options.locator._selector
} : undefined;
return await this._channel.expectScreenshot({
...options,
isNot: !!options.isNot,
locator,
screenshotOptions: {
...options.screenshotOptions,
mask
}
});
}
async title() {
return this._mainFrame.title();
}
async bringToFront() {
await this._channel.bringToFront();
}
async close(options = {
runBeforeUnload: undefined
}) {
try {
if (this._ownedContext) await this._ownedContext.close();else await this._channel.close(options);
} catch (e) {
if ((0, _errors.isSafeCloseError)(e) && !options.runBeforeUnload) return;
throw e;
}
}
isClosed() {
return this._closed;
}
async click(selector, options) {
return this._mainFrame.click(selector, options);
}
async dragAndDrop(source, target, options) {
return this._mainFrame.dragAndDrop(source, target, options);
}
async dblclick(selector, options) {
return this._mainFrame.dblclick(selector, options);
}
async tap(selector, options) {
return this._mainFrame.tap(selector, options);
}
async fill(selector, value, options) {
return this._mainFrame.fill(selector, value, options);
}
locator(selector, options) {
return this.mainFrame().locator(selector, options);
}
getByTestId(testId) {
return this.mainFrame().getByTestId(testId);
}
getByAltText(text, options) {
return this.mainFrame().getByAltText(text, options);
}
getByLabel(text, options) {
return this.mainFrame().getByLabel(text, options);
}
getByPlaceholder(text, options) {
return this.mainFrame().getByPlaceholder(text, options);
}
getByText(text, options) {
return this.mainFrame().getByText(text, options);
}
getByTitle(text, options) {
return this.mainFrame().getByTitle(text, options);
}
getByRole(role, options = {}) {
return this.mainFrame().getByRole(role, options);
}
frameLocator(selector) {
return this.mainFrame().frameLocator(selector);
}
async focus(selector, options) {
return this._mainFrame.focus(selector, options);
}
async textContent(selector, options) {
return this._mainFrame.textContent(selector, options);
}
async innerText(selector, options) {
return this._mainFrame.innerText(selector, options);
}
async innerHTML(selector, options) {
return this._mainFrame.innerHTML(selector, options);
}
async getAttribute(selector, name, options) {
return this._mainFrame.getAttribute(selector, name, options);
}
async inputValue(selector, options) {
return this._mainFrame.inputValue(selector, options);
}
async isChecked(selector, options) {
return this._mainFrame.isChecked(selector, options);
}
async isDisabled(selector, options) {
return this._mainFrame.isDisabled(selector, options);
}
async isEditable(selector, options) {
return this._mainFrame.isEditable(selector, options);
}
async isEnabled(selector, options) {
return this._mainFrame.isEnabled(selector, options);
}
async isHidden(selector, options) {
return this._mainFrame.isHidden(selector, options);
}
async isVisible(selector, options) {
return this._mainFrame.isVisible(selector, options);
}
async hover(selector, options) {
return this._mainFrame.hover(selector, options);
}
async selectOption(selector, values, options) {
return this._mainFrame.selectOption(selector, values, options);
}
async setInputFiles(selector, files, options) {
return this._mainFrame.setInputFiles(selector, files, options);
}
async type(selector, text, options) {
return this._mainFrame.type(selector, text, options);
}
async press(selector, key, options) {
return this._mainFrame.press(selector, key, options);
}
async check(selector, options) {
return this._mainFrame.check(selector, options);
}
async uncheck(selector, options) {
return this._mainFrame.uncheck(selector, options);
}
async setChecked(selector, checked, options) {
return this._mainFrame.setChecked(selector, checked, options);
}
async waitForTimeout(timeout) {
return this._mainFrame.waitForTimeout(timeout);
}
async waitForFunction(pageFunction, arg, options) {
return this._mainFrame.waitForFunction(pageFunction, arg, options);
}
workers() {
return [...this._workers];
}
async pause() {
var _this$_instrumentatio;
if (require('inspector').url()) return;
const defaultNavigationTimeout = this._browserContext._timeoutSettings.defaultNavigationTimeout();
const defaultTimeout = this._browserContext._timeoutSettings.defaultTimeout();
this._browserContext.setDefaultNavigationTimeout(0);
this._browserContext.setDefaultTimeout(0);
(_this$_instrumentatio = this._instrumentation) === null || _this$_instrumentatio === void 0 ? void 0 : _this$_instrumentatio.onWillPause();
await this._closedOrCrashedScope.safeRace(this.context()._channel.pause());
this._browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
this._browserContext.setDefaultTimeout(defaultTimeout);
}
async pdf(options = {}) {
const transportOptions = {
...options
};
if (transportOptions.margin) transportOptions.margin = {
...transportOptions.margin
};
if (typeof options.width === 'number') transportOptions.width = options.width + 'px';
if (typeof options.height === 'number') transportOptions.height = options.height + 'px';
for (const margin of ['top', 'right', 'bottom', 'left']) {
const index = margin;
if (options.margin && typeof options.margin[index] === 'number') transportOptions.margin[index] = transportOptions.margin[index] + 'px';
}
const result = await this._channel.pdf(transportOptions);
if (options.path) {
await _fs.default.promises.mkdir(_path.default.dirname(options.path), {
recursive: true
});
await _fs.default.promises.writeFile(options.path, result.pdf);
}
return result.pdf;
}
}
exports.Page = Page;
class BindingCall extends _channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
async call(func) {
try {
const frame = _frame.Frame.from(this._initializer.frame);
const source = {
context: frame._page.context(),
page: frame._page,
frame
};
let result;
if (this._initializer.handle) result = await func(source, _jsHandle.JSHandle.from(this._initializer.handle));else result = await func(source, ...this._initializer.args.map(_jsHandle.parseResult));
this._channel.resolve({
result: (0, _jsHandle.serializeArgument)(result)
}).catch(() => {});
} catch (e) {
this._channel.reject({
error: (0, _serializers.serializeError)(e)
}).catch(() => {});
}
}
}
exports.BindingCall = BindingCall;
function trimEnd(s) {
if (s.length > 50) s = s.substring(0, 50) + '\u2026';
return s;
}
function trimUrl(param) {
if ((0, _utils.isRegExp)(param)) return `/${trimEnd(param.source)}/${param.flags}`;
if ((0, _utils.isString)(param)) return `"${trimEnd(param)}"`;
}

View File

@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Playwright = void 0;
var _errors = require("../common/errors");
var _android = require("./android");
var _browserType = require("./browserType");
var _channelOwner = require("./channelOwner");
var _electron = require("./electron");
var _fetch = require("./fetch");
var _selectors = require("./selectors");
/**
* 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.
*/
class Playwright extends _channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._android = void 0;
this._electron = void 0;
this.chromium = void 0;
this.firefox = void 0;
this.webkit = void 0;
this.devices = void 0;
this.selectors = void 0;
this.request = void 0;
this.errors = void 0;
this.request = new _fetch.APIRequest(this);
this.chromium = _browserType.BrowserType.from(initializer.chromium);
this.chromium._playwright = this;
this.firefox = _browserType.BrowserType.from(initializer.firefox);
this.firefox._playwright = this;
this.webkit = _browserType.BrowserType.from(initializer.webkit);
this.webkit._playwright = this;
this._android = _android.Android.from(initializer.android);
this._electron = _electron.Electron.from(initializer.electron);
this.devices = {};
for (const {
name,
descriptor
} of initializer.deviceDescriptors) this.devices[name] = descriptor;
this.selectors = new _selectors.Selectors();
this.errors = {
TimeoutError: _errors.TimeoutError
};
const selectorsOwner = _selectors.SelectorsOwner.from(initializer.selectors);
this.selectors._addChannel(selectorsOwner);
this._connection.on('close', () => {
this.selectors._removeChannel(selectorsOwner);
});
global._playwrightInstance = this;
}
_setSelectors(selectors) {
const selectorsOwner = _selectors.SelectorsOwner.from(this._initializer.selectors);
this.selectors._removeChannel(selectorsOwner);
this.selectors = selectors;
this.selectors._addChannel(selectorsOwner);
}
static from(channel) {
return channel._object;
}
}
exports.Playwright = Playwright;

View File

@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SelectorsOwner = exports.Selectors = void 0;
var _clientHelper = require("./clientHelper");
var _channelOwner = require("./channelOwner");
var _locator = require("./locator");
/**
* 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.
*/
class Selectors {
constructor() {
this._channels = new Set();
this._registrations = [];
}
async register(name, script, options = {}) {
const source = await (0, _clientHelper.evaluationScript)(script, undefined, false);
const params = {
...options,
name,
source
};
for (const channel of this._channels) await channel._channel.register(params);
this._registrations.push(params);
}
setTestIdAttribute(attributeName) {
(0, _locator.setTestIdAttribute)(attributeName);
for (const channel of this._channels) channel._channel.setTestIdAttributeName({
testIdAttributeName: attributeName
}).catch(() => {});
}
_addChannel(channel) {
this._channels.add(channel);
for (const params of this._registrations) {
// This should not fail except for connection closure, but just in case we catch.
channel._channel.register(params).catch(() => {});
channel._channel.setTestIdAttributeName({
testIdAttributeName: (0, _locator.testIdAttributeName)()
}).catch(() => {});
}
}
_removeChannel(channel) {
this._channels.delete(channel);
}
}
exports.Selectors = Selectors;
class SelectorsOwner extends _channelOwner.ChannelOwner {
static from(browser) {
return browser._object;
}
}
exports.SelectorsOwner = SelectorsOwner;

View File

@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Stream = void 0;
var _stream = require("stream");
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class Stream extends _channelOwner.ChannelOwner {
static from(Stream) {
return Stream._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
stream() {
return new StreamImpl(this._channel);
}
}
exports.Stream = Stream;
class StreamImpl extends _stream.Readable {
constructor(channel) {
super();
this._channel = void 0;
this._channel = channel;
}
async _read() {
const result = await this._channel.read({
size: 1024 * 1024
});
if (result.binary.byteLength) this.push(result.binary);else this.push(null);
}
_destroy(error, callback) {
// Stream might be destroyed after the connection was closed.
this._channel.close().catch(e => null);
super._destroy(error, callback);
}
}

View File

@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Tracing = void 0;
var _artifact = require("./artifact");
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class Tracing extends _channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._includeSources = false;
this._tracesDir = void 0;
this._stacksId = void 0;
this._isTracing = false;
}
async start(options = {}) {
this._includeSources = !!options.sources;
const traceName = await this._wrapApiCall(async () => {
await this._channel.tracingStart(options);
const response = await this._channel.tracingStartChunk({
name: options.name,
title: options.title
});
return response.traceName;
});
await this._startCollectingStacks(traceName);
}
async startChunk(options = {}) {
const {
traceName
} = await this._channel.tracingStartChunk(options);
await this._startCollectingStacks(traceName);
}
async _startCollectingStacks(traceName) {
if (!this._isTracing) {
this._isTracing = true;
this._connection.setIsTracing(true);
}
const result = await this._connection.localUtils()._channel.tracingStarted({
tracesDir: this._tracesDir,
traceName
});
this._stacksId = result.stacksId;
}
async stopChunk(options = {}) {
await this._doStopChunk(options.path);
}
async stop(options = {}) {
await this._wrapApiCall(async () => {
await this._doStopChunk(options.path);
await this._channel.tracingStop();
});
}
async _doStopChunk(filePath) {
if (this._isTracing) {
this._isTracing = false;
this._connection.setIsTracing(false);
}
if (!filePath) {
// Not interested in artifacts.
await this._channel.tracingStopChunk({
mode: 'discard'
});
if (this._stacksId) await this._connection.localUtils()._channel.traceDiscarded({
stacksId: this._stacksId
});
return;
}
const isLocal = !this._connection.isRemote();
if (isLocal) {
const result = await this._channel.tracingStopChunk({
mode: 'entries'
});
await this._connection.localUtils()._channel.zip({
zipFile: filePath,
entries: result.entries,
mode: 'write',
stacksId: this._stacksId,
includeSources: this._includeSources
});
return;
}
const result = await this._channel.tracingStopChunk({
mode: 'archive'
});
// The artifact may be missing if the browser closed while stopping tracing.
if (!result.artifact) {
if (this._stacksId) await this._connection.localUtils()._channel.traceDiscarded({
stacksId: this._stacksId
});
return;
}
// Save trace to the final local file.
const artifact = _artifact.Artifact.from(result.artifact);
await artifact.saveAs(filePath);
await artifact.delete();
await this._connection.localUtils()._channel.zip({
zipFile: filePath,
entries: [],
mode: 'append',
stacksId: this._stacksId,
includeSources: this._includeSources
});
}
}
exports.Tracing = Tracing;

View File

@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kLifecycleEvents = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications 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.
*/
const kLifecycleEvents = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
exports.kLifecycleEvents = kLifecycleEvents;

View File

@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Video = void 0;
var _utils = require("../utils");
/**
* 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.
*/
class Video {
constructor(page, connection) {
this._artifact = null;
this._artifactReadyPromise = new _utils.ManualPromise();
this._isRemote = false;
this._isRemote = connection.isRemote();
this._artifact = page._closedOrCrashedScope.safeRace(this._artifactReadyPromise);
}
_artifactReady(artifact) {
this._artifactReadyPromise.resolve(artifact);
}
async path() {
if (this._isRemote) throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
const artifact = await this._artifact;
if (!artifact) throw new Error('Page did not produce any video frames');
return artifact._initializer.absolutePath;
}
async saveAs(path) {
const artifact = await this._artifact;
if (!artifact) throw new Error('Page did not produce any video frames');
return artifact.saveAs(path);
}
async delete() {
const artifact = await this._artifact;
if (artifact) await artifact.delete();
}
}
exports.Video = Video;

View File

@ -0,0 +1,158 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Waiter = void 0;
var _stackTrace = require("../utils/stackTrace");
var _errors = require("../common/errors");
var _utils = require("../utils");
/**
* 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.
*/
class Waiter {
constructor(channelOwner, event) {
this._dispose = void 0;
this._failures = [];
this._immediateError = void 0;
this._logs = [];
this._channelOwner = void 0;
this._waitId = void 0;
this._error = void 0;
this._waitId = (0, _utils.createGuid)();
this._channelOwner = channelOwner;
this._channelOwner._channel.waitForEventInfo({
info: {
waitId: this._waitId,
phase: 'before',
event
}
}).catch(() => {});
this._dispose = [() => this._channelOwner._wrapApiCall(async () => {
await this._channelOwner._channel.waitForEventInfo({
info: {
waitId: this._waitId,
phase: 'after',
error: this._error
}
});
}, true).catch(() => {})];
}
static createForEvent(channelOwner, event) {
return new Waiter(channelOwner, event);
}
async waitForEvent(emitter, event, predicate) {
const {
promise,
dispose
} = waitForEvent(emitter, event, predicate);
return this.waitForPromise(promise, dispose);
}
rejectOnEvent(emitter, event, error, predicate) {
const {
promise,
dispose
} = waitForEvent(emitter, event, predicate);
this._rejectOn(promise.then(() => {
throw error;
}), dispose);
}
rejectOnTimeout(timeout, message) {
if (!timeout) return;
const {
promise,
dispose
} = waitForTimeout(timeout);
this._rejectOn(promise.then(() => {
throw new _errors.TimeoutError(message);
}), dispose);
}
rejectImmediately(error) {
this._immediateError = error;
}
dispose() {
for (const dispose of this._dispose) dispose();
}
async waitForPromise(promise, dispose) {
try {
if (this._immediateError) throw this._immediateError;
const result = await Promise.race([promise, ...this._failures]);
if (dispose) dispose();
return result;
} catch (e) {
if (dispose) dispose();
this._error = e.message;
this.dispose();
(0, _stackTrace.rewriteErrorMessage)(e, e.message + formatLogRecording(this._logs));
throw e;
}
}
log(s) {
this._logs.push(s);
this._channelOwner._wrapApiCall(async () => {
await this._channelOwner._channel.waitForEventInfo({
info: {
waitId: this._waitId,
phase: 'log',
message: s
}
}).catch(() => {});
}, true);
}
_rejectOn(promise, dispose) {
this._failures.push(promise);
if (dispose) this._dispose.push(dispose);
}
}
exports.Waiter = Waiter;
function waitForEvent(emitter, event, predicate) {
let listener;
const promise = new Promise((resolve, reject) => {
listener = async eventArg => {
try {
if (predicate && !(await predicate(eventArg))) return;
emitter.removeListener(event, listener);
resolve(eventArg);
} catch (e) {
emitter.removeListener(event, listener);
reject(e);
}
};
emitter.addListener(event, listener);
});
const dispose = () => emitter.removeListener(event, listener);
return {
promise,
dispose
};
}
function waitForTimeout(timeout) {
let timeoutId;
const promise = new Promise(resolve => timeoutId = setTimeout(resolve, timeout));
const dispose = () => clearTimeout(timeoutId);
return {
promise,
dispose
};
}
function formatLogRecording(log) {
if (!log.length) return '';
const header = ` logs `;
const headerLength = 60;
const leftLength = (headerLength - header.length) / 2;
const rightLength = headerLength - header.length - leftLength;
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
}

View File

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Worker = void 0;
var _events = require("./events");
var _channelOwner = require("./channelOwner");
var _jsHandle = require("./jsHandle");
var _utils = require("../utils");
var _errors = require("../common/errors");
/**
* 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.
*/
class Worker extends _channelOwner.ChannelOwner {
// Set for web workers.
// Set for service workers.
static from(worker) {
return worker._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = void 0;
this._context = void 0;
this._closedScope = new _utils.LongStandingScope();
this._channel.on('close', () => {
if (this._page) this._page._workers.delete(this);
if (this._context) this._context._serviceWorkers.delete(this);
this.emit(_events.Events.Worker.Close, this);
});
this.once(_events.Events.Worker.Close, () => this._closedScope.close(_errors.kBrowserOrContextClosedError));
}
url() {
return this._initializer.url;
}
async evaluate(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
}
async evaluateHandle(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
}
}
exports.Worker = Worker;

View File

@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WritableStream = void 0;
var _stream = require("stream");
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class WritableStream extends _channelOwner.ChannelOwner {
static from(Stream) {
return Stream._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
stream() {
return new WritableStreamImpl(this._channel);
}
}
exports.WritableStream = WritableStream;
class WritableStreamImpl extends _stream.Writable {
constructor(channel) {
super();
this._channel = void 0;
this._channel = channel;
}
async _write(chunk, encoding, callback) {
const error = await this._channel.write({
binary: typeof chunk === 'string' ? Buffer.from(chunk) : chunk
}).catch(e => e);
callback(error || null);
}
async _final(callback) {
// Stream might be destroyed after the connection was closed.
const error = await this._channel.close().catch(e => e);
callback(error || null);
}
}

View File

@ -0,0 +1,93 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.debugLogger = exports.RecentLogsCollector = void 0;
var _utilsBundle = require("../utilsBundle");
var _fs = _interopRequireDefault(require("fs"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
const debugLoggerColorMap = {
'api': 45,
// cyan
'protocol': 34,
// green
'install': 34,
// green
'download': 34,
// green
'browser': 0,
// reset
'socks': 92,
// purple
'error': 160,
// red,
'channel:command': 33,
// blue
'channel:response': 202,
// orange
'channel:event': 207,
// magenta
'server': 45,
// cyan
'server:channel': 34 // green
};
class DebugLogger {
constructor() {
this._debuggers = new Map();
if (process.env.DEBUG_FILE) {
const ansiRegex = new RegExp(['[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'].join('|'), 'g');
const stream = _fs.default.createWriteStream(process.env.DEBUG_FILE);
_utilsBundle.debug.log = data => {
stream.write(data.replace(ansiRegex, ''));
stream.write('\n');
};
}
}
log(name, message) {
let cachedDebugger = this._debuggers.get(name);
if (!cachedDebugger) {
cachedDebugger = (0, _utilsBundle.debug)(`pw:${name}`);
this._debuggers.set(name, cachedDebugger);
cachedDebugger.color = debugLoggerColorMap[name];
}
cachedDebugger(message);
}
isEnabled(name) {
return _utilsBundle.debug.enabled(`pw:${name}`);
}
}
const debugLogger = new DebugLogger();
exports.debugLogger = debugLogger;
const kLogCount = 150;
class RecentLogsCollector {
constructor() {
this._logs = [];
}
log(message) {
this._logs.push(message);
if (this._logs.length === kLogCount * 2) this._logs.splice(0, kLogCount);
}
recentLogs() {
if (this._logs.length > kLogCount) return this._logs.slice(-kLogCount);
return this._logs;
}
}
exports.RecentLogsCollector = RecentLogsCollector;

View File

@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TimeoutError = void 0;
exports.isSafeCloseError = isSafeCloseError;
exports.kBrowserOrContextClosedError = exports.kBrowserClosedError = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications 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.
*/
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class TimeoutError extends CustomError {}
exports.TimeoutError = TimeoutError;
const kBrowserClosedError = 'Browser has been closed';
exports.kBrowserClosedError = kBrowserClosedError;
const kBrowserOrContextClosedError = 'Target page, context or browser has been closed';
exports.kBrowserOrContextClosedError = kBrowserOrContextClosedError;
function isSafeCloseError(error) {
return error.message.endsWith(kBrowserClosedError) || error.message.endsWith(kBrowserOrContextClosedError);
}

View File

@ -0,0 +1,574 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SocksProxyHandler = exports.SocksProxy = void 0;
exports.parsePattern = parsePattern;
var _events = _interopRequireDefault(require("events"));
var _net = _interopRequireDefault(require("net"));
var _debugLogger = require("./debugLogger");
var _happyEyeballs = require("../utils/happy-eyeballs");
var _utils = require("../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
// https://tools.ietf.org/html/rfc1928
var SocksAuth;
(function (SocksAuth) {
SocksAuth[SocksAuth["NO_AUTHENTICATION_REQUIRED"] = 0] = "NO_AUTHENTICATION_REQUIRED";
SocksAuth[SocksAuth["GSSAPI"] = 1] = "GSSAPI";
SocksAuth[SocksAuth["USERNAME_PASSWORD"] = 2] = "USERNAME_PASSWORD";
SocksAuth[SocksAuth["NO_ACCEPTABLE_METHODS"] = 255] = "NO_ACCEPTABLE_METHODS";
})(SocksAuth || (SocksAuth = {}));
var SocksAddressType;
(function (SocksAddressType) {
SocksAddressType[SocksAddressType["IPv4"] = 1] = "IPv4";
SocksAddressType[SocksAddressType["FqName"] = 3] = "FqName";
SocksAddressType[SocksAddressType["IPv6"] = 4] = "IPv6";
})(SocksAddressType || (SocksAddressType = {}));
var SocksCommand;
(function (SocksCommand) {
SocksCommand[SocksCommand["CONNECT"] = 1] = "CONNECT";
SocksCommand[SocksCommand["BIND"] = 2] = "BIND";
SocksCommand[SocksCommand["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE";
})(SocksCommand || (SocksCommand = {}));
var SocksReply;
(function (SocksReply) {
SocksReply[SocksReply["Succeeded"] = 0] = "Succeeded";
SocksReply[SocksReply["GeneralServerFailure"] = 1] = "GeneralServerFailure";
SocksReply[SocksReply["NotAllowedByRuleSet"] = 2] = "NotAllowedByRuleSet";
SocksReply[SocksReply["NetworkUnreachable"] = 3] = "NetworkUnreachable";
SocksReply[SocksReply["HostUnreachable"] = 4] = "HostUnreachable";
SocksReply[SocksReply["ConnectionRefused"] = 5] = "ConnectionRefused";
SocksReply[SocksReply["TtlExpired"] = 6] = "TtlExpired";
SocksReply[SocksReply["CommandNotSupported"] = 7] = "CommandNotSupported";
SocksReply[SocksReply["AddressTypeNotSupported"] = 8] = "AddressTypeNotSupported";
})(SocksReply || (SocksReply = {}));
class SocksConnection {
constructor(uid, socket, client) {
this._buffer = Buffer.from([]);
this._offset = 0;
this._fence = 0;
this._fenceCallback = void 0;
this._socket = void 0;
this._boundOnData = void 0;
this._uid = void 0;
this._client = void 0;
this._uid = uid;
this._socket = socket;
this._client = client;
this._boundOnData = this._onData.bind(this);
socket.on('data', this._boundOnData);
socket.on('close', () => this._onClose());
socket.on('end', () => this._onClose());
socket.on('error', () => this._onClose());
this._run().catch(() => this._socket.end());
}
async _run() {
(0, _utils.assert)(await this._authenticate());
const {
command,
host,
port
} = await this._parseRequest();
if (command !== SocksCommand.CONNECT) {
this._writeBytes(Buffer.from([0x05, SocksReply.CommandNotSupported, 0x00,
// RSV
0x01,
// IPv4
0x00, 0x00, 0x00, 0x00,
// Address
0x00, 0x00 // Port
]));
return;
}
this._socket.off('data', this._boundOnData);
this._client.onSocketRequested({
uid: this._uid,
host,
port
});
}
async _authenticate() {
// Request:
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
// Response:
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
const version = await this._readByte();
(0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
const nMethods = await this._readByte();
(0, _utils.assert)(nMethods, 'No authentication methods specified');
const methods = await this._readBytes(nMethods);
for (const method of methods) {
if (method === 0) {
this._writeBytes(Buffer.from([version, method]));
return true;
}
}
this._writeBytes(Buffer.from([version, SocksAuth.NO_ACCEPTABLE_METHODS]));
return false;
}
async _parseRequest() {
// Request.
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
// Response.
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
const version = await this._readByte();
(0, _utils.assert)(version === 0x05, 'The VER field must be set to x05 for this version of the protocol, was ' + version);
const command = await this._readByte();
await this._readByte(); // skip reserved.
const addressType = await this._readByte();
let host = '';
switch (addressType) {
case SocksAddressType.IPv4:
host = (await this._readBytes(4)).join('.');
break;
case SocksAddressType.FqName:
const length = await this._readByte();
host = (await this._readBytes(length)).toString();
break;
case SocksAddressType.IPv6:
const bytes = await this._readBytes(16);
const tokens = [];
for (let i = 0; i < 8; ++i) tokens.push(bytes.readUInt16BE(i * 2).toString(16));
host = tokens.join(':');
break;
}
const port = (await this._readBytes(2)).readUInt16BE(0);
this._buffer = Buffer.from([]);
this._offset = 0;
this._fence = 0;
return {
command,
host,
port
};
}
async _readByte() {
const buffer = await this._readBytes(1);
return buffer[0];
}
async _readBytes(length) {
this._fence = this._offset + length;
if (!this._buffer || this._buffer.length < this._fence) await new Promise(f => this._fenceCallback = f);
this._offset += length;
return this._buffer.slice(this._offset - length, this._offset);
}
_writeBytes(buffer) {
if (this._socket.writable) this._socket.write(buffer);
}
_onClose() {
this._client.onSocketClosed({
uid: this._uid
});
}
_onData(buffer) {
this._buffer = Buffer.concat([this._buffer, buffer]);
if (this._fenceCallback && this._buffer.length >= this._fence) {
const callback = this._fenceCallback;
this._fenceCallback = undefined;
callback();
}
}
socketConnected(host, port) {
this._writeBytes(Buffer.from([0x05, SocksReply.Succeeded, 0x00,
// RSV
...ipToSocksAddress(host),
// ATYP, Address
port >> 8, port & 0xFF // Port
]));
this._socket.on('data', data => this._client.onSocketData({
uid: this._uid,
data
}));
}
socketFailed(errorCode) {
const buffer = Buffer.from([0x05, 0, 0x00,
// RSV
...ipToSocksAddress('0.0.0.0'),
// ATYP, Address
0, 0 // Port
]);
switch (errorCode) {
case 'ENOENT':
case 'ENOTFOUND':
case 'ETIMEDOUT':
case 'EHOSTUNREACH':
buffer[1] = SocksReply.HostUnreachable;
break;
case 'ENETUNREACH':
buffer[1] = SocksReply.NetworkUnreachable;
break;
case 'ECONNREFUSED':
buffer[1] = SocksReply.ConnectionRefused;
break;
case 'ERULESET':
buffer[1] = SocksReply.NotAllowedByRuleSet;
break;
}
this._writeBytes(buffer);
this._socket.end();
}
sendData(data) {
this._socket.write(data);
}
end() {
this._socket.end();
}
error(error) {
this._socket.destroy(new Error(error));
}
}
function hexToNumber(hex) {
// Note: parseInt has a few issues including ignoring trailing characters and allowing leading 0x.
return [...hex].reduce((value, digit) => {
const code = digit.charCodeAt(0);
if (code >= 48 && code <= 57)
// 0..9
return value + code;
if (code >= 97 && code <= 102)
// a..f
return value + (code - 97) + 10;
if (code >= 65 && code <= 70)
// A..F
return value + (code - 65) + 10;
throw new Error('Invalid IPv6 token ' + hex);
}, 0);
}
function ipToSocksAddress(address) {
if (_net.default.isIPv4(address)) {
return [0x01,
// IPv4
...address.split('.', 4).map(t => +t & 0xFF) // Address
];
}
if (_net.default.isIPv6(address)) {
const result = [0x04]; // IPv6
const tokens = address.split(':', 8);
while (tokens.length < 8) tokens.unshift('');
for (const token of tokens) {
const value = hexToNumber(token);
result.push(value >> 8 & 0xFF, value & 0xFF); // Big-endian
}
return result;
}
throw new Error('Only IPv4 and IPv6 addresses are supported');
}
function starMatchToRegex(pattern) {
const source = pattern.split('*').map(s => {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}).join('.*');
return new RegExp('^' + source + '$');
}
// This follows "Proxy bypass rules" syntax without implicit and negative rules.
// https://source.chromium.org/chromium/chromium/src/+/main:net/docs/proxy.md;l=331
function parsePattern(pattern) {
if (!pattern) return () => false;
const matchers = pattern.split(',').map(token => {
const match = token.match(/^(.*?)(?::(\d+))?$/);
if (!match) throw new Error(`Unsupported token "${token}" in pattern "${pattern}"`);
const tokenPort = match[2] ? +match[2] : undefined;
const portMatches = port => tokenPort === undefined || tokenPort === port;
let tokenHost = match[1];
if (tokenHost === '<loopback>') {
return (host, port) => {
if (!portMatches(port)) return false;
return host === 'localhost' || host.endsWith('.localhost') || host === '127.0.0.1' || host === '[::1]';
};
}
if (tokenHost === '*') return (host, port) => portMatches(port);
if (_net.default.isIPv4(tokenHost) || _net.default.isIPv6(tokenHost)) return (host, port) => host === tokenHost && portMatches(port);
if (tokenHost[0] === '.') tokenHost = '*' + tokenHost;
const tokenRegex = starMatchToRegex(tokenHost);
return (host, port) => {
if (!portMatches(port)) return false;
if (_net.default.isIPv4(host) || _net.default.isIPv6(host)) return false;
return !!host.match(tokenRegex);
};
});
return (host, port) => matchers.some(matcher => matcher(host, port));
}
class SocksProxy extends _events.default {
constructor() {
super();
this._server = void 0;
this._connections = new Map();
this._sockets = new Set();
this._closed = false;
this._port = void 0;
this._patternMatcher = () => false;
this._directSockets = new Map();
this._server = new _net.default.Server(socket => {
const uid = (0, _utils.createGuid)();
const connection = new SocksConnection(uid, socket, this);
this._connections.set(uid, connection);
});
this._server.on('connection', socket => {
if (this._closed) {
socket.destroy();
return;
}
this._sockets.add(socket);
socket.once('close', () => this._sockets.delete(socket));
});
}
setPattern(pattern) {
try {
this._patternMatcher = parsePattern(pattern);
} catch (e) {
this._patternMatcher = () => false;
}
}
async _handleDirect(request) {
try {
var _this$_connections$ge4;
const socket = await (0, _happyEyeballs.createSocket)(request.host, request.port);
socket.on('data', data => {
var _this$_connections$ge;
return (_this$_connections$ge = this._connections.get(request.uid)) === null || _this$_connections$ge === void 0 ? void 0 : _this$_connections$ge.sendData(data);
});
socket.on('error', error => {
var _this$_connections$ge2;
(_this$_connections$ge2 = this._connections.get(request.uid)) === null || _this$_connections$ge2 === void 0 ? void 0 : _this$_connections$ge2.error(error.message);
this._directSockets.delete(request.uid);
});
socket.on('end', () => {
var _this$_connections$ge3;
(_this$_connections$ge3 = this._connections.get(request.uid)) === null || _this$_connections$ge3 === void 0 ? void 0 : _this$_connections$ge3.end();
this._directSockets.delete(request.uid);
});
const localAddress = socket.localAddress;
const localPort = socket.localPort;
this._directSockets.set(request.uid, socket);
(_this$_connections$ge4 = this._connections.get(request.uid)) === null || _this$_connections$ge4 === void 0 ? void 0 : _this$_connections$ge4.socketConnected(localAddress, localPort);
} catch (error) {
var _this$_connections$ge5;
(_this$_connections$ge5 = this._connections.get(request.uid)) === null || _this$_connections$ge5 === void 0 ? void 0 : _this$_connections$ge5.socketFailed(error.code);
}
}
port() {
return this._port;
}
async listen(port) {
return new Promise(f => {
this._server.listen(port, () => {
const port = this._server.address().port;
this._port = port;
f(port);
});
});
}
async close() {
if (this._closed) return;
this._closed = true;
for (const socket of this._sockets) socket.destroy();
this._sockets.clear();
await new Promise(f => this._server.close(f));
}
onSocketRequested(payload) {
if (!this._patternMatcher(payload.host, payload.port)) {
this._handleDirect(payload);
return;
}
this.emit(SocksProxy.Events.SocksRequested, payload);
}
onSocketData(payload) {
const direct = this._directSockets.get(payload.uid);
if (direct) {
direct.write(payload.data);
return;
}
this.emit(SocksProxy.Events.SocksData, payload);
}
onSocketClosed(payload) {
const direct = this._directSockets.get(payload.uid);
if (direct) {
direct.destroy();
this._directSockets.delete(payload.uid);
return;
}
this.emit(SocksProxy.Events.SocksClosed, payload);
}
socketConnected({
uid,
host,
port
}) {
var _this$_connections$ge6;
(_this$_connections$ge6 = this._connections.get(uid)) === null || _this$_connections$ge6 === void 0 ? void 0 : _this$_connections$ge6.socketConnected(host, port);
}
socketFailed({
uid,
errorCode
}) {
var _this$_connections$ge7;
(_this$_connections$ge7 = this._connections.get(uid)) === null || _this$_connections$ge7 === void 0 ? void 0 : _this$_connections$ge7.socketFailed(errorCode);
}
sendSocketData({
uid,
data
}) {
var _this$_connections$ge8;
(_this$_connections$ge8 = this._connections.get(uid)) === null || _this$_connections$ge8 === void 0 ? void 0 : _this$_connections$ge8.sendData(data);
}
sendSocketEnd({
uid
}) {
var _this$_connections$ge9;
(_this$_connections$ge9 = this._connections.get(uid)) === null || _this$_connections$ge9 === void 0 ? void 0 : _this$_connections$ge9.end();
}
sendSocketError({
uid,
error
}) {
var _this$_connections$ge10;
(_this$_connections$ge10 = this._connections.get(uid)) === null || _this$_connections$ge10 === void 0 ? void 0 : _this$_connections$ge10.error(error);
}
}
exports.SocksProxy = SocksProxy;
SocksProxy.Events = {
SocksRequested: 'socksRequested',
SocksData: 'socksData',
SocksClosed: 'socksClosed'
};
class SocksProxyHandler extends _events.default {
constructor(pattern, redirectPortForTest) {
super();
this._sockets = new Map();
this._patternMatcher = () => false;
this._redirectPortForTest = void 0;
this._patternMatcher = parsePattern(pattern);
this._redirectPortForTest = redirectPortForTest;
}
cleanup() {
for (const uid of this._sockets.keys()) this.socketClosed({
uid
});
}
async socketRequested({
uid,
host,
port
}) {
_debugLogger.debugLogger.log('socks', `[${uid}] => request ${host}:${port}`);
if (!this._patternMatcher(host, port)) {
const payload = {
uid,
errorCode: 'ERULESET'
};
_debugLogger.debugLogger.log('socks', `[${uid}] <= pattern error ${payload.errorCode}`);
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
return;
}
if (host === 'local.playwright') host = 'localhost';
try {
if (this._redirectPortForTest) port = this._redirectPortForTest;
const socket = await (0, _happyEyeballs.createSocket)(host, port);
socket.on('data', data => {
const payload = {
uid,
data
};
this.emit(SocksProxyHandler.Events.SocksData, payload);
});
socket.on('error', error => {
const payload = {
uid,
error: error.message
};
_debugLogger.debugLogger.log('socks', `[${uid}] <= network socket error ${payload.error}`);
this.emit(SocksProxyHandler.Events.SocksError, payload);
this._sockets.delete(uid);
});
socket.on('end', () => {
const payload = {
uid
};
_debugLogger.debugLogger.log('socks', `[${uid}] <= network socket closed`);
this.emit(SocksProxyHandler.Events.SocksEnd, payload);
this._sockets.delete(uid);
});
const localAddress = socket.localAddress;
const localPort = socket.localPort;
this._sockets.set(uid, socket);
const payload = {
uid,
host: localAddress,
port: localPort
};
_debugLogger.debugLogger.log('socks', `[${uid}] <= connected to network ${payload.host}:${payload.port}`);
this.emit(SocksProxyHandler.Events.SocksConnected, payload);
} catch (error) {
const payload = {
uid,
errorCode: error.code
};
_debugLogger.debugLogger.log('socks', `[${uid}] <= connect error ${payload.errorCode}`);
this.emit(SocksProxyHandler.Events.SocksFailed, payload);
}
}
sendSocketData({
uid,
data
}) {
var _this$_sockets$get;
(_this$_sockets$get = this._sockets.get(uid)) === null || _this$_sockets$get === void 0 ? void 0 : _this$_sockets$get.write(data);
}
socketClosed({
uid
}) {
var _this$_sockets$get2;
_debugLogger.debugLogger.log('socks', `[${uid}] <= browser socket closed`);
(_this$_sockets$get2 = this._sockets.get(uid)) === null || _this$_sockets$get2 === void 0 ? void 0 : _this$_sockets$get2.destroy();
this._sockets.delete(uid);
}
}
exports.SocksProxyHandler = SocksProxyHandler;
SocksProxyHandler.Events = {
SocksConnected: 'socksConnected',
SocksData: 'socksData',
SocksError: 'socksError',
SocksFailed: 'socksFailed',
SocksEnd: 'socksEnd'
};

View File

@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TimeoutSettings = exports.DEFAULT_TIMEOUT = exports.DEFAULT_LAUNCH_TIMEOUT = void 0;
var _utils = require("../utils");
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications 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.
*/
const DEFAULT_TIMEOUT = 30000;
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
const DEFAULT_LAUNCH_TIMEOUT = 3 * 60 * 1000; // 3 minutes
exports.DEFAULT_LAUNCH_TIMEOUT = DEFAULT_LAUNCH_TIMEOUT;
class TimeoutSettings {
constructor(parent) {
this._parent = void 0;
this._defaultTimeout = void 0;
this._defaultNavigationTimeout = void 0;
this._parent = parent;
}
setDefaultTimeout(timeout) {
this._defaultTimeout = timeout;
}
setDefaultNavigationTimeout(timeout) {
this._defaultNavigationTimeout = timeout;
}
defaultNavigationTimeout() {
return this._defaultNavigationTimeout;
}
defaultTimeout() {
return this._defaultTimeout;
}
navigationTimeout(options) {
if (typeof options.timeout === 'number') return options.timeout;
if (this._defaultNavigationTimeout !== undefined) return this._defaultNavigationTimeout;
if ((0, _utils.debugMode)()) return 0;
if (this._defaultTimeout !== undefined) return this._defaultTimeout;
if (this._parent) return this._parent.navigationTimeout(options);
return DEFAULT_TIMEOUT;
}
timeout(options) {
if (typeof options.timeout === 'number') return options.timeout;
if ((0, _utils.debugMode)()) return 0;
if (this._defaultTimeout !== undefined) return this._defaultTimeout;
if (this._parent) return this._parent.timeout(options);
return DEFAULT_TIMEOUT;
}
static timeout(options) {
if (typeof options.timeout === 'number') return options.timeout;
if ((0, _utils.debugMode)()) return 0;
return DEFAULT_TIMEOUT;
}
static launchTimeout(options) {
if (typeof options.timeout === 'number') return options.timeout;
if ((0, _utils.debugMode)()) return 0;
return DEFAULT_LAUNCH_TIMEOUT;
}
}
exports.TimeoutSettings = TimeoutSettings;

View File

@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.blendWithWhite = blendWithWhite;
exports.colorDeltaE94 = colorDeltaE94;
exports.rgb2gray = rgb2gray;
exports.srgb2xyz = srgb2xyz;
exports.xyz2lab = xyz2lab;
/**
* 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.
*/
function blendWithWhite(c, a) {
return 255 + (c - 255) * a;
}
function rgb2gray(r, g, b) {
// NOTE: this is the exact integer formula from SSIM.js.
// See https://github.com/obartra/ssim/blob/ca8e3c6a6ff5f4f2e232239e0c3d91806f3c97d5/src/matlab/rgb2gray.ts#L56
return 77 * r + 150 * g + 29 * b + 128 >> 8;
}
// Percieved color difference defined by CIE94.
// See https://en.wikipedia.org/wiki/Color_difference#CIE94
//
// The result of 1.0 is a "just-noticiable difference".
//
// Other results interpretation (taken from http://zschuessler.github.io/DeltaE/learn/):
// < 1.0 Not perceptible by human eyes.
// 1-2 Perceptible through close observation.
// 2-10 Perceptible at a glance.
// 11-49 Colors are more similar than opposite
// 100 Colors are exact opposite
function colorDeltaE94(rgb1, rgb2) {
const [l1, a1, b1] = xyz2lab(srgb2xyz(rgb1));
const [l2, a2, b2] = xyz2lab(srgb2xyz(rgb2));
const deltaL = l1 - l2;
const deltaA = a1 - a2;
const deltaB = b1 - b2;
const c1 = Math.sqrt(a1 ** 2 + b1 ** 2);
const c2 = Math.sqrt(a2 ** 2 + b2 ** 2);
const deltaC = c1 - c2;
let deltaH = deltaA ** 2 + deltaB ** 2 - deltaC ** 2;
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
// The k1, k2, kL, kC, kH values for "graphic arts" applications.
// See https://en.wikipedia.org/wiki/Color_difference#CIE94
const k1 = 0.045;
const k2 = 0.015;
const kL = 1;
const kC = 1;
const kH = 1;
const sC = 1.0 + k1 * c1;
const sH = 1.0 + k2 * c1;
const sL = 1;
return Math.sqrt((deltaL / sL / kL) ** 2 + (deltaC / sC / kC) ** 2 + (deltaH / sH / kH) ** 2);
}
// sRGB -> 1-normalized XYZ (i.e. Y ∈ [0, 1]) with D65 illuminant
// See https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
function srgb2xyz(rgb) {
let r = rgb[0] / 255;
let g = rgb[1] / 255;
let b = rgb[2] / 255;
r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
return [r * 0.4124 + g * 0.3576 + b * 0.1805, r * 0.2126 + g * 0.7152 + b * 0.0722, r * 0.0193 + g * 0.1192 + b * 0.9505];
}
const sigma_pow2 = 6 * 6 / 29 / 29;
const sigma_pow3 = 6 * 6 * 6 / 29 / 29 / 29;
// 1-normalized CIE XYZ with D65 to L*a*b*
// See https://en.wikipedia.org/wiki/CIELAB_color_space#From_CIEXYZ_to_CIELAB
function xyz2lab(xyz) {
const x = xyz[0] / 0.950489;
const y = xyz[1];
const z = xyz[2] / 1.088840;
const fx = x > sigma_pow3 ? x ** (1 / 3) : x / 3 / sigma_pow2 + 4 / 29;
const fy = y > sigma_pow3 ? y ** (1 / 3) : y / 3 / sigma_pow2 + 4 / 29;
const fz = z > sigma_pow3 ? z ** (1 / 3) : z / 3 / sigma_pow2 + 4 / 29;
const l = 116 * fy - 16;
const a = 500 * (fx - fy);
const b = 200 * (fy - fz);
return [l, a, b];
}

View File

@ -0,0 +1,108 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.compare = compare;
var _colorUtils = require("./colorUtils");
var _imageChannel = require("./imageChannel");
var _stats = require("./stats");
/**
* 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.
*/
const SSIM_WINDOW_RADIUS = 15;
const VARIANCE_WINDOW_RADIUS = 1;
function drawPixel(width, data, x, y, r, g, b) {
const idx = (y * width + x) * 4;
data[idx + 0] = r;
data[idx + 1] = g;
data[idx + 2] = b;
data[idx + 3] = 255;
}
function compare(actual, expected, diff, width, height, options = {}) {
const {
maxColorDeltaE94 = 1.0
} = options;
const paddingSize = Math.max(VARIANCE_WINDOW_RADIUS, SSIM_WINDOW_RADIUS);
const paddingColorEven = [255, 0, 255];
const paddingColorOdd = [0, 255, 0];
const [r1, g1, b1] = _imageChannel.ImageChannel.intoRGB(width, height, expected, {
paddingSize,
paddingColorEven,
paddingColorOdd
});
const [r2, g2, b2] = _imageChannel.ImageChannel.intoRGB(width, height, actual, {
paddingSize,
paddingColorEven,
paddingColorOdd
});
const noop = (x, y) => {};
const drawRedPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 0, 0) : noop;
const drawYellowPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 255, 0) : noop;
const drawGrayPixel = diff ? (x, y) => {
const gray = (0, _colorUtils.rgb2gray)(r1.get(x, y), g1.get(x, y), b1.get(x, y));
const value = (0, _colorUtils.blendWithWhite)(gray, 0.1);
drawPixel(width, diff, x - paddingSize, y - paddingSize, value, value, value);
} : noop;
let fastR, fastG, fastB;
let diffCount = 0;
for (let y = paddingSize; y < r1.height - paddingSize; ++y) {
for (let x = paddingSize; x < r1.width - paddingSize; ++x) {
// Fast-path: equal pixels.
if (r1.get(x, y) === r2.get(x, y) && g1.get(x, y) === g2.get(x, y) && b1.get(x, y) === b2.get(x, y)) {
drawGrayPixel(x, y);
continue;
}
// Compare pixel colors using the dE94 color difference formulae.
// The dE94 is normalized so that the value of 1.0 is the "just-noticeable-difference".
// Color difference below 1.0 is not noticeable to a human eye, so we can disregard it.
// See https://en.wikipedia.org/wiki/Color_difference
const delta = (0, _colorUtils.colorDeltaE94)([r1.get(x, y), g1.get(x, y), b1.get(x, y)], [r2.get(x, y), g2.get(x, y), b2.get(x, y)]);
if (delta <= maxColorDeltaE94) {
drawGrayPixel(x, y);
continue;
}
if (!fastR || !fastG || !fastB) {
fastR = new _stats.FastStats(r1, r2);
fastG = new _stats.FastStats(g1, g2);
fastB = new _stats.FastStats(b1, b2);
}
const [varX1, varY1] = r1.boundXY(x - VARIANCE_WINDOW_RADIUS, y - VARIANCE_WINDOW_RADIUS);
const [varX2, varY2] = r1.boundXY(x + VARIANCE_WINDOW_RADIUS, y + VARIANCE_WINDOW_RADIUS);
const var1 = fastR.varianceC1(varX1, varY1, varX2, varY2) + fastG.varianceC1(varX1, varY1, varX2, varY2) + fastB.varianceC1(varX1, varY1, varX2, varY2);
const var2 = fastR.varianceC2(varX1, varY1, varX2, varY2) + fastG.varianceC2(varX1, varY1, varX2, varY2) + fastB.varianceC2(varX1, varY1, varX2, varY2);
// if this pixel is a part of a flood fill of a 3x3 square of either of the images, then it cannot be
// anti-aliasing pixel so it must be a pixel difference.
if (var1 === 0 || var2 === 0) {
drawRedPixel(x, y);
++diffCount;
continue;
}
const [ssimX1, ssimY1] = r1.boundXY(x - SSIM_WINDOW_RADIUS, y - SSIM_WINDOW_RADIUS);
const [ssimX2, ssimY2] = r1.boundXY(x + SSIM_WINDOW_RADIUS, y + SSIM_WINDOW_RADIUS);
const ssimRGB = ((0, _stats.ssim)(fastR, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastG, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastB, ssimX1, ssimY1, ssimX2, ssimY2)) / 3.0;
const isAntialiassed = ssimRGB >= 0.99;
if (isAntialiassed) {
drawYellowPixel(x, y);
} else {
drawRedPixel(x, y);
++diffCount;
}
}
}
return diffCount;
}

View File

@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ImageChannel = void 0;
var _colorUtils = require("./colorUtils");
/**
* 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.
*/
class ImageChannel {
static intoRGB(width, height, data, options = {}) {
const {
paddingSize = 0,
paddingColorOdd = [255, 0, 255],
paddingColorEven = [0, 255, 0]
} = options;
const newWidth = width + 2 * paddingSize;
const newHeight = height + 2 * paddingSize;
const r = new Uint8Array(newWidth * newHeight);
const g = new Uint8Array(newWidth * newHeight);
const b = new Uint8Array(newWidth * newHeight);
for (let y = 0; y < newHeight; ++y) {
for (let x = 0; x < newWidth; ++x) {
const index = y * newWidth + x;
if (y >= paddingSize && y < newHeight - paddingSize && x >= paddingSize && x < newWidth - paddingSize) {
const offset = ((y - paddingSize) * width + (x - paddingSize)) * 4;
const alpha = data[offset + 3] === 255 ? 1 : data[offset + 3] / 255;
r[index] = (0, _colorUtils.blendWithWhite)(data[offset], alpha);
g[index] = (0, _colorUtils.blendWithWhite)(data[offset + 1], alpha);
b[index] = (0, _colorUtils.blendWithWhite)(data[offset + 2], alpha);
} else {
const color = (y + x) % 2 === 0 ? paddingColorEven : paddingColorOdd;
r[index] = color[0];
g[index] = color[1];
b[index] = color[2];
}
}
}
return [new ImageChannel(newWidth, newHeight, r), new ImageChannel(newWidth, newHeight, g), new ImageChannel(newWidth, newHeight, b)];
}
constructor(width, height, data) {
this.data = void 0;
this.width = void 0;
this.height = void 0;
this.data = data;
this.width = width;
this.height = height;
}
get(x, y) {
return this.data[y * this.width + x];
}
boundXY(x, y) {
return [Math.min(Math.max(x, 0), this.width - 1), Math.min(Math.max(y, 0), this.height - 1)];
}
}
exports.ImageChannel = ImageChannel;

View File

@ -0,0 +1,102 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FastStats = void 0;
exports.ssim = ssim;
/**
* 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.
*/
// Image channel has a 8-bit depth.
const DYNAMIC_RANGE = 2 ** 8 - 1;
function ssim(stats, x1, y1, x2, y2) {
const mean1 = stats.meanC1(x1, y1, x2, y2);
const mean2 = stats.meanC2(x1, y1, x2, y2);
const var1 = stats.varianceC1(x1, y1, x2, y2);
const var2 = stats.varianceC2(x1, y1, x2, y2);
const cov = stats.covariance(x1, y1, x2, y2);
const c1 = (0.01 * DYNAMIC_RANGE) ** 2;
const c2 = (0.03 * DYNAMIC_RANGE) ** 2;
return (2 * mean1 * mean2 + c1) * (2 * cov + c2) / (mean1 ** 2 + mean2 ** 2 + c1) / (var1 + var2 + c2);
}
class FastStats {
constructor(c1, c2) {
this.c1 = void 0;
this.c2 = void 0;
this._partialSumC1 = void 0;
this._partialSumC2 = void 0;
this._partialSumMult = void 0;
this._partialSumSq1 = void 0;
this._partialSumSq2 = void 0;
this.c1 = c1;
this.c2 = c2;
const {
width,
height
} = c1;
this._partialSumC1 = new Array(width * height);
this._partialSumC2 = new Array(width * height);
this._partialSumSq1 = new Array(width * height);
this._partialSumSq2 = new Array(width * height);
this._partialSumMult = new Array(width * height);
const recalc = (mx, idx, initial, x, y) => {
mx[idx] = initial;
if (y > 0) mx[idx] += mx[(y - 1) * width + x];
if (x > 0) mx[idx] += mx[y * width + x - 1];
if (x > 0 && y > 0) mx[idx] -= mx[(y - 1) * width + x - 1];
};
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
const idx = y * width + x;
recalc(this._partialSumC1, idx, this.c1.data[idx], x, y);
recalc(this._partialSumC2, idx, this.c2.data[idx], x, y);
recalc(this._partialSumSq1, idx, this.c1.data[idx] * this.c1.data[idx], x, y);
recalc(this._partialSumSq2, idx, this.c2.data[idx] * this.c2.data[idx], x, y);
recalc(this._partialSumMult, idx, this.c1.data[idx] * this.c2.data[idx], x, y);
}
}
}
_sum(partialSum, x1, y1, x2, y2) {
const width = this.c1.width;
let result = partialSum[y2 * width + x2];
if (y1 > 0) result -= partialSum[(y1 - 1) * width + x2];
if (x1 > 0) result -= partialSum[y2 * width + x1 - 1];
if (x1 > 0 && y1 > 0) result += partialSum[(y1 - 1) * width + x1 - 1];
return result;
}
meanC1(x1, y1, x2, y2) {
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
return this._sum(this._partialSumC1, x1, y1, x2, y2) / N;
}
meanC2(x1, y1, x2, y2) {
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
return this._sum(this._partialSumC2, x1, y1, x2, y2) / N;
}
varianceC1(x1, y1, x2, y2) {
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
return (this._sum(this._partialSumSq1, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) ** 2 / N) / N;
}
varianceC2(x1, y1, x2, y2) {
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
return (this._sum(this._partialSumSq2, x1, y1, x2, y2) - this._sum(this._partialSumC2, x1, y1, x2, y2) ** 2 / N) / N;
}
covariance(x1, y1, x2, y2) {
const N = (y2 - y1 + 1) * (x2 - x1 + 1);
return (this._sum(this._partialSumMult, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) * this._sum(this._partialSumC2, x1, y1, x2, y2) / N) / N;
}
}
exports.FastStats = FastStats;

View File

@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createInProcessPlaywright = createInProcessPlaywright;
var _server = require("./server");
var _connection = require("./client/connection");
var _browserServerImpl = require("./browserServerImpl");
var _androidServerImpl = require("./androidServerImpl");
/**
* 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.
*/
function createInProcessPlaywright() {
const playwright = (0, _server.createPlaywright)({
sdkLanguage: process.env.PW_LANG_NAME || 'javascript'
});
const clientConnection = new _connection.Connection(undefined, undefined);
const dispatcherConnection = new _server.DispatcherConnection(true /* local */);
// Dispatch synchronously at first.
dispatcherConnection.onmessage = message => clientConnection.dispatch(message);
clientConnection.onmessage = message => dispatcherConnection.dispatch(message);
const rootScope = new _server.RootDispatcher(dispatcherConnection);
// Initialize Playwright channel.
new _server.PlaywrightDispatcher(rootScope, playwright);
const playwrightAPI = clientConnection.getObjectWithKnownName('Playwright');
playwrightAPI.chromium._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('chromium');
playwrightAPI.firefox._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('firefox');
playwrightAPI.webkit._serverLauncher = new _browserServerImpl.BrowserServerLauncherImpl('webkit');
playwrightAPI._android._serverLauncher = new _androidServerImpl.AndroidServerLauncherImpl();
// Switch to async dispatch after we got Playwright object.
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message));
clientConnection.toImpl = x => x ? dispatcherConnection._dispatchers.get(x._guid)._object : dispatcherConnection._dispatchers.get('');
playwrightAPI._toImpl = clientConnection.toImpl;
return playwrightAPI;
}

View File

@ -0,0 +1,20 @@
"use strict";
var _inProcessFactory = require("./inProcessFactory");
/**
* 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.
*/
module.exports = (0, _inProcessFactory.createInProcessPlaywright)();

View File

@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.start = start;
var _connection = require("./client/connection");
var _transport = require("./protocol/transport");
var childProcess = _interopRequireWildcard(require("child_process"));
var path = _interopRequireWildcard(require("path"));
var _manualPromise = require("./utils/manualPromise");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* 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.
*/
async function start(env = {}) {
const client = new PlaywrightClient(env);
const playwright = await client._playwright;
playwright.driverProcess = client._driverProcess;
return {
playwright,
stop: () => client.stop()
};
}
class PlaywrightClient {
constructor(env) {
this._playwright = void 0;
this._driverProcess = void 0;
this._closePromise = new _manualPromise.ManualPromise();
this._driverProcess = childProcess.fork(path.join(__dirname, 'cli', 'cli.js'), ['run-driver'], {
stdio: 'pipe',
detached: true,
env: {
...process.env,
...env
}
});
this._driverProcess.unref();
this._driverProcess.stderr.on('data', data => process.stderr.write(data));
const connection = new _connection.Connection(undefined, undefined);
connection.markAsRemote();
const transport = new _transport.PipeTransport(this._driverProcess.stdin, this._driverProcess.stdout);
connection.onmessage = message => transport.send(JSON.stringify(message));
transport.onmessage = message => connection.dispatch(JSON.parse(message));
transport.onclose = () => this._closePromise.resolve();
this._playwright = connection.initializePlaywright();
}
async stop() {
this._driverProcess.stdin.destroy();
this._driverProcess.stdout.destroy();
this._driverProcess.stderr.destroy();
await this._closePromise;
}
}

View File

@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.slowMoActions = exports.pausesBeforeInputActions = exports.commandsWithTracingSnapshots = void 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.
*/
// This file is generated by generate_channels.js, do not edit manually.
const slowMoActions = new Set(['Page.goBack', 'Page.goForward', 'Page.reload', 'Page.keyboardDown', 'Page.keyboardUp', 'Page.keyboardInsertText', 'Page.keyboardType', 'Page.keyboardPress', 'Page.mouseMove', 'Page.mouseDown', 'Page.mouseUp', 'Page.mouseClick', 'Page.mouseWheel', 'Page.touchscreenTap', 'Frame.blur', 'Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.dispatchEvent', 'Frame.fill', 'Frame.focus', 'Frame.goto', 'Frame.hover', 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.dispatchEvent', 'ElementHandle.fill', 'ElementHandle.focus', 'ElementHandle.hover', 'ElementHandle.press', 'ElementHandle.scrollIntoViewIfNeeded', 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck']);
exports.slowMoActions = slowMoActions;
const commandsWithTracingSnapshots = new Set(['EventTarget.waitForEventInfo', 'BrowserContext.waitForEventInfo', 'Page.waitForEventInfo', 'WebSocket.waitForEventInfo', 'ElectronApplication.waitForEventInfo', 'AndroidDevice.waitForEventInfo', 'Page.emulateMedia', 'Page.goBack', 'Page.goForward', 'Page.reload', 'Page.expectScreenshot', 'Page.screenshot', 'Page.setViewportSize', 'Page.keyboardDown', 'Page.keyboardUp', 'Page.keyboardInsertText', 'Page.keyboardType', 'Page.keyboardPress', 'Page.mouseMove', 'Page.mouseDown', 'Page.mouseUp', 'Page.mouseClick', 'Page.mouseWheel', 'Page.touchscreenTap', 'Frame.evalOnSelector', 'Frame.evalOnSelectorAll', 'Frame.addScriptTag', 'Frame.addStyleTag', 'Frame.blur', 'Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.dispatchEvent', 'Frame.evaluateExpression', 'Frame.evaluateExpressionHandle', 'Frame.fill', 'Frame.focus', 'Frame.getAttribute', 'Frame.goto', 'Frame.hover', 'Frame.innerHTML', 'Frame.innerText', 'Frame.inputValue', 'Frame.isChecked', 'Frame.isDisabled', 'Frame.isEnabled', 'Frame.isHidden', 'Frame.isVisible', 'Frame.isEditable', 'Frame.press', 'Frame.selectOption', 'Frame.setContent', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.textContent', 'Frame.type', 'Frame.uncheck', 'Frame.waitForTimeout', 'Frame.waitForFunction', 'Frame.waitForSelector', 'Frame.expect', 'JSHandle.evaluateExpression', 'ElementHandle.evaluateExpression', 'JSHandle.evaluateExpressionHandle', 'ElementHandle.evaluateExpressionHandle', 'ElementHandle.evalOnSelector', 'ElementHandle.evalOnSelectorAll', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.dispatchEvent', 'ElementHandle.fill', 'ElementHandle.focus', 'ElementHandle.hover', 'ElementHandle.innerHTML', 'ElementHandle.innerText', 'ElementHandle.inputValue', 'ElementHandle.isChecked', 'ElementHandle.isDisabled', 'ElementHandle.isEditable', 'ElementHandle.isEnabled', 'ElementHandle.isHidden', 'ElementHandle.isVisible', 'ElementHandle.press', 'ElementHandle.screenshot', 'ElementHandle.scrollIntoViewIfNeeded', 'ElementHandle.selectOption', 'ElementHandle.selectText', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.textContent', 'ElementHandle.type', 'ElementHandle.uncheck', 'ElementHandle.waitForElementState', 'ElementHandle.waitForSelector']);
exports.commandsWithTracingSnapshots = commandsWithTracingSnapshots;
const pausesBeforeInputActions = new Set(['Frame.check', 'Frame.click', 'Frame.dragAndDrop', 'Frame.dblclick', 'Frame.fill', 'Frame.hover', 'Frame.press', 'Frame.selectOption', 'Frame.setInputFiles', 'Frame.setInputFilePaths', 'Frame.tap', 'Frame.type', 'Frame.uncheck', 'ElementHandle.check', 'ElementHandle.click', 'ElementHandle.dblclick', 'ElementHandle.fill', 'ElementHandle.hover', 'ElementHandle.press', 'ElementHandle.selectOption', 'ElementHandle.setInputFiles', 'ElementHandle.setInputFilePaths', 'ElementHandle.tap', 'ElementHandle.type', 'ElementHandle.uncheck']);
exports.pausesBeforeInputActions = pausesBeforeInputActions;

View File

@ -0,0 +1,204 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.parseError = parseError;
exports.parseSerializedValue = parseSerializedValue;
exports.serializeError = serializeError;
exports.serializeValue = serializeValue;
var _errors = require("../common/errors");
/**
* 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.
*/
function serializeError(e) {
if (isError(e)) return {
error: {
message: e.message,
stack: e.stack,
name: e.name
}
};
return {
value: serializeValue(e, value => ({
fallThrough: value
}))
};
}
function parseError(error) {
if (!error.error) {
if (error.value === undefined) throw new Error('Serialized error must have either an error or a value');
return parseSerializedValue(error.value, undefined);
}
if (error.error.name === 'TimeoutError') {
const e = new _errors.TimeoutError(error.error.message);
e.stack = error.error.stack || '';
return e;
}
const e = new Error(error.error.message);
e.stack = error.error.stack || '';
e.name = error.error.name;
return e;
}
function parseSerializedValue(value, handles) {
return innerParseSerializedValue(value, handles, new Map());
}
function innerParseSerializedValue(value, handles, refs) {
if (value.ref !== undefined) return refs.get(value.ref);
if (value.n !== undefined) return value.n;
if (value.s !== undefined) return value.s;
if (value.b !== undefined) return value.b;
if (value.v !== undefined) {
if (value.v === 'undefined') return undefined;
if (value.v === 'null') return null;
if (value.v === 'NaN') return NaN;
if (value.v === 'Infinity') return Infinity;
if (value.v === '-Infinity') return -Infinity;
if (value.v === '-0') return -0;
}
if (value.d !== undefined) return new Date(value.d);
if (value.u !== undefined) return new URL(value.u);
if (value.bi !== undefined) return BigInt(value.bi);
if (value.r !== undefined) return new RegExp(value.r.p, value.r.f);
if (value.a !== undefined) {
const result = [];
refs.set(value.id, result);
for (const v of value.a) result.push(innerParseSerializedValue(v, handles, refs));
return result;
}
if (value.o !== undefined) {
const result = {};
refs.set(value.id, result);
for (const {
k,
v
} of value.o) result[k] = innerParseSerializedValue(v, handles, refs);
return result;
}
if (value.h !== undefined) {
if (handles === undefined) throw new Error('Unexpected handle');
return handles[value.h];
}
throw new Error('Unexpected value');
}
function serializeValue(value, handleSerializer) {
return innerSerializeValue(value, handleSerializer, {
lastId: 0,
visited: new Map()
});
}
function innerSerializeValue(value, handleSerializer, visitorInfo) {
const handle = handleSerializer(value);
if ('fallThrough' in handle) value = handle.fallThrough;else return handle;
if (typeof value === 'symbol') return {
v: 'undefined'
};
if (Object.is(value, undefined)) return {
v: 'undefined'
};
if (Object.is(value, null)) return {
v: 'null'
};
if (Object.is(value, NaN)) return {
v: 'NaN'
};
if (Object.is(value, Infinity)) return {
v: 'Infinity'
};
if (Object.is(value, -Infinity)) return {
v: '-Infinity'
};
if (Object.is(value, -0)) return {
v: '-0'
};
if (typeof value === 'boolean') return {
b: value
};
if (typeof value === 'number') return {
n: value
};
if (typeof value === 'string') return {
s: value
};
if (typeof value === 'bigint') return {
bi: value.toString()
};
if (isError(value)) {
const error = value;
if ('captureStackTrace' in globalThis.Error) {
// v8
return {
s: error.stack || ''
};
}
return {
s: `${error.name}: ${error.message}\n${error.stack}`
};
}
if (isDate(value)) return {
d: value.toJSON()
};
if (isURL(value)) return {
u: value.toJSON()
};
if (isRegExp(value)) return {
r: {
p: value.source,
f: value.flags
}
};
const id = visitorInfo.visited.get(value);
if (id) return {
ref: id
};
if (Array.isArray(value)) {
const a = [];
const id = ++visitorInfo.lastId;
visitorInfo.visited.set(value, id);
for (let i = 0; i < value.length; ++i) a.push(innerSerializeValue(value[i], handleSerializer, visitorInfo));
return {
a,
id
};
}
if (typeof value === 'object') {
const o = [];
const id = ++visitorInfo.lastId;
visitorInfo.visited.set(value, id);
for (const name of Object.keys(value)) o.push({
k: name,
v: innerSerializeValue(value[name], handleSerializer, visitorInfo)
});
return {
o,
id
};
}
throw new Error('Unexpected value');
}
function isRegExp(obj) {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
}
function isDate(obj) {
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
}
function isURL(obj) {
return obj instanceof URL || Object.prototype.toString.call(obj) === '[object URL]';
}
function isError(obj) {
const proto = obj ? Object.getPrototypeOf(obj) : null;
return obj instanceof Error || (proto === null || proto === void 0 ? void 0 : proto.name) === 'Error' || proto && isError(proto);
}

View File

@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PipeTransport = void 0;
var _utils = require("../utils");
/**
* 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.
*/
class PipeTransport {
constructor(pipeWrite, pipeRead, closeable, endian = 'le') {
this._pipeWrite = void 0;
this._data = Buffer.from([]);
this._waitForNextTask = (0, _utils.makeWaitForNextTask)();
this._closed = false;
this._bytesLeft = 0;
this.onmessage = void 0;
this.onclose = void 0;
this._endian = void 0;
this._closeableStream = void 0;
this._pipeWrite = pipeWrite;
this._endian = endian;
this._closeableStream = closeable;
pipeRead.on('data', buffer => this._dispatch(buffer));
pipeRead.on('close', () => {
this._closed = true;
if (this.onclose) this.onclose();
});
this.onmessage = undefined;
this.onclose = undefined;
}
send(message) {
if (this._closed) throw new Error('Pipe has been closed');
const data = Buffer.from(message, 'utf-8');
const dataLength = Buffer.alloc(4);
if (this._endian === 'be') dataLength.writeUInt32BE(data.length, 0);else dataLength.writeUInt32LE(data.length, 0);
this._pipeWrite.write(dataLength);
this._pipeWrite.write(data);
}
close() {
// Let it throw.
this._closeableStream.close();
}
_dispatch(buffer) {
this._data = Buffer.concat([this._data, buffer]);
while (true) {
if (!this._bytesLeft && this._data.length < 4) {
// Need more data.
break;
}
if (!this._bytesLeft) {
this._bytesLeft = this._endian === 'be' ? this._data.readUInt32BE(0) : this._data.readUInt32LE(0);
this._data = this._data.slice(4);
}
if (!this._bytesLeft || this._data.length < this._bytesLeft) {
// Need more data.
break;
}
const message = this._data.slice(0, this._bytesLeft);
this._data = this._data.slice(this._bytesLeft);
this._bytesLeft = 0;
this._waitForNextTask(() => {
if (this.onmessage) this.onmessage(message.toString('utf-8'));
});
}
}
}
exports.PipeTransport = PipeTransport;

View File

@ -0,0 +1,140 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ValidationError = void 0;
exports.createMetadataValidator = createMetadataValidator;
exports.findValidator = findValidator;
exports.maybeFindValidator = maybeFindValidator;
exports.tUndefined = exports.tType = exports.tString = exports.tOptional = exports.tObject = exports.tNumber = exports.tEnum = exports.tChannel = exports.tBoolean = exports.tBinary = exports.tArray = exports.tAny = exports.scheme = void 0;
var _utils = require("../utils");
/**
* 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.
*/
class ValidationError extends Error {}
exports.ValidationError = ValidationError;
const scheme = {};
exports.scheme = scheme;
function findValidator(type, method, kind) {
const validator = maybeFindValidator(type, method, kind);
if (!validator) throw new ValidationError(`Unknown scheme for ${kind}: ${type}.${method}`);
return validator;
}
function maybeFindValidator(type, method, kind) {
const schemeName = type + (kind === 'Initializer' ? '' : method[0].toUpperCase() + method.substring(1)) + kind;
return scheme[schemeName];
}
function createMetadataValidator() {
return tOptional(scheme['Metadata']);
}
const tNumber = (arg, path, context) => {
if (arg instanceof Number) return arg.valueOf();
if (typeof arg === 'number') return arg;
throw new ValidationError(`${path}: expected number, got ${typeof arg}`);
};
exports.tNumber = tNumber;
const tBoolean = (arg, path, context) => {
if (arg instanceof Boolean) return arg.valueOf();
if (typeof arg === 'boolean') return arg;
throw new ValidationError(`${path}: expected boolean, got ${typeof arg}`);
};
exports.tBoolean = tBoolean;
const tString = (arg, path, context) => {
if (arg instanceof String) return arg.valueOf();
if (typeof arg === 'string') return arg;
throw new ValidationError(`${path}: expected string, got ${typeof arg}`);
};
exports.tString = tString;
const tBinary = (arg, path, context) => {
if (context.binary === 'fromBase64') {
if (arg instanceof String) return Buffer.from(arg.valueOf(), 'base64');
if (typeof arg === 'string') return Buffer.from(arg, 'base64');
throw new ValidationError(`${path}: expected base64-encoded buffer, got ${typeof arg}`);
}
if (context.binary === 'toBase64') {
if (!(arg instanceof Buffer)) throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
return arg.toString('base64');
}
if (context.binary === 'buffer') {
if (!(arg instanceof Buffer)) throw new ValidationError(`${path}: expected Buffer, got ${typeof arg}`);
return arg;
}
throw new ValidationError(`Unsupported binary behavior "${context.binary}"`);
};
exports.tBinary = tBinary;
const tUndefined = (arg, path, context) => {
if (Object.is(arg, undefined)) return arg;
throw new ValidationError(`${path}: expected undefined, got ${typeof arg}`);
};
exports.tUndefined = tUndefined;
const tAny = (arg, path, context) => {
return arg;
};
exports.tAny = tAny;
const tOptional = v => {
return (arg, path, context) => {
if (Object.is(arg, undefined)) return arg;
return v(arg, path, context);
};
};
exports.tOptional = tOptional;
const tArray = v => {
return (arg, path, context) => {
if (!Array.isArray(arg)) throw new ValidationError(`${path}: expected array, got ${typeof arg}`);
return arg.map((x, index) => v(x, path + '[' + index + ']', context));
};
};
exports.tArray = tArray;
const tObject = s => {
return (arg, path, context) => {
if (Object.is(arg, null)) throw new ValidationError(`${path}: expected object, got null`);
if (typeof arg !== 'object') throw new ValidationError(`${path}: expected object, got ${typeof arg}`);
const result = {};
for (const [key, v] of Object.entries(s)) {
const value = v(arg[key], path ? path + '.' + key : key, context);
if (!Object.is(value, undefined)) result[key] = value;
}
if ((0, _utils.isUnderTest)()) {
for (const [key, value] of Object.entries(arg)) {
if (key.startsWith('__testHook')) result[key] = value;
}
}
return result;
};
};
exports.tObject = tObject;
const tEnum = e => {
return (arg, path, context) => {
if (!e.includes(arg)) throw new ValidationError(`${path}: expected one of (${e.join('|')})`);
return arg;
};
};
exports.tEnum = tEnum;
const tChannel = names => {
return (arg, path, context) => {
return context.tChannelImpl(names, arg, path, context);
};
};
exports.tChannel = tChannel;
const tType = name => {
return (arg, path, context) => {
const v = scheme[name];
if (!v) throw new ValidationError(path + ': unknown type "' + name + '"');
return v(arg, path, context);
};
};
exports.tType = tType;

View File

@ -0,0 +1,251 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PlaywrightConnection = void 0;
var _server = require("../server");
var _browser = require("../server/browser");
var _instrumentation = require("../server/instrumentation");
var _socksProxy = require("../common/socksProxy");
var _utils = require("../utils");
var _android = require("../server/android/android");
var _debugControllerDispatcher = require("../server/dispatchers/debugControllerDispatcher");
var _debugLogger = require("../common/debugLogger");
/**
* 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.
*/
class PlaywrightConnection {
constructor(lock, clientType, ws, options, preLaunched, id, onClose) {
this._ws = void 0;
this._onClose = void 0;
this._dispatcherConnection = void 0;
this._cleanups = [];
this._id = void 0;
this._disconnected = false;
this._preLaunched = void 0;
this._options = void 0;
this._root = void 0;
this._profileName = void 0;
this._ws = ws;
this._preLaunched = preLaunched;
this._options = options;
options.launchOptions = filterLaunchOptions(options.launchOptions);
if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser-or-android') (0, _utils.assert)(preLaunched.playwright);
if (clientType === 'pre-launched-browser-or-android') (0, _utils.assert)(preLaunched.browser || preLaunched.androidDevice);
this._onClose = onClose;
this._id = id;
this._profileName = `${new Date().toISOString()}-${clientType}`;
this._dispatcherConnection = new _server.DispatcherConnection();
this._dispatcherConnection.onmessage = async message => {
await lock;
if (ws.readyState !== ws.CLOSING) {
const messageString = JSON.stringify(message);
if (_debugLogger.debugLogger.isEnabled('server:channel')) _debugLogger.debugLogger.log('server:channel', `[${this._id}] ${(0, _utils.monotonicTime)() * 1000} SEND ► ${messageString}`);
ws.send(messageString);
}
};
ws.on('message', async message => {
await lock;
const messageString = Buffer.from(message).toString();
if (_debugLogger.debugLogger.isEnabled('server:channel')) _debugLogger.debugLogger.log('server:channel', `[${this._id}] ${(0, _utils.monotonicTime)() * 1000} ◀ RECV ${messageString}`);
this._dispatcherConnection.dispatch(JSON.parse(messageString));
});
ws.on('close', () => this._onDisconnect());
ws.on('error', error => this._onDisconnect(error));
if (clientType === 'controller') {
this._root = this._initDebugControllerMode();
return;
}
this._root = new _server.RootDispatcher(this._dispatcherConnection, async scope => {
await (0, _utils.startProfiling)();
if (clientType === 'reuse-browser') return await this._initReuseBrowsersMode(scope);
if (clientType === 'pre-launched-browser-or-android') return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
if (clientType === 'launch-browser') return await this._initLaunchBrowserMode(scope);
throw new Error('Unsupported client type: ' + clientType);
});
}
async _initLaunchBrowserMode(scope) {
_debugLogger.debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`);
const playwright = (0, _server.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
const browser = await playwright[this._options.browserName].launch((0, _instrumentation.serverSideCallMetadata)(), this._options.launchOptions);
this._cleanups.push(async () => {
for (const browser of playwright.allBrowsers()) await browser.close();
});
browser.on(_browser.Browser.Events.Disconnected, () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({
code: 1001,
reason: 'Browser closed'
});
});
return new _server.PlaywrightDispatcher(scope, playwright, ownedSocksProxy, browser);
}
async _initPreLaunchedBrowserMode(scope) {
var _this$_preLaunched$so;
_debugLogger.debugLogger.log('server', `[${this._id}] engaged pre-launched (browser) mode`);
const playwright = this._preLaunched.playwright;
// Note: connected client owns the socks proxy and configures the pattern.
(_this$_preLaunched$so = this._preLaunched.socksProxy) === null || _this$_preLaunched$so === void 0 ? void 0 : _this$_preLaunched$so.setPattern(this._options.socksProxyPattern);
const browser = this._preLaunched.browser;
browser.on(_browser.Browser.Events.Disconnected, () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({
code: 1001,
reason: 'Browser closed'
});
});
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, this._preLaunched.socksProxy, browser);
// In pre-launched mode, keep only the pre-launched browser.
for (const b of playwright.allBrowsers()) {
if (b !== browser) await b.close();
}
this._cleanups.push(() => playwrightDispatcher.cleanup());
return playwrightDispatcher;
}
async _initPreLaunchedAndroidMode(scope) {
_debugLogger.debugLogger.log('server', `[${this._id}] engaged pre-launched (Android) mode`);
const playwright = this._preLaunched.playwright;
const androidDevice = this._preLaunched.androidDevice;
androidDevice.on(_android.AndroidDevice.Events.Close, () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({
code: 1001,
reason: 'Android device disconnected'
});
});
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, undefined, undefined, androidDevice);
this._cleanups.push(() => playwrightDispatcher.cleanup());
return playwrightDispatcher;
}
_initDebugControllerMode() {
_debugLogger.debugLogger.log('server', `[${this._id}] engaged reuse controller mode`);
const playwright = this._preLaunched.playwright;
// Always create new instance based on the reused Playwright instance.
return new _debugControllerDispatcher.DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController);
}
async _initReuseBrowsersMode(scope) {
// Note: reuse browser mode does not support socks proxy, because
// clients come and go, while the browser stays the same.
_debugLogger.debugLogger.log('server', `[${this._id}] engaged reuse browsers mode for ${this._options.browserName}`);
const playwright = this._preLaunched.playwright;
const requestedOptions = launchOptionsHash(this._options.launchOptions);
let browser = playwright.allBrowsers().find(b => {
if (b.options.name !== this._options.browserName) return false;
const existingOptions = launchOptionsHash(b.options.originalLaunchOptions);
return existingOptions === requestedOptions;
});
// Close remaining browsers of this type+channel. Keep different browser types for the speed.
for (const b of playwright.allBrowsers()) {
if (b === browser) continue;
if (b.options.name === this._options.browserName && b.options.channel === this._options.launchOptions.channel) await b.close();
}
if (!browser) {
browser = await playwright[this._options.browserName || 'chromium'].launch((0, _instrumentation.serverSideCallMetadata)(), {
...this._options.launchOptions,
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS
});
browser.on(_browser.Browser.Events.Disconnected, () => {
// Underlying browser did close for some reason - force disconnect the client.
this.close({
code: 1001,
reason: 'Browser closed'
});
});
}
this._cleanups.push(async () => {
// Don't close the pages so that user could debug them,
// but close all the empty browsers and contexts to clean up.
for (const browser of playwright.allBrowsers()) {
for (const context of browser.contexts()) {
if (!context.pages().length) await context.close((0, _instrumentation.serverSideCallMetadata)());else await context.stopPendingOperations();
}
if (!browser.contexts()) await browser.close();
}
});
const playwrightDispatcher = new _server.PlaywrightDispatcher(scope, playwright, undefined, browser);
return playwrightDispatcher;
}
async _createOwnedSocksProxy(playwright) {
if (!this._options.socksProxyPattern) return;
const socksProxy = new _socksProxy.SocksProxy();
socksProxy.setPattern(this._options.socksProxyPattern);
playwright.options.socksProxyPort = await socksProxy.listen(0);
_debugLogger.debugLogger.log('server', `[${this._id}] started socks proxy on port ${playwright.options.socksProxyPort}`);
this._cleanups.push(() => socksProxy.close());
return socksProxy;
}
async _onDisconnect(error) {
this._disconnected = true;
_debugLogger.debugLogger.log('server', `[${this._id}] disconnected. error: ${error}`);
this._root._dispose();
_debugLogger.debugLogger.log('server', `[${this._id}] starting cleanup`);
for (const cleanup of this._cleanups) await cleanup().catch(() => {});
await (0, _utils.stopProfiling)(this._profileName);
this._onClose();
_debugLogger.debugLogger.log('server', `[${this._id}] finished cleanup`);
}
async close(reason) {
if (this._disconnected) return;
_debugLogger.debugLogger.log('server', `[${this._id}] force closing connection: ${(reason === null || reason === void 0 ? void 0 : reason.reason) || ''} (${(reason === null || reason === void 0 ? void 0 : reason.code) || 0})`);
try {
this._ws.close(reason === null || reason === void 0 ? void 0 : reason.code, reason === null || reason === void 0 ? void 0 : reason.reason);
} catch (e) {}
}
}
exports.PlaywrightConnection = PlaywrightConnection;
function launchOptionsHash(options) {
const copy = {
...options
};
for (const k of Object.keys(copy)) {
const key = k;
if (copy[key] === defaultLaunchOptions[key]) delete copy[key];
}
for (const key of optionsThatAllowBrowserReuse) delete copy[key];
return JSON.stringify(copy);
}
function filterLaunchOptions(options) {
return {
channel: options.channel,
args: options.args,
ignoreAllDefaultArgs: options.ignoreAllDefaultArgs,
ignoreDefaultArgs: options.ignoreDefaultArgs,
timeout: options.timeout,
headless: options.headless,
proxy: options.proxy,
chromiumSandbox: options.chromiumSandbox,
firefoxUserPrefs: options.firefoxUserPrefs,
slowMo: options.slowMo,
executablePath: (0, _utils.isUnderTest)() ? options.executablePath : undefined
};
}
const defaultLaunchOptions = {
ignoreAllDefaultArgs: false,
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
headless: true,
devtools: false
};
const optionsThatAllowBrowserReuse = ['headless', 'tracesDir'];

View File

@ -0,0 +1,178 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Semaphore = exports.PlaywrightServer = void 0;
var _utilsBundle = require("../utilsBundle");
var _playwright = require("../server/playwright");
var _playwrightConnection = require("./playwrightConnection");
var _manualPromise = require("../utils/manualPromise");
var _debugLogger = require("../common/debugLogger");
var _utils = require("../utils");
/**
* 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.
*/
let lastConnectionId = 0;
const kConnectionSymbol = Symbol('kConnection');
class PlaywrightServer {
constructor(options) {
this._preLaunchedPlaywright = void 0;
this._wsServer = void 0;
this._options = void 0;
this._options = options;
if (options.preLaunchedBrowser) this._preLaunchedPlaywright = options.preLaunchedBrowser.attribution.playwright;
if (options.preLaunchedAndroidDevice) this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android.attribution.playwright;
}
async listen(port = 0) {
_debugLogger.debugLogger.log('server', `Server started at ${new Date()}`);
const server = (0, _utils.createHttpServer)((request, response) => {
if (request.method === 'GET' && request.url === '/json') {
response.setHeader('Content-Type', 'application/json');
response.end(JSON.stringify({
wsEndpointPath: this._options.path
}));
return;
}
response.end('Running');
});
server.on('error', error => _debugLogger.debugLogger.log('server', String(error)));
const wsEndpoint = await new Promise((resolve, reject) => {
server.listen(port, () => {
const address = server.address();
if (!address) {
reject(new Error('Could not bind server socket'));
return;
}
const wsEndpoint = typeof address === 'string' ? `${address}${this._options.path}` : `ws://127.0.0.1:${address.port}${this._options.path}`;
resolve(wsEndpoint);
}).on('error', reject);
});
_debugLogger.debugLogger.log('server', 'Listening at ' + wsEndpoint);
this._wsServer = new _utilsBundle.wsServer({
server,
path: this._options.path
});
const browserSemaphore = new Semaphore(this._options.maxConnections);
const controllerSemaphore = new Semaphore(1);
const reuseBrowserSemaphore = new Semaphore(1);
if (process.env.PWTEST_SERVER_WS_HEADERS) {
this._wsServer.on('headers', (headers, request) => {
headers.push(process.env.PWTEST_SERVER_WS_HEADERS);
});
}
this._wsServer.on('connection', (ws, request) => {
const url = new URL('http://localhost' + (request.url || ''));
const browserHeader = request.headers['x-playwright-browser'];
const browserName = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
const proxyHeader = request.headers['x-playwright-proxy'];
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
const launchOptionsHeader = request.headers['x-playwright-launch-options'] || '';
const launchOptionsHeaderValue = Array.isArray(launchOptionsHeader) ? launchOptionsHeader[0] : launchOptionsHeader;
const launchOptionsParam = url.searchParams.get('launch-options');
let launchOptions = {};
try {
launchOptions = JSON.parse(launchOptionsParam || launchOptionsHeaderValue);
} catch (e) {}
const id = String(++lastConnectionId);
_debugLogger.debugLogger.log('server', `[${id}] serving connection: ${request.url}`);
// Instantiate playwright for the extension modes.
const isExtension = this._options.mode === 'extension';
if (isExtension) {
if (!this._preLaunchedPlaywright) this._preLaunchedPlaywright = (0, _playwright.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
}
let clientType = 'launch-browser';
let semaphore = browserSemaphore;
if (isExtension && url.searchParams.has('debug-controller')) {
clientType = 'controller';
semaphore = controllerSemaphore;
} else if (isExtension) {
clientType = 'reuse-browser';
semaphore = reuseBrowserSemaphore;
} else if (this._options.mode === 'launchServer') {
clientType = 'pre-launched-browser-or-android';
semaphore = browserSemaphore;
}
const connection = new _playwrightConnection.PlaywrightConnection(semaphore.aquire(), clientType, ws, {
socksProxyPattern: proxyValue,
browserName,
launchOptions
}, {
playwright: this._preLaunchedPlaywright,
browser: this._options.preLaunchedBrowser,
androidDevice: this._options.preLaunchedAndroidDevice,
socksProxy: this._options.preLaunchedSocksProxy
}, id, () => semaphore.release());
ws[kConnectionSymbol] = connection;
});
return wsEndpoint;
}
async close() {
const server = this._wsServer;
if (!server) return;
_debugLogger.debugLogger.log('server', 'closing websocket server');
const waitForClose = new Promise(f => server.close(f));
// First disconnect all remaining clients.
await Promise.all(Array.from(server.clients).map(async ws => {
const connection = ws[kConnectionSymbol];
if (connection) await connection.close();
try {
ws.terminate();
} catch (e) {}
}));
await waitForClose;
_debugLogger.debugLogger.log('server', 'closing http server');
await new Promise(f => server.options.server.close(f));
this._wsServer = undefined;
_debugLogger.debugLogger.log('server', 'closed server');
_debugLogger.debugLogger.log('server', 'closing browsers');
if (this._preLaunchedPlaywright) await Promise.all(this._preLaunchedPlaywright.allBrowsers().map(browser => browser.close()));
_debugLogger.debugLogger.log('server', 'closed browsers');
}
}
exports.PlaywrightServer = PlaywrightServer;
class Semaphore {
constructor(max) {
this._max = void 0;
this._aquired = 0;
this._queue = [];
this._max = max;
}
setMax(max) {
this._max = max;
}
aquire() {
const lock = new _manualPromise.ManualPromise();
this._queue.push(lock);
this._flush();
return lock;
}
release() {
--this._aquired;
this._flush();
}
_flush() {
while (this._aquired < this._max && this._queue.length) {
++this._aquired;
this._queue.shift().resolve();
}
}
}
exports.Semaphore = Semaphore;

View File

@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Accessibility = void 0;
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications 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.
*/
class Accessibility {
constructor(getAXTree) {
this._getAXTree = void 0;
this._getAXTree = getAXTree;
}
async snapshot(options = {}) {
const {
interestingOnly = true,
root = null
} = options;
const {
tree,
needle
} = await this._getAXTree(root || undefined);
if (!interestingOnly) {
if (root) return needle && serializeTree(needle)[0];
return serializeTree(tree)[0];
}
const interestingNodes = new Set();
collectInterestingNodes(interestingNodes, tree, false);
if (root && (!needle || !interestingNodes.has(needle))) return null;
return serializeTree(needle || tree, interestingNodes)[0];
}
}
exports.Accessibility = Accessibility;
function collectInterestingNodes(collection, node, insideControl) {
if (node.isInteresting(insideControl)) collection.add(node);
if (node.isLeafNode()) return;
insideControl = insideControl || node.isControl();
for (const child of node.children()) collectInterestingNodes(collection, child, insideControl);
}
function serializeTree(node, whitelistedNodes) {
const children = [];
for (const child of node.children()) children.push(...serializeTree(child, whitelistedNodes));
if (whitelistedNodes && !whitelistedNodes.has(node)) return children;
const serializedNode = node.serialize();
if (children.length) serializedNode.children = children;
return [serializedNode];
}

View File

@ -0,0 +1,438 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AndroidDevice = exports.Android = void 0;
var _utilsBundle = require("../../utilsBundle");
var _events = require("events");
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _utils = require("../../utils");
var _fileUtils = require("../../utils/fileUtils");
var _browserContext = require("../browserContext");
var _progress = require("../progress");
var _crBrowser = require("../chromium/crBrowser");
var _helper = require("../helper");
var _transport = require("../../protocol/transport");
var _debugLogger = require("../../common/debugLogger");
var _processLauncher = require("../../utils/processLauncher");
var _timeoutSettings = require("../../common/timeoutSettings");
var _instrumentation = require("../instrumentation");
var _chromiumSwitches = require("../chromium/chromiumSwitches");
var _registry = require("../registry");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright Microsoft Corporation. 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 ARTIFACTS_FOLDER = _path.default.join(_os.default.tmpdir(), 'playwright-artifacts-');
class Android extends _instrumentation.SdkObject {
constructor(parent, backend) {
super(parent, 'android');
this._backend = void 0;
this._devices = new Map();
this._timeoutSettings = void 0;
this._backend = backend;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async devices(options) {
const devices = (await this._backend.devices(options)).filter(d => d.status === 'device');
const newSerials = new Set();
for (const d of devices) {
newSerials.add(d.serial);
if (this._devices.has(d.serial)) continue;
const device = await AndroidDevice.create(this, d, options);
this._devices.set(d.serial, device);
}
for (const d of this._devices.keys()) {
if (!newSerials.has(d)) this._devices.delete(d);
}
return [...this._devices.values()];
}
_deviceClosed(device) {
this._devices.delete(device.serial);
}
}
exports.Android = Android;
class AndroidDevice extends _instrumentation.SdkObject {
constructor(android, backend, model, options) {
super(android, 'android-device');
this._backend = void 0;
this.model = void 0;
this.serial = void 0;
this._options = void 0;
this._driverPromise = void 0;
this._lastId = 0;
this._callbacks = new Map();
this._pollingWebViews = void 0;
this._timeoutSettings = void 0;
this._webViews = new Map();
this._browserConnections = new Set();
this._android = void 0;
this._isClosed = false;
this._android = android;
this._backend = backend;
this.model = model;
this.serial = backend.serial;
this._options = options;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(android._timeoutSettings);
}
static async create(android, backend, options) {
await backend.init();
const model = await backend.runCommand('shell:getprop ro.product.model');
const device = new AndroidDevice(android, backend, model.toString().trim(), options);
await device._init();
return device;
}
async _init() {
await this._refreshWebViews();
const poll = () => {
this._pollingWebViews = setTimeout(() => this._refreshWebViews().then(poll).catch(() => {
this.close().catch(() => {});
}), 500);
};
poll();
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async shell(command) {
const result = await this._backend.runCommand(`shell:${command}`);
await this._refreshWebViews();
return result;
}
async open(command) {
return await this._backend.open(`${command}`);
}
async screenshot() {
return await this._backend.runCommand(`shell:screencap -p`);
}
async _driver() {
if (this._isClosed) return;
if (!this._driverPromise) this._driverPromise = this._installDriver();
return this._driverPromise;
}
async _installDriver() {
(0, _utilsBundle.debug)('pw:android')('Stopping the old driver');
await this.shell(`am force-stop com.microsoft.playwright.androiddriver`);
// uninstall and install driver on every excution
if (!this._options.omitDriverInstall) {
(0, _utilsBundle.debug)('pw:android')('Uninstalling the old driver');
await this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver`);
await this.shell(`cmd package uninstall com.microsoft.playwright.androiddriver.test`);
(0, _utilsBundle.debug)('pw:android')('Installing the new driver');
const executable = _registry.registry.findExecutable('android');
for (const file of ['android-driver.apk', 'android-driver-target.apk']) {
const fullName = _path.default.join(executable.directory, file);
if (!_fs.default.existsSync(fullName)) throw new Error('Please install Android driver apk using `npx playwright install android`');
await this.installApk(await _fs.default.promises.readFile(fullName));
}
} else {
(0, _utilsBundle.debug)('pw:android')('Skipping the driver installation');
}
(0, _utilsBundle.debug)('pw:android')('Starting the new driver');
this.shell('am instrument -w com.microsoft.playwright.androiddriver.test/androidx.test.runner.AndroidJUnitRunner').catch(e => (0, _utilsBundle.debug)('pw:android')(e));
const socket = await this._waitForLocalAbstract('playwright_android_driver_socket');
const transport = new _transport.PipeTransport(socket, socket, socket, 'be');
transport.onmessage = message => {
const response = JSON.parse(message);
const {
id,
result,
error
} = response;
const callback = this._callbacks.get(id);
if (!callback) return;
if (error) callback.reject(new Error(error));else callback.fulfill(result);
this._callbacks.delete(id);
};
return transport;
}
async _waitForLocalAbstract(socketName) {
let socket;
(0, _utilsBundle.debug)('pw:android')(`Polling the socket localabstract:${socketName}`);
while (!socket) {
try {
socket = await this._backend.open(`localabstract:${socketName}`);
} catch (e) {
await new Promise(f => setTimeout(f, 250));
}
}
(0, _utilsBundle.debug)('pw:android')(`Connected to localabstract:${socketName}`);
return socket;
}
async send(method, params = {}) {
// Patch the timeout in!
params.timeout = this._timeoutSettings.timeout(params);
const driver = await this._driver();
if (!driver) throw new Error('Device is closed');
const id = ++this._lastId;
const result = new Promise((fulfill, reject) => this._callbacks.set(id, {
fulfill,
reject
}));
driver.send(JSON.stringify({
id,
method,
params
}));
return result;
}
async close() {
if (this._isClosed) return;
this._isClosed = true;
if (this._pollingWebViews) clearTimeout(this._pollingWebViews);
for (const connection of this._browserConnections) await connection.close();
if (this._driverPromise) {
const driver = await this._driver();
driver === null || driver === void 0 ? void 0 : driver.close();
}
await this._backend.close();
this._android._deviceClosed(this);
this.emit(AndroidDevice.Events.Close);
}
async launchBrowser(pkg = 'com.android.chrome', options) {
(0, _utilsBundle.debug)('pw:android')('Force-stopping', pkg);
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
const socketName = (0, _utils.isUnderTest)() ? 'webview_devtools_remote_playwright_test' : 'playwright-' + (0, _utils.createGuid)();
const commandLine = this._defaultArgs(options, socketName).join(' ');
(0, _utilsBundle.debug)('pw:android')('Starting', pkg, commandLine);
// encode commandLine to base64 to avoid issues (bash encoding) with special characters
await this._backend.runCommand(`shell:echo "${Buffer.from(commandLine).toString('base64')}" | base64 -d > /data/local/tmp/chrome-command-line`);
await this._backend.runCommand(`shell:am start -a android.intent.action.VIEW -d about:blank ${pkg}`);
return await this._connectToBrowser(socketName, options);
}
_defaultArgs(options, socketName) {
const chromeArguments = ['_', '--disable-fre', '--no-default-browser-check', `--remote-debugging-socket-name=${socketName}`, ..._chromiumSwitches.chromiumSwitches, ...this._innerDefaultArgs(options)];
return chromeArguments;
}
_innerDefaultArgs(options) {
const {
args = [],
proxy
} = options;
const chromeArguments = [];
if (proxy) {
chromeArguments.push(`--proxy-server=${proxy.server}`);
const proxyBypassRules = [];
if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) proxyBypassRules.push('<-loopback>');
if (proxyBypassRules.length > 0) chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`);
}
chromeArguments.push(...args);
return chromeArguments;
}
async connectToWebView(socketName) {
const webView = this._webViews.get(socketName);
if (!webView) throw new Error('WebView has been closed');
return await this._connectToBrowser(socketName);
}
async _connectToBrowser(socketName, options = {}) {
const socket = await this._waitForLocalAbstract(socketName);
const androidBrowser = new AndroidBrowser(this, socket);
await androidBrowser._init();
this._browserConnections.add(androidBrowser);
const artifactsDir = await _fs.default.promises.mkdtemp(ARTIFACTS_FOLDER);
const cleanupArtifactsDir = async () => {
const errors = await (0, _fileUtils.removeFolders)([artifactsDir]);
for (let i = 0; i < (errors || []).length; ++i) (0, _utilsBundle.debug)('pw:android')(`exception while removing ${artifactsDir}: ${errors[i]}`);
};
_processLauncher.gracefullyCloseSet.add(cleanupArtifactsDir);
socket.on('close', async () => {
_processLauncher.gracefullyCloseSet.delete(cleanupArtifactsDir);
cleanupArtifactsDir().catch(e => (0, _utilsBundle.debug)('pw:android')(`could not cleanup artifacts dir: ${e}`));
});
const browserOptions = {
name: 'clank',
isChromium: true,
slowMo: 0,
persistent: {
...options,
noDefaultViewport: true
},
artifactsDir,
downloadsPath: artifactsDir,
tracesDir: artifactsDir,
browserProcess: new ClankBrowserProcess(androidBrowser),
proxy: options.proxy,
protocolLogger: _helper.helper.debugProtocolLogger(),
browserLogsCollector: new _debugLogger.RecentLogsCollector(),
originalLaunchOptions: {}
};
(0, _browserContext.validateBrowserContextOptions)(options, browserOptions);
const browser = await _crBrowser.CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions);
const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), this);
const defaultContext = browser._defaultContext;
await controller.run(async progress => {
await defaultContext._loadDefaultContextAsIs(progress);
});
return defaultContext;
}
webViews() {
return [...this._webViews.values()];
}
async installApk(content, options) {
const args = options && options.args ? options.args : ['-r', '-t', '-S'];
(0, _utilsBundle.debug)('pw:android')('Opening install socket');
const installSocket = await this._backend.open(`shell:cmd package install ${args.join(' ')} ${content.length}`);
(0, _utilsBundle.debug)('pw:android')('Writing driver bytes: ' + content.length);
await installSocket.write(content);
const success = await new Promise(f => installSocket.on('data', f));
(0, _utilsBundle.debug)('pw:android')('Written driver bytes: ' + success);
installSocket.close();
}
async push(content, path, mode = 0o644) {
const socket = await this._backend.open(`sync:`);
const sendHeader = async (command, length) => {
const buffer = Buffer.alloc(command.length + 4);
buffer.write(command, 0);
buffer.writeUInt32LE(length, command.length);
await socket.write(buffer);
};
const send = async (command, data) => {
await sendHeader(command, data.length);
await socket.write(data);
};
await send('SEND', Buffer.from(`${path},${mode}`));
const maxChunk = 65535;
for (let i = 0; i < content.length; i += maxChunk) await send('DATA', content.slice(i, i + maxChunk));
await sendHeader('DONE', Date.now() / 1000 | 0);
const result = await new Promise(f => socket.once('data', f));
const code = result.slice(0, 4).toString();
if (code !== 'OKAY') throw new Error('Could not push: ' + code);
socket.close();
}
async _refreshWebViews() {
// possible socketName, eg: webview_devtools_remote_32327, webview_devtools_remote_32327_zeus, webview_devtools_remote_zeus
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).toString().split('\n');
if (this._isClosed) return;
const socketNames = new Set();
for (const line of sockets) {
const matchSocketName = line.match(/[^@]+@(.*?webview_devtools_remote_?.*)/);
if (!matchSocketName) continue;
const socketName = matchSocketName[1];
socketNames.add(socketName);
if (this._webViews.has(socketName)) continue;
// possible line: 0000000000000000: 00000002 00000000 00010000 0001 01 5841881 @webview_devtools_remote_zeus
// the result: match[1] = ''
const match = line.match(/[^@]+@.*?webview_devtools_remote_?(\d*)/);
let pid = -1;
if (match && match[1]) pid = +match[1];
const pkg = await this._extractPkg(pid);
if (this._isClosed) return;
const webView = {
pid,
pkg,
socketName
};
this._webViews.set(socketName, webView);
this.emit(AndroidDevice.Events.WebViewAdded, webView);
}
for (const p of this._webViews.keys()) {
if (!socketNames.has(p)) {
this._webViews.delete(p);
this.emit(AndroidDevice.Events.WebViewRemoved, p);
}
}
}
async _extractPkg(pid) {
let pkg = '';
if (pid === -1) return pkg;
const procs = (await this._backend.runCommand(`shell:ps -A | grep ${pid}`)).toString().split('\n');
for (const proc of procs) {
const match = proc.match(/[^\s]+\s+(\d+).*$/);
if (!match) continue;
pkg = proc.substring(proc.lastIndexOf(' ') + 1);
}
return pkg;
}
}
exports.AndroidDevice = AndroidDevice;
AndroidDevice.Events = {
WebViewAdded: 'webViewAdded',
WebViewRemoved: 'webViewRemoved',
Close: 'close'
};
class AndroidBrowser extends _events.EventEmitter {
constructor(device, socket) {
super();
this.device = void 0;
this._socket = void 0;
this._receiver = void 0;
this._waitForNextTask = (0, _utils.makeWaitForNextTask)();
this.onmessage = void 0;
this.onclose = void 0;
this.setMaxListeners(0);
this.device = device;
this._socket = socket;
this._socket.on('close', () => {
this._waitForNextTask(() => {
if (this.onclose) this.onclose();
});
});
this._receiver = new _utilsBundle.wsReceiver();
this._receiver.on('message', message => {
this._waitForNextTask(() => {
if (this.onmessage) this.onmessage(JSON.parse(message));
});
});
}
async _init() {
await this._socket.write(Buffer.from(`GET /devtools/browser HTTP/1.1\r
Upgrade: WebSocket\r
Connection: Upgrade\r
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r
Sec-WebSocket-Version: 13\r
\r
`));
// HTTP Upgrade response.
await new Promise(f => this._socket.once('data', f));
// Start sending web frame to receiver.
this._socket.on('data', data => this._receiver._write(data, 'binary', () => {}));
}
async send(s) {
await this._socket.write(encodeWebFrame(JSON.stringify(s)));
}
async close() {
this._socket.close();
}
}
function encodeWebFrame(data) {
return _utilsBundle.wsSender.frame(Buffer.from(data), {
opcode: 1,
mask: true,
fin: true,
readOnly: true
})[0];
}
class ClankBrowserProcess {
constructor(browser) {
this._browser = void 0;
this.onclose = void 0;
this._browser = browser;
}
async kill() {}
async close() {
await this._browser.close();
}
}

View File

@ -0,0 +1,172 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AdbBackend = void 0;
var _utilsBundle = require("../../utilsBundle");
var net = _interopRequireWildcard(require("net"));
var _events = require("events");
var _utils = require("../../utils");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright Microsoft Corporation. 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.
*/
class AdbBackend {
async devices(options = {}) {
const result = await runCommand('host:devices', options.host, options.port);
const lines = result.toString().trim().split('\n');
return lines.map(line => {
const [serial, status] = line.trim().split('\t');
return new AdbDevice(serial, status, options.host, options.port);
});
}
}
exports.AdbBackend = AdbBackend;
class AdbDevice {
constructor(serial, status, host, port) {
this.serial = void 0;
this.status = void 0;
this.host = void 0;
this.port = void 0;
this._closed = false;
this.serial = serial;
this.status = status;
this.host = host;
this.port = port;
}
async init() {}
async close() {
this._closed = true;
}
runCommand(command) {
if (this._closed) throw new Error('Device is closed');
return runCommand(command, this.host, this.port, this.serial);
}
async open(command) {
if (this._closed) throw new Error('Device is closed');
const result = await open(command, this.host, this.port, this.serial);
result.becomeSocket();
return result;
}
}
async function runCommand(command, host = '127.0.0.1', port = 5037, serial) {
(0, _utilsBundle.debug)('pw:adb:runCommand')(command, serial);
const socket = new BufferedSocketWrapper(command, net.createConnection({
host,
port
}));
try {
if (serial) {
await socket.write(encodeMessage(`host:transport:${serial}`));
const status = await socket.read(4);
(0, _utils.assert)(status.toString() === 'OKAY', status.toString());
}
await socket.write(encodeMessage(command));
const status = await socket.read(4);
(0, _utils.assert)(status.toString() === 'OKAY', status.toString());
let commandOutput;
if (!command.startsWith('shell:')) {
const remainingLength = parseInt((await socket.read(4)).toString(), 16);
commandOutput = await socket.read(remainingLength);
} else {
commandOutput = await socket.readAll();
}
return commandOutput;
} finally {
socket.close();
}
}
async function open(command, host = '127.0.0.1', port = 5037, serial) {
const socket = new BufferedSocketWrapper(command, net.createConnection({
host,
port
}));
if (serial) {
await socket.write(encodeMessage(`host:transport:${serial}`));
const status = await socket.read(4);
(0, _utils.assert)(status.toString() === 'OKAY', status.toString());
}
await socket.write(encodeMessage(command));
const status = await socket.read(4);
(0, _utils.assert)(status.toString() === 'OKAY', status.toString());
return socket;
}
function encodeMessage(message) {
let lenHex = message.length.toString(16);
lenHex = '0'.repeat(4 - lenHex.length) + lenHex;
return Buffer.from(lenHex + message);
}
class BufferedSocketWrapper extends _events.EventEmitter {
constructor(command, socket) {
super();
this.guid = (0, _utils.createGuid)();
this._socket = void 0;
this._buffer = Buffer.from([]);
this._isSocket = false;
this._notifyReader = void 0;
this._connectPromise = void 0;
this._isClosed = false;
this._command = void 0;
this._command = command;
this._socket = socket;
this._connectPromise = new Promise(f => this._socket.on('connect', f));
this._socket.on('data', data => {
(0, _utilsBundle.debug)('pw:adb:data')(data.toString());
if (this._isSocket) {
this.emit('data', data);
return;
}
this._buffer = Buffer.concat([this._buffer, data]);
if (this._notifyReader) this._notifyReader();
});
this._socket.on('close', () => {
this._isClosed = true;
if (this._notifyReader) this._notifyReader();
this.close();
this.emit('close');
});
this._socket.on('error', error => this.emit('error', error));
}
async write(data) {
(0, _utilsBundle.debug)('pw:adb:send')(data.toString().substring(0, 100) + '...');
await this._connectPromise;
await new Promise(f => this._socket.write(data, f));
}
close() {
if (this._isClosed) return;
(0, _utilsBundle.debug)('pw:adb')('Close ' + this._command);
this._socket.destroy();
}
async read(length) {
await this._connectPromise;
(0, _utils.assert)(!this._isSocket, 'Can not read by length in socket mode');
while (this._buffer.length < length) await new Promise(f => this._notifyReader = f);
const result = this._buffer.slice(0, length);
this._buffer = this._buffer.slice(length);
(0, _utilsBundle.debug)('pw:adb:recv')(result.toString().substring(0, 100) + '...');
return result;
}
async readAll() {
while (!this._isClosed) await new Promise(f => this._notifyReader = f);
return this._buffer;
}
becomeSocket() {
(0, _utils.assert)(!this._buffer.length);
this._isSocket = true;
}
}

View File

@ -0,0 +1,102 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Artifact = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
var _instrumentation = require("./instrumentation");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
class Artifact extends _instrumentation.SdkObject {
constructor(parent, localPath, unaccessibleErrorMessage, cancelCallback) {
super(parent, 'artifact');
this._localPath = void 0;
this._unaccessibleErrorMessage = void 0;
this._cancelCallback = void 0;
this._finishedPromise = new _manualPromise.ManualPromise();
this._saveCallbacks = [];
this._finished = false;
this._deleted = false;
this._failureError = null;
this._localPath = localPath;
this._unaccessibleErrorMessage = unaccessibleErrorMessage;
this._cancelCallback = cancelCallback;
}
finishedPromise() {
return this._finishedPromise;
}
localPath() {
return this._localPath;
}
async localPathAfterFinished() {
if (this._unaccessibleErrorMessage) throw new Error(this._unaccessibleErrorMessage);
await this._finishedPromise;
if (this._failureError) return null;
return this._localPath;
}
saveAs(saveCallback) {
if (this._unaccessibleErrorMessage) throw new Error(this._unaccessibleErrorMessage);
if (this._deleted) throw new Error(`File already deleted. Save before deleting.`);
if (this._failureError) throw new Error(`File not found on disk. Check download.failure() for details.`);
if (this._finished) {
saveCallback(this._localPath).catch(e => {});
return;
}
this._saveCallbacks.push(saveCallback);
}
async failureError() {
if (this._unaccessibleErrorMessage) return this._unaccessibleErrorMessage;
await this._finishedPromise;
return this._failureError;
}
async cancel() {
(0, _utils.assert)(this._cancelCallback !== undefined);
return this._cancelCallback();
}
async delete() {
if (this._unaccessibleErrorMessage) return;
const fileName = await this.localPathAfterFinished();
if (this._deleted) return;
this._deleted = true;
if (fileName) await _fs.default.promises.unlink(fileName).catch(e => {});
}
async deleteOnContextClose() {
// Compared to "delete", this method does not wait for the artifact to finish.
// We use it when closing the context to avoid stalling.
if (this._deleted) return;
this._deleted = true;
if (!this._unaccessibleErrorMessage) await _fs.default.promises.unlink(this._localPath).catch(e => {});
await this.reportFinished('File deleted upon browser context closure.');
}
async reportFinished(error) {
if (this._finished) return;
this._finished = true;
this._failureError = error || null;
if (error) {
for (const callback of this._saveCallbacks) await callback('', error);
} else {
for (const callback of this._saveCallbacks) await callback(this._localPath);
}
this._saveCallbacks = [];
this._finishedPromise.resolve();
}
}
exports.Artifact = Artifact;

View File

@ -0,0 +1,120 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Browser = void 0;
var _browserContext = require("./browserContext");
var _page = require("./page");
var _download = require("./download");
var _instrumentation = require("./instrumentation");
var _artifact = require("./artifact");
/**
* 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.
*/
class Browser extends _instrumentation.SdkObject {
constructor(parent, options) {
super(parent, 'browser');
this.options = void 0;
this._downloads = new Map();
this._defaultContext = null;
this._startedClosing = false;
this._idToVideo = new Map();
this._contextForReuse = void 0;
this.attribution.browser = this;
this.options = options;
this.instrumentation.onBrowserOpen(this);
}
async newContext(metadata, options) {
(0, _browserContext.validateBrowserContextOptions)(options, this.options);
const context = await this.doCreateNewContext(options);
if (options.storageState) await context.setStorageState(metadata, options.storageState);
return context;
}
async newContextForReuse(params, metadata) {
const hash = _browserContext.BrowserContext.reusableContextHash(params);
if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) {
if (this._contextForReuse) await this._contextForReuse.context.close(metadata);
this._contextForReuse = {
context: await this.newContext(metadata, params),
hash
};
return {
context: this._contextForReuse.context,
needsReset: false
};
}
await this._contextForReuse.context.stopPendingOperations();
return {
context: this._contextForReuse.context,
needsReset: true
};
}
_downloadCreated(page, uuid, url, suggestedFilename) {
const download = new _download.Download(page, this.options.downloadsPath || '', uuid, url, suggestedFilename);
this._downloads.set(uuid, download);
}
_downloadFilenameSuggested(uuid, suggestedFilename) {
const download = this._downloads.get(uuid);
if (!download) return;
download._filenameSuggested(suggestedFilename);
}
_downloadFinished(uuid, error) {
const download = this._downloads.get(uuid);
if (!download) return;
download.artifact.reportFinished(error);
this._downloads.delete(uuid);
}
_videoStarted(context, videoId, path, pageOrError) {
const artifact = new _artifact.Artifact(context, path);
this._idToVideo.set(videoId, {
context,
artifact
});
pageOrError.then(page => {
if (page instanceof _page.Page) {
page._video = artifact;
page.emitOnContext(_browserContext.BrowserContext.Events.VideoStarted, artifact);
page.emit(_page.Page.Events.Video, artifact);
}
});
}
_takeVideo(videoId) {
const video = this._idToVideo.get(videoId);
this._idToVideo.delete(videoId);
return video === null || video === void 0 ? void 0 : video.artifact;
}
_didClose() {
for (const context of this.contexts()) context._browserClosed();
if (this._defaultContext) this._defaultContext._browserClosed();
this.emit(Browser.Events.Disconnected);
this.instrumentation.onBrowserClose(this);
}
async close() {
if (!this._startedClosing) {
this._startedClosing = true;
await this.options.browserProcess.close();
}
if (this.isConnected()) await new Promise(x => this.once(Browser.Events.Disconnected, x));
}
async killForTests() {
await this.options.browserProcess.kill();
}
}
exports.Browser = Browser;
Browser.Events = {
Disconnected: 'disconnected'
};

View File

@ -0,0 +1,607 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserContext = void 0;
exports.assertBrowserContextIsNotOwned = assertBrowserContextIsNotOwned;
exports.normalizeProxySettings = normalizeProxySettings;
exports.validateBrowserContextOptions = validateBrowserContextOptions;
exports.verifyGeolocation = verifyGeolocation;
var os = _interopRequireWildcard(require("os"));
var _timeoutSettings = require("../common/timeoutSettings");
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _helper = require("./helper");
var network = _interopRequireWildcard(require("./network"));
var _page6 = require("./page");
var _path = _interopRequireDefault(require("path"));
var _fs = _interopRequireDefault(require("fs"));
var _instrumentation = require("./instrumentation");
var _debugger = require("./debugger");
var _tracing = require("./trace/recorder/tracing");
var _harRecorder = require("./har/harRecorder");
var _recorder = require("./recorder");
var consoleApiSource = _interopRequireWildcard(require("../generated/consoleApiSource"));
var _fetch = require("./fetch");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class BrowserContext extends _instrumentation.SdkObject {
constructor(browser, options, browserContextId) {
super(browser, 'browser-context');
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._pageBindings = new Map();
this._activeProgressControllers = new Set();
this._options = void 0;
this._requestInterceptor = void 0;
this._isPersistentContext = void 0;
this._closedStatus = 'open';
this._closePromise = void 0;
this._closePromiseFulfill = void 0;
this._permissions = new Map();
this._downloads = new Set();
this._browser = void 0;
this._browserContextId = void 0;
this._selectors = void 0;
this._origins = new Set();
this._harRecorders = new Map();
this.tracing = void 0;
this.fetchRequest = void 0;
this._customCloseHandler = void 0;
this._tempDirs = [];
this._settingStorageState = false;
this.initScripts = [];
this._routesInFlight = new Set();
this._debugger = void 0;
this.attribution.context = this;
this._browser = browser;
this._options = options;
this._browserContextId = browserContextId;
this._isPersistentContext = !browserContextId;
this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
this.fetchRequest = new _fetch.BrowserContextAPIRequestContext(this);
if (this._options.recordHar) this._harRecorders.set('', new _harRecorder.HarRecorder(this, null, this._options.recordHar));
this.tracing = new _tracing.Tracing(this, browser.options.tracesDir);
}
isPersistentContext() {
return this._isPersistentContext;
}
setSelectors(selectors) {
this._selectors = selectors;
}
selectors() {
return this._selectors || this.attribution.playwright.selectors;
}
async _initialize() {
if (this.attribution.playwright.options.isInternalPlaywright) return;
// Debugger will pause execution upon page.pause in headed mode.
this._debugger = new _debugger.Debugger(this);
// When PWDEBUG=1, show inspector for each context.
if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show(this, {
pauseOnNextStatement: true
});
// When paused, show inspector.
if (this._debugger.isPaused()) _recorder.Recorder.showInspector(this);
this._debugger.on(_debugger.Debugger.Events.PausedStateChanged, () => {
_recorder.Recorder.showInspector(this);
});
if ((0, _utils.debugMode)() === 'console') await this.extendInjectedScript(consoleApiSource.source);
if (this._options.serviceWorkers === 'block') await this.addInitScript(`\nnavigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };\n`);
if (this._options.permissions) await this.grantPermissions(this._options.permissions);
}
debugger() {
return this._debugger;
}
async _ensureVideosPath() {
if (this._options.recordVideo) await (0, _fileUtils.mkdirIfNeeded)(_path.default.join(this._options.recordVideo.dir, 'dummy'));
}
canResetForReuse() {
if (this._closedStatus !== 'open') return false;
return true;
}
async stopPendingOperations() {
for (const controller of this._activeProgressControllers) controller.abort(new Error(`Context was reset for reuse.`));
}
static reusableContextHash(params) {
const paramsCopy = {
...params
};
for (const k of Object.keys(paramsCopy)) {
const key = k;
if (paramsCopy[key] === defaultNewContextParamValues[key]) delete paramsCopy[key];
}
for (const key of paramsThatAllowContextReuse) delete paramsCopy[key];
return JSON.stringify(paramsCopy);
}
async resetForReuse(metadata, params) {
var _page, _page2, _page3, _page4, _page5;
this.setDefaultNavigationTimeout(undefined);
this.setDefaultTimeout(undefined);
this.tracing.resetForReuse();
if (params) {
for (const key of paramsThatAllowContextReuse) this._options[key] = params[key];
}
await this._cancelAllRoutesInFlight();
// Close extra pages early.
let page = this.pages()[0];
const [, ...otherPages] = this.pages();
for (const p of otherPages) await p.close(metadata);
if (page && page._crashedScope.isClosed()) {
await page.close(metadata);
page = undefined;
}
// Unless dialogs are dismissed, setting extra http headers below does not respond.
(_page = page) === null || _page === void 0 ? void 0 : _page._frameManager.setCloseAllOpeningDialogs(true);
await ((_page2 = page) === null || _page2 === void 0 ? void 0 : _page2._frameManager.closeOpenDialogs());
// Navigate to about:blank first to ensure no page scripts are running after this point.
await ((_page3 = page) === null || _page3 === void 0 ? void 0 : _page3.mainFrame().goto(metadata, 'about:blank', {
timeout: 0
}));
(_page4 = page) === null || _page4 === void 0 ? void 0 : _page4._frameManager.setCloseAllOpeningDialogs(false);
await this._resetStorage();
await this._removeExposedBindings();
await this._removeInitScripts();
// TODO: following can be optimized to not perform noops.
if (this._options.permissions) await this.grantPermissions(this._options.permissions);else await this.clearPermissions();
await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []);
await this.setGeolocation(this._options.geolocation);
await this.setOffline(!!this._options.offline);
await this.setUserAgent(this._options.userAgent);
await this.clearCache();
await this._resetCookies();
await ((_page5 = page) === null || _page5 === void 0 ? void 0 : _page5.resetForReuse(metadata));
}
_browserClosed() {
for (const page of this.pages()) page._didClose();
this._didCloseInternal();
}
_didCloseInternal() {
if (this._closedStatus === 'closed') {
// We can come here twice if we close browser context and browser
// at the same time.
return;
}
const gotClosedGracefully = this._closedStatus === 'closing';
this._closedStatus = 'closed';
if (!gotClosedGracefully) {
this._deleteAllDownloads();
this._downloads.clear();
}
this.tracing.dispose().catch(() => {});
if (this._isPersistentContext) this.onClosePersistent();
this._closePromiseFulfill(new Error('Context closed'));
this.emit(BrowserContext.Events.Close);
}
// BrowserContext methods.
async cookies(urls = []) {
if (urls && !Array.isArray(urls)) urls = [urls];
return await this.doGetCookies(urls);
}
setHTTPCredentials(httpCredentials) {
return this.doSetHTTPCredentials(httpCredentials);
}
async exposeBinding(name, needsHandle, playwrightBinding) {
if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
for (const page of this.pages()) {
if (page.getBinding(name)) throw new Error(`Function "${name}" has been already registered in one of the pages`);
}
const binding = new _page6.PageBinding(name, playwrightBinding, needsHandle);
this._pageBindings.set(name, binding);
await this.doExposeBinding(binding);
}
async _removeExposedBindings() {
for (const key of this._pageBindings.keys()) {
if (!key.startsWith('__pw')) this._pageBindings.delete(key);
}
await this.doRemoveExposedBindings();
}
async grantPermissions(permissions, origin) {
let resolvedOrigin = '*';
if (origin) {
const url = new URL(origin);
resolvedOrigin = url.origin;
}
const existing = new Set(this._permissions.get(resolvedOrigin) || []);
permissions.forEach(p => existing.add(p));
const list = [...existing.values()];
this._permissions.set(resolvedOrigin, list);
await this.doGrantPermissions(resolvedOrigin, list);
}
async clearPermissions() {
this._permissions.clear();
await this.doClearPermissions();
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async _loadDefaultContextAsIs(progress) {
if (!this.pages().length) {
const waitForEvent = _helper.helper.waitForEvent(progress, this, BrowserContext.Events.Page);
progress.cleanupWhenAborted(() => waitForEvent.dispose);
const page = await waitForEvent.promise;
if (page._pageIsError) throw page._pageIsError;
}
const pages = this.pages();
if (pages[0]._pageIsError) throw pages[0]._pageIsError;
await pages[0].mainFrame()._waitForLoadState(progress, 'load');
return pages;
}
async _loadDefaultContext(progress) {
const pages = await this._loadDefaultContextAsIs(progress);
const browserName = this._browser.options.name;
if (this._options.isMobile && browserName === 'chromium' || this._options.locale && browserName === 'webkit') {
// Workaround for:
// - chromium fails to change isMobile for existing page;
// - webkit fails to change locale for existing page.
const oldPage = pages[0];
await this.newPage(progress.metadata);
await oldPage.close(progress.metadata);
}
}
_authenticateProxyViaHeader() {
const proxy = this._options.proxy || this._browser.options.proxy || {
username: undefined,
password: undefined
};
const {
username,
password
} = proxy;
if (username) {
this._options.httpCredentials = {
username,
password: password
};
const token = Buffer.from(`${username}:${password}`).toString('base64');
this._options.extraHTTPHeaders = network.mergeHeaders([this._options.extraHTTPHeaders, network.singleHeader('Proxy-Authorization', `Basic ${token}`)]);
}
}
_authenticateProxyViaCredentials() {
const proxy = this._options.proxy || this._browser.options.proxy;
if (!proxy) return;
const {
username,
password
} = proxy;
if (username) this._options.httpCredentials = {
username,
password: password || ''
};
}
async addInitScript(script) {
this.initScripts.push(script);
await this.doAddInitScript(script);
}
async _removeInitScripts() {
this.initScripts.splice(0, this.initScripts.length);
await this.doRemoveInitScripts();
}
async setRequestInterceptor(handler) {
this._requestInterceptor = handler;
await this.doUpdateRequestInterception();
}
isClosingOrClosed() {
return this._closedStatus !== 'open';
}
async _deleteAllDownloads() {
await Promise.all(Array.from(this._downloads).map(download => download.artifact.deleteOnContextClose()));
}
async _deleteAllTempDirs() {
await Promise.all(this._tempDirs.map(async dir => await _fs.default.promises.unlink(dir).catch(e => {})));
}
setCustomCloseHandler(handler) {
this._customCloseHandler = handler;
}
async close(metadata) {
if (this._closedStatus === 'open') {
this.emit(BrowserContext.Events.BeforeClose);
this._closedStatus = 'closing';
for (const harRecorder of this._harRecorders.values()) await harRecorder.flush();
await this.tracing.dispose();
// Cleanup.
const promises = [];
for (const {
context,
artifact
} of this._browser._idToVideo.values()) {
// Wait for the videos to finish.
if (context === this) promises.push(artifact.finishedPromise());
}
if (this._customCloseHandler) {
await this._customCloseHandler();
} else {
// Close the context.
await this.doClose();
}
// We delete downloads after context closure
// so that browser does not write to the download file anymore.
promises.push(this._deleteAllDownloads());
promises.push(this._deleteAllTempDirs());
await Promise.all(promises);
// Custom handler should trigger didCloseInternal itself.
if (!this._customCloseHandler) this._didCloseInternal();
}
await this._closePromise;
}
async newPage(metadata) {
const pageDelegate = await this.newPageDelegate();
if (metadata.isServerSide) pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
const pageOrError = await pageDelegate.pageOrError();
if (pageOrError instanceof _page6.Page) {
if (pageOrError.isClosed()) throw new Error('Page has been closed.');
return pageOrError;
}
throw pageOrError;
}
addVisitedOrigin(origin) {
this._origins.add(origin);
}
async storageState() {
const result = {
cookies: await this.cookies(),
origins: []
};
if (this._origins.size) {
const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
const page = await this.newPage(internalMetadata);
await page._setServerRequestInterceptor(handler => {
handler.fulfill({
body: '<html></html>',
requestUrl: handler.request().url()
}).catch(() => {});
return true;
});
for (const origin of this._origins) {
const originStorage = {
origin,
localStorage: []
};
const frame = page.mainFrame();
await frame.goto(internalMetadata, origin);
const storage = await frame.evaluateExpression(`({
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
})`, {
world: 'utility'
});
originStorage.localStorage = storage.localStorage;
if (storage.localStorage.length) result.origins.push(originStorage);
}
await page.close(internalMetadata);
}
return result;
}
async _resetStorage() {
var _this$_options$storag, _this$_options$storag2;
const oldOrigins = this._origins;
const newOrigins = new Map(((_this$_options$storag = this._options.storageState) === null || _this$_options$storag === void 0 ? void 0 : (_this$_options$storag2 = _this$_options$storag.origins) === null || _this$_options$storag2 === void 0 ? void 0 : _this$_options$storag2.map(p => [p.origin, p])) || []);
if (!oldOrigins.size && !newOrigins.size) return;
let page = this.pages()[0];
const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
page = page || (await this.newPage({
...internalMetadata,
// Do not mark this page as internal, because we will leave it for later reuse
// as a user-visible page.
isServerSide: false
}));
await page._setServerRequestInterceptor(handler => {
handler.fulfill({
body: '<html></html>',
requestUrl: handler.request().url()
}).catch(() => {});
return true;
});
for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) {
const frame = page.mainFrame();
await frame.goto(internalMetadata, origin);
await frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin));
}
await page._setServerRequestInterceptor(undefined);
this._origins = new Set([...newOrigins.keys()]);
// It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
}
async _resetCookies() {
var _this$_options$storag3, _this$_options$storag4;
await this.clearCookies();
if ((_this$_options$storag3 = this._options.storageState) !== null && _this$_options$storag3 !== void 0 && _this$_options$storag3.cookies) await this.addCookies((_this$_options$storag4 = this._options.storageState) === null || _this$_options$storag4 === void 0 ? void 0 : _this$_options$storag4.cookies);
}
isSettingStorageState() {
return this._settingStorageState;
}
async setStorageState(metadata, state) {
this._settingStorageState = true;
try {
if (state.cookies) await this.addCookies(state.cookies);
if (state.origins && state.origins.length) {
const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
const page = await this.newPage(internalMetadata);
await page._setServerRequestInterceptor(handler => {
handler.fulfill({
body: '<html></html>',
requestUrl: handler.request().url()
}).catch(() => {});
return true;
});
for (const originState of state.origins) {
const frame = page.mainFrame();
await frame.goto(metadata, originState.origin);
await frame.evaluateExpression(`
originState => {
for (const { name, value } of (originState.localStorage || []))
localStorage.setItem(name, value);
}`, {
isFunction: true,
world: 'utility'
}, originState);
}
await page.close(internalMetadata);
}
} finally {
this._settingStorageState = false;
}
}
async extendInjectedScript(source, arg) {
const installInFrame = frame => frame.extendInjectedScript(source, arg).catch(() => {});
const installInPage = page => {
page.on(_page6.Page.Events.InternalFrameNavigatedToNewDocument, installInFrame);
return Promise.all(page.frames().map(installInFrame));
};
this.on(BrowserContext.Events.Page, installInPage);
return Promise.all(this.pages().map(installInPage));
}
async _harStart(page, options) {
const harId = (0, _utils.createGuid)();
this._harRecorders.set(harId, new _harRecorder.HarRecorder(this, page, options));
return harId;
}
async _harExport(harId) {
const recorder = this._harRecorders.get(harId || '');
return recorder.export();
}
addRouteInFlight(route) {
this._routesInFlight.add(route);
}
removeRouteInFlight(route) {
this._routesInFlight.delete(route);
}
async _cancelAllRoutesInFlight() {
await Promise.all([...this._routesInFlight].map(r => r.abort())).catch(() => {});
this._routesInFlight.clear();
}
}
exports.BrowserContext = BrowserContext;
BrowserContext.Events = {
Console: 'console',
Close: 'close',
Dialog: 'dialog',
Page: 'page',
Request: 'request',
Response: 'response',
RequestFailed: 'requestfailed',
RequestFinished: 'requestfinished',
RequestAborted: 'requestaborted',
RequestFulfilled: 'requestfulfilled',
RequestContinued: 'requestcontinued',
BeforeClose: 'beforeclose',
VideoStarted: 'videostarted'
};
function assertBrowserContextIsNotOwned(context) {
for (const page of context.pages()) {
if (page._ownedContext) throw new Error('Please use browser.newContext() for multi-page scripts that share the context.');
}
}
function validateBrowserContextOptions(options, browserOptions) {
if (options.noDefaultViewport && options.deviceScaleFactor !== undefined) throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
if (options.noDefaultViewport && !!options.isMobile) throw new Error(`"isMobile" option is not supported with null "viewport"`);
if (options.acceptDownloads === undefined) options.acceptDownloads = true;
if (!options.viewport && !options.noDefaultViewport) options.viewport = {
width: 1280,
height: 720
};
if (options.recordVideo) {
if (!options.recordVideo.size) {
if (options.noDefaultViewport) {
options.recordVideo.size = {
width: 800,
height: 600
};
} else {
const size = options.viewport;
const scale = Math.min(1, 800 / Math.max(size.width, size.height));
options.recordVideo.size = {
width: Math.floor(size.width * scale),
height: Math.floor(size.height * scale)
};
}
}
// Make sure both dimensions are odd, this is required for vp8
options.recordVideo.size.width &= ~1;
options.recordVideo.size.height &= ~1;
}
if (options.proxy) {
if (!browserOptions.proxy && browserOptions.isChromium && os.platform() === 'win32') throw new Error(`Browser needs to be launched with the global proxy. If all contexts override the proxy, global proxy will be never used and can be any string, for example "launch({ proxy: { server: 'http://per-context' } })"`);
options.proxy = normalizeProxySettings(options.proxy);
}
verifyGeolocation(options.geolocation);
}
function verifyGeolocation(geolocation) {
if (!geolocation) return;
geolocation.accuracy = geolocation.accuracy || 0;
const {
longitude,
latitude,
accuracy
} = geolocation;
if (longitude < -180 || longitude > 180) throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90) throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0) throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
}
function normalizeProxySettings(proxy) {
let {
server,
bypass
} = proxy;
let url;
try {
// new URL('127.0.0.1:8080') throws
// new URL('localhost:8080') fails to parse host or protocol
// In both of these cases, we need to try re-parse URL with `http://` prefix.
url = new URL(server);
if (!url.host || !url.protocol) url = new URL('http://' + server);
} catch (e) {
url = new URL('http://' + server);
}
if (url.protocol === 'socks4:' && (proxy.username || proxy.password)) throw new Error(`Socks4 proxy protocol does not support authentication`);
if (url.protocol === 'socks5:' && (proxy.username || proxy.password)) throw new Error(`Browser does not support socks5 proxy authentication`);
server = url.protocol + '//' + url.host;
if (bypass) bypass = bypass.split(',').map(t => t.trim()).join(',');
return {
...proxy,
server,
bypass
};
}
const paramsThatAllowContextReuse = ['colorScheme', 'forcedColors', 'reducedMotion', 'screen', 'userAgent', 'viewport'];
const defaultNewContextParamValues = {
noDefaultViewport: false,
ignoreHTTPSErrors: false,
javaScriptEnabled: true,
bypassCSP: false,
offline: false,
isMobile: false,
hasTouch: false,
acceptDownloads: true,
strictSelectors: false,
serviceWorkers: 'allow',
locale: 'en-US'
};

View File

@ -0,0 +1,284 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kNoXServerRunningError = exports.BrowserType = void 0;
var _fs = _interopRequireDefault(require("fs"));
var os = _interopRequireWildcard(require("os"));
var _path = _interopRequireDefault(require("path"));
var _browserContext = require("./browserContext");
var _registry = require("./registry");
var _transport = require("./transport");
var _processLauncher = require("../utils/processLauncher");
var _pipeTransport = require("./pipeTransport");
var _progress = require("./progress");
var _timeoutSettings = require("../common/timeoutSettings");
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _helper = require("./helper");
var _debugLogger = require("../common/debugLogger");
var _instrumentation = require("./instrumentation");
var _manualPromise = require("../utils/manualPromise");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' + 'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team';
exports.kNoXServerRunningError = kNoXServerRunningError;
class BrowserType extends _instrumentation.SdkObject {
constructor(parent, browserName) {
super(parent, 'browser-type');
this._name = void 0;
this.attribution.browserType = this;
this._name = browserName;
}
executablePath() {
return _registry.registry.findExecutable(this._name).executablePath(this.attribution.playwright.options.sdkLanguage) || '';
}
name() {
return this._name;
}
async launch(metadata, options, protocolLogger) {
options = this._validateLaunchOptions(options);
const controller = new _progress.ProgressController(metadata, this);
controller.setLogName('browser');
const browser = await controller.run(progress => {
const seleniumHubUrl = options.__testHookSeleniumRemoteURL || process.env.SELENIUM_REMOTE_URL;
if (seleniumHubUrl) return this._launchWithSeleniumHub(progress, seleniumHubUrl, options);
return this._innerLaunchWithRetries(progress, options, undefined, _helper.helper.debugProtocolLogger(protocolLogger)).catch(e => {
throw this._rewriteStartupError(e);
});
}, _timeoutSettings.TimeoutSettings.launchTimeout(options));
return browser;
}
async launchPersistentContext(metadata, userDataDir, options) {
options = this._validateLaunchOptions(options);
const controller = new _progress.ProgressController(metadata, this);
const persistent = options;
controller.setLogName('browser');
const browser = await controller.run(progress => {
return this._innerLaunchWithRetries(progress, options, persistent, _helper.helper.debugProtocolLogger(), userDataDir).catch(e => {
throw this._rewriteStartupError(e);
});
}, _timeoutSettings.TimeoutSettings.launchTimeout(options));
return browser._defaultContext;
}
async _innerLaunchWithRetries(progress, options, persistent, protocolLogger, userDataDir) {
try {
return await this._innerLaunch(progress, options, persistent, protocolLogger, userDataDir);
} catch (error) {
// @see https://github.com/microsoft/playwright/issues/5214
const errorMessage = typeof error === 'object' && typeof error.message === 'string' ? error.message : '';
if (errorMessage.includes('Inconsistency detected by ld.so')) {
progress.log(`<restarting browser due to hitting race condition in glibc>`);
return this._innerLaunch(progress, options, persistent, protocolLogger, userDataDir);
}
throw error;
}
}
async _innerLaunch(progress, options, persistent, protocolLogger, maybeUserDataDir) {
options.proxy = options.proxy ? (0, _browserContext.normalizeProxySettings)(options.proxy) : undefined;
const browserLogsCollector = new _debugLogger.RecentLogsCollector();
const {
browserProcess,
userDataDir,
artifactsDir,
transport
} = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, maybeUserDataDir);
if (options.__testHookBeforeCreateBrowser) await options.__testHookBeforeCreateBrowser();
const browserOptions = {
name: this._name,
isChromium: this._name === 'chromium',
channel: options.channel,
slowMo: options.slowMo,
persistent,
headful: !options.headless,
artifactsDir,
downloadsPath: options.downloadsPath || artifactsDir,
tracesDir: options.tracesDir || artifactsDir,
browserProcess,
customExecutablePath: options.executablePath,
proxy: options.proxy,
protocolLogger,
browserLogsCollector,
wsEndpoint: options.useWebSocket ? transport.wsEndpoint : undefined,
originalLaunchOptions: options
};
if (persistent) (0, _browserContext.validateBrowserContextOptions)(persistent, browserOptions);
copyTestHooks(options, browserOptions);
const browser = await this._connectToTransport(transport, browserOptions);
browser._userDataDirForTest = userDataDir;
// We assume no control when using custom arguments, and do not prepare the default context in that case.
if (persistent && !options.ignoreAllDefaultArgs) await browser._defaultContext._loadDefaultContext(progress);
return browser;
}
async _launchProcess(progress, options, isPersistent, browserLogsCollector, userDataDir) {
var _options$args;
const {
ignoreDefaultArgs,
ignoreAllDefaultArgs,
args = [],
executablePath = null,
handleSIGINT = true,
handleSIGTERM = true,
handleSIGHUP = true
} = options;
const env = options.env ? (0, _processLauncher.envArrayToObject)(options.env) : process.env;
await this._createArtifactDirs(options);
const tempDirectories = [];
const artifactsDir = await _fs.default.promises.mkdtemp(_path.default.join(os.tmpdir(), 'playwright-artifacts-'));
tempDirectories.push(artifactsDir);
if (userDataDir) {
// Firefox bails if the profile directory does not exist, Chrome creates it. We ensure consistent behavior here.
if (!(await (0, _fileUtils.existsAsync)(userDataDir))) await _fs.default.promises.mkdir(userDataDir, {
recursive: true,
mode: 0o700
});
} else {
userDataDir = await _fs.default.promises.mkdtemp(_path.default.join(os.tmpdir(), `playwright_${this._name}dev_profile-`));
tempDirectories.push(userDataDir);
}
const browserArguments = [];
if (ignoreAllDefaultArgs) browserArguments.push(...args);else if (ignoreDefaultArgs) browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));else browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir));
let executable;
if (executablePath) {
if (!(await (0, _fileUtils.existsAsync)(executablePath))) throw new Error(`Failed to launch ${this._name} because executable doesn't exist at ${executablePath}`);
executable = executablePath;
} else {
const registryExecutable = _registry.registry.findExecutable(options.channel || this._name);
if (!registryExecutable || registryExecutable.browserName !== this._name) throw new Error(`Unsupported ${this._name} channel "${options.channel}"`);
executable = registryExecutable.executablePathOrDie(this.attribution.playwright.options.sdkLanguage);
await registryExecutable.validateHostRequirements(this.attribution.playwright.options.sdkLanguage);
}
const waitForWSEndpoint = options.useWebSocket || (_options$args = options.args) !== null && _options$args !== void 0 && _options$args.some(a => a.startsWith('--remote-debugging-port')) ? new _manualPromise.ManualPromise() : undefined;
const waitForJuggler = this._name === 'firefox' ? new _manualPromise.ManualPromise() : undefined;
// Note: it is important to define these variables before launchProcess, so that we don't get
// "Cannot access 'browserServer' before initialization" if something went wrong.
let transport = undefined;
let browserProcess = undefined;
const {
launchedProcess,
gracefullyClose,
kill
} = await (0, _processLauncher.launchProcess)({
command: executable,
args: browserArguments,
env: this._amendEnvironment(env, userDataDir, executable, browserArguments),
handleSIGINT,
handleSIGTERM,
handleSIGHUP,
log: message => {
if (waitForWSEndpoint) {
const match = message.match(/DevTools listening on (.*)/);
if (match) waitForWSEndpoint.resolve(match[1]);
}
if (waitForJuggler && message.includes('Juggler listening to the pipe')) waitForJuggler.resolve();
progress.log(message);
browserLogsCollector.log(message);
},
stdio: 'pipe',
tempDirectories,
attemptToGracefullyClose: async () => {
if (options.__testHookGracefullyClose) await options.__testHookGracefullyClose();
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
this._attemptToGracefullyCloseBrowser(transport);
},
onExit: (exitCode, signal) => {
// Unblock launch when browser prematurely exits.
waitForJuggler === null || waitForJuggler === void 0 ? void 0 : waitForJuggler.resolve();
if (browserProcess && browserProcess.onclose) browserProcess.onclose(exitCode, signal);
}
});
async function closeOrKill(timeout) {
let timer;
try {
await Promise.race([gracefullyClose(), new Promise((resolve, reject) => timer = setTimeout(reject, timeout))]);
} catch (ignored) {
await kill().catch(ignored => {}); // Make sure to await actual process exit.
} finally {
clearTimeout(timer);
}
}
browserProcess = {
onclose: undefined,
process: launchedProcess,
close: () => closeOrKill(options.__testHookBrowserCloseTimeout || _timeoutSettings.DEFAULT_TIMEOUT),
kill
};
progress.cleanupWhenAborted(() => closeOrKill(progress.timeUntilDeadline()));
const wsEndpoint = await waitForWSEndpoint;
await waitForJuggler;
if (options.useWebSocket) {
transport = await _transport.WebSocketTransport.connect(progress, wsEndpoint);
} else {
const stdio = launchedProcess.stdio;
transport = new _pipeTransport.PipeTransport(stdio[3], stdio[4]);
}
return {
browserProcess,
artifactsDir,
userDataDir,
transport
};
}
async _createArtifactDirs(options) {
if (options.downloadsPath) await _fs.default.promises.mkdir(options.downloadsPath, {
recursive: true
});
if (options.tracesDir) await _fs.default.promises.mkdir(options.tracesDir, {
recursive: true
});
}
async connectOverCDP(metadata, endpointURL, options, timeout) {
throw new Error('CDP connections are only supported by Chromium');
}
async _launchWithSeleniumHub(progress, hubUrl, options) {
throw new Error('Connecting to SELENIUM_REMOTE_URL is only supported by Chromium');
}
_validateLaunchOptions(options) {
const {
devtools = false
} = options;
let {
headless = !devtools,
downloadsPath,
proxy
} = options;
if ((0, _utils.debugMode)()) headless = false;
if (downloadsPath && !_path.default.isAbsolute(downloadsPath)) downloadsPath = _path.default.join(process.cwd(), downloadsPath);
if (this.attribution.playwright.options.socksProxyPort) proxy = {
server: `socks5://127.0.0.1:${this.attribution.playwright.options.socksProxyPort}`
};
return {
...options,
devtools,
headless,
downloadsPath,
proxy
};
}
}
exports.BrowserType = BrowserType;
function copyTestHooks(from, to) {
for (const [key, value] of Object.entries(from)) {
if (key.startsWith('__testHook')) to[key] = value;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,345 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Chromium = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _crBrowser = require("./crBrowser");
var _processLauncher = require("../../utils/processLauncher");
var _crConnection = require("./crConnection");
var _stackTrace = require("../../utils/stackTrace");
var _browserType = require("../browserType");
var _transport = require("../transport");
var _crDevTools = require("./crDevTools");
var _browser = require("../browser");
var _network = require("../../utils/network");
var _userAgent = require("../../utils/userAgent");
var _ascii = require("../../utils/ascii");
var _utils = require("../../utils");
var _fileUtils = require("../../utils/fileUtils");
var _debugLogger = require("../../common/debugLogger");
var _progress = require("../progress");
var _timeoutSettings = require("../../common/timeoutSettings");
var _helper = require("../helper");
var _registry = require("../registry");
var _manualPromise = require("../../utils/manualPromise");
var _browserContext = require("../browserContext");
var _chromiumSwitches = require("./chromiumSwitches");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
const ARTIFACTS_FOLDER = _path.default.join(_os.default.tmpdir(), 'playwright-artifacts-');
class Chromium extends _browserType.BrowserType {
constructor(parent) {
super(parent, 'chromium');
this._devtools = void 0;
if ((0, _utils.debugMode)()) this._devtools = this._createDevTools();
}
async connectOverCDP(metadata, endpointURL, options, timeout) {
const controller = new _progress.ProgressController(metadata, this);
controller.setLogName('browser');
return controller.run(async progress => {
return await this._connectOverCDPInternal(progress, endpointURL, options);
}, _timeoutSettings.TimeoutSettings.timeout({
timeout
}));
}
async _connectOverCDPInternal(progress, endpointURL, options, onClose) {
let headersMap;
if (options.headers) headersMap = (0, _utils.headersArrayToObject)(options.headers, false);
if (!headersMap) headersMap = {
'User-Agent': (0, _userAgent.getUserAgent)()
};else if (headersMap && !Object.keys(headersMap).some(key => key.toLowerCase() === 'user-agent')) headersMap['User-Agent'] = (0, _userAgent.getUserAgent)();
const artifactsDir = await _fs.default.promises.mkdtemp(ARTIFACTS_FOLDER);
const wsEndpoint = await urlToWSEndpoint(progress, endpointURL);
progress.throwIfAborted();
const chromeTransport = await _transport.WebSocketTransport.connect(progress, wsEndpoint, headersMap);
const cleanedUp = new _manualPromise.ManualPromise();
const doCleanup = async () => {
await (0, _fileUtils.removeFolders)([artifactsDir]);
await (onClose === null || onClose === void 0 ? void 0 : onClose());
cleanedUp.resolve();
};
const doClose = async () => {
await chromeTransport.closeAndWait();
await cleanedUp;
};
const browserProcess = {
close: doClose,
kill: doClose
};
const persistent = {
noDefaultViewport: true
};
const browserOptions = {
slowMo: options.slowMo,
name: 'chromium',
isChromium: true,
persistent,
browserProcess,
protocolLogger: _helper.helper.debugProtocolLogger(),
browserLogsCollector: new _debugLogger.RecentLogsCollector(),
artifactsDir,
downloadsPath: options.downloadsPath || artifactsDir,
tracesDir: options.tracesDir || artifactsDir,
// On Windows context level proxies only work, if there isn't a global proxy
// set. This is currently a bug in the CR/Windows networking stack. By
// passing an arbitrary value we disable the check in PW land which warns
// users in normal (launch/launchServer) mode since otherwise connectOverCDP
// does not work at all with proxies on Windows.
proxy: {
server: 'per-context'
},
originalLaunchOptions: {}
};
(0, _browserContext.validateBrowserContextOptions)(persistent, browserOptions);
progress.throwIfAborted();
const browser = await _crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions);
browser.on(_browser.Browser.Events.Disconnected, doCleanup);
return browser;
}
_createDevTools() {
// TODO: this is totally wrong when using channels.
const directory = _registry.registry.findExecutable('chromium').directory;
return directory ? new _crDevTools.CRDevTools(_path.default.join(directory, 'devtools-preferences.json')) : undefined;
}
async _connectToTransport(transport, options) {
let devtools = this._devtools;
if (options.__testHookForDevTools) {
devtools = this._createDevTools();
await options.__testHookForDevTools(devtools);
}
return _crBrowser.CRBrowser.connect(this.attribution.playwright, transport, options, devtools);
}
_rewriteStartupError(error) {
if (error.message.includes('Missing X server')) return (0, _stackTrace.rewriteErrorMessage)(error, '\n' + (0, _ascii.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1));
// These error messages are taken from Chromium source code as of July, 2020:
// https://github.com/chromium/chromium/blob/70565f67e79f79e17663ad1337dc6e63ee207ce9/content/browser/zygote_host/zygote_host_impl_linux.cc
if (!error.message.includes('crbug.com/357670') && !error.message.includes('No usable sandbox!') && !error.message.includes('crbug.com/638180')) return error;
return (0, _stackTrace.rewriteErrorMessage)(error, [`Chromium sandboxing failed!`, `================================`, `To workaround sandboxing issues, do either of the following:`, ` - (preferred): Configure environment to support sandboxing: https://playwright.dev/docs/troubleshooting`, ` - (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option`, `================================`, ``].join('\n'));
}
_amendEnvironment(env, userDataDir, executable, browserArguments) {
return env;
}
_attemptToGracefullyCloseBrowser(transport) {
const message = {
method: 'Browser.close',
id: _crConnection.kBrowserCloseMessageId,
params: {}
};
transport.send(message);
}
async _launchWithSeleniumHub(progress, hubUrl, options) {
await this._createArtifactDirs(options);
if (!hubUrl.endsWith('/')) hubUrl = hubUrl + '/';
const args = this._innerDefaultArgs(options);
args.push('--remote-debugging-port=0');
const isEdge = options.channel && options.channel.startsWith('msedge');
let desiredCapabilities = {
'browserName': isEdge ? 'MicrosoftEdge' : 'chrome',
[isEdge ? 'ms:edgeOptions' : 'goog:chromeOptions']: {
args
}
};
if (process.env.SELENIUM_REMOTE_CAPABILITIES) {
const remoteCapabilities = parseSeleniumRemoteParams({
name: 'capabilities',
value: process.env.SELENIUM_REMOTE_CAPABILITIES
}, progress);
if (remoteCapabilities) desiredCapabilities = {
...desiredCapabilities,
...remoteCapabilities
};
}
let headers = {};
if (process.env.SELENIUM_REMOTE_HEADERS) {
const remoteHeaders = parseSeleniumRemoteParams({
name: 'headers',
value: process.env.SELENIUM_REMOTE_HEADERS
}, progress);
if (remoteHeaders) headers = remoteHeaders;
}
progress.log(`<selenium> connecting to ${hubUrl}`);
const response = await (0, _network.fetchData)({
url: hubUrl + 'session',
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
...headers
},
data: JSON.stringify({
desiredCapabilities,
capabilities: {
alwaysMatch: desiredCapabilities
}
}),
timeout: progress.timeUntilDeadline()
}, seleniumErrorHandler);
const value = JSON.parse(response).value;
const sessionId = value.sessionId;
progress.log(`<selenium> connected to sessionId=${sessionId}`);
const disconnectFromSelenium = async () => {
progress.log(`<selenium> disconnecting from sessionId=${sessionId}`);
await (0, _network.fetchData)({
url: hubUrl + 'session/' + sessionId,
method: 'DELETE',
headers
}).catch(error => progress.log(`<error disconnecting from selenium>: ${error}`));
progress.log(`<selenium> disconnected from sessionId=${sessionId}`);
_processLauncher.gracefullyCloseSet.delete(disconnectFromSelenium);
};
_processLauncher.gracefullyCloseSet.add(disconnectFromSelenium);
try {
const capabilities = value.capabilities;
let endpointURL;
if (capabilities['se:cdp']) {
// Selenium 4 - use built-in CDP websocket proxy.
progress.log(`<selenium> using selenium v4`);
const endpointURLString = addProtocol(capabilities['se:cdp']);
endpointURL = new URL(endpointURLString);
if (endpointURL.hostname === 'localhost' || endpointURL.hostname === '127.0.0.1') endpointURL.hostname = new URL(hubUrl).hostname;
progress.log(`<selenium> retrieved endpoint ${endpointURL.toString()} for sessionId=${sessionId}`);
} else {
// Selenium 3 - resolve target node IP to use instead of localhost ws url.
progress.log(`<selenium> using selenium v3`);
const maybeChromeOptions = capabilities['goog:chromeOptions'];
const chromeOptions = maybeChromeOptions && typeof maybeChromeOptions === 'object' ? maybeChromeOptions : undefined;
const debuggerAddress = chromeOptions && typeof chromeOptions.debuggerAddress === 'string' ? chromeOptions.debuggerAddress : undefined;
const chromeOptionsURL = typeof maybeChromeOptions === 'string' ? maybeChromeOptions : undefined;
// TODO(dgozman): figure out if we can make ChromeDriver to return 127.0.0.1 instead of localhost.
const endpointURLString = addProtocol(debuggerAddress || chromeOptionsURL).replace('localhost', '127.0.0.1');
progress.log(`<selenium> retrieved endpoint ${endpointURLString} for sessionId=${sessionId}`);
endpointURL = new URL(endpointURLString);
if (endpointURL.hostname === 'localhost' || endpointURL.hostname === '127.0.0.1') {
const sessionInfoUrl = new URL(hubUrl).origin + '/grid/api/testsession?session=' + sessionId;
try {
const sessionResponse = await (0, _network.fetchData)({
url: sessionInfoUrl,
method: 'GET',
timeout: progress.timeUntilDeadline(),
headers
}, seleniumErrorHandler);
const proxyId = JSON.parse(sessionResponse).proxyId;
endpointURL.hostname = new URL(proxyId).hostname;
progress.log(`<selenium> resolved endpoint ip ${endpointURL.toString()} for sessionId=${sessionId}`);
} catch (e) {
progress.log(`<selenium> unable to resolve endpoint ip for sessionId=${sessionId}, running in standalone?`);
}
}
}
return await this._connectOverCDPInternal(progress, endpointURL.toString(), {
...options,
headers: (0, _utils.headersObjectToArray)(headers)
}, disconnectFromSelenium);
} catch (e) {
await disconnectFromSelenium();
throw e;
}
}
_defaultArgs(options, isPersistent, userDataDir) {
const chromeArguments = this._innerDefaultArgs(options);
chromeArguments.push(`--user-data-dir=${userDataDir}`);
if (options.useWebSocket) chromeArguments.push('--remote-debugging-port=0');else chromeArguments.push('--remote-debugging-pipe');
if (isPersistent) chromeArguments.push('about:blank');else chromeArguments.push('--no-startup-window');
return chromeArguments;
}
_innerDefaultArgs(options) {
const {
args = [],
proxy
} = options;
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
if (userDataDirArg) throw new Error('Pass userDataDir parameter to `browserType.launchPersistentContext(userDataDir, ...)` instead of specifying --user-data-dir argument');
if (args.find(arg => arg.startsWith('--remote-debugging-pipe'))) throw new Error('Playwright manages remote debugging connection itself.');
if (args.find(arg => !arg.startsWith('-'))) throw new Error('Arguments can not specify page to be opened');
const chromeArguments = [..._chromiumSwitches.chromiumSwitches];
if (_os.default.platform() === 'darwin') {
// See https://github.com/microsoft/playwright/issues/7362
chromeArguments.push('--enable-use-zoom-for-dsf=false');
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025.
if (options.headless) chromeArguments.push('--use-angle');
}
if (options.devtools) chromeArguments.push('--auto-open-devtools-for-tabs');
if (options.headless) {
if (process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW) chromeArguments.push('--headless=new');else chromeArguments.push('--headless');
chromeArguments.push('--hide-scrollbars', '--mute-audio', '--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4');
}
if (options.chromiumSandbox !== true) chromeArguments.push('--no-sandbox');
if (proxy) {
const proxyURL = new URL(proxy.server);
const isSocks = proxyURL.protocol === 'socks5:';
// https://www.chromium.org/developers/design-documents/network-settings
if (isSocks && !this.attribution.playwright.options.socksProxyPort) {
// https://www.chromium.org/developers/design-documents/network-stack/socks-proxy
chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`);
}
chromeArguments.push(`--proxy-server=${proxy.server}`);
const proxyBypassRules = [];
// https://source.chromium.org/chromium/chromium/src/+/master:net/docs/proxy.md;l=548;drc=71698e610121078e0d1a811054dcf9fd89b49578
if (this.attribution.playwright.options.socksProxyPort) proxyBypassRules.push('<-loopback>');
if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t));
if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) proxyBypassRules.push('<-loopback>');
if (proxyBypassRules.length > 0) chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`);
}
chromeArguments.push(...args);
return chromeArguments;
}
}
exports.Chromium = Chromium;
async function urlToWSEndpoint(progress, endpointURL) {
if (endpointURL.startsWith('ws')) return endpointURL;
progress.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
const httpURL = endpointURL.endsWith('/') ? `${endpointURL}json/version/` : `${endpointURL}/json/version/`;
const json = await (0, _network.fetchData)({
url: httpURL
}, async (_, resp) => new Error(`Unexpected status ${resp.statusCode} when connecting to ${httpURL}.\n` + `This does not look like a DevTools server, try connecting via ws://.`));
return JSON.parse(json).webSocketDebuggerUrl;
}
async function seleniumErrorHandler(params, response) {
const body = await streamToString(response);
let message = body;
try {
const json = JSON.parse(body);
message = json.value.localizedMessage || json.value.message;
} catch (e) {}
return new Error(`Error connecting to Selenium at ${params.url}: ${message}`);
}
function addProtocol(url) {
if (!['ws://', 'wss://', 'http://', 'https://'].some(protocol => url.startsWith(protocol))) return 'http://' + url;
return url;
}
function streamToString(stream) {
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
});
}
function parseSeleniumRemoteParams(env, progress) {
try {
const parsed = JSON.parse(env.value);
progress.log(`<selenium> using additional ${env.name} "${env.value}"`);
return parsed;
} catch (e) {
progress.log(`<selenium> ignoring additional ${env.name} "${env.value}": ${e}`);
}
}

View File

@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.chromiumSwitches = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
// No dependencies as it is used from the Electron loader.
const chromiumSwitches = ['--disable-field-trial-config',
// https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md
'--disable-background-networking', '--enable-features=NetworkService,NetworkServiceInProcess', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-back-forward-cache',
// Avoids surprises like main request not being intercepted during page.goBack().
'--disable-breakpad', '--disable-client-side-phishing-detection', '--disable-component-extensions-with-background-pages', '--disable-component-update',
// Avoids unneeded network activity after startup.
'--no-default-browser-check', '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions',
// AvoidUnnecessaryBeforeUnloadCheckSync - https://github.com/microsoft/playwright/issues/14047
// Translate - https://github.com/microsoft/playwright/issues/16126
'--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate', '--allow-pre-commit-input', '--disable-hang-monitor', '--disable-ipc-flooding-protection', '--disable-popup-blocking', '--disable-prompt-on-repost', '--disable-renderer-backgrounding', '--force-color-profile=srgb', '--metrics-recording-only', '--no-first-run', '--enable-automation', '--password-store=basic', '--use-mock-keychain',
// See https://chromium-review.googlesource.com/c/chromium/src/+/2436773
'--no-service-autorun', '--export-tagged-pdf'];
exports.chromiumSwitches = chromiumSwitches;

View File

@ -0,0 +1,237 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getAccessibilityTree = getAccessibilityTree;
/**
* Copyright 2018 Google Inc. All rights reserved.
* Modifications 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.
*/
async function getAccessibilityTree(client, needle) {
const {
nodes
} = await client.send('Accessibility.getFullAXTree');
const tree = CRAXNode.createTree(client, nodes);
return {
tree,
needle: needle ? await tree._findElement(needle) : null
};
}
class CRAXNode {
constructor(client, payload) {
this._payload = void 0;
this._children = [];
this._richlyEditable = false;
this._editable = false;
this._focusable = false;
this._expanded = false;
this._hidden = false;
this._name = void 0;
this._role = void 0;
this._cachedHasFocusableChild = void 0;
this._client = void 0;
this._client = client;
this._payload = payload;
this._name = this._payload.name ? this._payload.name.value : '';
this._role = this._payload.role ? this._payload.role.value : 'Unknown';
for (const property of this._payload.properties || []) {
if (property.name === 'editable') {
this._richlyEditable = property.value.value === 'richtext';
this._editable = true;
}
if (property.name === 'focusable') this._focusable = property.value.value;
if (property.name === 'expanded') this._expanded = property.value.value;
if (property.name === 'hidden') this._hidden = property.value.value;
}
}
_isPlainTextField() {
if (this._richlyEditable) return false;
if (this._editable) return true;
return this._role === 'textbox' || this._role === 'ComboBox' || this._role === 'searchbox';
}
_isTextOnlyObject() {
const role = this._role;
return role === 'LineBreak' || role === 'text' || role === 'InlineTextBox' || role === 'StaticText';
}
_hasFocusableChild() {
if (this._cachedHasFocusableChild === undefined) {
this._cachedHasFocusableChild = false;
for (const child of this._children) {
if (child._focusable || child._hasFocusableChild()) {
this._cachedHasFocusableChild = true;
break;
}
}
}
return this._cachedHasFocusableChild;
}
children() {
return this._children;
}
async _findElement(element) {
const objectId = element._objectId;
const {
node: {
backendNodeId
}
} = await this._client.send('DOM.describeNode', {
objectId
});
const needle = this.find(node => node._payload.backendDOMNodeId === backendNodeId);
return needle || null;
}
find(predicate) {
if (predicate(this)) return this;
for (const child of this._children) {
const result = child.find(predicate);
if (result) return result;
}
return null;
}
isLeafNode() {
if (!this._children.length) return true;
// These types of objects may have children that we use as internal
// implementation details, but we want to expose them as leaves to platform
// accessibility APIs because screen readers might be confused if they find
// any children.
if (this._isPlainTextField() || this._isTextOnlyObject()) return true;
// Roles whose children are only presentational according to the ARIA and
// HTML5 Specs should be hidden from screen readers.
// (Note that whilst ARIA buttons can have only presentational children, HTML5
// buttons are allowed to have content.)
switch (this._role) {
case 'doc-cover':
case 'graphics-symbol':
case 'img':
case 'Meter':
case 'scrollbar':
case 'slider':
case 'separator':
case 'progressbar':
return true;
default:
break;
}
// Here and below: Android heuristics
if (this._hasFocusableChild()) return false;
if (this._focusable && this._role !== 'WebArea' && this._role !== 'RootWebArea' && this._name) return true;
if (this._role === 'heading' && this._name) return true;
return false;
}
isControl() {
switch (this._role) {
case 'button':
case 'checkbox':
case 'ColorWell':
case 'combobox':
case 'DisclosureTriangle':
case 'listbox':
case 'menu':
case 'menubar':
case 'menuitem':
case 'menuitemcheckbox':
case 'menuitemradio':
case 'radio':
case 'scrollbar':
case 'searchbox':
case 'slider':
case 'spinbutton':
case 'switch':
case 'tab':
case 'textbox':
case 'tree':
return true;
default:
return false;
}
}
isInteresting(insideControl) {
const role = this._role;
if (role === 'Ignored' || this._hidden) return false;
if (this._focusable || this._richlyEditable) return true;
// If it's not focusable but has a control role, then it's interesting.
if (this.isControl()) return true;
// A non focusable child of a control is not interesting
if (insideControl) return false;
return this.isLeafNode() && !!this._name;
}
normalizedRole() {
switch (this._role) {
case 'RootWebArea':
return 'WebArea';
case 'StaticText':
return 'text';
default:
return this._role;
}
}
serialize() {
const properties = new Map();
for (const property of this._payload.properties || []) properties.set(property.name.toLowerCase(), property.value.value);
if (this._payload.description) properties.set('description', this._payload.description.value);
const node = {
role: this.normalizedRole(),
name: this._payload.name ? this._payload.name.value || '' : ''
};
const userStringProperties = ['description', 'keyshortcuts', 'roledescription', 'valuetext'];
for (const userStringProperty of userStringProperties) {
if (!properties.has(userStringProperty)) continue;
node[userStringProperty] = properties.get(userStringProperty);
}
const booleanProperties = ['disabled', 'expanded', 'focused', 'modal', 'multiline', 'multiselectable', 'readonly', 'required', 'selected'];
for (const booleanProperty of booleanProperties) {
// WebArea's treat focus differently than other nodes. They report whether their frame has focus,
// not whether focus is specifically on the root node.
if (booleanProperty === 'focused' && (this._role === 'WebArea' || this._role === 'RootWebArea')) continue;
const value = properties.get(booleanProperty);
if (!value) continue;
node[booleanProperty] = value;
}
const numericalProperties = ['level', 'valuemax', 'valuemin'];
for (const numericalProperty of numericalProperties) {
if (!properties.has(numericalProperty)) continue;
node[numericalProperty] = properties.get(numericalProperty);
}
const tokenProperties = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
for (const tokenProperty of tokenProperties) {
const value = properties.get(tokenProperty);
if (!value || value === 'false') continue;
node[tokenProperty] = value;
}
const axNode = node;
if (this._payload.value) {
if (typeof this._payload.value.value === 'string') axNode.valueString = this._payload.value.value;
if (typeof this._payload.value.value === 'number') axNode.valueNumber = this._payload.value.value;
}
if (properties.has('checked')) axNode.checked = properties.get('checked') === 'true' ? 'checked' : properties.get('checked') === 'false' ? 'unchecked' : 'mixed';
if (properties.has('pressed')) axNode.pressed = properties.get('pressed') === 'true' ? 'pressed' : properties.get('pressed') === 'false' ? 'released' : 'mixed';
return axNode;
}
static createTree(client, payloads) {
const nodeById = new Map();
for (const payload of payloads) nodeById.set(payload.nodeId, new CRAXNode(client, payload));
for (const node of nodeById.values()) {
for (const childId of node._payload.childIds || []) node._children.push(nodeById.get(childId));
}
return nodeById.values().next().value;
}
}

View File

@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.installAppIcon = installAppIcon;
exports.syncLocalStorageWithSettings = syncLocalStorageWithSettings;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _utils = require("../../utils");
var _registry = require("../registry");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
async function installAppIcon(page) {
const icon = await _fs.default.promises.readFile(require.resolve('./appIcon.png'));
const crPage = page._delegate;
await crPage._mainFrameSession._client.send('Browser.setDockTile', {
image: icon.toString('base64')
});
}
async function syncLocalStorageWithSettings(page, appName) {
if ((0, _utils.isUnderTest)()) return;
const settingsFile = _path.default.join(_registry.registryDirectory, '.settings', `${appName}.json`);
await page.exposeBinding('_saveSerializedSettings', false, (_, settings) => {
_fs.default.mkdirSync(_path.default.dirname(settingsFile), {
recursive: true
});
_fs.default.writeFileSync(settingsFile, settings);
});
const settings = await _fs.default.promises.readFile(settingsFile, 'utf-8').catch(() => '{}');
await page.addInitScript(`(${String(settings => {
// iframes w/ snapshots, etc.
if (location && location.protocol === 'data:') return;
Object.entries(settings).map(([k, v]) => localStorage[k] = v);
window.saveSettings = () => {
window._saveSerializedSettings(JSON.stringify({
...localStorage
}));
};
})})(${settings});
`);
}

View File

@ -0,0 +1,527 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRBrowserContext = exports.CRBrowser = void 0;
var _path = _interopRequireDefault(require("path"));
var _browser = require("../browser");
var _browserContext = require("../browserContext");
var _utils = require("../../utils");
var network = _interopRequireWildcard(require("../network"));
var _page = require("../page");
var _frames = require("../frames");
var _crConnection = require("./crConnection");
var _crPage = require("./crPage");
var _crProtocolHelper = require("./crProtocolHelper");
var _crServiceWorker = require("./crServiceWorker");
var _artifact = require("../artifact");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class CRBrowser extends _browser.Browser {
static async connect(parent, transport, options, devtools) {
// Make a copy in case we need to update `headful` property below.
options = {
...options
};
const connection = new _crConnection.CRConnection(transport, options.protocolLogger, options.browserLogsCollector);
const browser = new CRBrowser(parent, connection, options);
browser._devtools = devtools;
const session = connection.rootSession;
if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
const version = await session.send('Browser.getVersion');
browser._version = version.product.substring(version.product.indexOf('/') + 1);
browser._userAgent = version.userAgent;
// We don't trust the option as it may lie in case of connectOverCDP where remote browser
// may have been launched with different options.
browser.options.headful = !version.userAgent.includes('Headless');
if (!options.persistent) {
await session.send('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true
});
return browser;
}
browser._defaultContext = new CRBrowserContext(browser, undefined, options.persistent);
await Promise.all([session.send('Target.setAutoAttach', {
autoAttach: true,
waitForDebuggerOnStart: true,
flatten: true
}).then(async () => {
// Target.setAutoAttach has a bug where it does not wait for new Targets being attached.
// However making a dummy call afterwards fixes this.
// This can be removed after https://chromium-review.googlesource.com/c/chromium/src/+/2885888 lands in stable.
await session.send('Target.getTargetInfo');
}), browser._defaultContext._initialize()]);
await browser._waitForAllPagesToBeInitialized();
return browser;
}
constructor(parent, connection, options) {
super(parent, options);
this._connection = void 0;
this._session = void 0;
this._clientRootSessionPromise = null;
this._contexts = new Map();
this._crPages = new Map();
this._backgroundPages = new Map();
this._serviceWorkers = new Map();
this._devtools = void 0;
this._version = '';
this._tracingRecording = false;
this._tracingClient = void 0;
this._userAgent = '';
this._connection = connection;
this._session = this._connection.rootSession;
this._connection.on(_crConnection.ConnectionEvents.Disconnected, () => this._didClose());
this._session.on('Target.attachedToTarget', this._onAttachedToTarget.bind(this));
this._session.on('Target.detachedFromTarget', this._onDetachedFromTarget.bind(this));
this._session.on('Browser.downloadWillBegin', this._onDownloadWillBegin.bind(this));
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
}
async doCreateNewContext(options) {
let proxyBypassList = undefined;
if (options.proxy) {
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK) proxyBypassList = options.proxy.bypass;else proxyBypassList = '<-loopback>' + (options.proxy.bypass ? `,${options.proxy.bypass}` : '');
}
const {
browserContextId
} = await this._session.send('Target.createBrowserContext', {
disposeOnDetach: true,
proxyServer: options.proxy ? options.proxy.server : undefined,
proxyBypassList
});
const context = new CRBrowserContext(this, browserContextId, options);
await context._initialize();
this._contexts.set(browserContextId, context);
return context;
}
contexts() {
return Array.from(this._contexts.values());
}
version() {
return this._version;
}
userAgent() {
return this._userAgent;
}
_platform() {
if (this._userAgent.includes('Windows')) return 'win';
if (this._userAgent.includes('Macintosh')) return 'mac';
return 'linux';
}
isClank() {
return this.options.name === 'clank';
}
async _waitForAllPagesToBeInitialized() {
await Promise.all([...this._crPages.values()].map(page => page.pageOrError()));
}
_onAttachedToTarget({
targetInfo,
sessionId,
waitingForDebugger
}) {
if (targetInfo.type === 'browser') return;
const session = this._connection.session(sessionId);
(0, _utils.assert)(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
let context = this._contexts.get(targetInfo.browserContextId) || null;
if (!context) {
// TODO: auto attach only to pages from our contexts.
// assert(this._defaultContext);
context = this._defaultContext;
}
if (targetInfo.type === 'other' && targetInfo.url.startsWith('devtools://devtools') && this._devtools) {
this._devtools.install(session);
return;
}
const treatOtherAsPage = targetInfo.type === 'other' && process.env.PW_CHROMIUM_ATTACH_TO_OTHER;
if (!context || targetInfo.type === 'other' && !treatOtherAsPage) {
if (waitingForDebugger) {
// Ideally, detaching should resume any target, but there is a bug in the backend.
session._sendMayFail('Runtime.runIfWaitingForDebugger').then(() => {
this._session._sendMayFail('Target.detachFromTarget', {
sessionId
});
});
}
return;
}
(0, _utils.assert)(!this._crPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
(0, _utils.assert)(!this._backgroundPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
(0, _utils.assert)(!this._serviceWorkers.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);
if (targetInfo.type === 'background_page') {
const backgroundPage = new _crPage.CRPage(session, targetInfo.targetId, context, null, {
hasUIWindow: false,
isBackgroundPage: true
});
this._backgroundPages.set(targetInfo.targetId, backgroundPage);
return;
}
if (targetInfo.type === 'page' || treatOtherAsPage) {
const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null;
const crPage = new _crPage.CRPage(session, targetInfo.targetId, context, opener, {
hasUIWindow: targetInfo.type === 'page',
isBackgroundPage: false
});
this._crPages.set(targetInfo.targetId, crPage);
return;
}
if (targetInfo.type === 'service_worker') {
const serviceWorker = new _crServiceWorker.CRServiceWorker(context, session, targetInfo.url);
this._serviceWorkers.set(targetInfo.targetId, serviceWorker);
context.emit(CRBrowserContext.CREvents.ServiceWorker, serviceWorker);
return;
}
// Detach from any targets we are not interested in, to avoid side-effects.
//
// One example of a side effect: upon shared worker restart, we receive
// Inspector.targetReloadedAfterCrash and backend waits for Runtime.runIfWaitingForDebugger
// from any attached client. If we do not resume, shared worker will stall.
//
// Ideally, detaching should resume any target, but there is a bug in the backend,
// so we must Runtime.runIfWaitingForDebugger first.
session._sendMayFail('Runtime.runIfWaitingForDebugger').then(() => {
this._session._sendMayFail('Target.detachFromTarget', {
sessionId
});
});
}
_onDetachedFromTarget(payload) {
const targetId = payload.targetId;
const crPage = this._crPages.get(targetId);
if (crPage) {
this._crPages.delete(targetId);
crPage.didClose();
return;
}
const backgroundPage = this._backgroundPages.get(targetId);
if (backgroundPage) {
this._backgroundPages.delete(targetId);
backgroundPage.didClose();
return;
}
const serviceWorker = this._serviceWorkers.get(targetId);
if (serviceWorker) {
this._serviceWorkers.delete(targetId);
serviceWorker.didClose();
return;
}
}
_findOwningPage(frameId) {
for (const crPage of this._crPages.values()) {
const frame = crPage._page._frameManager.frame(frameId);
if (frame) return crPage;
}
return null;
}
_onDownloadWillBegin(payload) {
const page = this._findOwningPage(payload.frameId);
if (!page) {
// There might be no page when download originates from something unusual, like
// a DevTools window or maybe an extension page.
// See https://github.com/microsoft/playwright/issues/22551.
return;
}
page.willBeginDownload();
let originPage = page._initializedPage;
// If it's a new window download, report it on the opener page.
if (!originPage && page._opener) originPage = page._opener._initializedPage;
if (!originPage) return;
this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
}
_onDownloadProgress(payload) {
if (payload.state === 'completed') this._downloadFinished(payload.guid, '');
if (payload.state === 'canceled') this._downloadFinished(payload.guid, 'canceled');
}
async _closePage(crPage) {
await this._session.send('Target.closeTarget', {
targetId: crPage._targetId
});
}
async newBrowserCDPSession() {
return await this._connection.createBrowserSession();
}
async startTracing(page, options = {}) {
(0, _utils.assert)(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
this._tracingClient = page ? page._delegate._mainFrameSession._client : this._session;
const defaultCategories = ['-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline', 'disabled-by-default-devtools.timeline.frame', 'toplevel', 'blink.console', 'blink.user_timing', 'latencyInfo', 'disabled-by-default-devtools.timeline.stack', 'disabled-by-default-v8.cpu_profiler', 'disabled-by-default-v8.cpu_profiler.hires'];
const {
screenshots = false,
categories = defaultCategories
} = options;
if (screenshots) categories.push('disabled-by-default-devtools.screenshot');
this._tracingRecording = true;
await this._tracingClient.send('Tracing.start', {
transferMode: 'ReturnAsStream',
categories: categories.join(',')
});
}
async stopTracing() {
(0, _utils.assert)(this._tracingClient, 'Tracing was not started.');
const [event] = await Promise.all([new Promise(f => this._tracingClient.once('Tracing.tracingComplete', f)), this._tracingClient.send('Tracing.end')]);
const tracingPath = _path.default.join(this.options.artifactsDir, (0, _utils.createGuid)() + '.crtrace');
await (0, _crProtocolHelper.saveProtocolStream)(this._tracingClient, event.stream, tracingPath);
this._tracingRecording = false;
const artifact = new _artifact.Artifact(this, tracingPath);
artifact.reportFinished();
return artifact;
}
isConnected() {
return !this._connection._closed;
}
async _clientRootSession() {
if (!this._clientRootSessionPromise) this._clientRootSessionPromise = this._connection.createBrowserSession();
return this._clientRootSessionPromise;
}
}
exports.CRBrowser = CRBrowser;
class CRBrowserContext extends _browserContext.BrowserContext {
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
this._authenticateProxyViaCredentials();
}
async _initialize() {
(0, _utils.assert)(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this));
const promises = [super._initialize()];
if (this._browser.options.name !== 'electron' && this._browser.options.name !== 'clank') {
promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny',
browserContextId: this._browserContextId,
downloadPath: this._browser.options.downloadsPath,
eventsEnabled: true
}));
}
await Promise.all(promises);
}
_crPages() {
return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
}
pages() {
return this._crPages().map(crPage => crPage._initializedPage).filter(Boolean);
}
async newPageDelegate() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
let {
targetId
} = await this._browser._session.send('Target.createTarget', {
url: 'about:blank',
browserContextId: this._browserContextId
});
if (oldKeys) {
// Chrome for Android returns tab ids (1, 2, 3, 4, 5) instead of content target ids here, work around it via the
// heuristic assuming that there is only one page created at a time.
const newKeys = new Set(this._browser._crPages.keys());
// Remove old keys.
for (const key of oldKeys) newKeys.delete(key);
// Remove potential concurrent popups.
for (const key of newKeys) {
const page = this._browser._crPages.get(key);
if (page._opener) newKeys.delete(key);
}
(0, _utils.assert)(newKeys.size === 1);
[targetId] = [...newKeys];
}
return this._browser._crPages.get(targetId);
}
async doGetCookies(urls) {
const {
cookies
} = await this._browser._session.send('Storage.getCookies', {
browserContextId: this._browserContextId
});
return network.filterCookies(cookies.map(c => {
const copy = {
sameSite: 'Lax',
...c
};
delete copy.size;
delete copy.priority;
delete copy.session;
delete copy.sameParty;
delete copy.sourceScheme;
delete copy.sourcePort;
return copy;
}), urls);
}
async addCookies(cookies) {
await this._browser._session.send('Storage.setCookies', {
cookies: network.rewriteCookies(cookies),
browserContextId: this._browserContextId
});
}
async clearCookies() {
await this._browser._session.send('Storage.clearCookies', {
browserContextId: this._browserContextId
});
}
async doGrantPermissions(origin, permissions) {
const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['accessibility-events', 'accessibilityEvents'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
// chrome-specific permissions we have.
['midi-sysex', 'midiSysex']]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission) throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._browser._session.send('Browser.grantPermissions', {
origin: origin === '*' ? undefined : origin,
browserContextId: this._browserContextId,
permissions: filtered
});
}
async doClearPermissions() {
await this._browser._session.send('Browser.resetPermissions', {
browserContextId: this._browserContextId
});
}
async setGeolocation(geolocation) {
(0, _browserContext.verifyGeolocation)(geolocation);
this._options.geolocation = geolocation;
for (const page of this.pages()) await page._delegate.updateGeolocation();
}
async setExtraHTTPHeaders(headers) {
this._options.extraHTTPHeaders = headers;
for (const page of this.pages()) await page._delegate.updateExtraHTTPHeaders();
for (const sw of this.serviceWorkers()) await sw.updateExtraHTTPHeaders(false);
}
async setUserAgent(userAgent) {
this._options.userAgent = userAgent;
for (const page of this.pages()) await page._delegate.updateUserAgent();
// TODO: service workers don't have Emulation domain?
}
async setOffline(offline) {
this._options.offline = offline;
for (const page of this.pages()) await page._delegate.updateOffline();
for (const sw of this.serviceWorkers()) await sw.updateOffline(false);
}
async doSetHTTPCredentials(httpCredentials) {
this._options.httpCredentials = httpCredentials;
for (const page of this.pages()) await page._delegate.updateHttpCredentials();
for (const sw of this.serviceWorkers()) await sw.updateHttpCredentials(false);
}
async doAddInitScript(source) {
for (const page of this.pages()) await page._delegate.addInitScript(source);
}
async doRemoveInitScripts() {
for (const page of this.pages()) await page._delegate.removeInitScripts();
}
async doExposeBinding(binding) {
for (const page of this.pages()) await page._delegate.exposeBinding(binding);
}
async doRemoveExposedBindings() {
for (const page of this.pages()) await page._delegate.removeExposedBindings();
}
async doUpdateRequestInterception() {
for (const page of this.pages()) await page._delegate.updateRequestInterception();
for (const sw of this.serviceWorkers()) await sw.updateRequestInterception();
}
async doClose() {
// Headful chrome cannot dispose browser context with opened 'beforeunload'
// dialogs, so we should close all that are currently opened.
// We also won't get new ones since `Target.disposeBrowserContext` does not trigger
// beforeunload.
const openedBeforeUnloadDialogs = [];
for (const crPage of this._crPages()) {
const dialogs = [...crPage._page._frameManager._openedDialogs].filter(dialog => dialog.type() === 'beforeunload');
openedBeforeUnloadDialogs.push(...dialogs);
}
await Promise.all(openedBeforeUnloadDialogs.map(dialog => dialog.dismiss()));
if (!this._browserContextId) {
await Promise.all(this._crPages().map(crPage => crPage._mainFrameSession._stopVideoRecording()));
// Closing persistent context should close the browser.
await this._browser.close();
return;
}
await this._browser._session.send('Target.disposeBrowserContext', {
browserContextId: this._browserContextId
});
this._browser._contexts.delete(this._browserContextId);
for (const [targetId, serviceWorker] of this._browser._serviceWorkers) {
if (serviceWorker._browserContext !== this) continue;
// When closing a browser context, service workers are shutdown
// asynchronously and we get detached from them later.
// To avoid the wrong order of notifications, we manually fire
// "close" event here and forget about the serivce worker.
serviceWorker.didClose();
this._browser._serviceWorkers.delete(targetId);
}
}
onClosePersistent() {
// When persistent context is closed, we do not necessary get Target.detachedFromTarget
// for all the background pages.
for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
if (backgroundPage._browserContext === this && backgroundPage._initializedPage) {
backgroundPage.didClose();
this._browser._backgroundPages.delete(targetId);
}
}
}
async clearCache() {
for (const page of this._crPages()) await page._mainFrameSession._networkManager.clearCache();
}
async cancelDownload(guid) {
// The upstream CDP method is implemented in a way that no explicit error would be given
// regarding the requested `guid`, even if the download is in a state not suitable for
// cancellation (finished, cancelled, etc.) or the guid is invalid at all.
await this._browser._session.send('Browser.cancelDownload', {
guid: guid,
browserContextId: this._browserContextId
});
}
backgroundPages() {
const result = [];
for (const backgroundPage of this._browser._backgroundPages.values()) {
if (backgroundPage._browserContext === this && backgroundPage._initializedPage) result.push(backgroundPage._initializedPage);
}
return result;
}
serviceWorkers() {
return Array.from(this._browser._serviceWorkers.values()).filter(serviceWorker => serviceWorker._browserContext === this);
}
async newCDPSession(page) {
let targetId = null;
if (page instanceof _page.Page) {
targetId = page._delegate._targetId;
} else if (page instanceof _frames.Frame) {
const session = page._page._delegate._sessions.get(page._id);
if (!session) throw new Error(`This frame does not have a separate CDP session, it is a part of the parent frame's session`);
targetId = session._targetId;
} else {
throw new Error('page: expected Page or Frame');
}
const rootSession = await this._browser._clientRootSession();
const {
sessionId
} = await rootSession.send('Target.attachToTarget', {
targetId,
flatten: true
});
return this._browser._connection.session(sessionId);
}
}
exports.CRBrowserContext = CRBrowserContext;
CRBrowserContext.CREvents = {
BackgroundPage: 'backgroundpage',
ServiceWorker: 'serviceworker'
};

View File

@ -0,0 +1,219 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kBrowserCloseMessageId = exports.ConnectionEvents = exports.CRSessionEvents = exports.CRSession = exports.CRConnection = void 0;
var _utils = require("../../utils");
var _events = require("events");
var _stackTrace = require("../../utils/stackTrace");
var _debugLogger = require("../../common/debugLogger");
var _helper = require("../helper");
var _protocolError = require("../protocolError");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
const ConnectionEvents = {
Disconnected: Symbol('ConnectionEvents.Disconnected')
};
// CRPlaywright uses this special id to issue Browser.close command which we
// should ignore.
exports.ConnectionEvents = ConnectionEvents;
const kBrowserCloseMessageId = -9999;
exports.kBrowserCloseMessageId = kBrowserCloseMessageId;
class CRConnection extends _events.EventEmitter {
constructor(transport, protocolLogger, browserLogsCollector) {
super();
this._lastId = 0;
this._transport = void 0;
this._sessions = new Map();
this._protocolLogger = void 0;
this._browserLogsCollector = void 0;
this.rootSession = void 0;
this._closed = false;
this.setMaxListeners(0);
this._transport = transport;
this._protocolLogger = protocolLogger;
this._browserLogsCollector = browserLogsCollector;
this.rootSession = new CRSession(this, '', 'browser', '');
this._sessions.set('', this.rootSession);
this._transport.onmessage = this._onMessage.bind(this);
// onclose should be set last, since it can be immediately called.
this._transport.onclose = this._onClose.bind(this);
}
static fromSession(session) {
return session._connection;
}
session(sessionId) {
return this._sessions.get(sessionId) || null;
}
_rawSend(sessionId, method, params) {
const id = ++this._lastId;
const message = {
id,
method,
params
};
if (sessionId) message.sessionId = sessionId;
this._protocolLogger('send', message);
this._transport.send(message);
return id;
}
async _onMessage(message) {
this._protocolLogger('receive', message);
if (message.id === kBrowserCloseMessageId) return;
if (message.method === 'Target.attachedToTarget') {
const sessionId = message.params.sessionId;
const rootSessionId = message.sessionId || '';
const session = new CRSession(this, rootSessionId, message.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
} else if (message.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(message.params.sessionId);
if (session) {
session._onClosed(undefined);
this._sessions.delete(message.params.sessionId);
}
}
const session = this._sessions.get(message.sessionId || '');
if (session) session._onMessage(message);
}
_onClose() {
this._closed = true;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
const browserDisconnectedLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
for (const session of this._sessions.values()) session._onClosed(browserDisconnectedLogs);
this._sessions.clear();
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
}
close() {
if (!this._closed) this._transport.close();
}
async createSession(targetInfo) {
const {
sessionId
} = await this.rootSession.send('Target.attachToTarget', {
targetId: targetInfo.targetId,
flatten: true
});
return this._sessions.get(sessionId);
}
async createBrowserSession() {
const {
sessionId
} = await this.rootSession.send('Target.attachToBrowserTarget');
return this._sessions.get(sessionId);
}
}
exports.CRConnection = CRConnection;
const CRSessionEvents = {
Disconnected: Symbol('Events.CDPSession.Disconnected')
};
exports.CRSessionEvents = CRSessionEvents;
class CRSession extends _events.EventEmitter {
constructor(connection, rootSessionId, targetType, sessionId) {
super();
this._connection = void 0;
this._eventListener = void 0;
this._callbacks = new Map();
this._targetType = void 0;
this._sessionId = void 0;
this._rootSessionId = void 0;
this._crashed = false;
this._browserDisconnectedLogs = void 0;
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.guid = void 0;
this.guid = `cdp-session@${sessionId}`;
this.setMaxListeners(0);
this._connection = connection;
this._rootSessionId = rootSessionId;
this._targetType = targetType;
this._sessionId = sessionId;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
_markAsCrashed() {
this._crashed = true;
}
async send(method, params) {
if (this._crashed) throw new _protocolError.ProtocolError(true, 'Target crashed');
if (this._browserDisconnectedLogs !== undefined) throw new _protocolError.ProtocolError(true, `Browser closed.` + this._browserDisconnectedLogs);
if (!this._connection) throw new _protocolError.ProtocolError(true, `Target closed`);
const id = this._connection._rawSend(this._sessionId, method, params);
return new Promise((resolve, reject) => {
this._callbacks.set(id, {
resolve,
reject,
error: new _protocolError.ProtocolError(false),
method
});
});
}
_sendMayFail(method, params) {
return this.send(method, params).catch(error => _debugLogger.debugLogger.log('error', error));
}
_onMessage(object) {
var _object$error;
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
this._callbacks.delete(object.id);
if (object.error) callback.reject(createProtocolError(callback.error, callback.method, object.error));else callback.resolve(object.result);
} else if (object.id && ((_object$error = object.error) === null || _object$error === void 0 ? void 0 : _object$error.code) === -32001) {
// Message to a closed session, just ignore it.
} else {
var _object$error2;
(0, _utils.assert)(!object.id, (object === null || object === void 0 ? void 0 : (_object$error2 = object.error) === null || _object$error2 === void 0 ? void 0 : _object$error2.message) || undefined);
Promise.resolve().then(() => {
if (this._eventListener) this._eventListener(object.method, object.params);
this.emit(object.method, object.params);
});
}
}
async detach() {
if (!this._connection) throw new Error(`Session already detached. Most likely the ${this._targetType} has been closed.`);
const rootSession = this._connection.session(this._rootSessionId);
if (!rootSession) throw new Error('Root session has been closed');
await rootSession.send('Target.detachFromTarget', {
sessionId: this._sessionId
});
}
_onClosed(browserDisconnectedLogs) {
this._browserDisconnectedLogs = browserDisconnectedLogs;
const errorMessage = browserDisconnectedLogs !== undefined ? 'Browser closed.' + browserDisconnectedLogs : 'Target closed';
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject((0, _stackTrace.rewriteErrorMessage)(callback.error, errorMessage));
}
this._callbacks.clear();
this._connection = null;
Promise.resolve().then(() => this.emit(CRSessionEvents.Disconnected));
}
}
exports.CRSession = CRSession;
function createProtocolError(error, method, protocolError) {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError) message += ` ${protocolError.data}`;
return (0, _stackTrace.rewriteErrorMessage)(error, message);
}

View File

@ -0,0 +1,246 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRCoverage = void 0;
var _eventsHelper = require("../../utils/eventsHelper");
var _utils = require("../../utils");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class CRCoverage {
constructor(client) {
this._jsCoverage = void 0;
this._cssCoverage = void 0;
this._jsCoverage = new JSCoverage(client);
this._cssCoverage = new CSSCoverage(client);
}
async startJSCoverage(options) {
return await this._jsCoverage.start(options);
}
async stopJSCoverage() {
return await this._jsCoverage.stop();
}
async startCSSCoverage(options) {
return await this._cssCoverage.start(options);
}
async stopCSSCoverage() {
return await this._cssCoverage.stop();
}
}
exports.CRCoverage = CRCoverage;
class JSCoverage {
constructor(client) {
this._client = void 0;
this._enabled = void 0;
this._scriptIds = void 0;
this._scriptSources = void 0;
this._eventListeners = void 0;
this._resetOnNavigation = void 0;
this._reportAnonymousScripts = false;
this._client = client;
this._enabled = false;
this._scriptIds = new Set();
this._scriptSources = new Map();
this._eventListeners = [];
this._resetOnNavigation = false;
}
async start(options) {
(0, _utils.assert)(!this._enabled, 'JSCoverage is already enabled');
const {
resetOnNavigation = true,
reportAnonymousScripts = false
} = options;
this._resetOnNavigation = resetOnNavigation;
this._reportAnonymousScripts = reportAnonymousScripts;
this._enabled = true;
this._scriptIds.clear();
this._scriptSources.clear();
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._client, 'Debugger.scriptParsed', this._onScriptParsed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Debugger.paused', this._onDebuggerPaused.bind(this))];
await Promise.all([this._client.send('Profiler.enable'), this._client.send('Profiler.startPreciseCoverage', {
callCount: true,
detailed: true
}), this._client.send('Debugger.enable'), this._client.send('Debugger.setSkipAllPauses', {
skip: true
})]);
}
_onDebuggerPaused() {
this._client.send('Debugger.resume');
}
_onExecutionContextsCleared() {
if (!this._resetOnNavigation) return;
this._scriptIds.clear();
this._scriptSources.clear();
}
async _onScriptParsed(event) {
this._scriptIds.add(event.scriptId);
// Ignore other anonymous scripts unless the reportAnonymousScripts option is true.
if (!event.url && !this._reportAnonymousScripts) return;
// This might fail if the page has already navigated away.
const response = await this._client._sendMayFail('Debugger.getScriptSource', {
scriptId: event.scriptId
});
if (response) this._scriptSources.set(event.scriptId, response.scriptSource);
}
async stop() {
(0, _utils.assert)(this._enabled, 'JSCoverage is not enabled');
this._enabled = false;
const [profileResponse] = await Promise.all([this._client.send('Profiler.takePreciseCoverage'), this._client.send('Profiler.stopPreciseCoverage'), this._client.send('Profiler.disable'), this._client.send('Debugger.disable')]);
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
const coverage = {
entries: []
};
for (const entry of profileResponse.result) {
if (!this._scriptIds.has(entry.scriptId)) continue;
if (!entry.url && !this._reportAnonymousScripts) continue;
const source = this._scriptSources.get(entry.scriptId);
if (source) coverage.entries.push({
...entry,
source
});else coverage.entries.push(entry);
}
return coverage;
}
}
class CSSCoverage {
constructor(client) {
this._client = void 0;
this._enabled = void 0;
this._stylesheetURLs = void 0;
this._stylesheetSources = void 0;
this._eventListeners = void 0;
this._resetOnNavigation = void 0;
this._client = client;
this._enabled = false;
this._stylesheetURLs = new Map();
this._stylesheetSources = new Map();
this._eventListeners = [];
this._resetOnNavigation = false;
}
async start(options) {
(0, _utils.assert)(!this._enabled, 'CSSCoverage is already enabled');
const {
resetOnNavigation = true
} = options;
this._resetOnNavigation = resetOnNavigation;
this._enabled = true;
this._stylesheetURLs.clear();
this._stylesheetSources.clear();
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._client, 'CSS.styleSheetAdded', this._onStyleSheet.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._client, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this))];
await Promise.all([this._client.send('DOM.enable'), this._client.send('CSS.enable'), this._client.send('CSS.startRuleUsageTracking')]);
}
_onExecutionContextsCleared() {
if (!this._resetOnNavigation) return;
this._stylesheetURLs.clear();
this._stylesheetSources.clear();
}
async _onStyleSheet(event) {
const header = event.header;
// Ignore anonymous scripts
if (!header.sourceURL) return;
// This might fail if the page has already navigated away.
const response = await this._client._sendMayFail('CSS.getStyleSheetText', {
styleSheetId: header.styleSheetId
});
if (response) {
this._stylesheetURLs.set(header.styleSheetId, header.sourceURL);
this._stylesheetSources.set(header.styleSheetId, response.text);
}
}
async stop() {
(0, _utils.assert)(this._enabled, 'CSSCoverage is not enabled');
this._enabled = false;
const ruleTrackingResponse = await this._client.send('CSS.stopRuleUsageTracking');
await Promise.all([this._client.send('CSS.disable'), this._client.send('DOM.disable')]);
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
// aggregate by styleSheetId
const styleSheetIdToCoverage = new Map();
for (const entry of ruleTrackingResponse.ruleUsage) {
let ranges = styleSheetIdToCoverage.get(entry.styleSheetId);
if (!ranges) {
ranges = [];
styleSheetIdToCoverage.set(entry.styleSheetId, ranges);
}
ranges.push({
startOffset: entry.startOffset,
endOffset: entry.endOffset,
count: entry.used ? 1 : 0
});
}
const coverage = {
entries: []
};
for (const styleSheetId of this._stylesheetURLs.keys()) {
const url = this._stylesheetURLs.get(styleSheetId);
const text = this._stylesheetSources.get(styleSheetId);
const ranges = convertToDisjointRanges(styleSheetIdToCoverage.get(styleSheetId) || []);
coverage.entries.push({
url,
ranges,
text
});
}
return coverage;
}
}
function convertToDisjointRanges(nestedRanges) {
const points = [];
for (const range of nestedRanges) {
points.push({
offset: range.startOffset,
type: 0,
range
});
points.push({
offset: range.endOffset,
type: 1,
range
});
}
// Sort points to form a valid parenthesis sequence.
points.sort((a, b) => {
// Sort with increasing offsets.
if (a.offset !== b.offset) return a.offset - b.offset;
// All "end" points should go before "start" points.
if (a.type !== b.type) return b.type - a.type;
const aLength = a.range.endOffset - a.range.startOffset;
const bLength = b.range.endOffset - b.range.startOffset;
// For two "start" points, the one with longer range goes first.
if (a.type === 0) return bLength - aLength;
// For two "end" points, the one with shorter range goes first.
return aLength - bLength;
});
const hitCountStack = [];
const results = [];
let lastOffset = 0;
// Run scanning line to intersect all ranges.
for (const point of points) {
if (hitCountStack.length && lastOffset < point.offset && hitCountStack[hitCountStack.length - 1] > 0) {
const lastResult = results.length ? results[results.length - 1] : null;
if (lastResult && lastResult.end === lastOffset) lastResult.end = point.offset;else results.push({
start: lastOffset,
end: point.offset
});
}
lastOffset = point.offset;
if (point.type === 0) hitCountStack.push(point.range.count);else hitCountStack.pop();
}
// Filter out empty ranges.
return results.filter(range => range.end - range.start > 1);
}

View File

@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRDevTools = void 0;
var _fs = _interopRequireDefault(require("fs"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
const kBindingName = '__pw_devtools__';
// This class intercepts preferences-related DevTools embedder methods
// and stores preferences as a json file in the browser installation directory.
class CRDevTools {
constructor(preferencesPath) {
this._preferencesPath = void 0;
this._prefs = void 0;
this._savePromise = void 0;
this.__testHookOnBinding = void 0;
this._preferencesPath = preferencesPath;
this._savePromise = Promise.resolve();
}
install(session) {
session.on('Runtime.bindingCalled', async event => {
if (event.name !== kBindingName) return;
const parsed = JSON.parse(event.payload);
let result = undefined;
if (this.__testHookOnBinding) this.__testHookOnBinding(parsed);
if (parsed.method === 'getPreferences') {
if (this._prefs === undefined) {
try {
const json = await _fs.default.promises.readFile(this._preferencesPath, 'utf8');
this._prefs = JSON.parse(json);
} catch (e) {
this._prefs = {};
}
}
result = this._prefs;
} else if (parsed.method === 'setPreference') {
this._prefs[parsed.params[0]] = parsed.params[1];
this._save();
} else if (parsed.method === 'removePreference') {
delete this._prefs[parsed.params[0]];
this._save();
} else if (parsed.method === 'clearPreferences') {
this._prefs = {};
this._save();
}
session.send('Runtime.evaluate', {
expression: `window.DevToolsAPI.embedderMessageAck(${parsed.id}, ${JSON.stringify(result)})`,
contextId: event.executionContextId
}).catch(e => null);
});
Promise.all([session.send('Runtime.enable'), session.send('Runtime.addBinding', {
name: kBindingName
}), session.send('Page.enable'), session.send('Page.addScriptToEvaluateOnNewDocument', {
source: `
(() => {
const init = () => {
// Lazy init happens when InspectorFrontendHost is initialized.
// At this point DevToolsHost is ready to be used.
const host = window.DevToolsHost;
const old = host.sendMessageToEmbedder.bind(host);
host.sendMessageToEmbedder = message => {
if (['getPreferences', 'setPreference', 'removePreference', 'clearPreferences'].includes(JSON.parse(message).method))
window.${kBindingName}(message);
else
old(message);
};
};
let value;
Object.defineProperty(window, 'InspectorFrontendHost', {
configurable: true,
enumerable: true,
get() { return value; },
set(v) { value = v; init(); },
});
})()
`
}), session.send('Runtime.runIfWaitingForDebugger')]).catch(e => null);
}
_save() {
// Serialize saves to avoid corruption.
this._savePromise = this._savePromise.then(async () => {
await _fs.default.promises.writeFile(this._preferencesPath, JSON.stringify(this._prefs)).catch(e => null);
});
}
}
exports.CRDevTools = CRDevTools;

View File

@ -0,0 +1,144 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DragManager = void 0;
var _utils = require("../../utils");
var _crProtocolHelper = require("./crProtocolHelper");
/**
* 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.
*/
class DragManager {
constructor(page) {
this._crPage = void 0;
this._dragState = null;
this._lastPosition = {
x: 0,
y: 0
};
this._crPage = page;
}
async cancelDrag() {
if (!this._dragState) return false;
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
type: 'dragCancel',
x: this._lastPosition.x,
y: this._lastPosition.y,
data: {
items: [],
dragOperationsMask: 0xFFFF
}
});
this._dragState = null;
return true;
}
async interceptDragCausedByMove(x, y, button, buttons, modifiers, moveCallback) {
this._lastPosition = {
x,
y
};
if (this._dragState) {
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
type: 'dragOver',
x,
y,
data: this._dragState,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
});
return;
}
if (button !== 'left') return moveCallback();
const client = this._crPage._mainFrameSession._client;
let onDragIntercepted;
const dragInterceptedPromise = new Promise(x => onDragIntercepted = x);
await Promise.all(this._crPage._page.frames().map(async frame => {
await frame.nonStallingEvaluateInExistingContext(function () {
let didStartDrag = Promise.resolve(false);
let dragEvent = null;
const dragListener = event => dragEvent = event;
const mouseListener = () => {
didStartDrag = new Promise(callback => {
window.addEventListener('dragstart', dragListener, {
once: true,
capture: true
});
setTimeout(() => callback(dragEvent ? !dragEvent.defaultPrevented : false), 0);
});
};
window.addEventListener('mousemove', mouseListener, {
once: true,
capture: true
});
window.__cleanupDrag = async () => {
const val = await didStartDrag;
window.removeEventListener('mousemove', mouseListener, {
capture: true
});
window.removeEventListener('dragstart', dragListener, {
capture: true
});
delete window.__cleanupDrag;
return val;
};
}.toString(), true, 'utility').catch(() => {});
}));
client.on('Input.dragIntercepted', onDragIntercepted);
try {
await client.send('Input.setInterceptDrags', {
enabled: true
});
} catch {
// If Input.setInterceptDrags is not supported, just do a regular move.
// This can be removed once we stop supporting old Electron.
client.off('Input.dragIntercepted', onDragIntercepted);
return moveCallback();
}
await moveCallback();
const expectingDrag = (await Promise.all(this._crPage._page.frames().map(async frame => {
return frame.nonStallingEvaluateInExistingContext('window.__cleanupDrag && window.__cleanupDrag()', false, 'utility').catch(() => false);
}))).some(x => x);
this._dragState = expectingDrag ? (await dragInterceptedPromise).data : null;
client.off('Input.dragIntercepted', onDragIntercepted);
await client.send('Input.setInterceptDrags', {
enabled: false
});
if (this._dragState) {
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
type: 'dragEnter',
x,
y,
data: this._dragState,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
});
}
}
isDragging() {
return !!this._dragState;
}
async drop(x, y, modifiers) {
(0, _utils.assert)(this._dragState, 'missing drag state');
await this._crPage._mainFrameSession._client.send('Input.dispatchDragEvent', {
type: 'drop',
x,
y,
data: this._dragState,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
});
this._dragState = null;
}
}
exports.DragManager = DragManager;

View File

@ -0,0 +1,156 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRExecutionContext = void 0;
var _crProtocolHelper = require("./crProtocolHelper");
var js = _interopRequireWildcard(require("../javascript"));
var _stackTrace = require("../../utils/stackTrace");
var _utilityScriptSerializers = require("../isomorphic/utilityScriptSerializers");
var _protocolError = require("../protocolError");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class CRExecutionContext {
constructor(client, contextPayload) {
this._client = void 0;
this._contextId = void 0;
this._client = client;
this._contextId = contextPayload.id;
}
async rawEvaluateJSON(expression) {
const {
exceptionDetails,
result: remoteObject
} = await this._client.send('Runtime.evaluate', {
expression,
contextId: this._contextId,
returnByValue: true
}).catch(rewriteError);
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
return remoteObject.value;
}
async rawEvaluateHandle(expression) {
const {
exceptionDetails,
result: remoteObject
} = await this._client.send('Runtime.evaluate', {
expression,
contextId: this._contextId
}).catch(rewriteError);
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
return remoteObject.objectId;
}
rawCallFunctionNoReply(func, ...args) {
this._client.send('Runtime.callFunctionOn', {
functionDeclaration: func.toString(),
arguments: args.map(a => a instanceof js.JSHandle ? {
objectId: a._objectId
} : {
value: a
}),
returnByValue: true,
executionContextId: this._contextId,
userGesture: true
}).catch(() => {});
}
async evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
const {
exceptionDetails,
result: remoteObject
} = await this._client.send('Runtime.callFunctionOn', {
functionDeclaration: expression,
objectId: utilityScript._objectId,
arguments: [{
objectId: utilityScript._objectId
}, ...values.map(value => ({
value
})), ...objectIds.map(objectId => ({
objectId
}))],
returnByValue,
awaitPromise: true,
userGesture: true
}).catch(rewriteError);
if (exceptionDetails) throw new js.JavaScriptErrorInEvaluate((0, _crProtocolHelper.getExceptionMessage)(exceptionDetails));
return returnByValue ? (0, _utilityScriptSerializers.parseEvaluationResultValue)(remoteObject.value) : utilityScript._context.createHandle(remoteObject);
}
async getProperties(context, objectId) {
const response = await this._client.send('Runtime.getProperties', {
objectId,
ownProperties: true
});
const result = new Map();
for (const property of response.result) {
if (!property.enumerable || !property.value) continue;
result.set(property.name, context.createHandle(property.value));
}
return result;
}
createHandle(context, remoteObject) {
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}
async releaseHandle(objectId) {
await (0, _crProtocolHelper.releaseObject)(this._client, objectId);
}
async objectCount(objectId) {
const result = await this._client.send('Runtime.queryObjects', {
prototypeObjectId: objectId
});
const match = result.objects.description.match(/Array\((\d+)\)/);
return +match[1];
}
}
exports.CRExecutionContext = CRExecutionContext;
function rewriteError(error) {
if (error.message.includes('Object reference chain is too long')) return {
result: {
type: 'undefined'
}
};
if (error.message.includes('Object couldn\'t be returned by value')) return {
result: {
type: 'undefined'
}
};
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON')) (0, _stackTrace.rewriteErrorMessage)(error, error.message + ' Are you passing a nested JSHandle?');
if (!js.isJavaScriptErrorInEvaluate(error) && !(0, _protocolError.isSessionClosedError)(error)) throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}
function potentiallyUnserializableValue(remoteObject) {
const value = remoteObject.value;
const unserializableValue = remoteObject.unserializableValue;
return unserializableValue ? js.parseUnserializableValue(unserializableValue) : value;
}
function renderPreview(object) {
if (object.type === 'undefined') return 'undefined';
if ('value' in object) return String(object.value);
if (object.unserializableValue) return String(object.unserializableValue);
if (object.description === 'Object' && object.preview) {
const tokens = [];
for (const {
name,
value
} of object.preview.properties) tokens.push(`${name}: ${value}`);
return `{${tokens.join(', ')}}`;
}
if (object.subtype === 'array' && object.preview) return js.sparseArrayToString(object.preview.properties);
return object.description;
}

View File

@ -0,0 +1,171 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = void 0;
var input = _interopRequireWildcard(require("../input"));
var _macEditingCommands = require("../macEditingCommands");
var _utils = require("../../utils");
var _crProtocolHelper = require("./crProtocolHelper");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class RawKeyboardImpl {
constructor(_client, _isMac, _dragManger) {
this._client = _client;
this._isMac = _isMac;
this._dragManger = _dragManger;
}
_commandsForCode(code, modifiers) {
if (!this._isMac) return [];
const parts = [];
for (const modifier of ['Shift', 'Control', 'Alt', 'Meta']) {
if (modifiers.has(modifier)) parts.push(modifier);
}
parts.push(code);
const shortcut = parts.join('+');
let commands = _macEditingCommands.macEditingCommands[shortcut] || [];
if ((0, _utils.isString)(commands)) commands = [commands];
// Commands that insert text are not supported
commands = commands.filter(x => !x.startsWith('insert'));
// remove the trailing : to match the Chromium command names.
return commands.map(c => c.substring(0, c.length - 1));
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
if (code === 'Escape' && (await this._dragManger.cancelDrag())) return;
const commands = this._commandsForCode(code, modifiers);
await this._client.send('Input.dispatchKeyEvent', {
type: text ? 'keyDown' : 'rawKeyDown',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
windowsVirtualKeyCode: keyCodeWithoutLocation,
code,
commands,
key,
text,
unmodifiedText: text,
autoRepeat,
location,
isKeypad: location === input.keypadLocation
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
await this._client.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
key,
windowsVirtualKeyCode: keyCodeWithoutLocation,
code,
location
});
}
async sendText(text) {
await this._client.send('Input.insertText', {
text
});
}
}
exports.RawKeyboardImpl = RawKeyboardImpl;
class RawMouseImpl {
constructor(page, client, dragManager) {
this._client = void 0;
this._page = void 0;
this._dragManager = void 0;
this._page = page;
this._client = client;
this._dragManager = dragManager;
}
async move(x, y, button, buttons, modifiers, forClick) {
const actualMove = async () => {
await this._client.send('Input.dispatchMouseEvent', {
type: 'mouseMoved',
button,
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
x,
y,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers)
});
};
if (forClick) {
// Avoid extra protocol calls related to drag and drop, because click relies on
// move-down-up protocol commands being sent synchronously.
return actualMove();
}
await this._dragManager.interceptDragCausedByMove(x, y, button, buttons, modifiers, actualMove);
}
async down(x, y, button, buttons, modifiers, clickCount) {
if (this._dragManager.isDragging()) return;
await this._client.send('Input.dispatchMouseEvent', {
type: 'mousePressed',
button,
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
x,
y,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
clickCount
});
}
async up(x, y, button, buttons, modifiers, clickCount) {
if (this._dragManager.isDragging()) {
await this._dragManager.drop(x, y, modifiers);
return;
}
await this._client.send('Input.dispatchMouseEvent', {
type: 'mouseReleased',
button,
buttons: (0, _crProtocolHelper.toButtonsMask)(buttons),
x,
y,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
clickCount
});
}
async wheel(x, y, buttons, modifiers, deltaX, deltaY) {
await this._client.send('Input.dispatchMouseEvent', {
type: 'mouseWheel',
x,
y,
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
deltaX,
deltaY
});
}
}
exports.RawMouseImpl = RawMouseImpl;
class RawTouchscreenImpl {
constructor(client) {
this._client = void 0;
this._client = client;
}
async tap(x, y, modifiers) {
await Promise.all([this._client.send('Input.dispatchTouchEvent', {
type: 'touchStart',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
touchPoints: [{
x,
y
}]
}), this._client.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
touchPoints: []
})]);
}
}
exports.RawTouchscreenImpl = RawTouchscreenImpl;

View File

@ -0,0 +1,686 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRNetworkManager = void 0;
var _helper = require("../helper");
var _eventsHelper = require("../../utils/eventsHelper");
var network = _interopRequireWildcard(require("../network"));
var _utils = require("../../utils");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class CRNetworkManager {
constructor(session, page, serviceWorker, parentManager) {
this._session = void 0;
this._page = void 0;
this._serviceWorker = void 0;
this._parentManager = void 0;
this._requestIdToRequest = new Map();
this._requestIdToRequestWillBeSentEvent = new Map();
this._credentials = null;
this._attemptedAuthentications = new Set();
this._userRequestInterceptionEnabled = false;
this._protocolRequestInterceptionEnabled = false;
this._requestIdToRequestPausedEvent = new Map();
this._eventListeners = void 0;
this._responseExtraInfoTracker = new ResponseExtraInfoTracker();
this._session = session;
this._page = page;
this._serviceWorker = serviceWorker;
this._parentManager = parentManager;
this._eventListeners = this.instrumentNetworkEvents({
session
});
}
instrumentNetworkEvents(sessionInfo) {
const listeners = [_eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Fetch.authRequired', this._onAuthRequired.bind(this)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.responseReceived', this._onResponseReceived.bind(this, sessionInfo)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, sessionInfo))];
if (this._page) {
listeners.push(...[_eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)), _eventsHelper.eventsHelper.addEventListener(sessionInfo.session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage))]);
}
return listeners;
}
async initialize() {
await this._session.send('Network.enable');
}
dispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
}
async authenticate(credentials) {
this._credentials = credentials;
await this._updateProtocolRequestInterception();
}
async setOffline(offline) {
await this._session.send('Network.emulateNetworkConditions', {
offline,
// values of 0 remove any active throttling. crbug.com/456324#c9
latency: 0,
downloadThroughput: -1,
uploadThroughput: -1
});
}
async setRequestInterception(value) {
this._userRequestInterceptionEnabled = value;
await this._updateProtocolRequestInterception();
}
async _updateProtocolRequestInterception() {
const enabled = this._userRequestInterceptionEnabled || !!this._credentials;
if (enabled === this._protocolRequestInterceptionEnabled) return;
this._protocolRequestInterceptionEnabled = enabled;
if (enabled) {
await Promise.all([this._session.send('Network.setCacheDisabled', {
cacheDisabled: true
}), this._session.send('Fetch.enable', {
handleAuthRequests: true,
patterns: [{
urlPattern: '*',
requestStage: 'Request'
}]
})]);
} else {
await Promise.all([this._session.send('Network.setCacheDisabled', {
cacheDisabled: false
}), this._session.send('Fetch.disable')]);
}
}
async clearCache() {
// Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache.
await this._session.send('Network.setCacheDisabled', {
cacheDisabled: true
});
if (!this._protocolRequestInterceptionEnabled) await this._session.send('Network.setCacheDisabled', {
cacheDisabled: false
});
await this._session.send('Network.clearBrowserCache');
}
_onRequestWillBeSent(sessionInfo, event) {
// Request interception doesn't happen for data URLs with Network Service.
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
const requestId = event.requestId;
const requestPausedEvent = this._requestIdToRequestPausedEvent.get(requestId);
if (requestPausedEvent) {
this._onRequest(sessionInfo, event, requestPausedEvent);
this._requestIdToRequestPausedEvent.delete(requestId);
} else {
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
}
} else {
this._onRequest(sessionInfo, event, null);
}
}
_onRequestServedFromCache(event) {
this._responseExtraInfoTracker.requestServedFromCache(event);
}
_onRequestWillBeSentExtraInfo(event) {
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
}
_onAuthRequired(event) {
let response = 'Default';
const shouldProvideCredentials = this._shouldProvideCredentials(event.request.url);
if (this._attemptedAuthentications.has(event.requestId)) {
response = 'CancelAuth';
} else if (shouldProvideCredentials) {
response = 'ProvideCredentials';
this._attemptedAuthentications.add(event.requestId);
}
const {
username,
password
} = shouldProvideCredentials && this._credentials ? this._credentials : {
username: undefined,
password: undefined
};
this._session._sendMayFail('Fetch.continueWithAuth', {
requestId: event.requestId,
authChallengeResponse: {
response,
username,
password
}
});
}
_shouldProvideCredentials(url) {
if (!this._credentials) return false;
return !this._credentials.origin || new URL(url).origin.toLowerCase() === this._credentials.origin.toLowerCase();
}
_onRequestPaused(sessionInfo, event) {
if (!event.networkId) {
// Fetch without networkId means that request was not recongnized by inspector, and
// it will never receive Network.requestWillBeSent. Most likely, this is an internal request
// that we can safely fail.
this._session._sendMayFail('Fetch.failRequest', {
requestId: event.requestId,
errorReason: 'Aborted'
});
return;
}
if (event.request.url.startsWith('data:')) return;
const requestId = event.networkId;
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
if (requestWillBeSentEvent) {
this._onRequest(sessionInfo, requestWillBeSentEvent, event);
this._requestIdToRequestWillBeSentEvent.delete(requestId);
} else {
this._requestIdToRequestPausedEvent.set(requestId, event);
}
}
_onRequest(sessionInfo, requestWillBeSentEvent, requestPausedEvent) {
var _this$_page, _this$_page2, _this$_page3;
if (requestWillBeSentEvent.request.url.startsWith('data:')) return;
let redirectedFrom = null;
if (requestWillBeSentEvent.redirectResponse) {
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
// If we connect late to the target, we could have missed the requestWillBeSent event.
if (request) {
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp, requestWillBeSentEvent.redirectHasExtraInfo);
redirectedFrom = request;
}
}
let frame = requestWillBeSentEvent.frameId ? (_this$_page = this._page) === null || _this$_page === void 0 ? void 0 : _this$_page._frameManager.frame(requestWillBeSentEvent.frameId) : sessionInfo.workerFrame;
// Requests from workers lack frameId, because we receive Network.requestWillBeSent
// on the worker target. However, we receive Fetch.requestPaused on the page target,
// and lack workerFrame there. Luckily, Fetch.requestPaused provides a frameId.
if (!frame && this._page && requestPausedEvent && requestPausedEvent.frameId) frame = this._page._frameManager.frame(requestPausedEvent.frameId);
// Check if it's main resource request interception (targetId === main frame id).
if (!frame && this._page && requestWillBeSentEvent.frameId === ((_this$_page2 = this._page) === null || _this$_page2 === void 0 ? void 0 : _this$_page2._delegate)._targetId) {
// Main resource request for the page is being intercepted so the Frame is not created
// yet. Precreate it here for the purposes of request interception. It will be updated
// later as soon as the request continues and we receive frame tree from the page.
frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null);
}
// CORS options preflight request is generated by the network stack. If interception is enabled,
// we accept all CORS options, assuming that this was intended when setting route.
//
// Note: it would be better to match the URL against interception patterns.
const isInterceptedOptionsPreflight = !!requestPausedEvent && requestPausedEvent.request.method === 'OPTIONS' && requestWillBeSentEvent.initiator.type === 'preflight';
if (isInterceptedOptionsPreflight && (this._page || this._serviceWorker).needsRequestInterception()) {
const requestHeaders = requestPausedEvent.request.headers;
const responseHeaders = [{
name: 'Access-Control-Allow-Origin',
value: requestHeaders['Origin'] || '*'
}, {
name: 'Access-Control-Allow-Methods',
value: requestHeaders['Access-Control-Request-Method'] || 'GET, POST, OPTIONS, DELETE'
}, {
name: 'Access-Control-Allow-Credentials',
value: 'true'
}];
if (requestHeaders['Access-Control-Request-Headers']) responseHeaders.push({
name: 'Access-Control-Allow-Headers',
value: requestHeaders['Access-Control-Request-Headers']
});
this._session._sendMayFail('Fetch.fulfillRequest', {
requestId: requestPausedEvent.requestId,
responseCode: 204,
responsePhrase: network.STATUS_TEXTS['204'],
responseHeaders,
body: ''
});
return;
}
// Non-service-worker requests MUST have a frame—if they don't, we pretend there was no request
if (!frame && !this._serviceWorker) {
if (requestPausedEvent) this._session._sendMayFail('Fetch.continueRequest', {
requestId: requestPausedEvent.requestId
});
return;
}
let route = null;
if (requestPausedEvent) {
// We do not support intercepting redirects.
if (redirectedFrom || !this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) this._session._sendMayFail('Fetch.continueRequest', {
requestId: requestPausedEvent.requestId
});else route = new RouteImpl(this._session, requestPausedEvent.requestId);
}
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
const request = new InterceptableRequest({
session: sessionInfo.session,
context: (this._page || this._serviceWorker)._browserContext,
frame: frame || null,
serviceWorker: this._serviceWorker || null,
documentId,
route,
requestWillBeSentEvent,
requestPausedEvent,
redirectedFrom
});
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
if (requestPausedEvent && !requestPausedEvent.responseStatusCode && !requestPausedEvent.responseErrorReason) {
// We will not receive extra info when intercepting the request.
// Use the headers from the Fetch.requestPausedPayload and release the allHeaders()
// right away, so that client can call it from the route handler.
request.request.setRawRequestHeaders((0, _utils.headersObjectToArray)(requestPausedEvent.request.headers, '\n'));
}
(((_this$_page3 = this._page) === null || _this$_page3 === void 0 ? void 0 : _this$_page3._frameManager) || this._serviceWorker).requestStarted(request.request, route || undefined);
}
_createResponse(request, responsePayload, hasExtraInfo) {
var _responsePayload$secu, _responsePayload$secu2, _responsePayload$secu3, _responsePayload$secu4, _responsePayload$secu5;
const getResponseBody = async () => {
const contentLengthHeader = Object.entries(responsePayload.headers).find(header => header[0].toLowerCase() === 'content-length');
const expectedLength = contentLengthHeader ? +contentLengthHeader[1] : undefined;
const session = request.session;
const response = await session.send('Network.getResponseBody', {
requestId: request._requestId
});
if (response.body || !expectedLength) return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
// For <link prefetch we are going to receive empty body with non-emtpy content-length expectation. Reach out for the actual content.
const resource = await session.send('Network.loadNetworkResource', {
url: request.request.url(),
frameId: this._serviceWorker ? undefined : request.request.frame()._id,
options: {
disableCache: false,
includeCredentials: true
}
});
const chunks = [];
while (resource.resource.stream) {
const chunk = await session.send('IO.read', {
handle: resource.resource.stream
});
chunks.push(Buffer.from(chunk.data, chunk.base64Encoded ? 'base64' : 'utf-8'));
if (chunk.eof) {
await session.send('IO.close', {
handle: resource.resource.stream
});
break;
}
}
return Buffer.concat(chunks);
};
const timingPayload = responsePayload.timing;
let timing;
if (timingPayload && !this._responseExtraInfoTracker.servedFromCache(request._requestId)) {
timing = {
startTime: (timingPayload.requestTime - request._timestamp + request._wallTime) * 1000,
domainLookupStart: timingPayload.dnsStart,
domainLookupEnd: timingPayload.dnsEnd,
connectStart: timingPayload.connectStart,
secureConnectionStart: timingPayload.sslStart,
connectEnd: timingPayload.connectEnd,
requestStart: timingPayload.sendStart,
responseStart: timingPayload.receiveHeadersEnd
};
} else {
timing = {
startTime: request._wallTime * 1000,
domainLookupStart: -1,
domainLookupEnd: -1,
connectStart: -1,
secureConnectionStart: -1,
connectEnd: -1,
requestStart: -1,
responseStart: -1
};
}
const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, (0, _utils.headersObjectToArray)(responsePayload.headers), timing, getResponseBody, !!responsePayload.fromServiceWorker, responsePayload.protocol);
if (responsePayload !== null && responsePayload !== void 0 && responsePayload.remoteIPAddress && typeof (responsePayload === null || responsePayload === void 0 ? void 0 : responsePayload.remotePort) === 'number') {
response._serverAddrFinished({
ipAddress: responsePayload.remoteIPAddress,
port: responsePayload.remotePort
});
} else {
response._serverAddrFinished();
}
response._securityDetailsFinished({
protocol: responsePayload === null || responsePayload === void 0 ? void 0 : (_responsePayload$secu = responsePayload.securityDetails) === null || _responsePayload$secu === void 0 ? void 0 : _responsePayload$secu.protocol,
subjectName: responsePayload === null || responsePayload === void 0 ? void 0 : (_responsePayload$secu2 = responsePayload.securityDetails) === null || _responsePayload$secu2 === void 0 ? void 0 : _responsePayload$secu2.subjectName,
issuer: responsePayload === null || responsePayload === void 0 ? void 0 : (_responsePayload$secu3 = responsePayload.securityDetails) === null || _responsePayload$secu3 === void 0 ? void 0 : _responsePayload$secu3.issuer,
validFrom: responsePayload === null || responsePayload === void 0 ? void 0 : (_responsePayload$secu4 = responsePayload.securityDetails) === null || _responsePayload$secu4 === void 0 ? void 0 : _responsePayload$secu4.validFrom,
validTo: responsePayload === null || responsePayload === void 0 ? void 0 : (_responsePayload$secu5 = responsePayload.securityDetails) === null || _responsePayload$secu5 === void 0 ? void 0 : _responsePayload$secu5.validTo
});
this._responseExtraInfoTracker.processResponse(request._requestId, response, hasExtraInfo);
return response;
}
_handleRequestRedirect(request, responsePayload, timestamp, hasExtraInfo) {
var _this$_page4, _this$_page5;
const response = this._createResponse(request, responsePayload, hasExtraInfo);
response.setTransferSize(null);
response.setEncodedBodySize(null);
response._requestFinished((timestamp - request._timestamp) * 1000);
this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId) this._attemptedAuthentications.delete(request._interceptionId);
(((_this$_page4 = this._page) === null || _this$_page4 === void 0 ? void 0 : _this$_page4._frameManager) || this._serviceWorker).requestReceivedResponse(response);
(((_this$_page5 = this._page) === null || _this$_page5 === void 0 ? void 0 : _this$_page5._frameManager) || this._serviceWorker).reportRequestFinished(request.request, response);
}
_onResponseReceivedExtraInfo(event) {
this._responseExtraInfoTracker.responseReceivedExtraInfo(event);
}
_onResponseReceived(sessionInfo, event) {
var _this$_page6;
let request = this._requestIdToRequest.get(event.requestId);
// For frame-level Requests that are handled by a Service Worker's fetch handler, we'll never get a requestPaused event, so we need to
// manually create the request. In an ideal world, crNetworkManager would be able to know this on Network.requestWillBeSent, but there
// is not enough metadata there.
if (!request && event.response.fromServiceWorker) {
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
if (requestWillBeSentEvent) {
this._requestIdToRequestWillBeSentEvent.delete(event.requestId);
this._onRequest(sessionInfo, requestWillBeSentEvent, null /* requestPausedPayload */);
request = this._requestIdToRequest.get(event.requestId);
}
}
// FileUpload sends a response without a matching request.
if (!request) return;
const response = this._createResponse(request, event.response, event.hasExtraInfo);
(((_this$_page6 = this._page) === null || _this$_page6 === void 0 ? void 0 : _this$_page6._frameManager) || this._serviceWorker).requestReceivedResponse(response);
}
_onLoadingFinished(event) {
var _this$_page7;
this._responseExtraInfoTracker.loadingFinished(event);
let request = this._requestIdToRequest.get(event.requestId);
if (!request) request = this._maybeAdoptMainRequest(event.requestId);
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request) return;
// Under certain conditions we never get the Network.responseReceived
// event from protocol. @see https://crbug.com/883475
const response = request.request._existingResponse();
if (response) {
response.setTransferSize(event.encodedDataLength);
response.responseHeadersSize().then(size => response.setEncodedBodySize(event.encodedDataLength - size));
response._requestFinished(_helper.helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
}
this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId) this._attemptedAuthentications.delete(request._interceptionId);
(((_this$_page7 = this._page) === null || _this$_page7 === void 0 ? void 0 : _this$_page7._frameManager) || this._serviceWorker).reportRequestFinished(request.request, response);
}
_onLoadingFailed(sessionInfo, event) {
var _this$_page8;
this._responseExtraInfoTracker.loadingFailed(event);
let request = this._requestIdToRequest.get(event.requestId);
if (!request) request = this._maybeAdoptMainRequest(event.requestId);
if (!request) {
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
if (requestWillBeSentEvent) {
// This is a case where request has failed before we had a chance to intercept it.
// We stop waiting for Fetch.requestPaused (it might never come), and dispatch request event
// right away, followed by requestfailed event.
this._requestIdToRequestWillBeSentEvent.delete(event.requestId);
this._onRequest(sessionInfo, requestWillBeSentEvent, null);
request = this._requestIdToRequest.get(event.requestId);
}
}
// For certain requestIds we never receive requestWillBeSent event.
// @see https://crbug.com/750469
if (!request) return;
const response = request.request._existingResponse();
if (response) {
response.setTransferSize(null);
response.setEncodedBodySize(null);
response._requestFinished(_helper.helper.secondsToRoundishMillis(event.timestamp - request._timestamp));
}
this._requestIdToRequest.delete(request._requestId);
if (request._interceptionId) this._attemptedAuthentications.delete(request._interceptionId);
request.request._setFailureText(event.errorText);
(((_this$_page8 = this._page) === null || _this$_page8 === void 0 ? void 0 : _this$_page8._frameManager) || this._serviceWorker).requestFailed(request.request, !!event.canceled);
}
_maybeAdoptMainRequest(requestId) {
// OOPIF has a main request that starts in the parent session but finishes in the child session.
if (!this._parentManager) return;
const request = this._parentManager._requestIdToRequest.get(requestId);
// Main requests have matching loaderId and requestId.
if (!request || request._documentId !== requestId) return;
this._requestIdToRequest.set(requestId, request);
request.session = this._session;
this._parentManager._requestIdToRequest.delete(requestId);
if (request._interceptionId && this._parentManager._attemptedAuthentications.has(request._interceptionId)) {
this._parentManager._attemptedAuthentications.delete(request._interceptionId);
this._attemptedAuthentications.add(request._interceptionId);
}
return request;
}
}
exports.CRNetworkManager = CRNetworkManager;
class InterceptableRequest {
constructor(options) {
this.request = void 0;
this._requestId = void 0;
this._interceptionId = void 0;
this._documentId = void 0;
this._timestamp = void 0;
this._wallTime = void 0;
this._route = void 0;
this._redirectedFrom = void 0;
this.session = void 0;
const {
session,
context,
frame,
documentId,
route,
requestWillBeSentEvent,
requestPausedEvent,
redirectedFrom,
serviceWorker
} = options;
this.session = session;
this._timestamp = requestWillBeSentEvent.timestamp;
this._wallTime = requestWillBeSentEvent.wallTime;
this._requestId = requestWillBeSentEvent.requestId;
this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
this._documentId = documentId;
this._route = route;
this._redirectedFrom = redirectedFrom;
const {
headers,
method,
url,
postDataEntries = null
} = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
const type = (requestWillBeSentEvent.type || '').toLowerCase();
let postDataBuffer = null;
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes) postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
this.request = new network.Request(context, frame, serviceWorker, (redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom.request) || null, documentId, url, type, method, postDataBuffer, (0, _utils.headersObjectToArray)(headers));
}
_routeForRedirectChain() {
let request = this;
while (request._redirectedFrom) request = request._redirectedFrom;
return request._route;
}
}
class RouteImpl {
constructor(session, interceptionId) {
this._session = void 0;
this._interceptionId = void 0;
this._session = session;
this._interceptionId = interceptionId;
}
async continue(request, overrides) {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session._sendMayFail('Fetch.continueRequest', {
requestId: this._interceptionId,
url: overrides.url,
headers: overrides.headers,
method: overrides.method,
postData: overrides.postData ? overrides.postData.toString('base64') : undefined
});
}
async fulfill(response) {
const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
const responseHeaders = splitSetCookieHeader(response.headers);
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session._sendMayFail('Fetch.fulfillRequest', {
requestId: this._interceptionId,
responseCode: response.status,
responsePhrase: network.STATUS_TEXTS[String(response.status)],
responseHeaders,
body
});
}
async abort(errorCode = 'failed') {
const errorReason = errorReasons[errorCode];
(0, _utils.assert)(errorReason, 'Unknown error code: ' + errorCode);
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session._sendMayFail('Fetch.failRequest', {
requestId: this._interceptionId,
errorReason
});
}
}
function splitSetCookieHeader(headers) {
const index = headers.findIndex(({
name
}) => name.toLowerCase() === 'set-cookie');
if (index === -1) return headers;
const header = headers[index];
const values = header.value.split('\n');
if (values.length === 1) return headers;
const result = headers.slice();
result.splice(index, 1, ...values.map(value => ({
name: header.name,
value
})));
return result;
}
const errorReasons = {
'aborted': 'Aborted',
'accessdenied': 'AccessDenied',
'addressunreachable': 'AddressUnreachable',
'blockedbyclient': 'BlockedByClient',
'blockedbyresponse': 'BlockedByResponse',
'connectionaborted': 'ConnectionAborted',
'connectionclosed': 'ConnectionClosed',
'connectionfailed': 'ConnectionFailed',
'connectionrefused': 'ConnectionRefused',
'connectionreset': 'ConnectionReset',
'internetdisconnected': 'InternetDisconnected',
'namenotresolved': 'NameNotResolved',
'timedout': 'TimedOut',
'failed': 'Failed'
};
// This class aligns responses with response headers from extra info:
// - Network.requestWillBeSent, Network.responseReceived, Network.loadingFinished/loadingFailed are
// dispatched using one channel.
// - Network.requestWillBeSentExtraInfo and Network.responseReceivedExtraInfo are dispatched on
// another channel. Those channels are not associated, so events come in random order.
//
// This class will associate responses with the new headers. These extra info headers will become
// available to client reliably upon requestfinished event only. It consumes CDP
// signals on one end and processResponse(network.Response) signals on the other hands. It then makes
// sure that responses have all the extra headers in place by the time request finishes.
//
// The shape of the instrumentation API is deliberately following the CDP, so that it
// is clear what is called when and what this means to the tracker without extra
// documentation.
class ResponseExtraInfoTracker {
constructor() {
this._requests = new Map();
}
requestWillBeSentExtraInfo(event) {
const info = this._getOrCreateEntry(event.requestId);
info.requestWillBeSentExtraInfo.push(event);
this._patchHeaders(info, info.requestWillBeSentExtraInfo.length - 1);
this._checkFinished(info);
}
requestServedFromCache(event) {
const info = this._getOrCreateEntry(event.requestId);
info.servedFromCache = true;
}
servedFromCache(requestId) {
const info = this._requests.get(requestId);
return !!(info !== null && info !== void 0 && info.servedFromCache);
}
responseReceivedExtraInfo(event) {
const info = this._getOrCreateEntry(event.requestId);
info.responseReceivedExtraInfo.push(event);
this._patchHeaders(info, info.responseReceivedExtraInfo.length - 1);
this._checkFinished(info);
}
processResponse(requestId, response, hasExtraInfo) {
var _info;
let info = this._requests.get(requestId);
// Cached responses have erroneous "hasExtraInfo" flag.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1340398
if (!hasExtraInfo || (_info = info) !== null && _info !== void 0 && _info.servedFromCache) {
// Use "provisional" headers as "raw" ones.
response.request().setRawRequestHeaders(null);
response.setResponseHeadersSize(null);
response.setRawResponseHeaders(null);
return;
}
info = this._getOrCreateEntry(requestId);
info.responses.push(response);
this._patchHeaders(info, info.responses.length - 1);
}
loadingFinished(event) {
const info = this._requests.get(event.requestId);
if (!info) return;
info.loadingFinished = event;
this._checkFinished(info);
}
loadingFailed(event) {
const info = this._requests.get(event.requestId);
if (!info) return;
info.loadingFailed = event;
this._checkFinished(info);
}
_getOrCreateEntry(requestId) {
let info = this._requests.get(requestId);
if (!info) {
info = {
requestId: requestId,
requestWillBeSentExtraInfo: [],
responseReceivedExtraInfo: [],
responses: []
};
this._requests.set(requestId, info);
}
return info;
}
_patchHeaders(info, index) {
const response = info.responses[index];
const requestExtraInfo = info.requestWillBeSentExtraInfo[index];
if (response && requestExtraInfo) {
response.request().setRawRequestHeaders((0, _utils.headersObjectToArray)(requestExtraInfo.headers, '\n'));
info.requestWillBeSentExtraInfo[index] = undefined;
}
const responseExtraInfo = info.responseReceivedExtraInfo[index];
if (response && responseExtraInfo) {
var _responseExtraInfo$he;
response.setResponseHeadersSize(((_responseExtraInfo$he = responseExtraInfo.headersText) === null || _responseExtraInfo$he === void 0 ? void 0 : _responseExtraInfo$he.length) || 0);
response.setRawResponseHeaders((0, _utils.headersObjectToArray)(responseExtraInfo.headers, '\n'));
info.responseReceivedExtraInfo[index] = undefined;
}
}
_checkFinished(info) {
if (!info.loadingFinished && !info.loadingFailed) return;
if (info.responses.length <= info.responseReceivedExtraInfo.length) {
// We have extra info for each response.
this._stopTracking(info.requestId);
return;
}
// We are not done yet.
}
_stopTracking(requestId) {
this._requests.delete(requestId);
}
}

View File

@ -0,0 +1,147 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRPDF = void 0;
var _utils = require("../../utils");
var _crProtocolHelper = require("./crProtocolHelper");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
const PagePaperFormats = {
letter: {
width: 8.5,
height: 11
},
legal: {
width: 8.5,
height: 14
},
tabloid: {
width: 11,
height: 17
},
ledger: {
width: 17,
height: 11
},
a0: {
width: 33.1,
height: 46.8
},
a1: {
width: 23.4,
height: 33.1
},
a2: {
width: 16.54,
height: 23.4
},
a3: {
width: 11.7,
height: 16.54
},
a4: {
width: 8.27,
height: 11.7
},
a5: {
width: 5.83,
height: 8.27
},
a6: {
width: 4.13,
height: 5.83
}
};
const unitToPixels = {
'px': 1,
'in': 96,
'cm': 37.8,
'mm': 3.78
};
function convertPrintParameterToInches(text) {
if (text === undefined) return undefined;
let unit = text.substring(text.length - 2).toLowerCase();
let valueText = '';
if (unitToPixels.hasOwnProperty(unit)) {
valueText = text.substring(0, text.length - 2);
} else {
// In case of unknown unit try to parse the whole parameter as number of pixels.
// This is consistent with phantom's paperSize behavior.
unit = 'px';
valueText = text;
}
const value = Number(valueText);
(0, _utils.assert)(!isNaN(value), 'Failed to parse parameter value: ' + text);
const pixels = value * unitToPixels[unit];
return pixels / 96;
}
class CRPDF {
constructor(client) {
this._client = void 0;
this._client = client;
}
async generate(options) {
const {
scale = 1,
displayHeaderFooter = false,
headerTemplate = '',
footerTemplate = '',
printBackground = false,
landscape = false,
pageRanges = '',
preferCSSPageSize = false,
margin = {}
} = options;
let paperWidth = 8.5;
let paperHeight = 11;
if (options.format) {
const format = PagePaperFormats[options.format.toLowerCase()];
(0, _utils.assert)(format, 'Unknown paper format: ' + options.format);
paperWidth = format.width;
paperHeight = format.height;
} else {
paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
}
const marginTop = convertPrintParameterToInches(margin.top) || 0;
const marginLeft = convertPrintParameterToInches(margin.left) || 0;
const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
const marginRight = convertPrintParameterToInches(margin.right) || 0;
const result = await this._client.send('Page.printToPDF', {
transferMode: 'ReturnAsStream',
landscape,
displayHeaderFooter,
headerTemplate,
footerTemplate,
printBackground,
scale,
paperWidth,
paperHeight,
marginTop,
marginBottom,
marginLeft,
marginRight,
pageRanges,
preferCSSPageSize
});
return await (0, _crProtocolHelper.readProtocolStream)(this._client, result.stream);
}
}
exports.CRPDF = CRPDF;

View File

@ -0,0 +1,131 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.exceptionToError = exceptionToError;
exports.getExceptionMessage = getExceptionMessage;
exports.readProtocolStream = readProtocolStream;
exports.releaseObject = releaseObject;
exports.saveProtocolStream = saveProtocolStream;
exports.toButtonsMask = toButtonsMask;
exports.toConsoleMessageLocation = toConsoleMessageLocation;
exports.toModifiersMask = toModifiersMask;
var _fs = _interopRequireDefault(require("fs"));
var _fileUtils = require("../../utils/fileUtils");
var _stackTrace = require("../../utils/stackTrace");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
function getExceptionMessage(exceptionDetails) {
if (exceptionDetails.exception) return exceptionDetails.exception.description || String(exceptionDetails.exception.value);
let message = exceptionDetails.text;
if (exceptionDetails.stackTrace) {
for (const callframe of exceptionDetails.stackTrace.callFrames) {
const location = callframe.url + ':' + callframe.lineNumber + ':' + callframe.columnNumber;
const functionName = callframe.functionName || '<anonymous>';
message += `\n at ${functionName} (${location})`;
}
}
return message;
}
async function releaseObject(client, objectId) {
await client.send('Runtime.releaseObject', {
objectId
}).catch(error => {});
}
async function saveProtocolStream(client, handle, path) {
let eof = false;
await (0, _fileUtils.mkdirIfNeeded)(path);
const fd = await _fs.default.promises.open(path, 'w');
while (!eof) {
const response = await client.send('IO.read', {
handle
});
eof = response.eof;
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
await fd.write(buf);
}
await fd.close();
await client.send('IO.close', {
handle
});
}
async function readProtocolStream(client, handle) {
let eof = false;
const chunks = [];
while (!eof) {
const response = await client.send('IO.read', {
handle
});
eof = response.eof;
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
chunks.push(buf);
}
await client.send('IO.close', {
handle
});
return Buffer.concat(chunks);
}
function toConsoleMessageLocation(stackTrace) {
return stackTrace && stackTrace.callFrames.length ? {
url: stackTrace.callFrames[0].url,
lineNumber: stackTrace.callFrames[0].lineNumber,
columnNumber: stackTrace.callFrames[0].columnNumber
} : {
url: '',
lineNumber: 0,
columnNumber: 0
};
}
function exceptionToError(exceptionDetails) {
const messageWithStack = getExceptionMessage(exceptionDetails);
const lines = messageWithStack.split('\n');
const firstStackTraceLine = lines.findIndex(line => line.startsWith(' at'));
let messageWithName = '';
let stack = '';
if (firstStackTraceLine === -1) {
messageWithName = messageWithStack;
} else {
messageWithName = lines.slice(0, firstStackTraceLine).join('\n');
stack = messageWithStack;
}
const {
name,
message
} = (0, _stackTrace.splitErrorMessage)(messageWithName);
const err = new Error(message);
err.stack = stack;
err.name = name;
return err;
}
function toModifiersMask(modifiers) {
let mask = 0;
if (modifiers.has('Alt')) mask |= 1;
if (modifiers.has('Control')) mask |= 2;
if (modifiers.has('Meta')) mask |= 4;
if (modifiers.has('Shift')) mask |= 8;
return mask;
}
function toButtonsMask(buttons) {
let mask = 0;
if (buttons.has('left')) mask |= 1;
if (buttons.has('right')) mask |= 2;
if (buttons.has('middle')) mask |= 4;
return mask;
}

View File

@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRServiceWorker = void 0;
var _page = require("../page");
var _crExecutionContext = require("./crExecutionContext");
var _crNetworkManager = require("./crNetworkManager");
var network = _interopRequireWildcard(require("../network"));
var _browserContext = require("../browserContext");
var _utils = require("../../utils");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* 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.
*/
class CRServiceWorker extends _page.Worker {
constructor(browserContext, session, url) {
super(browserContext, url);
this._browserContext = void 0;
this._networkManager = void 0;
this._session = void 0;
this._extraHTTPHeaders = null;
this._session = session;
this._browserContext = browserContext;
if (!!process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS) this._networkManager = new _crNetworkManager.CRNetworkManager(session, null, this, null);
session.once('Runtime.executionContextCreated', event => {
this._createExecutionContext(new _crExecutionContext.CRExecutionContext(session, event.context));
});
if (this._networkManager && this._isNetworkInspectionEnabled()) {
this._networkManager.initialize().catch(() => {});
this.updateRequestInterception();
this.updateExtraHTTPHeaders(true);
this.updateHttpCredentials(true);
this.updateOffline(true);
}
session.send('Runtime.enable', {}).catch(e => {});
session.send('Runtime.runIfWaitingForDebugger').catch(e => {});
session.on('Inspector.targetReloadedAfterCrash', () => {
// Resume service worker after restart.
session._sendMayFail('Runtime.runIfWaitingForDebugger', {});
});
}
async updateOffline(initial) {
var _this$_networkManager;
if (!this._isNetworkInspectionEnabled()) return;
const offline = !!this._browserContext._options.offline;
if (!initial || offline) await ((_this$_networkManager = this._networkManager) === null || _this$_networkManager === void 0 ? void 0 : _this$_networkManager.setOffline(offline));
}
async updateHttpCredentials(initial) {
var _this$_networkManager2;
if (!this._isNetworkInspectionEnabled()) return;
const credentials = this._browserContext._options.httpCredentials || null;
if (!initial || credentials) await ((_this$_networkManager2 = this._networkManager) === null || _this$_networkManager2 === void 0 ? void 0 : _this$_networkManager2.authenticate(credentials));
}
async updateExtraHTTPHeaders(initial) {
if (!this._isNetworkInspectionEnabled()) return;
const headers = network.mergeHeaders([this._browserContext._options.extraHTTPHeaders, this._extraHTTPHeaders]);
if (!initial || headers.length) await this._session.send('Network.setExtraHTTPHeaders', {
headers: (0, _utils.headersArrayToObject)(headers, false /* lowerCase */)
});
}
updateRequestInterception() {
if (!this._networkManager || !this._isNetworkInspectionEnabled()) return Promise.resolve();
return this._networkManager.setRequestInterception(this.needsRequestInterception()).catch(e => {});
}
needsRequestInterception() {
return this._isNetworkInspectionEnabled() && !!this._browserContext._requestInterceptor;
}
reportRequestFinished(request, response) {
this._browserContext.emit(_browserContext.BrowserContext.Events.RequestFinished, {
request,
response
});
}
requestFailed(request, _canceled) {
this._browserContext.emit(_browserContext.BrowserContext.Events.RequestFailed, request);
}
requestReceivedResponse(response) {
this._browserContext.emit(_browserContext.BrowserContext.Events.Response, response);
}
requestStarted(request, route) {
this._browserContext.emit(_browserContext.BrowserContext.Events.Request, request);
if (route) {
var _this$_browserContext, _this$_browserContext2;
const r = new network.Route(request, route);
if ((_this$_browserContext = (_this$_browserContext2 = this._browserContext)._requestInterceptor) !== null && _this$_browserContext !== void 0 && _this$_browserContext.call(_this$_browserContext2, r, request)) return;
r.continue({
isFallback: true
});
}
}
_isNetworkInspectionEnabled() {
return this._browserContext._options.serviceWorkers !== 'block';
}
}
exports.CRServiceWorker = CRServiceWorker;

View File

@ -0,0 +1,146 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.platformToFontFamilies = void 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.
*/
// DO NOT EDIT: this map is generated from Chromium source code by utils/generate_chromium_default_font_families.js
const platformToFontFamilies = {
'linux': {
'fontFamilies': {
'standard': 'Times New Roman',
'fixed': 'Monospace',
'serif': 'Times New Roman',
'sansSerif': 'Arial',
'cursive': 'Comic Sans MS',
'fantasy': 'Impact'
}
},
'mac': {
'fontFamilies': {
'standard': 'Times',
'fixed': 'Courier',
'serif': 'Times',
'sansSerif': 'Helvetica',
'cursive': 'Apple Chancery',
'fantasy': 'Papyrus'
},
'forScripts': [{
'script': 'jpan',
'fontFamilies': {
'standard': 'Hiragino Kaku Gothic ProN',
'fixed': 'Osaka-Mono',
'serif': 'Hiragino Mincho ProN',
'sansSerif': 'Hiragino Kaku Gothic ProN'
}
}, {
'script': 'hang',
'fontFamilies': {
'standard': 'Apple SD Gothic Neo',
'serif': 'AppleMyungjo',
'sansSerif': 'Apple SD Gothic Neo'
}
}, {
'script': 'hans',
'fontFamilies': {
'standard': ',PingFang SC,STHeiti',
'serif': 'Songti SC',
'sansSerif': ',PingFang SC,STHeiti',
'cursive': 'Kaiti SC'
}
}, {
'script': 'hant',
'fontFamilies': {
'standard': ',PingFang TC,Heiti TC',
'serif': 'Songti TC',
'sansSerif': ',PingFang TC,Heiti TC',
'cursive': 'Kaiti TC'
}
}]
},
'win': {
'fontFamilies': {
'standard': 'Times New Roman',
'fixed': 'Consolas',
'serif': 'Times New Roman',
'sansSerif': 'Arial',
'cursive': 'Comic Sans MS',
'fantasy': 'Impact'
},
'forScripts': [{
'script': 'cyrl',
'fontFamilies': {
'standard': 'Times New Roman',
'fixed': 'Courier New',
'serif': 'Times New Roman',
'sansSerif': 'Arial'
}
}, {
'script': 'arab',
'fontFamilies': {
'fixed': 'Courier New',
'sansSerif': 'Segoe UI'
}
}, {
'script': 'grek',
'fontFamilies': {
'standard': 'Times New Roman',
'fixed': 'Courier New',
'serif': 'Times New Roman',
'sansSerif': 'Arial'
}
}, {
'script': 'jpan',
'fontFamilies': {
'standard': ',Meiryo,Yu Gothic',
'fixed': 'MS Gothic',
'serif': ',Yu Mincho,MS PMincho',
'sansSerif': ',Meiryo,Yu Gothic'
}
}, {
'script': 'hang',
'fontFamilies': {
'standard': 'Malgun Gothic',
'fixed': 'Gulimche',
'serif': 'Batang',
'sansSerif': 'Malgun Gothic',
'cursive': 'Gungsuh'
}
}, {
'script': 'hans',
'fontFamilies': {
'standard': 'Microsoft YaHei',
'fixed': 'NSimsun',
'serif': 'Simsun',
'sansSerif': 'Microsoft YaHei',
'cursive': 'KaiTi'
}
}, {
'script': 'hant',
'fontFamilies': {
'standard': 'Microsoft JhengHei',
'fixed': 'MingLiU',
'serif': 'PMingLiU',
'sansSerif': 'Microsoft JhengHei',
'cursive': 'DFKai-SB'
}
}]
}
};
exports.platformToFontFamilies = platformToFontFamilies;

View File

@ -0,0 +1,155 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.VideoRecorder = void 0;
var _utils = require("../../utils");
var _page = require("../page");
var _processLauncher = require("../../utils/processLauncher");
var _progress = require("../progress");
var _instrumentation = require("../instrumentation");
/**
* 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.
*/
const fps = 25;
class VideoRecorder {
static async launch(page, ffmpegPath, options) {
if (!options.outputFile.endsWith('.webm')) throw new Error('File must have .webm extension');
const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), page);
controller.setLogName('browser');
return await controller.run(async progress => {
const recorder = new VideoRecorder(page, ffmpegPath, progress);
await recorder._launch(options);
return recorder;
});
}
constructor(page, ffmpegPath, progress) {
this._process = null;
this._gracefullyClose = null;
this._lastWritePromise = Promise.resolve();
this._lastFrameTimestamp = 0;
this._lastFrameBuffer = null;
this._lastWriteTimestamp = 0;
this._progress = void 0;
this._frameQueue = [];
this._isStopped = false;
this._ffmpegPath = void 0;
this._progress = progress;
this._ffmpegPath = ffmpegPath;
page.on(_page.Page.Events.ScreencastFrame, frame => this.writeFrame(frame.buffer, frame.timestamp));
}
async _launch(options) {
// How to tune the codec:
// 1. Read vp8 documentation to figure out the options.
// https://www.webmproject.org/docs/encoder-parameters/
// 2. Use the following command to map the options to ffmpeg arguments.
// $ ./third_party/ffmpeg/ffmpeg-mac -h encoder=vp8
// 3. A bit more about passing vp8 options to ffmpeg.
// https://trac.ffmpeg.org/wiki/Encode/VP8
// 4. Tuning for VP9:
// https://developers.google.com/media/vp9/live-encoding
//
// How to stress-test video recording (runs 10 recorders in parallel to book all cpus available):
// $ node ./utils/video_stress.js
//
// We use the following vp8 options:
// "-qmin 0 -qmax 50" - quality variation from 0 to 50.
// Suggested here: https://trac.ffmpeg.org/wiki/Encode/VP8
// "-crf 8" - constant quality mode, 4-63, lower means better quality.
// "-deadline realtime -speed 8" - do not use too much cpu to keep up with incoming frames.
// "-b:v 1M" - video bitrate. Default value is too low for vp8
// Suggested here: https://trac.ffmpeg.org/wiki/Encode/VP8
// Note that we can switch to "-qmin 20 -qmax 50 -crf 30" for smaller video size but worse quality.
//
// We use "pad" and "crop" video filters (-vf option) to resize incoming frames
// that might be of the different size to the desired video size.
// https://ffmpeg.org/ffmpeg-filters.html#pad-1
// https://ffmpeg.org/ffmpeg-filters.html#crop
//
// We use "image2pipe" mode to pipe frames and get a single video - https://trac.ffmpeg.org/wiki/Slideshow
// "-f image2pipe -c:v mjpeg -i -" forces input to be read from standard input, and forces
// mjpeg input image format.
// "-avioflags direct" reduces general buffering.
// "-fpsprobesize 0 -probesize 32 -analyzeduration 0" reduces initial buffering
// while analyzing input fps and other stats.
//
// "-y" means overwrite output.
// "-an" means no audio.
// "-threads 1" means using one thread. This drastically reduces stalling when
// cpu is overbooked. By default vp8 tries to use all available threads?
const w = options.width;
const h = options.height;
const args = `-loglevel error -f image2pipe -avioflags direct -fpsprobesize 0 -probesize 32 -analyzeduration 0 -c:v mjpeg -i - -y -an -r ${fps} -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf pad=${w}:${h}:0:0:gray,crop=${w}:${h}:0:0`.split(' ');
args.push(options.outputFile);
const progress = this._progress;
const {
launchedProcess,
gracefullyClose
} = await (0, _processLauncher.launchProcess)({
command: this._ffmpegPath,
args,
stdio: 'stdin',
log: message => progress.log(message),
tempDirectories: [],
attemptToGracefullyClose: async () => {
progress.log('Closing stdin...');
launchedProcess.stdin.end();
},
onExit: (exitCode, signal) => {
progress.log(`ffmpeg onkill exitCode=${exitCode} signal=${signal}`);
}
});
launchedProcess.stdin.on('finish', () => {
progress.log('ffmpeg finished input.');
});
launchedProcess.stdin.on('error', () => {
progress.log('ffmpeg error.');
});
this._process = launchedProcess;
this._gracefullyClose = gracefullyClose;
}
writeFrame(frame, timestamp) {
(0, _utils.assert)(this._process);
if (this._isStopped) return;
if (this._lastFrameBuffer) {
const durationSec = timestamp - this._lastFrameTimestamp;
const repeatCount = Math.max(1, Math.round(fps * durationSec));
for (let i = 0; i < repeatCount; ++i) this._frameQueue.push(this._lastFrameBuffer);
this._lastWritePromise = this._lastWritePromise.then(() => this._sendFrames());
}
this._lastFrameBuffer = frame;
this._lastFrameTimestamp = timestamp;
this._lastWriteTimestamp = (0, _utils.monotonicTime)();
}
async _sendFrames() {
while (this._frameQueue.length) await this._sendFrame(this._frameQueue.shift());
}
async _sendFrame(frame) {
return new Promise(f => this._process.stdin.write(frame, f)).then(error => {
if (error) this._progress.log(`ffmpeg failed to write: ${String(error)}`);
});
}
async stop() {
if (this._isStopped) return;
this.writeFrame(Buffer.from([]), this._lastFrameTimestamp + ((0, _utils.monotonicTime)() - this._lastWriteTimestamp) / 1000);
this._isStopped = true;
await this._lastWritePromise;
await this._gracefullyClose();
}
}
exports.VideoRecorder = VideoRecorder;

View File

@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ConsoleMessage = void 0;
var _instrumentation = require("./instrumentation");
/**
* 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.
*/
class ConsoleMessage extends _instrumentation.SdkObject {
constructor(page, type, text, args, location) {
super(page, 'console-message');
this._type = void 0;
this._text = void 0;
this._args = void 0;
this._location = void 0;
this._page = void 0;
this._page = page;
this._type = type;
this._text = text;
this._args = args;
this._location = location || {
url: '',
lineNumber: 0,
columnNumber: 0
};
}
page() {
return this._page;
}
type() {
return this._type;
}
text() {
if (this._text === undefined) this._text = this._args.map(arg => arg.preview()).join(' ');
return this._text;
}
args() {
return this._args;
}
location() {
return this._location;
}
}
exports.ConsoleMessage = ConsoleMessage;

View File

@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CookieStore = void 0;
exports.domainMatches = domainMatches;
/**
* 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.
*/
class Cookie {
constructor(data) {
this._raw = void 0;
this._raw = data;
}
name() {
return this._raw.name;
}
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.4
matches(url) {
if (this._raw.secure && url.protocol !== 'https:' && url.hostname !== 'localhost') return false;
if (!domainMatches(url.hostname, this._raw.domain)) return false;
if (!pathMatches(url.pathname, this._raw.path)) return false;
return true;
}
equals(other) {
return this._raw.name === other._raw.name && this._raw.domain === other._raw.domain && this._raw.path === other._raw.path;
}
networkCookie() {
return this._raw;
}
updateExpiresFrom(other) {
this._raw.expires = other._raw.expires;
}
expired() {
if (this._raw.expires === -1) return false;
return this._raw.expires * 1000 < Date.now();
}
}
class CookieStore {
constructor() {
this._nameToCookies = new Map();
}
addCookies(cookies) {
for (const cookie of cookies) this._addCookie(new Cookie(cookie));
}
cookies(url) {
const result = [];
for (const cookie of this._cookiesIterator()) {
if (cookie.matches(url)) result.push(cookie.networkCookie());
}
return result;
}
allCookies() {
const result = [];
for (const cookie of this._cookiesIterator()) result.push(cookie.networkCookie());
return result;
}
_addCookie(cookie) {
let set = this._nameToCookies.get(cookie.name());
if (!set) {
set = new Set();
this._nameToCookies.set(cookie.name(), set);
}
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.3
for (const other of set) {
if (other.equals(cookie)) set.delete(other);
}
set.add(cookie);
CookieStore.pruneExpired(set);
}
*_cookiesIterator() {
for (const [name, cookies] of this._nameToCookies) {
CookieStore.pruneExpired(cookies);
for (const cookie of cookies) yield cookie;
if (cookies.size === 0) this._nameToCookies.delete(name);
}
}
static pruneExpired(cookies) {
for (const cookie of cookies) {
if (cookie.expired()) cookies.delete(cookie);
}
}
}
exports.CookieStore = CookieStore;
function domainMatches(value, domain) {
if (value === domain) return true;
// Only strict match is allowed if domain doesn't start with '.' (host-only-flag is true in the spec)
if (!domain.startsWith('.')) return false;
value = '.' + value;
return value.endsWith(domain);
}
function pathMatches(value, path) {
if (value === path) return true;
if (!value.endsWith('/')) value = value + '/';
if (!path.endsWith('/')) path = path + '/';
return value.startsWith(path);
}

View File

@ -0,0 +1,228 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DebugController = void 0;
var _processLauncher = require("../utils/processLauncher");
var _instrumentation = require("./instrumentation");
var _recorder = require("./recorder");
var _recorderApp = require("./recorder/recorderApp");
var _locatorGenerators = require("../utils/isomorphic/locatorGenerators");
/**
* 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.
*/
const internalMetadata = (0, _instrumentation.serverSideCallMetadata)();
class DebugController extends _instrumentation.SdkObject {
// TODO: remove in 1.27
constructor(playwright) {
super({
attribution: {
isInternalPlaywright: true
},
instrumentation: (0, _instrumentation.createInstrumentation)()
}, undefined, 'DebugController');
this._autoCloseTimer = void 0;
this._autoCloseAllowed = false;
this._trackHierarchyListener = void 0;
this._playwright = void 0;
this._sdkLanguage = 'javascript';
this._codegenId = 'playwright-test';
this._playwright = playwright;
}
initialize(codegenId, sdkLanguage) {
this._codegenId = codegenId;
this._sdkLanguage = sdkLanguage;
_recorder.Recorder.setAppFactory(async () => new InspectingRecorderApp(this));
}
setAutoCloseAllowed(allowed) {
this._autoCloseAllowed = allowed;
}
dispose() {
this.setReportStateChanged(false);
this.setAutoCloseAllowed(false);
_recorder.Recorder.setAppFactory(undefined);
}
setReportStateChanged(enabled) {
if (enabled && !this._trackHierarchyListener) {
this._trackHierarchyListener = {
onPageOpen: () => this._emitSnapshot(),
onPageClose: () => this._emitSnapshot()
};
this._playwright.instrumentation.addListener(this._trackHierarchyListener, null);
} else if (!enabled && this._trackHierarchyListener) {
this._playwright.instrumentation.removeListener(this._trackHierarchyListener);
this._trackHierarchyListener = undefined;
}
}
async resetForReuse() {
const contexts = new Set();
for (const page of this._playwright.allPages()) contexts.add(page.context());
for (const context of contexts) await context.resetForReuse(internalMetadata, null);
}
async navigate(url) {
for (const p of this._playwright.allPages()) await p.mainFrame().goto(internalMetadata, url);
}
async setRecorderMode(params) {
// TODO: |file| is only used in the legacy mode.
await this._closeBrowsersWithoutPages();
if (params.mode === 'none') {
for (const recorder of await this._allRecorders()) {
recorder.hideHighlightedSelecor();
recorder.setMode('none');
}
this.setAutoCloseEnabled(true);
return;
}
if (!this._playwright.allBrowsers().length) await this._playwright.chromium.launch(internalMetadata, {
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS
});
// Create page if none.
const pages = this._playwright.allPages();
if (!pages.length) {
const [browser] = this._playwright.allBrowsers();
const {
context
} = await browser.newContextForReuse({}, internalMetadata);
await context.newPage(internalMetadata);
}
// Update test id attribute.
if (params.testIdAttributeName) {
for (const page of this._playwright.allPages()) page.context().selectors().setTestIdAttributeName(params.testIdAttributeName);
}
// Toggle the mode.
for (const recorder of await this._allRecorders()) {
recorder.hideHighlightedSelecor();
if (params.mode === 'recording') recorder.setOutput(this._codegenId, params.file);
recorder.setMode(params.mode);
}
this.setAutoCloseEnabled(true);
}
async setAutoCloseEnabled(enabled) {
if (!this._autoCloseAllowed) return;
if (this._autoCloseTimer) clearTimeout(this._autoCloseTimer);
if (!enabled) return;
const heartBeat = () => {
if (!this._playwright.allPages().length) (0, _processLauncher.gracefullyProcessExitDoNotHang)(0);else this._autoCloseTimer = setTimeout(heartBeat, 5000);
};
this._autoCloseTimer = setTimeout(heartBeat, 30000);
}
async highlight(selector) {
for (const recorder of await this._allRecorders()) recorder.setHighlightedSelector(this._sdkLanguage, selector);
}
async hideHighlight() {
// Hide all active recorder highlights.
for (const recorder of await this._allRecorders()) recorder.hideHighlightedSelecor();
// Hide all locator.highlight highlights.
await this._playwright.hideHighlight();
}
allBrowsers() {
return [...this._playwright.allBrowsers()];
}
async resume() {
for (const recorder of await this._allRecorders()) recorder.resume();
}
async kill() {
(0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
}
async closeAllBrowsers() {
await Promise.all(this.allBrowsers().map(browser => browser.close()));
}
_emitSnapshot() {
const browsers = [];
let pageCount = 0;
for (const browser of this._playwright.allBrowsers()) {
const b = {
contexts: []
};
browsers.push(b);
for (const context of browser.contexts()) {
const c = {
pages: []
};
b.contexts.push(c);
for (const page of context.pages()) c.pages.push(page.mainFrame().url());
pageCount += context.pages().length;
}
}
// TODO: browsers is deprecated, remove it.
this.emit(DebugController.Events.BrowsersChanged, browsers);
this.emit(DebugController.Events.StateChanged, {
pageCount
});
}
async _allRecorders() {
const contexts = new Set();
for (const page of this._playwright.allPages()) contexts.add(page.context());
const result = await Promise.all([...contexts].map(c => _recorder.Recorder.show(c, {
omitCallTracking: true
})));
return result.filter(Boolean);
}
async _closeBrowsersWithoutPages() {
for (const browser of this._playwright.allBrowsers()) {
for (const context of browser.contexts()) {
if (!context.pages().length) await context.close((0, _instrumentation.serverSideCallMetadata)());
}
if (!browser.contexts()) await browser.close();
}
}
}
exports.DebugController = DebugController;
DebugController.Events = {
BrowsersChanged: 'browsersChanged',
StateChanged: 'stateChanged',
InspectRequested: 'inspectRequested',
SourceChanged: 'sourceChanged',
Paused: 'paused'
};
class InspectingRecorderApp extends _recorderApp.EmptyRecorderApp {
constructor(debugController) {
super();
this._debugController = void 0;
this._debugController = debugController;
}
async setSelector(selector) {
const locator = (0, _locatorGenerators.asLocator)(this._debugController._sdkLanguage, selector);
this._debugController.emit(DebugController.Events.InspectRequested, {
selector,
locator
});
}
async setSources(sources) {
const source = sources.find(s => s.id === this._debugController._codegenId);
const {
text,
header,
footer,
actions
} = source || {
text: ''
};
this._debugController.emit(DebugController.Events.SourceChanged, {
text,
header,
footer,
actions
});
}
async setPaused(paused) {
this._debugController.emit(DebugController.Events.Paused, {
paused
});
}
}

View File

@ -0,0 +1,132 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Debugger = void 0;
exports.shouldSlowMo = shouldSlowMo;
var _events = require("events");
var _utils = require("../utils");
var _browserContext = require("./browserContext");
var _debug = require("../protocol/debug");
/**
* 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.
*/
const symbol = Symbol('Debugger');
class Debugger extends _events.EventEmitter {
constructor(context) {
super();
this._pauseOnNextStatement = false;
this._pausedCallsMetadata = new Map();
this._enabled = void 0;
this._context = void 0;
this._muted = false;
this._slowMo = void 0;
this._context = context;
this._context[symbol] = this;
this._enabled = (0, _utils.debugMode)() === 'inspector';
if (this._enabled) this.pauseOnNextStatement();
context.instrumentation.addListener(this, context);
this._context.once(_browserContext.BrowserContext.Events.Close, () => {
this._context.instrumentation.removeListener(this);
});
this._slowMo = this._context._browser.options.slowMo;
}
async setMuted(muted) {
this._muted = muted;
}
async onBeforeCall(sdkObject, metadata) {
if (this._muted) return;
if (shouldPauseOnCall(sdkObject, metadata) || this._pauseOnNextStatement && shouldPauseBeforeStep(metadata)) await this.pause(sdkObject, metadata);
}
async _doSlowMo() {
await new Promise(f => setTimeout(f, this._slowMo));
}
async onAfterCall(sdkObject, metadata) {
if (this._slowMo && shouldSlowMo(metadata)) await this._doSlowMo();
}
async onBeforeInputAction(sdkObject, metadata) {
if (this._muted) return;
if (this._enabled && this._pauseOnNextStatement) await this.pause(sdkObject, metadata);
}
async pause(sdkObject, metadata) {
if (this._muted) return;
this._enabled = true;
metadata.pauseStartTime = (0, _utils.monotonicTime)();
const result = new Promise(resolve => {
this._pausedCallsMetadata.set(metadata, {
resolve,
sdkObject
});
});
this.emit(Debugger.Events.PausedStateChanged);
return result;
}
resume(step) {
if (!this.isPaused()) return;
this._pauseOnNextStatement = step;
const endTime = (0, _utils.monotonicTime)();
for (const [metadata, {
resolve
}] of this._pausedCallsMetadata) {
metadata.pauseEndTime = endTime;
resolve();
}
this._pausedCallsMetadata.clear();
this.emit(Debugger.Events.PausedStateChanged);
}
pauseOnNextStatement() {
this._pauseOnNextStatement = true;
}
isPaused(metadata) {
if (metadata) return this._pausedCallsMetadata.has(metadata);
return !!this._pausedCallsMetadata.size;
}
pausedDetails() {
const result = [];
for (const [metadata, {
sdkObject
}] of this._pausedCallsMetadata) result.push({
metadata,
sdkObject
});
return result;
}
}
exports.Debugger = Debugger;
Debugger.Events = {
PausedStateChanged: 'pausedstatechanged'
};
function shouldPauseOnCall(sdkObject, metadata) {
var _sdkObject$attributio;
if (sdkObject.attribution.playwright.options.isServer) return false;
if (!((_sdkObject$attributio = sdkObject.attribution.browser) !== null && _sdkObject$attributio !== void 0 && _sdkObject$attributio.options.headful) && !(0, _utils.isUnderTest)()) return false;
return metadata.method === 'pause';
}
function shouldPauseBeforeStep(metadata) {
// Don't stop on internal.
if (!metadata.apiName) return false;
// Always stop on 'close'
if (metadata.method === 'close') return true;
if (metadata.method === 'waitForSelector' || metadata.method === 'waitForEventInfo') return false; // Never stop on those, primarily for the test harness.
const step = metadata.type + '.' + metadata.method;
// Stop before everything that generates snapshot. But don't stop before those marked as pausesBeforeInputActions
// since we stop in them on a separate instrumentation signal.
return _debug.commandsWithTracingSnapshots.has(step) && !_debug.pausesBeforeInputActions.has(metadata.type + '.' + metadata.method);
}
function shouldSlowMo(metadata) {
return _debug.slowMoActions.has(metadata.type + '.' + metadata.method);
}

View File

@ -0,0 +1,21 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
/**
* @type {import('./types').Devices}
*/
module.exports = require("./deviceDescriptorsSource.json")

View File

@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Dialog = void 0;
var _utils = require("../utils");
var _instrumentation = require("./instrumentation");
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class Dialog extends _instrumentation.SdkObject {
constructor(page, type, message, onHandle, defaultValue) {
super(page, 'dialog');
this._page = void 0;
this._type = void 0;
this._message = void 0;
this._onHandle = void 0;
this._handled = false;
this._defaultValue = void 0;
this._page = page;
this._type = type;
this._message = message;
this._onHandle = onHandle;
this._defaultValue = defaultValue || '';
this._page._frameManager.dialogDidOpen(this);
}
page() {
return this._page;
}
type() {
return this._type;
}
message() {
return this._message;
}
defaultValue() {
return this._defaultValue;
}
async accept(promptText) {
(0, _utils.assert)(!this._handled, 'Cannot accept dialog which is already handled!');
this._handled = true;
this._page._frameManager.dialogWillClose(this);
await this._onHandle(true, promptText);
}
async dismiss() {
(0, _utils.assert)(!this._handled, 'Cannot dismiss dialog which is already handled!');
this._handled = true;
this._page._frameManager.dialogWillClose(this);
await this._onHandle(false);
}
async close() {
if (this._type === 'beforeunload') await this.accept();else await this.dismiss();
}
}
exports.Dialog = Dialog;

Some files were not shown because too many files have changed in this diff Show More