init commit

This commit is contained in:
iFlip721
2025-04-16 09:55:57 -04:00
commit a03209dc9e
935 changed files with 177492 additions and 0 deletions

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,602 @@
"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._crashedPromise.isDone()) {
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));
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;
}
}

View File

@@ -0,0 +1,324 @@
"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
}
};
try {
if (process.env.SELENIUM_REMOTE_CAPABILITIES) {
const parsed = JSON.parse(process.env.SELENIUM_REMOTE_CAPABILITIES);
desiredCapabilities = {
...desiredCapabilities,
...parsed
};
progress.log(`<selenium> using additional capabilities "${process.env.SELENIUM_REMOTE_CAPABILITIES}"`);
}
} catch (e) {
progress.log(`<selenium> ignoring additional capabilities "${process.env.SELENIUM_REMOTE_CAPABILITIES}": ${e}`);
}
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'
},
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'
}).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()
}, 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, 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')));
});
}

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("playwright-core/lib/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,524 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CRBrowserContext = exports.CRBrowser = void 0;
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");
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 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._tracingPath = '';
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 {
path = null,
screenshots = false,
categories = defaultCategories
} = options;
if (screenshots) categories.push('disabled-by-default-devtools.screenshot');
this._tracingPath = path;
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 result = await (0, _crProtocolHelper.readProtocolStream)(this._tracingClient, event.stream, this._tracingPath);
this._tracingRecording = false;
return result;
}
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,689 @@
"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(client, page, serviceWorker, parentManager) {
this._client = 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._client = client;
this._page = page;
this._serviceWorker = serviceWorker;
this._parentManager = parentManager;
this._eventListeners = this.instrumentNetworkEvents(client);
}
instrumentNetworkEvents(session, workerFrame) {
const listeners = [_eventsHelper.eventsHelper.addEventListener(session, 'Fetch.requestPaused', this._onRequestPaused.bind(this, workerFrame)), _eventsHelper.eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, workerFrame)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.loadingFailed', this._onLoadingFailed.bind(this, workerFrame))];
if (this._page) {
listeners.push(...[_eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketCreated', e => this._page._frameManager.onWebSocketCreated(e.requestId, e.url)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketWillSendHandshakeRequest', e => this._page._frameManager.onWebSocketRequest(e.requestId)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketHandshakeResponseReceived', e => this._page._frameManager.onWebSocketResponse(e.requestId, e.response.status, e.response.statusText)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameSent', e => e.response.payloadData && this._page._frameManager.onWebSocketFrameSent(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameReceived', e => e.response.payloadData && this._page._frameManager.webSocketFrameReceived(e.requestId, e.response.opcode, e.response.payloadData)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketClosed', e => this._page._frameManager.webSocketClosed(e.requestId)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.webSocketFrameError', e => this._page._frameManager.webSocketError(e.requestId, e.errorMessage))]);
}
return listeners;
}
async initialize() {
await this._client.send('Network.enable');
}
dispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
}
async authenticate(credentials) {
this._credentials = credentials;
await this._updateProtocolRequestInterception();
}
async setOffline(offline) {
await this._client.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._client.send('Network.setCacheDisabled', {
cacheDisabled: true
}), this._client.send('Fetch.enable', {
handleAuthRequests: true,
patterns: [{
urlPattern: '*',
requestStage: 'Request'
}]
})]);
} else {
await Promise.all([this._client.send('Network.setCacheDisabled', {
cacheDisabled: false
}), this._client.send('Fetch.disable')]);
}
}
async clearCache() {
// Sending 'Network.setCacheDisabled' with 'cacheDisabled = true' will clear the MemoryCache.
await this._client.send('Network.setCacheDisabled', {
cacheDisabled: true
});
if (!this._protocolRequestInterceptionEnabled) await this._client.send('Network.setCacheDisabled', {
cacheDisabled: false
});
await this._client.send('Network.clearBrowserCache');
}
_onRequestWillBeSent(workerFrame, 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(workerFrame, event, requestPausedEvent);
this._requestIdToRequestPausedEvent.delete(requestId);
} else {
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
}
} else {
this._onRequest(workerFrame, 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._client._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(workerFrame, 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._client._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(workerFrame, requestWillBeSentEvent, event);
this._requestIdToRequestWillBeSentEvent.delete(requestId);
} else {
this._requestIdToRequestPausedEvent.set(requestId, event);
}
}
_onRequest(workerFrame, 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) : 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._client._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._client._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._client._sendMayFail('Fetch.continueRequest', {
requestId: requestPausedEvent.requestId
});else route = new RouteImpl(this._client, requestPausedEvent.requestId);
}
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
const request = new InterceptableRequest({
owningNetworkManager: this,
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 client = request._owningNetworkManager._client;
const response = await client.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 client.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 client.send('IO.read', {
handle: resource.resource.stream
});
chunks.push(Buffer.from(chunk.data, chunk.base64Encoded ? 'base64' : 'utf-8'));
if (chunk.eof) {
await client.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(event) {
var _this$_page7;
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.
//
// PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS we guard with, since this would fix an old bug where, when using routing,
// request would not be emitted to the user for requests made by a page with a SW (and fetch handler) registered
if (!!process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS && !request && event.response.fromServiceWorker) {
var _this$_page6;
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(event.requestId);
const frame = requestWillBeSentEvent !== null && requestWillBeSentEvent !== void 0 && requestWillBeSentEvent.frameId ? (_this$_page6 = this._page) === null || _this$_page6 === void 0 ? void 0 : _this$_page6._frameManager.frame(requestWillBeSentEvent.frameId) : null;
if (requestWillBeSentEvent && frame) {
this._onRequest(frame, requestWillBeSentEvent, null /* requestPausedPayload */);
request = this._requestIdToRequest.get(event.requestId);
this._requestIdToRequestWillBeSentEvent.delete(event.requestId);
}
}
// FileUpload sends a response without a matching request.
if (!request) return;
const response = this._createResponse(request, event.response, event.hasExtraInfo);
(((_this$_page7 = this._page) === null || _this$_page7 === void 0 ? void 0 : _this$_page7._frameManager) || this._serviceWorker).requestReceivedResponse(response);
}
_onLoadingFinished(event) {
var _this$_page8;
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$_page8 = this._page) === null || _this$_page8 === void 0 ? void 0 : _this$_page8._frameManager) || this._serviceWorker).reportRequestFinished(request.request, response);
}
_onLoadingFailed(workerFrame, event) {
var _this$_page9;
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(workerFrame, 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$_page9 = this._page) === null || _this$_page9 === void 0 ? void 0 : _this$_page9._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._owningNetworkManager = this;
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._owningNetworkManager = void 0;
const {
owningNetworkManager,
context,
frame,
documentId,
route,
requestWillBeSentEvent,
requestPausedEvent,
redirectedFrom,
serviceWorker
} = options;
this._owningNetworkManager = owningNetworkManager;
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(client, interceptionId) {
this._client = void 0;
this._interceptionId = void 0;
this._client = client;
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._client._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._client._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._client._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);
}
}

File diff suppressed because it is too large Load Diff

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, null);
}
}
exports.CRPDF = CRPDF;

View File

@@ -0,0 +1,120 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.exceptionToError = exceptionToError;
exports.getExceptionMessage = getExceptionMessage;
exports.readProtocolStream = readProtocolStream;
exports.releaseObject = releaseObject;
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 readProtocolStream(client, handle, path) {
let eof = false;
let fd;
if (path) {
await (0, _fileUtils.mkdirIfNeeded)(path);
fd = await _fs.default.promises.open(path, 'w');
}
const bufs = [];
while (!eof) {
const response = await client.send('IO.read', {
handle
});
eof = response.eof;
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
bufs.push(buf);
if (fd) await fd.write(buf);
}
if (fd) await fd.close();
await client.send('IO.close', {
handle
});
return Buffer.concat(bufs);
}
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,110 @@
"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();
}
}
_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,236 @@
"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) selfDestruct();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() {
selfDestruct();
}
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'
};
function selfDestruct() {
// Force exit after 30 seconds.
setTimeout(() => process.exit(0), 30000);
// Meanwhile, try to gracefully close all browsers.
(0, _processLauncher.gracefullyCloseAll)().then(() => {
process.exit(0);
});
}
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")

File diff suppressed because it is too large Load Diff

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;

View File

@@ -0,0 +1,193 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AndroidSocketDispatcher = exports.AndroidDispatcher = exports.AndroidDeviceDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _android = require("../android/android");
var _browserContextDispatcher = require("./browserContextDispatcher");
/**
* 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 AndroidDispatcher extends _dispatcher.Dispatcher {
constructor(scope, android) {
super(scope, android, 'Android', {});
this._type_Android = true;
}
async devices(params) {
const devices = await this._object.devices(params);
return {
devices: devices.map(d => AndroidDeviceDispatcher.from(this, d))
};
}
async setDefaultTimeoutNoReply(params) {
this._object.setDefaultTimeout(params.timeout);
}
}
exports.AndroidDispatcher = AndroidDispatcher;
class AndroidDeviceDispatcher extends _dispatcher.Dispatcher {
static from(scope, device) {
const result = (0, _dispatcher.existingDispatcher)(device);
return result || new AndroidDeviceDispatcher(scope, device);
}
constructor(scope, device) {
super(scope, device, 'AndroidDevice', {
model: device.model,
serial: device.serial
});
this._type_EventTarget = true;
this._type_AndroidDevice = true;
for (const webView of device.webViews()) this._dispatchEvent('webViewAdded', {
webView
});
this.addObjectListener(_android.AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', {
webView
}));
this.addObjectListener(_android.AndroidDevice.Events.WebViewRemoved, socketName => this._dispatchEvent('webViewRemoved', {
socketName
}));
this.addObjectListener(_android.AndroidDevice.Events.Close, socketName => this._dispatchEvent('close'));
}
async wait(params) {
await this._object.send('wait', params);
}
async fill(params) {
await this._object.send('click', {
selector: params.selector
});
await this._object.send('fill', params);
}
async tap(params) {
await this._object.send('click', params);
}
async drag(params) {
await this._object.send('drag', params);
}
async fling(params) {
await this._object.send('fling', params);
}
async longTap(params) {
await this._object.send('longClick', params);
}
async pinchClose(params) {
await this._object.send('pinchClose', params);
}
async pinchOpen(params) {
await this._object.send('pinchOpen', params);
}
async scroll(params) {
await this._object.send('scroll', params);
}
async swipe(params) {
await this._object.send('swipe', params);
}
async info(params) {
return {
info: await this._object.send('info', params)
};
}
async inputType(params) {
const text = params.text;
const keyCodes = [];
for (let i = 0; i < text.length; ++i) {
const code = keyMap.get(text[i].toUpperCase());
if (code === undefined) throw new Error('No mapping for ' + text[i] + ' found');
keyCodes.push(code);
}
await Promise.all(keyCodes.map(keyCode => this._object.send('inputPress', {
keyCode
})));
}
async inputPress(params) {
if (!keyMap.has(params.key)) throw new Error('Unknown key: ' + params.key);
await this._object.send('inputPress', {
keyCode: keyMap.get(params.key)
});
}
async inputTap(params) {
await this._object.send('inputClick', params);
}
async inputSwipe(params) {
await this._object.send('inputSwipe', params);
}
async inputDrag(params) {
await this._object.send('inputDrag', params);
}
async screenshot(params) {
return {
binary: await this._object.screenshot()
};
}
async shell(params) {
return {
result: await this._object.shell(params.command)
};
}
async open(params, metadata) {
const socket = await this._object.open(params.command);
return {
socket: new AndroidSocketDispatcher(this, socket)
};
}
async installApk(params) {
await this._object.installApk(params.file, {
args: params.args
});
}
async push(params) {
await this._object.push(params.file, params.path, params.mode);
}
async launchBrowser(params) {
const context = await this._object.launchBrowser(params.pkg, params);
return {
context: new _browserContextDispatcher.BrowserContextDispatcher(this, context)
};
}
async close(params) {
await this._object.close();
}
async setDefaultTimeoutNoReply(params) {
this._object.setDefaultTimeout(params.timeout);
}
async connectToWebView(params) {
return {
context: new _browserContextDispatcher.BrowserContextDispatcher(this, await this._object.connectToWebView(params.socketName))
};
}
}
exports.AndroidDeviceDispatcher = AndroidDeviceDispatcher;
class AndroidSocketDispatcher extends _dispatcher.Dispatcher {
constructor(scope, socket) {
super(scope, socket, 'AndroidSocket', {});
this._type_AndroidSocket = true;
this.addObjectListener('data', data => this._dispatchEvent('data', {
data
}));
this.addObjectListener('close', () => {
this._dispatchEvent('close');
this._dispose();
});
}
async write(params, metadata) {
await this._object.write(params.data);
}
async close(params, metadata) {
this._object.close();
}
}
exports.AndroidSocketDispatcher = AndroidSocketDispatcher;
const keyMap = new Map([['Unknown', 0], ['SoftLeft', 1], ['SoftRight', 2], ['Home', 3], ['Back', 4], ['Call', 5], ['EndCall', 6], ['0', 7], ['1', 8], ['2', 9], ['3', 10], ['4', 11], ['5', 12], ['6', 13], ['7', 14], ['8', 15], ['9', 16], ['Star', 17], ['*', 17], ['Pound', 18], ['#', 18], ['DialUp', 19], ['DialDown', 20], ['DialLeft', 21], ['DialRight', 22], ['DialCenter', 23], ['VolumeUp', 24], ['VolumeDown', 25], ['Power', 26], ['Camera', 27], ['Clear', 28], ['A', 29], ['B', 30], ['C', 31], ['D', 32], ['E', 33], ['F', 34], ['G', 35], ['H', 36], ['I', 37], ['J', 38], ['K', 39], ['L', 40], ['M', 41], ['N', 42], ['O', 43], ['P', 44], ['Q', 45], ['R', 46], ['S', 47], ['T', 48], ['U', 49], ['V', 50], ['W', 51], ['X', 52], ['Y', 53], ['Z', 54], ['Comma', 55], [',', 55], ['Period', 56], ['.', 56], ['AltLeft', 57], ['AltRight', 58], ['ShiftLeft', 59], ['ShiftRight', 60], ['Tab', 61], ['\t', 61], ['Space', 62], [' ', 62], ['Sym', 63], ['Explorer', 64], ['Envelop', 65], ['Enter', 66], ['Del', 67], ['Grave', 68], ['Minus', 69], ['-', 69], ['Equals', 70], ['=', 70], ['LeftBracket', 71], ['(', 71], ['RightBracket', 72], [')', 72], ['Backslash', 73], ['\\', 73], ['Semicolon', 74], [';', 74], ['Apostrophe', 75], ['`', 75], ['Slash', 76], ['/', 76], ['At', 77], ['@', 77], ['Num', 78], ['HeadsetHook', 79], ['Focus', 80], ['Plus', 81], ['Menu', 82], ['Notification', 83], ['Search', 84], ['AppSwitch', 187], ['Assist', 219], ['Cut', 277], ['Copy', 278], ['Paste', 279]]);

View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ArtifactDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _streamDispatcher = require("./streamDispatcher");
var _fs = _interopRequireDefault(require("fs"));
var _fileUtils = require("../../utils/fileUtils");
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 ArtifactDispatcher extends _dispatcher.Dispatcher {
static from(parentScope, artifact) {
return ArtifactDispatcher.fromNullable(parentScope, artifact);
}
static fromNullable(parentScope, artifact) {
if (!artifact) return undefined;
const result = (0, _dispatcher.existingDispatcher)(artifact);
return result || new ArtifactDispatcher(parentScope, artifact);
}
constructor(scope, artifact) {
super(scope, artifact, 'Artifact', {
absolutePath: artifact.localPath()
});
this._type_Artifact = true;
}
async pathAfterFinished() {
const path = await this._object.localPathAfterFinished();
return {
value: path || undefined
};
}
async saveAs(params) {
return await new Promise((resolve, reject) => {
this._object.saveAs(async (localPath, error) => {
if (error !== undefined) {
reject(new Error(error));
return;
}
try {
await (0, _fileUtils.mkdirIfNeeded)(params.path);
await _fs.default.promises.copyFile(localPath, params.path);
resolve();
} catch (e) {
reject(e);
}
});
});
}
async saveAsStream() {
return await new Promise((resolve, reject) => {
this._object.saveAs(async (localPath, error) => {
if (error !== undefined) {
reject(new Error(error));
return;
}
try {
const readable = _fs.default.createReadStream(localPath, {
highWaterMark: 1024 * 1024
});
const stream = new _streamDispatcher.StreamDispatcher(this, readable);
// Resolve with a stream, so that client starts saving the data.
resolve({
stream
});
// Block the Artifact until the stream is consumed.
await new Promise(resolve => {
readable.on('close', resolve);
readable.on('end', resolve);
readable.on('error', resolve);
});
} catch (e) {
reject(e);
}
});
});
}
async stream() {
const fileName = await this._object.localPathAfterFinished();
if (!fileName) return {};
const readable = _fs.default.createReadStream(fileName, {
highWaterMark: 1024 * 1024
});
return {
stream: new _streamDispatcher.StreamDispatcher(this, readable)
};
}
async failure() {
const error = await this._object.failureError();
return {
error: error || undefined
};
}
async cancel() {
await this._object.cancel();
}
async delete() {
await this._object.delete();
this._dispose();
}
}
exports.ArtifactDispatcher = ArtifactDispatcher;

View File

@@ -0,0 +1,290 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserContextDispatcher = void 0;
var _browserContext = require("../browserContext");
var _dispatcher = require("./dispatcher");
var _pageDispatcher = require("./pageDispatcher");
var _networkDispatchers = require("./networkDispatchers");
var _crBrowser = require("../chromium/crBrowser");
var _cdpSessionDispatcher = require("./cdpSessionDispatcher");
var _recorder = require("../recorder");
var _artifactDispatcher = require("./artifactDispatcher");
var _tracingDispatcher = require("./tracingDispatcher");
var fs = _interopRequireWildcard(require("fs"));
var path = _interopRequireWildcard(require("path"));
var _utils = require("../../utils");
var _writableStreamDispatcher = require("./writableStreamDispatcher");
var _consoleMessageDispatcher = require("./consoleMessageDispatcher");
var _dialogDispatcher = require("./dialogDispatcher");
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 BrowserContextDispatcher extends _dispatcher.Dispatcher {
constructor(parentScope, context) {
// We will reparent these to the context below.
const requestContext = _networkDispatchers.APIRequestContextDispatcher.from(parentScope, context.fetchRequest);
const tracing = _tracingDispatcher.TracingDispatcher.from(parentScope, context.tracing);
super(parentScope, context, 'BrowserContext', {
isChromium: context._browser.options.isChromium,
requestContext,
tracing
});
this._type_EventTarget = true;
this._type_BrowserContext = true;
this._context = void 0;
this._subscriptions = new Set();
this.adopt(requestContext);
this.adopt(tracing);
this._context = context;
// Note: when launching persistent context, dispatcher is created very late,
// so we can already have pages, videos and everything else.
const onVideo = artifact => {
// Note: Video must outlive Page and BrowserContext, so that client can saveAs it
// after closing the context. We use |scope| for it.
const artifactDispatcher = _artifactDispatcher.ArtifactDispatcher.from(parentScope, artifact);
this._dispatchEvent('video', {
artifact: artifactDispatcher
});
};
this.addObjectListener(_browserContext.BrowserContext.Events.VideoStarted, onVideo);
for (const video of context._browser._idToVideo.values()) {
if (video.context === context) onVideo(video.artifact);
}
for (const page of context.pages()) this._dispatchEvent('page', {
page: _pageDispatcher.PageDispatcher.from(this, page)
});
this.addObjectListener(_browserContext.BrowserContext.Events.Page, page => {
this._dispatchEvent('page', {
page: _pageDispatcher.PageDispatcher.from(this, page)
});
});
this.addObjectListener(_browserContext.BrowserContext.Events.Close, () => {
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(_browserContext.BrowserContext.Events.Console, message => {
if (this._shouldDispatchEvent(message.page(), 'console')) this._dispatchEvent('console', {
message: new _consoleMessageDispatcher.ConsoleMessageDispatcher(_pageDispatcher.PageDispatcher.from(this, message.page()), message)
});
});
this.addObjectListener(_browserContext.BrowserContext.Events.Dialog, dialog => {
if (this._shouldDispatchEvent(dialog.page(), 'dialog')) this._dispatchEvent('dialog', {
dialog: new _dialogDispatcher.DialogDispatcher(this, dialog)
});else dialog.close().catch(() => {});
});
if (context._browser.options.name === 'chromium') {
for (const page of context.backgroundPages()) this._dispatchEvent('backgroundPage', {
page: _pageDispatcher.PageDispatcher.from(this, page)
});
this.addObjectListener(_crBrowser.CRBrowserContext.CREvents.BackgroundPage, page => this._dispatchEvent('backgroundPage', {
page: _pageDispatcher.PageDispatcher.from(this, page)
}));
for (const serviceWorker of context.serviceWorkers()) this._dispatchEvent('serviceWorker', {
worker: new _pageDispatcher.WorkerDispatcher(this, serviceWorker)
});
this.addObjectListener(_crBrowser.CRBrowserContext.CREvents.ServiceWorker, serviceWorker => this._dispatchEvent('serviceWorker', {
worker: new _pageDispatcher.WorkerDispatcher(this, serviceWorker)
}));
}
this.addObjectListener(_browserContext.BrowserContext.Events.Request, request => {
var _request$frame;
// Create dispatcher, if:
// - There are listeners to the requests.
// - We are redirected from a reported request so that redirectedTo was updated on client.
// - We are a navigation request and dispatcher will be reported as a part of the goto return value and newDocument param anyways.
// By the time requestFinished is triggered to update the request, we should have a request on the client already.
const redirectFromDispatcher = request.redirectedFrom() && (0, _dispatcher.existingDispatcher)(request.redirectedFrom());
if (!redirectFromDispatcher && !this._shouldDispatchNetworkEvent(request, 'request') && !request.isNavigationRequest()) return;
const requestDispatcher = _networkDispatchers.RequestDispatcher.from(this, request);
this._dispatchEvent('request', {
request: requestDispatcher,
page: _pageDispatcher.PageDispatcher.fromNullable(this, (_request$frame = request.frame()) === null || _request$frame === void 0 ? void 0 : _request$frame._page.initializedOrUndefined())
});
});
this.addObjectListener(_browserContext.BrowserContext.Events.Response, response => {
var _response$frame;
const requestDispatcher = (0, _dispatcher.existingDispatcher)(response.request());
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(response.request(), 'response')) return;
this._dispatchEvent('response', {
response: _networkDispatchers.ResponseDispatcher.from(this, response),
page: _pageDispatcher.PageDispatcher.fromNullable(this, (_response$frame = response.frame()) === null || _response$frame === void 0 ? void 0 : _response$frame._page.initializedOrUndefined())
});
});
this.addObjectListener(_browserContext.BrowserContext.Events.RequestFailed, request => {
var _request$frame2;
const requestDispatcher = (0, _dispatcher.existingDispatcher)(request);
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(request, 'requestFailed')) return;
this._dispatchEvent('requestFailed', {
request: _networkDispatchers.RequestDispatcher.from(this, request),
failureText: request._failureText || undefined,
responseEndTiming: request._responseEndTiming,
page: _pageDispatcher.PageDispatcher.fromNullable(this, (_request$frame2 = request.frame()) === null || _request$frame2 === void 0 ? void 0 : _request$frame2._page.initializedOrUndefined())
});
});
this.addObjectListener(_browserContext.BrowserContext.Events.RequestFinished, ({
request,
response
}) => {
var _request$frame3;
const requestDispatcher = (0, _dispatcher.existingDispatcher)(request);
if (!requestDispatcher && !this._shouldDispatchNetworkEvent(request, 'requestFinished')) return;
this._dispatchEvent('requestFinished', {
request: _networkDispatchers.RequestDispatcher.from(this, request),
response: _networkDispatchers.ResponseDispatcher.fromNullable(this, response),
responseEndTiming: request._responseEndTiming,
page: _pageDispatcher.PageDispatcher.fromNullable(this, (_request$frame3 = request.frame()) === null || _request$frame3 === void 0 ? void 0 : _request$frame3._page.initializedOrUndefined())
});
});
}
_shouldDispatchNetworkEvent(request, event) {
var _request$frame4, _request$frame4$_page;
return this._shouldDispatchEvent((_request$frame4 = request.frame()) === null || _request$frame4 === void 0 ? void 0 : (_request$frame4$_page = _request$frame4._page) === null || _request$frame4$_page === void 0 ? void 0 : _request$frame4$_page.initializedOrUndefined(), event);
}
_shouldDispatchEvent(page, event) {
if (this._subscriptions.has(event)) return true;
const pageDispatcher = page ? (0, _dispatcher.existingDispatcher)(page) : undefined;
if (pageDispatcher !== null && pageDispatcher !== void 0 && pageDispatcher._subscriptions.has(event)) return true;
return false;
}
async createTempFile(params) {
const dir = this._context._browser.options.artifactsDir;
const tmpDir = path.join(dir, 'upload-' + (0, _utils.createGuid)());
await fs.promises.mkdir(tmpDir);
this._context._tempDirs.push(tmpDir);
const file = fs.createWriteStream(path.join(tmpDir, params.name));
return {
writableStream: new _writableStreamDispatcher.WritableStreamDispatcher(this, file)
};
}
async setDefaultNavigationTimeoutNoReply(params) {
this._context.setDefaultNavigationTimeout(params.timeout);
}
async setDefaultTimeoutNoReply(params) {
this._context.setDefaultTimeout(params.timeout);
}
async exposeBinding(params) {
await this._context.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
// When reusing the context, we might have some bindings called late enough,
// after context and page dispatchers have been disposed.
if (this._disposed) return;
const pageDispatcher = _pageDispatcher.PageDispatcher.from(this, source.page);
const binding = new _pageDispatcher.BindingCallDispatcher(pageDispatcher, params.name, !!params.needsHandle, source, args);
this._dispatchEvent('bindingCall', {
binding
});
return binding.promise();
});
}
async newPage(params, metadata) {
return {
page: _pageDispatcher.PageDispatcher.from(this, await this._context.newPage(metadata))
};
}
async cookies(params) {
return {
cookies: await this._context.cookies(params.urls)
};
}
async addCookies(params) {
await this._context.addCookies(params.cookies);
}
async clearCookies() {
await this._context.clearCookies();
}
async grantPermissions(params) {
await this._context.grantPermissions(params.permissions, params.origin);
}
async clearPermissions() {
await this._context.clearPermissions();
}
async setGeolocation(params) {
await this._context.setGeolocation(params.geolocation);
}
async setExtraHTTPHeaders(params) {
await this._context.setExtraHTTPHeaders(params.headers);
}
async setOffline(params) {
await this._context.setOffline(params.offline);
}
async setHTTPCredentials(params) {
await this._context.setHTTPCredentials(params.httpCredentials);
}
async addInitScript(params) {
await this._context.addInitScript(params.source);
}
async setNetworkInterceptionPatterns(params) {
if (!params.patterns.length) {
await this._context.setRequestInterceptor(undefined);
return;
}
const urlMatchers = params.patterns.map(pattern => pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags) : pattern.glob);
await this._context.setRequestInterceptor((route, request) => {
const matchesSome = urlMatchers.some(urlMatch => (0, _utils.urlMatches)(this._context._options.baseURL, request.url(), urlMatch));
if (!matchesSome) return false;
this._dispatchEvent('route', {
route: _networkDispatchers.RouteDispatcher.from(_networkDispatchers.RequestDispatcher.from(this, request), route)
});
return true;
});
}
async storageState(params, metadata) {
return await this._context.storageState();
}
async close(params, metadata) {
await this._context.close(metadata);
}
async recorderSupplementEnable(params) {
await _recorder.Recorder.show(this._context, params);
}
async pause(params, metadata) {
// Debugger will take care of this.
}
async newCDPSession(params) {
if (!this._object._browser.options.isChromium) throw new Error(`CDP session is only available in Chromium`);
if (!params.page && !params.frame || params.page && params.frame) throw new Error(`CDP session must be initiated with either Page or Frame, not none or both`);
const crBrowserContext = this._object;
return {
session: new _cdpSessionDispatcher.CDPSessionDispatcher(this, await crBrowserContext.newCDPSession((params.page ? params.page : params.frame)._object))
};
}
async harStart(params) {
const harId = await this._context._harStart(params.page ? params.page._object : null, params.options);
return {
harId
};
}
async harExport(params) {
const artifact = await this._context._harExport(params.harId);
if (!artifact) throw new Error('No HAR artifact. Ensure record.harPath is set.');
return {
artifact: _artifactDispatcher.ArtifactDispatcher.from(this, artifact)
};
}
async updateSubscription(params) {
if (params.enabled) this._subscriptions.add(params.event);else this._subscriptions.delete(params.event);
}
_onDispose() {
// Avoid protocol calls for the closed context.
if (!this._context.isClosingOrClosed()) this._context.setRequestInterceptor(undefined).catch(() => {});
}
}
exports.BrowserContextDispatcher = BrowserContextDispatcher;

View File

@@ -0,0 +1,160 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ConnectedBrowserDispatcher = exports.BrowserDispatcher = void 0;
var _browser = require("../browser");
var _browserContextDispatcher = require("./browserContextDispatcher");
var _cdpSessionDispatcher = require("./cdpSessionDispatcher");
var _dispatcher = require("./dispatcher");
var _instrumentation = require("../instrumentation");
var _browserContext = require("../browserContext");
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 BrowserDispatcher extends _dispatcher.Dispatcher {
constructor(scope, browser) {
super(scope, browser, 'Browser', {
version: browser.version(),
name: browser.options.name
});
this._type_Browser = true;
this.addObjectListener(_browser.Browser.Events.Disconnected, () => this._didClose());
}
_didClose() {
this._dispatchEvent('close');
this._dispose();
}
async newContext(params, metadata) {
const context = await this._object.newContext(metadata, params);
return {
context: new _browserContextDispatcher.BrowserContextDispatcher(this, context)
};
}
async newContextForReuse(params, metadata) {
return await newContextForReuse(this._object, this, params, null, metadata);
}
async close() {
await this._object.close();
}
async killForTests() {
await this._object.killForTests();
}
async defaultUserAgentForTest() {
return {
userAgent: this._object.userAgent()
};
}
async newBrowserCDPSession() {
if (!this._object.options.isChromium) throw new Error(`CDP session is only available in Chromium`);
const crBrowser = this._object;
return {
session: new _cdpSessionDispatcher.CDPSessionDispatcher(this, await crBrowser.newBrowserCDPSession())
};
}
async startTracing(params) {
if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`);
const crBrowser = this._object;
await crBrowser.startTracing(params.page ? params.page._object : undefined, params);
}
async stopTracing() {
if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`);
const crBrowser = this._object;
return {
binary: await crBrowser.stopTracing()
};
}
}
// This class implements multiplexing browser dispatchers over a single Browser instance.
exports.BrowserDispatcher = BrowserDispatcher;
class ConnectedBrowserDispatcher extends _dispatcher.Dispatcher {
constructor(scope, browser) {
super(scope, browser, 'Browser', {
version: browser.version(),
name: browser.options.name
});
// When we have a remotely-connected browser, each client gets a fresh Selector instance,
// so that two clients do not interfere between each other.
this._type_Browser = true;
this._contexts = new Set();
this.selectors = void 0;
this.selectors = new _selectors.Selectors();
}
async newContext(params, metadata) {
if (params.recordVideo) params.recordVideo.dir = this._object.options.artifactsDir;
const context = await this._object.newContext(metadata, params);
this._contexts.add(context);
context.setSelectors(this.selectors);
context.on(_browserContext.BrowserContext.Events.Close, () => this._contexts.delete(context));
return {
context: new _browserContextDispatcher.BrowserContextDispatcher(this, context)
};
}
async newContextForReuse(params, metadata) {
return await newContextForReuse(this._object, this, params, this.selectors, metadata);
}
async close() {
// Client should not send us Browser.close.
}
async killForTests() {
// Client should not send us Browser.killForTests.
}
async defaultUserAgentForTest() {
throw new Error('Client should not send us Browser.defaultUserAgentForTest');
}
async newBrowserCDPSession() {
if (!this._object.options.isChromium) throw new Error(`CDP session is only available in Chromium`);
const crBrowser = this._object;
return {
session: new _cdpSessionDispatcher.CDPSessionDispatcher(this, await crBrowser.newBrowserCDPSession())
};
}
async startTracing(params) {
if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`);
const crBrowser = this._object;
await crBrowser.startTracing(params.page ? params.page._object : undefined, params);
}
async stopTracing() {
if (!this._object.options.isChromium) throw new Error(`Tracing is only available in Chromium`);
const crBrowser = this._object;
return {
binary: await crBrowser.stopTracing()
};
}
async cleanupContexts() {
await Promise.all(Array.from(this._contexts).map(context => context.close((0, _instrumentation.serverSideCallMetadata)())));
}
}
exports.ConnectedBrowserDispatcher = ConnectedBrowserDispatcher;
async function newContextForReuse(browser, scope, params, selectors, metadata) {
const {
context,
needsReset
} = await browser.newContextForReuse(params, metadata);
if (needsReset) {
const oldContextDispatcher = (0, _dispatcher.existingDispatcher)(context);
if (oldContextDispatcher) oldContextDispatcher._dispose();
await context.resetForReuse(metadata, params);
}
if (selectors) context.setSelectors(selectors);
const contextDispatcher = new _browserContextDispatcher.BrowserContextDispatcher(scope, context);
return {
context: contextDispatcher
};
}

View File

@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.BrowserTypeDispatcher = void 0;
var _browserDispatcher = require("./browserDispatcher");
var _dispatcher = require("./dispatcher");
var _browserContextDispatcher = require("./browserContextDispatcher");
/**
* 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 BrowserTypeDispatcher extends _dispatcher.Dispatcher {
constructor(scope, browserType) {
super(scope, browserType, 'BrowserType', {
executablePath: browserType.executablePath(),
name: browserType.name()
});
this._type_BrowserType = true;
}
async launch(params, metadata) {
const browser = await this._object.launch(metadata, params);
return {
browser: new _browserDispatcher.BrowserDispatcher(this, browser)
};
}
async launchPersistentContext(params, metadata) {
const browserContext = await this._object.launchPersistentContext(metadata, params.userDataDir, params);
return {
context: new _browserContextDispatcher.BrowserContextDispatcher(this, browserContext)
};
}
async connectOverCDP(params, metadata) {
const browser = await this._object.connectOverCDP(metadata, params.endpointURL, params, params.timeout);
const browserDispatcher = new _browserDispatcher.BrowserDispatcher(this, browser);
return {
browser: browserDispatcher,
defaultContext: browser._defaultContext ? new _browserContextDispatcher.BrowserContextDispatcher(browserDispatcher, browser._defaultContext) : undefined
};
}
}
exports.BrowserTypeDispatcher = BrowserTypeDispatcher;

View File

@@ -0,0 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CDPSessionDispatcher = void 0;
var _crConnection = require("../chromium/crConnection");
var _dispatcher = require("./dispatcher");
/**
* 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 CDPSessionDispatcher extends _dispatcher.Dispatcher {
constructor(scope, crSession) {
super(scope, crSession, 'CDPSession', {});
this._type_CDPSession = true;
crSession._eventListener = (method, params) => {
this._dispatchEvent('event', {
method,
params
});
};
this.addObjectListener(_crConnection.CRSessionEvents.Disconnected, () => this._dispose());
}
async send(params) {
return {
result: await this._object.send(params.method, params.params)
};
}
async detach() {
return this._object.detach();
}
}
exports.CDPSessionDispatcher = CDPSessionDispatcher;

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ConsoleMessageDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
/**
* 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 ConsoleMessageDispatcher extends _dispatcher.Dispatcher {
constructor(page, message) {
super(page, message, 'ConsoleMessage', {
type: message.type(),
text: message.text(),
args: message.args().map(a => _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(page, a)),
location: message.location(),
page
});
this._type_ConsoleMessage = true;
}
}
exports.ConsoleMessageDispatcher = ConsoleMessageDispatcher;

View File

@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DebugControllerDispatcher = void 0;
var _utils = require("playwright-core/lib/utils");
var _debugController = require("../debugController");
var _dispatcher = require("./dispatcher");
/**
* 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 DebugControllerDispatcher extends _dispatcher.Dispatcher {
constructor(connection, debugController) {
super(connection, debugController, 'DebugController', {});
this._type_DebugController = void 0;
this._listeners = void 0;
this._type_DebugController = true;
this._listeners = [_utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.StateChanged, params => {
this._dispatchEvent('stateChanged', params);
}), _utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.InspectRequested, ({
selector,
locator
}) => {
this._dispatchEvent('inspectRequested', {
selector,
locator
});
}), _utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.SourceChanged, ({
text,
header,
footer,
actions
}) => {
this._dispatchEvent('sourceChanged', {
text,
header,
footer,
actions
});
}), _utils.eventsHelper.addEventListener(this._object, _debugController.DebugController.Events.Paused, ({
paused
}) => {
this._dispatchEvent('paused', {
paused
});
})];
}
async initialize(params) {
this._object.initialize(params.codegenId, params.sdkLanguage);
}
async setReportStateChanged(params) {
this._object.setReportStateChanged(params.enabled);
}
async resetForReuse() {
await this._object.resetForReuse();
}
async navigate(params) {
await this._object.navigate(params.url);
}
async setRecorderMode(params) {
await this._object.setRecorderMode(params);
}
async highlight(params) {
await this._object.highlight(params.selector);
}
async hideHighlight() {
await this._object.hideHighlight();
}
async resume() {
await this._object.resume();
}
async kill() {
await this._object.kill();
}
async closeAllBrowsers() {
await this._object.closeAllBrowsers();
}
_onDispose() {
_utils.eventsHelper.removeEventListeners(this._listeners);
this._object.dispose();
}
}
exports.DebugControllerDispatcher = DebugControllerDispatcher;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DialogDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _pageDispatcher = require("./pageDispatcher");
/**
* 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 DialogDispatcher extends _dispatcher.Dispatcher {
constructor(scope, dialog) {
const page = _pageDispatcher.PageDispatcher.fromNullable(scope, dialog.page().initializedOrUndefined());
// Prefer scoping to the page, unless we don't have one.
super(page || scope, dialog, 'Dialog', {
page,
type: dialog.type(),
message: dialog.message(),
defaultValue: dialog.defaultValue()
});
this._type_Dialog = true;
}
async accept(params) {
await this._object.accept(params.promptText);
}
async dismiss() {
await this._object.dismiss();
}
}
exports.DialogDispatcher = DialogDispatcher;

View File

@@ -0,0 +1,344 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.dispatcherSymbol = exports.RootDispatcher = exports.DispatcherConnection = exports.Dispatcher = void 0;
exports.existingDispatcher = existingDispatcher;
var _events = require("events");
var _serializers = require("../../protocol/serializers");
var _validator = require("../../protocol/validator");
var _utils = require("../../utils");
var _errors = require("../../common/errors");
var _instrumentation = require("../instrumentation");
var _stackTrace = require("../../utils/stackTrace");
var _eventsHelper = require("../..//utils/eventsHelper");
/**
* 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 dispatcherSymbol = Symbol('dispatcher');
exports.dispatcherSymbol = dispatcherSymbol;
const metadataValidator = (0, _validator.createMetadataValidator)();
function existingDispatcher(object) {
return object[dispatcherSymbol];
}
class Dispatcher extends _events.EventEmitter {
// Parent is always "isScope".
// Only "isScope" channel owners have registered dispatchers inside.
constructor(parent, object, type, initializer) {
super();
this._connection = void 0;
this._parent = void 0;
this._dispatchers = new Map();
this._disposed = false;
this._eventListeners = [];
this._guid = void 0;
this._type = void 0;
this._object = void 0;
this._connection = parent instanceof DispatcherConnection ? parent : parent._connection;
this._parent = parent instanceof DispatcherConnection ? undefined : parent;
const guid = object.guid;
(0, _utils.assert)(!this._connection._dispatchers.has(guid));
this._connection._dispatchers.set(guid, this);
if (this._parent) {
(0, _utils.assert)(!this._parent._dispatchers.has(guid));
this._parent._dispatchers.set(guid, this);
}
this._type = type;
this._guid = guid;
this._object = object;
object[dispatcherSymbol] = this;
if (this._parent) this._connection.sendCreate(this._parent, type, guid, initializer, this._parent._object);
}
parentScope() {
return this._parent;
}
addObjectListener(eventName, handler) {
this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._object, eventName, handler));
}
adopt(child) {
const oldParent = child._parent;
oldParent._dispatchers.delete(child._guid);
this._dispatchers.set(child._guid, child);
child._parent = this;
this._connection.sendAdopt(this, child);
}
_dispatchEvent(method, params) {
if (this._disposed) {
if ((0, _utils.isUnderTest)()) throw new Error(`${this._guid} is sending "${String(method)}" event after being disposed`);
// Just ignore this event outside of tests.
return;
}
const sdkObject = this._object instanceof _instrumentation.SdkObject ? this._object : undefined;
this._connection.sendEvent(this, method, params, sdkObject);
}
_dispose() {
this._disposeRecursively();
this._connection.sendDispose(this);
}
_onDispose() {}
_disposeRecursively() {
(0, _utils.assert)(!this._disposed, `${this._guid} is disposed more than once`);
this._onDispose();
this._disposed = true;
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
// Clean up from parent and connection.
if (this._parent) this._parent._dispatchers.delete(this._guid);
this._connection._dispatchers.delete(this._guid);
// Dispose all children.
for (const dispatcher of [...this._dispatchers.values()]) dispatcher._disposeRecursively();
this._dispatchers.clear();
delete this._object[dispatcherSymbol];
}
_debugScopeState() {
return {
_guid: this._guid,
objects: Array.from(this._dispatchers.values()).map(o => o._debugScopeState())
};
}
async waitForEventInfo() {
// Instrumentation takes care of this.
}
}
exports.Dispatcher = Dispatcher;
class RootDispatcher extends Dispatcher {
constructor(connection, createPlaywright) {
super(connection, {
guid: ''
}, 'Root', {});
this._initialized = false;
this.createPlaywright = createPlaywright;
}
async initialize(params) {
(0, _utils.assert)(this.createPlaywright);
(0, _utils.assert)(!this._initialized);
this._initialized = true;
return {
playwright: await this.createPlaywright(this, params)
};
}
}
exports.RootDispatcher = RootDispatcher;
class DispatcherConnection {
constructor(isLocal) {
this._dispatchers = new Map();
this.onmessage = message => {};
this._waitOperations = new Map();
this._isLocal = void 0;
this._isLocal = !!isLocal;
}
sendEvent(dispatcher, event, params, sdkObject) {
const validator = (0, _validator.findValidator)(dispatcher._type, event, 'Event');
params = validator(params, '', {
tChannelImpl: this._tChannelImplToWire.bind(this),
binary: this._isLocal ? 'buffer' : 'toBase64'
});
this._sendMessageToClient(dispatcher._guid, dispatcher._type, event, params, sdkObject);
}
sendCreate(parent, type, guid, initializer, sdkObject) {
const validator = (0, _validator.findValidator)(type, '', 'Initializer');
initializer = validator(initializer, '', {
tChannelImpl: this._tChannelImplToWire.bind(this),
binary: this._isLocal ? 'buffer' : 'toBase64'
});
this._sendMessageToClient(parent._guid, type, '__create__', {
type,
initializer,
guid
}, sdkObject);
}
sendAdopt(parent, dispatcher) {
this._sendMessageToClient(parent._guid, dispatcher._type, '__adopt__', {
guid: dispatcher._guid
});
}
sendDispose(dispatcher) {
this._sendMessageToClient(dispatcher._guid, dispatcher._type, '__dispose__', {});
}
_sendMessageToClient(guid, type, method, params, sdkObject) {
if (sdkObject) {
var _sdkObject$attributio, _sdkObject$attributio2, _sdkObject$instrument;
const event = {
type: 'event',
class: type,
method,
params: params || {},
time: (0, _utils.monotonicTime)(),
pageId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio = sdkObject.attribution) === null || _sdkObject$attributio === void 0 ? void 0 : (_sdkObject$attributio2 = _sdkObject$attributio.page) === null || _sdkObject$attributio2 === void 0 ? void 0 : _sdkObject$attributio2.guid
};
(_sdkObject$instrument = sdkObject.instrumentation) === null || _sdkObject$instrument === void 0 ? void 0 : _sdkObject$instrument.onEvent(sdkObject, event);
}
this.onmessage({
guid,
method,
params
});
}
_tChannelImplFromWire(names, arg, path, context) {
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
const guid = arg.guid;
const dispatcher = this._dispatchers.get(guid);
if (!dispatcher) throw new _validator.ValidationError(`${path}: no object with guid ${guid}`);
if (names !== '*' && !names.includes(dispatcher._type)) throw new _validator.ValidationError(`${path}: object with guid ${guid} has type ${dispatcher._type}, expected ${names.toString()}`);
return dispatcher;
}
throw new _validator.ValidationError(`${path}: expected guid for ${names.toString()}`);
}
_tChannelImplToWire(names, arg, path, context) {
if (arg instanceof Dispatcher) {
if (names !== '*' && !names.includes(arg._type)) throw new _validator.ValidationError(`${path}: dispatcher with guid ${arg._guid} has type ${arg._type}, expected ${names.toString()}`);
return {
guid: arg._guid
};
}
throw new _validator.ValidationError(`${path}: expected dispatcher ${names.toString()}`);
}
async dispatch(message) {
var _sdkObject$attributio3, _sdkObject$attributio4, _sdkObject$attributio5, _sdkObject$attributio6, _params$info;
const {
id,
guid,
method,
params,
metadata
} = message;
const dispatcher = this._dispatchers.get(guid);
if (!dispatcher) {
this.onmessage({
id,
error: (0, _serializers.serializeError)(new Error(_errors.kBrowserOrContextClosedError))
});
return;
}
let validParams;
let validMetadata;
try {
const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Params');
validParams = validator(params, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._isLocal ? 'buffer' : 'fromBase64'
});
validMetadata = metadataValidator(metadata, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._isLocal ? 'buffer' : 'fromBase64'
});
if (typeof dispatcher[method] !== 'function') throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
} catch (e) {
this.onmessage({
id,
error: (0, _serializers.serializeError)(e)
});
return;
}
const sdkObject = dispatcher._object instanceof _instrumentation.SdkObject ? dispatcher._object : undefined;
const callMetadata = {
id: `call@${id}`,
wallTime: validMetadata.wallTime || Date.now(),
location: validMetadata.location,
apiName: validMetadata.apiName,
internal: validMetadata.internal,
objectId: sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.guid,
pageId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio3 = sdkObject.attribution) === null || _sdkObject$attributio3 === void 0 ? void 0 : (_sdkObject$attributio4 = _sdkObject$attributio3.page) === null || _sdkObject$attributio4 === void 0 ? void 0 : _sdkObject$attributio4.guid,
frameId: sdkObject === null || sdkObject === void 0 ? void 0 : (_sdkObject$attributio5 = sdkObject.attribution) === null || _sdkObject$attributio5 === void 0 ? void 0 : (_sdkObject$attributio6 = _sdkObject$attributio5.frame) === null || _sdkObject$attributio6 === void 0 ? void 0 : _sdkObject$attributio6.guid,
startTime: (0, _utils.monotonicTime)(),
endTime: 0,
type: dispatcher._type,
method,
params: params || {},
log: []
};
if (sdkObject && params !== null && params !== void 0 && (_params$info = params.info) !== null && _params$info !== void 0 && _params$info.waitId) {
// Process logs for waitForNavigation/waitForLoadState/etc.
const info = params.info;
switch (info.phase) {
case 'before':
{
this._waitOperations.set(info.waitId, callMetadata);
await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata);
this.onmessage({
id
});
return;
}
case 'log':
{
const originalMetadata = this._waitOperations.get(info.waitId);
originalMetadata.log.push(info.message);
sdkObject.instrumentation.onCallLog(sdkObject, originalMetadata, 'api', info.message);
this.onmessage({
id
});
return;
}
case 'after':
{
const originalMetadata = this._waitOperations.get(info.waitId);
originalMetadata.endTime = (0, _utils.monotonicTime)();
originalMetadata.error = info.error ? {
error: {
name: 'Error',
message: info.error
}
} : undefined;
this._waitOperations.delete(info.waitId);
await sdkObject.instrumentation.onAfterCall(sdkObject, originalMetadata);
this.onmessage({
id
});
return;
}
}
}
let error;
await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata));
try {
const result = await dispatcher[method](validParams, callMetadata);
const validator = (0, _validator.findValidator)(dispatcher._type, method, 'Result');
callMetadata.result = validator(result, '', {
tChannelImpl: this._tChannelImplToWire.bind(this),
binary: this._isLocal ? 'buffer' : 'toBase64'
});
} catch (e) {
// Dispatching error
// We want original, unmodified error in metadata.
callMetadata.error = (0, _serializers.serializeError)(e);
if (callMetadata.log.length) (0, _stackTrace.rewriteErrorMessage)(e, e.message + formatLogRecording(callMetadata.log));
error = (0, _serializers.serializeError)(e);
} finally {
callMetadata.endTime = (0, _utils.monotonicTime)();
await (sdkObject === null || sdkObject === void 0 ? void 0 : sdkObject.instrumentation.onAfterCall(sdkObject, callMetadata));
}
const response = {
id
};
if (callMetadata.result) response.result = callMetadata.result;
if (error) response.error = error;
this.onmessage(response);
}
}
exports.DispatcherConnection = DispatcherConnection;
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,80 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ElectronDispatcher = exports.ElectronApplicationDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _electron = require("../electron/electron");
var _browserContextDispatcher = require("./browserContextDispatcher");
var _jsHandleDispatcher = require("./jsHandleDispatcher");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
/**
* 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 ElectronDispatcher extends _dispatcher.Dispatcher {
constructor(scope, electron) {
super(scope, electron, 'Electron', {});
this._type_Electron = true;
}
async launch(params) {
const electronApplication = await this._object.launch(params);
return {
electronApplication: new ElectronApplicationDispatcher(this, electronApplication)
};
}
}
exports.ElectronDispatcher = ElectronDispatcher;
class ElectronApplicationDispatcher extends _dispatcher.Dispatcher {
constructor(scope, electronApplication) {
super(scope, electronApplication, 'ElectronApplication', {
context: new _browserContextDispatcher.BrowserContextDispatcher(scope, electronApplication.context())
});
this._type_EventTarget = true;
this._type_ElectronApplication = true;
this.addObjectListener(_electron.ElectronApplication.Events.Close, () => {
this._dispatchEvent('close');
this._dispose();
});
}
async browserWindow(params) {
const handle = await this._object.browserWindow(params.page.page());
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this, handle)
};
}
async evaluateExpression(params) {
const handle = await this._object._nodeElectronHandlePromise;
return {
value: (0, _jsHandleDispatcher.serializeResult)(await handle.evaluateExpression(params.expression, {
isFunction: params.isFunction
}, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async evaluateExpressionHandle(params) {
const handle = await this._object._nodeElectronHandlePromise;
const result = await handle.evaluateExpressionHandle(params.expression, {
isFunction: params.isFunction
}, (0, _jsHandleDispatcher.parseArgument)(params.arg));
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this, result)
};
}
async close() {
await this._object.close();
}
}
exports.ElectronApplicationDispatcher = ElectronApplicationDispatcher;

View File

@@ -0,0 +1,233 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ElementHandleDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _jsHandleDispatcher = require("./jsHandleDispatcher");
var _frameDispatcher = require("./frameDispatcher");
var _utils = require("../../utils");
var _path = _interopRequireDefault(require("path"));
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 ElementHandleDispatcher extends _jsHandleDispatcher.JSHandleDispatcher {
static from(scope, handle) {
return (0, _dispatcher.existingDispatcher)(handle) || new ElementHandleDispatcher(scope, handle);
}
static fromNullable(scope, handle) {
if (!handle) return undefined;
return (0, _dispatcher.existingDispatcher)(handle) || new ElementHandleDispatcher(scope, handle);
}
static fromJSHandle(scope, handle) {
const result = (0, _dispatcher.existingDispatcher)(handle);
if (result) return result;
return handle.asElement() ? new ElementHandleDispatcher(scope, handle.asElement()) : new _jsHandleDispatcher.JSHandleDispatcher(scope, handle);
}
constructor(scope, elementHandle) {
super(scope, elementHandle);
this._type_ElementHandle = true;
this._elementHandle = void 0;
this._elementHandle = elementHandle;
}
async ownerFrame(params, metadata) {
const frame = await this._elementHandle.ownerFrame();
return {
frame: frame ? _frameDispatcher.FrameDispatcher.from(this.parentScope(), frame) : undefined
};
}
async contentFrame(params, metadata) {
const frame = await this._elementHandle.contentFrame();
return {
frame: frame ? _frameDispatcher.FrameDispatcher.from(this.parentScope(), frame) : undefined
};
}
async getAttribute(params, metadata) {
const value = await this._elementHandle.getAttribute(params.name);
return {
value: value === null ? undefined : value
};
}
async inputValue(params, metadata) {
const value = await this._elementHandle.inputValue();
return {
value
};
}
async textContent(params, metadata) {
const value = await this._elementHandle.textContent();
return {
value: value === null ? undefined : value
};
}
async innerText(params, metadata) {
return {
value: await this._elementHandle.innerText()
};
}
async innerHTML(params, metadata) {
return {
value: await this._elementHandle.innerHTML()
};
}
async isChecked(params, metadata) {
return {
value: await this._elementHandle.isChecked()
};
}
async isDisabled(params, metadata) {
return {
value: await this._elementHandle.isDisabled()
};
}
async isEditable(params, metadata) {
return {
value: await this._elementHandle.isEditable()
};
}
async isEnabled(params, metadata) {
return {
value: await this._elementHandle.isEnabled()
};
}
async isHidden(params, metadata) {
return {
value: await this._elementHandle.isHidden()
};
}
async isVisible(params, metadata) {
return {
value: await this._elementHandle.isVisible()
};
}
async dispatchEvent(params, metadata) {
await this._elementHandle.dispatchEvent(params.type, (0, _jsHandleDispatcher.parseArgument)(params.eventInit));
}
async scrollIntoViewIfNeeded(params, metadata) {
await this._elementHandle.scrollIntoViewIfNeeded(metadata, params);
}
async hover(params, metadata) {
return await this._elementHandle.hover(metadata, params);
}
async click(params, metadata) {
return await this._elementHandle.click(metadata, params);
}
async dblclick(params, metadata) {
return await this._elementHandle.dblclick(metadata, params);
}
async tap(params, metadata) {
return await this._elementHandle.tap(metadata, params);
}
async selectOption(params, metadata) {
const elements = (params.elements || []).map(e => e._elementHandle);
return {
values: await this._elementHandle.selectOption(metadata, elements, params.options || [], params)
};
}
async fill(params, metadata) {
return await this._elementHandle.fill(metadata, params.value, params);
}
async selectText(params, metadata) {
await this._elementHandle.selectText(metadata, params);
}
async setInputFiles(params, metadata) {
return await this._elementHandle.setInputFiles(metadata, {
files: params.files
}, params);
}
async setInputFilePaths(params, metadata) {
let {
localPaths
} = params;
if (!localPaths) {
if (!params.streams) throw new Error('Neither localPaths nor streams is specified');
localPaths = params.streams.map(c => c.path());
}
for (const p of localPaths) (0, _utils.assert)(_path.default.isAbsolute(p) && _path.default.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.');
return await this._elementHandle.setInputFiles(metadata, {
localPaths
}, params);
}
async focus(params, metadata) {
await this._elementHandle.focus(metadata);
}
async type(params, metadata) {
return await this._elementHandle.type(metadata, params.text, params);
}
async press(params, metadata) {
return await this._elementHandle.press(metadata, params.key, params);
}
async check(params, metadata) {
return await this._elementHandle.check(metadata, params);
}
async uncheck(params, metadata) {
return await this._elementHandle.uncheck(metadata, params);
}
async boundingBox(params, metadata) {
const value = await this._elementHandle.boundingBox();
return {
value: value || undefined
};
}
async screenshot(params, metadata) {
const mask = (params.mask || []).map(({
frame,
selector
}) => ({
frame: frame._object,
selector
}));
return {
binary: await this._elementHandle.screenshot(metadata, {
...params,
mask
})
};
}
async querySelector(params, metadata) {
const handle = await this._elementHandle.querySelector(params.selector, params);
return {
element: ElementHandleDispatcher.fromNullable(this.parentScope(), handle)
};
}
async querySelectorAll(params, metadata) {
const elements = await this._elementHandle.querySelectorAll(params.selector);
return {
elements: elements.map(e => ElementHandleDispatcher.from(this.parentScope(), e))
};
}
async evalOnSelector(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._elementHandle.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async evalOnSelectorAll(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._elementHandle.evalOnSelectorAll(params.selector, params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async waitForElementState(params, metadata) {
await this._elementHandle.waitForElementState(metadata, params.state, params);
}
async waitForSelector(params, metadata) {
return {
element: ElementHandleDispatcher.fromNullable(this.parentScope(), await this._elementHandle.waitForSelector(metadata, params.selector, params))
};
}
}
exports.ElementHandleDispatcher = ElementHandleDispatcher;

View File

@@ -0,0 +1,300 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FrameDispatcher = void 0;
var _frames = require("../frames");
var _dispatcher = require("./dispatcher");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
var _jsHandleDispatcher = require("./jsHandleDispatcher");
var _networkDispatchers = require("./networkDispatchers");
var _utils = require("../../utils");
var _path = _interopRequireDefault(require("path"));
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 FrameDispatcher extends _dispatcher.Dispatcher {
static from(scope, frame) {
const result = (0, _dispatcher.existingDispatcher)(frame);
return result || new FrameDispatcher(scope, frame);
}
static fromNullable(scope, frame) {
if (!frame) return;
return FrameDispatcher.from(scope, frame);
}
constructor(scope, frame) {
super(scope, frame, 'Frame', {
url: frame.url(),
name: frame.name(),
parentFrame: FrameDispatcher.fromNullable(scope, frame.parentFrame()),
loadStates: Array.from(frame._firedLifecycleEvents)
});
this._type_Frame = true;
this._frame = void 0;
this._frame = frame;
this.addObjectListener(_frames.Frame.Events.AddLifecycle, lifecycleEvent => {
this._dispatchEvent('loadstate', {
add: lifecycleEvent
});
});
this.addObjectListener(_frames.Frame.Events.RemoveLifecycle, lifecycleEvent => {
this._dispatchEvent('loadstate', {
remove: lifecycleEvent
});
});
this.addObjectListener(_frames.Frame.Events.InternalNavigation, event => {
if (!event.isPublic) return;
const params = {
url: event.url,
name: event.name,
error: event.error ? event.error.message : undefined
};
if (event.newDocument) params.newDocument = {
request: _networkDispatchers.RequestDispatcher.fromNullable(this.parentScope().parentScope(), event.newDocument.request || null)
};
this._dispatchEvent('navigated', params);
});
}
async goto(params, metadata) {
return {
response: _networkDispatchers.ResponseDispatcher.fromNullable(this.parentScope().parentScope(), await this._frame.goto(metadata, params.url, params))
};
}
async frameElement() {
return {
element: _elementHandlerDispatcher.ElementHandleDispatcher.from(this.parentScope(), await this._frame.frameElement())
};
}
async evaluateExpression(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._frame.evaluateExpression(params.expression, {
isFunction: params.isFunction,
exposeUtilityScript: params.exposeUtilityScript
}, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async evaluateExpressionHandle(params, metadata) {
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this.parentScope(), await this._frame.evaluateExpressionHandle(params.expression, {
isFunction: params.isFunction
}, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async waitForSelector(params, metadata) {
return {
element: _elementHandlerDispatcher.ElementHandleDispatcher.fromNullable(this.parentScope(), await this._frame.waitForSelector(metadata, params.selector, params))
};
}
async dispatchEvent(params, metadata) {
return this._frame.dispatchEvent(metadata, params.selector, params.type, (0, _jsHandleDispatcher.parseArgument)(params.eventInit), params);
}
async evalOnSelector(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._frame.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async evalOnSelectorAll(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._frame.evalOnSelectorAll(params.selector, params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async querySelector(params, metadata) {
return {
element: _elementHandlerDispatcher.ElementHandleDispatcher.fromNullable(this.parentScope(), await this._frame.querySelector(params.selector, params))
};
}
async querySelectorAll(params, metadata) {
const elements = await this._frame.querySelectorAll(params.selector);
return {
elements: elements.map(e => _elementHandlerDispatcher.ElementHandleDispatcher.from(this.parentScope(), e))
};
}
async queryCount(params) {
return {
value: await this._frame.queryCount(params.selector)
};
}
async content() {
return {
value: await this._frame.content()
};
}
async setContent(params, metadata) {
return await this._frame.setContent(metadata, params.html, params);
}
async addScriptTag(params, metadata) {
return {
element: _elementHandlerDispatcher.ElementHandleDispatcher.from(this.parentScope(), await this._frame.addScriptTag(params))
};
}
async addStyleTag(params, metadata) {
return {
element: _elementHandlerDispatcher.ElementHandleDispatcher.from(this.parentScope(), await this._frame.addStyleTag(params))
};
}
async click(params, metadata) {
return await this._frame.click(metadata, params.selector, params);
}
async dblclick(params, metadata) {
return await this._frame.dblclick(metadata, params.selector, params);
}
async dragAndDrop(params, metadata) {
return await this._frame.dragAndDrop(metadata, params.source, params.target, params);
}
async tap(params, metadata) {
return await this._frame.tap(metadata, params.selector, params);
}
async fill(params, metadata) {
return await this._frame.fill(metadata, params.selector, params.value, params);
}
async focus(params, metadata) {
await this._frame.focus(metadata, params.selector, params);
}
async blur(params, metadata) {
await this._frame.blur(metadata, params.selector, params);
}
async textContent(params, metadata) {
const value = await this._frame.textContent(metadata, params.selector, params);
return {
value: value === null ? undefined : value
};
}
async innerText(params, metadata) {
return {
value: await this._frame.innerText(metadata, params.selector, params)
};
}
async innerHTML(params, metadata) {
return {
value: await this._frame.innerHTML(metadata, params.selector, params)
};
}
async getAttribute(params, metadata) {
const value = await this._frame.getAttribute(metadata, params.selector, params.name, params);
return {
value: value === null ? undefined : value
};
}
async inputValue(params, metadata) {
const value = await this._frame.inputValue(metadata, params.selector, params);
return {
value
};
}
async isChecked(params, metadata) {
return {
value: await this._frame.isChecked(metadata, params.selector, params)
};
}
async isDisabled(params, metadata) {
return {
value: await this._frame.isDisabled(metadata, params.selector, params)
};
}
async isEditable(params, metadata) {
return {
value: await this._frame.isEditable(metadata, params.selector, params)
};
}
async isEnabled(params, metadata) {
return {
value: await this._frame.isEnabled(metadata, params.selector, params)
};
}
async isHidden(params, metadata) {
return {
value: await this._frame.isHidden(metadata, params.selector, params)
};
}
async isVisible(params, metadata) {
return {
value: await this._frame.isVisible(metadata, params.selector, params)
};
}
async hover(params, metadata) {
return await this._frame.hover(metadata, params.selector, params);
}
async selectOption(params, metadata) {
const elements = (params.elements || []).map(e => e._elementHandle);
return {
values: await this._frame.selectOption(metadata, params.selector, elements, params.options || [], params)
};
}
async setInputFiles(params, metadata) {
return await this._frame.setInputFiles(metadata, params.selector, {
files: params.files
}, params);
}
async setInputFilePaths(params, metadata) {
let {
localPaths
} = params;
if (!localPaths) {
if (!params.streams) throw new Error('Neither localPaths nor streams is specified');
localPaths = params.streams.map(c => c.path());
}
for (const p of localPaths) (0, _utils.assert)(_path.default.isAbsolute(p) && _path.default.resolve(p) === p, 'Paths provided to localPaths must be absolute and fully resolved.');
return await this._frame.setInputFiles(metadata, params.selector, {
localPaths
}, params);
}
async type(params, metadata) {
return await this._frame.type(metadata, params.selector, params.text, params);
}
async press(params, metadata) {
return await this._frame.press(metadata, params.selector, params.key, params);
}
async check(params, metadata) {
return await this._frame.check(metadata, params.selector, params);
}
async uncheck(params, metadata) {
return await this._frame.uncheck(metadata, params.selector, params);
}
async waitForTimeout(params, metadata) {
return await this._frame.waitForTimeout(metadata, params.timeout);
}
async waitForFunction(params, metadata) {
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this.parentScope(), await this._frame._waitForFunctionExpression(metadata, params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg), params))
};
}
async title(params, metadata) {
return {
value: await this._frame.title()
};
}
async highlight(params, metadata) {
return await this._frame.highlight(params.selector);
}
async expect(params, metadata) {
const expectedValue = params.expectedValue ? (0, _jsHandleDispatcher.parseArgument)(params.expectedValue) : undefined;
const result = await this._frame.expect(metadata, params.selector, {
...params,
expectedValue
});
if (result.received !== undefined) result.received = (0, _jsHandleDispatcher.serializeResult)(result.received);
if (result.matches === params.isNot) metadata.error = {
error: {
name: 'Expect',
message: 'Expect failed'
}
};
return result;
}
}
exports.FrameDispatcher = FrameDispatcher;

View File

@@ -0,0 +1,100 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JSHandleDispatcher = void 0;
exports.parseArgument = parseArgument;
exports.parseValue = parseValue;
exports.serializeResult = serializeResult;
var _dispatcher = require("./dispatcher");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
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 JSHandleDispatcher extends _dispatcher.Dispatcher {
constructor(scope, jsHandle) {
// Do not call this directly, use createHandle() instead.
super(scope, jsHandle, jsHandle.asElement() ? 'ElementHandle' : 'JSHandle', {
preview: jsHandle.toString()
});
this._type_JSHandle = true;
jsHandle._setPreviewCallback(preview => this._dispatchEvent('previewUpdated', {
preview
}));
}
async evaluateExpression(params) {
return {
value: serializeResult(await this._object.evaluateExpression(params.expression, {
isFunction: params.isFunction
}, parseArgument(params.arg)))
};
}
async evaluateExpressionHandle(params) {
const jsHandle = await this._object.evaluateExpressionHandle(params.expression, {
isFunction: params.isFunction
}, parseArgument(params.arg));
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this.parentScope(), jsHandle)
};
}
async getProperty(params) {
const jsHandle = await this._object.getProperty(params.name);
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this.parentScope(), jsHandle)
};
}
async getPropertyList() {
const map = await this._object.getProperties();
const properties = [];
for (const [name, value] of map) properties.push({
name,
value: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this.parentScope(), value)
});
return {
properties
};
}
async jsonValue() {
return {
value: serializeResult(await this._object.jsonValue())
};
}
async objectCount(params) {
return {
count: await this._object.objectCount()
};
}
async dispose() {
await this._object.dispose();
}
}
// Generic channel parser converts guids to JSHandleDispatchers,
// and this function takes care of coverting them into underlying JSHandles.
exports.JSHandleDispatcher = JSHandleDispatcher;
function parseArgument(arg) {
return (0, _serializers.parseSerializedValue)(arg.value, arg.handles.map(a => a._object));
}
function parseValue(v) {
return (0, _serializers.parseSerializedValue)(v, []);
}
function serializeResult(arg) {
return (0, _serializers.serializeValue)(arg, value => ({
fallThrough: value
}));
}

View File

@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JsonPipeDispatcher = void 0;
var _dispatcher = require("./dispatcher");
var _utils = require("../../utils");
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 JsonPipeDispatcher extends _dispatcher.Dispatcher {
constructor(scope) {
super(scope, {
guid: 'jsonPipe@' + (0, _utils.createGuid)()
}, 'JsonPipe', {});
this._type_JsonPipe = true;
}
async send(params) {
this.emit('message', params.message);
}
async close() {
this.emit('close');
if (!this._disposed) {
this._dispatchEvent('closed', {});
this._dispose();
}
}
dispatch(message) {
if (!this._disposed) this._dispatchEvent('message', {
message
});
}
wasClosed(error) {
if (!this._disposed) {
const params = error ? {
error: (0, _serializers.serializeError)(error)
} : {};
this._dispatchEvent('closed', params);
this._dispose();
}
}
dispose() {
this._dispose();
}
}
exports.JsonPipeDispatcher = JsonPipeDispatcher;

View File

@@ -0,0 +1,392 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LocalUtilsDispatcher = void 0;
exports.urlToWSEndpoint = urlToWSEndpoint;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _os = _interopRequireDefault(require("os"));
var _manualPromise = require("../../utils/manualPromise");
var _utils = require("../../utils");
var _dispatcher = require("./dispatcher");
var _zipBundle = require("../../zipBundle");
var _zipFile = require("../../utils/zipFile");
var _jsonPipeDispatcher = require("../dispatchers/jsonPipeDispatcher");
var _transport = require("../transport");
var _socksInterceptor = require("../socksInterceptor");
var _userAgent = require("../../utils/userAgent");
var _progress = require("../progress");
var _network = require("../../utils/network");
var _instrumentation = require("../../server/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 LocalUtilsDispatcher extends _dispatcher.Dispatcher {
constructor(scope, playwright) {
const localUtils = new _instrumentation.SdkObject(playwright, 'localUtils', 'localUtils');
super(scope, localUtils, 'LocalUtils', {});
this._type_LocalUtils = void 0;
this._harBackends = new Map();
this._stackSessions = new Map();
this._type_LocalUtils = true;
}
async zip(params) {
const promise = new _manualPromise.ManualPromise();
const zipFile = new _zipBundle.yazl.ZipFile();
zipFile.on('error', error => promise.reject(error));
const addFile = (file, name) => {
try {
if (_fs.default.statSync(file).isFile()) zipFile.addFile(file, name);
} catch (e) {}
};
for (const entry of params.entries) addFile(entry.value, entry.name);
// Add stacks and the sources.
const stackSession = params.stacksId ? this._stackSessions.get(params.stacksId) : undefined;
if (stackSession !== null && stackSession !== void 0 && stackSession.callStacks.length) {
await stackSession.writer;
if (process.env.PW_LIVE_TRACE_STACKS) {
zipFile.addFile(stackSession.file, 'trace.stacks');
} else {
const buffer = Buffer.from(JSON.stringify((0, _utils.serializeClientSideCallMetadata)(stackSession.callStacks)));
zipFile.addBuffer(buffer, 'trace.stacks');
}
}
// Collect sources from stacks.
if (params.includeSources) {
const sourceFiles = new Set();
for (const {
stack
} of (stackSession === null || stackSession === void 0 ? void 0 : stackSession.callStacks) || []) {
if (!stack) continue;
for (const {
file
} of stack) sourceFiles.add(file);
}
for (const sourceFile of sourceFiles) addFile(sourceFile, 'resources/src@' + (0, _utils.calculateSha1)(sourceFile) + '.txt');
}
if (params.mode === 'write') {
// New file, just compress the entries.
await _fs.default.promises.mkdir(_path.default.dirname(params.zipFile), {
recursive: true
});
zipFile.end(undefined, () => {
zipFile.outputStream.pipe(_fs.default.createWriteStream(params.zipFile)).on('close', () => promise.resolve());
});
await promise;
await this._deleteStackSession(params.stacksId);
return;
}
// File already exists. Repack and add new entries.
const tempFile = params.zipFile + '.tmp';
await _fs.default.promises.rename(params.zipFile, tempFile);
_zipBundle.yauzl.open(tempFile, (err, inZipFile) => {
if (err) {
promise.reject(err);
return;
}
(0, _utils.assert)(inZipFile);
let pendingEntries = inZipFile.entryCount;
inZipFile.on('entry', entry => {
inZipFile.openReadStream(entry, (err, readStream) => {
if (err) {
promise.reject(err);
return;
}
zipFile.addReadStream(readStream, entry.fileName);
if (--pendingEntries === 0) {
zipFile.end(undefined, () => {
zipFile.outputStream.pipe(_fs.default.createWriteStream(params.zipFile)).on('close', () => {
_fs.default.promises.unlink(tempFile).then(() => {
promise.resolve();
});
});
});
}
});
});
});
await promise;
await this._deleteStackSession(params.stacksId);
}
async harOpen(params, metadata) {
let harBackend;
if (params.file.endsWith('.zip')) {
const zipFile = new _zipFile.ZipFile(params.file);
const entryNames = await zipFile.entries();
const harEntryName = entryNames.find(e => e.endsWith('.har'));
if (!harEntryName) return {
error: 'Specified archive does not have a .har file'
};
const har = await zipFile.read(harEntryName);
const harFile = JSON.parse(har.toString());
harBackend = new HarBackend(harFile, null, zipFile);
} else {
const harFile = JSON.parse(await _fs.default.promises.readFile(params.file, 'utf-8'));
harBackend = new HarBackend(harFile, _path.default.dirname(params.file), null);
}
this._harBackends.set(harBackend.id, harBackend);
return {
harId: harBackend.id
};
}
async harLookup(params, metadata) {
const harBackend = this._harBackends.get(params.harId);
if (!harBackend) return {
action: 'error',
message: `Internal error: har was not opened`
};
return await harBackend.lookup(params.url, params.method, params.headers, params.postData, params.isNavigationRequest);
}
async harClose(params, metadata) {
const harBackend = this._harBackends.get(params.harId);
if (harBackend) {
this._harBackends.delete(harBackend.id);
harBackend.dispose();
}
}
async harUnzip(params, metadata) {
const dir = _path.default.dirname(params.zipFile);
const zipFile = new _zipFile.ZipFile(params.zipFile);
for (const entry of await zipFile.entries()) {
const buffer = await zipFile.read(entry);
if (entry === 'har.har') await _fs.default.promises.writeFile(params.harFile, buffer);else await _fs.default.promises.writeFile(_path.default.join(dir, entry), buffer);
}
zipFile.close();
await _fs.default.promises.unlink(params.zipFile);
}
async connect(params, metadata) {
const controller = new _progress.ProgressController(metadata, this._object);
controller.setLogName('browser');
return await controller.run(async progress => {
var _params$exposeNetwork;
const wsHeaders = {
'User-Agent': (0, _userAgent.getUserAgent)(),
'x-playwright-proxy': (_params$exposeNetwork = params.exposeNetwork) !== null && _params$exposeNetwork !== void 0 ? _params$exposeNetwork : '',
...params.headers
};
const wsEndpoint = await urlToWSEndpoint(progress, params.wsEndpoint);
const transport = await _transport.WebSocketTransport.connect(progress, wsEndpoint, wsHeaders, true, 'x-playwright-debug-log');
const socksInterceptor = new _socksInterceptor.SocksInterceptor(transport, params.exposeNetwork, params.socksProxyRedirectPortForTest);
const pipe = new _jsonPipeDispatcher.JsonPipeDispatcher(this);
transport.onmessage = json => {
if (socksInterceptor.interceptMessage(json)) return;
const cb = () => {
try {
pipe.dispatch(json);
} catch (e) {
transport.close();
}
};
if (params.slowMo) setTimeout(cb, params.slowMo);else cb();
};
pipe.on('message', message => {
transport.send(message);
});
transport.onclose = () => {
socksInterceptor === null || socksInterceptor === void 0 ? void 0 : socksInterceptor.cleanup();
pipe.wasClosed();
};
pipe.on('close', () => transport.close());
return {
pipe,
headers: transport.headers
};
}, params.timeout || 0);
}
async tracingStarted(params, metadata) {
let tmpDir = undefined;
if (!params.tracesDir) tmpDir = await _fs.default.promises.mkdtemp(_path.default.join(_os.default.tmpdir(), 'playwright-tracing-'));
const traceStacksFile = _path.default.join(params.tracesDir || tmpDir, params.traceName + '.stacks');
this._stackSessions.set(traceStacksFile, {
callStacks: [],
file: traceStacksFile,
writer: Promise.resolve(),
tmpDir
});
return {
stacksId: traceStacksFile
};
}
async traceDiscarded(params, metadata) {
await this._deleteStackSession(params.stacksId);
}
async addStackToTracingNoReply(params, metadata) {
for (const session of this._stackSessions.values()) {
session.callStacks.push(params.callData);
if (process.env.PW_LIVE_TRACE_STACKS) {
session.writer = session.writer.then(() => {
const buffer = Buffer.from(JSON.stringify((0, _utils.serializeClientSideCallMetadata)(session.callStacks)));
return _fs.default.promises.writeFile(session.file, buffer);
});
}
}
}
async _deleteStackSession(stacksId) {
const session = stacksId ? this._stackSessions.get(stacksId) : undefined;
if (!session) return;
await session.writer;
if (session.tmpDir) await (0, _utils.removeFolders)([session.tmpDir]);
this._stackSessions.delete(stacksId);
}
}
exports.LocalUtilsDispatcher = LocalUtilsDispatcher;
const redirectStatus = [301, 302, 303, 307, 308];
class HarBackend {
constructor(harFile, baseDir, zipFile) {
this.id = (0, _utils.createGuid)();
this._harFile = void 0;
this._zipFile = void 0;
this._baseDir = void 0;
this._harFile = harFile;
this._baseDir = baseDir;
this._zipFile = zipFile;
}
async lookup(url, method, headers, postData, isNavigationRequest) {
let entry;
try {
entry = await this._harFindResponse(url, method, headers, postData);
} catch (e) {
return {
action: 'error',
message: 'HAR error: ' + e.message
};
}
if (!entry) return {
action: 'noentry'
};
// If navigation is being redirected, restart it with the final url to ensure the document's url changes.
if (entry.request.url !== url && isNavigationRequest) return {
action: 'redirect',
redirectURL: entry.request.url
};
const response = entry.response;
try {
const buffer = await this._loadContent(response.content);
return {
action: 'fulfill',
status: response.status,
headers: response.headers,
body: buffer
};
} catch (e) {
return {
action: 'error',
message: e.message
};
}
}
async _loadContent(content) {
const file = content._file;
let buffer;
if (file) {
if (this._zipFile) buffer = await this._zipFile.read(file);else buffer = await _fs.default.promises.readFile(_path.default.resolve(this._baseDir, file));
} else {
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
}
return buffer;
}
async _harFindResponse(url, method, headers, postData) {
const harLog = this._harFile.log;
const visited = new Set();
while (true) {
const entries = [];
for (const candidate of harLog.entries) {
if (candidate.request.url !== url || candidate.request.method !== method) continue;
if (method === 'POST' && postData && candidate.request.postData) {
const buffer = await this._loadContent(candidate.request.postData);
if (!buffer.equals(postData)) continue;
}
entries.push(candidate);
}
if (!entries.length) return;
let entry = entries[0];
// Disambiguate using headers - then one with most matching headers wins.
if (entries.length > 1) {
const list = [];
for (const candidate of entries) {
const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers);
list.push({
candidate,
matchingHeaders
});
}
list.sort((a, b) => b.matchingHeaders - a.matchingHeaders);
entry = list[0].candidate;
}
if (visited.has(entry)) throw new Error(`Found redirect cycle for ${url}`);
visited.add(entry);
// Follow redirects.
const locationHeader = entry.response.headers.find(h => h.name.toLowerCase() === 'location');
if (redirectStatus.includes(entry.response.status) && locationHeader) {
const locationURL = new URL(locationHeader.value, url);
url = locationURL.toString();
if ((entry.response.status === 301 || entry.response.status === 302) && method === 'POST' || entry.response.status === 303 && !['GET', 'HEAD'].includes(method)) {
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
method = 'GET';
}
continue;
}
return entry;
}
}
dispose() {
var _this$_zipFile;
(_this$_zipFile = this._zipFile) === null || _this$_zipFile === void 0 ? void 0 : _this$_zipFile.close();
}
}
function countMatchingHeaders(harHeaders, headers) {
const set = new Set(headers.map(h => h.name.toLowerCase() + ':' + h.value));
let matches = 0;
for (const h of harHeaders) {
if (set.has(h.name.toLowerCase() + ':' + h.value)) ++matches;
}
return matches;
}
async function urlToWSEndpoint(progress, endpointURL) {
var _progress$timeUntilDe;
if (endpointURL.startsWith('ws')) return endpointURL;
progress === null || progress === void 0 ? void 0 : progress.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
const fetchUrl = new URL(endpointURL);
if (!fetchUrl.pathname.endsWith('/')) fetchUrl.pathname += '/';
fetchUrl.pathname += 'json';
const json = await (0, _network.fetchData)({
url: fetchUrl.toString(),
method: 'GET',
timeout: (_progress$timeUntilDe = progress === null || progress === void 0 ? void 0 : progress.timeUntilDeadline()) !== null && _progress$timeUntilDe !== void 0 ? _progress$timeUntilDe : 30_000,
headers: {
'User-Agent': (0, _userAgent.getUserAgent)()
}
}, async (params, response) => {
return new Error(`Unexpected status ${response.statusCode} when connecting to ${fetchUrl.toString()}.\n` + `This does not look like a Playwright server, try connecting via ws://.`);
});
progress === null || progress === void 0 ? void 0 : progress.throwIfAborted();
const wsUrl = new URL(endpointURL);
let wsEndpointPath = JSON.parse(json).wsEndpointPath;
if (wsEndpointPath.startsWith('/')) wsEndpointPath = wsEndpointPath.substring(1);
if (!wsUrl.pathname.endsWith('/')) wsUrl.pathname += '/';
wsUrl.pathname += wsEndpointPath;
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
return wsUrl.toString();
}

View File

@@ -0,0 +1,213 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WebSocketDispatcher = exports.RouteDispatcher = exports.ResponseDispatcher = exports.RequestDispatcher = exports.APIRequestContextDispatcher = void 0;
var _network = require("../network");
var _dispatcher = require("./dispatcher");
var _tracingDispatcher = require("./tracingDispatcher");
var _frameDispatcher = require("./frameDispatcher");
var _pageDispatcher = require("./pageDispatcher");
/**
* 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 RequestDispatcher extends _dispatcher.Dispatcher {
static from(scope, request) {
const result = (0, _dispatcher.existingDispatcher)(request);
return result || new RequestDispatcher(scope, request);
}
static fromNullable(scope, request) {
return request ? RequestDispatcher.from(scope, request) : undefined;
}
constructor(scope, request) {
const postData = request.postDataBuffer();
super(scope, request, 'Request', {
frame: _frameDispatcher.FrameDispatcher.fromNullable(scope, request.frame()),
serviceWorker: _pageDispatcher.WorkerDispatcher.fromNullable(scope, request.serviceWorker()),
url: request.url(),
resourceType: request.resourceType(),
method: request.method(),
postData: postData === null ? undefined : postData,
headers: request.headers(),
isNavigationRequest: request.isNavigationRequest(),
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom())
});
this._type_Request = void 0;
this._type_Request = true;
}
async rawRequestHeaders(params) {
return {
headers: await this._object.rawRequestHeaders()
};
}
async response() {
return {
response: ResponseDispatcher.fromNullable(this.parentScope(), await this._object.response())
};
}
}
exports.RequestDispatcher = RequestDispatcher;
class ResponseDispatcher extends _dispatcher.Dispatcher {
static from(scope, response) {
const result = (0, _dispatcher.existingDispatcher)(response);
return result || new ResponseDispatcher(scope, response);
}
static fromNullable(scope, response) {
return response ? ResponseDispatcher.from(scope, response) : undefined;
}
constructor(scope, response) {
super(scope, response, 'Response', {
// TODO: responses in popups can point to non-reported requests.
request: RequestDispatcher.from(scope, response.request()),
url: response.url(),
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
timing: response.timing(),
fromServiceWorker: response.fromServiceWorker()
});
this._type_Response = true;
}
async body() {
return {
binary: await this._object.body()
};
}
async securityDetails() {
return {
value: (await this._object.securityDetails()) || undefined
};
}
async serverAddr() {
return {
value: (await this._object.serverAddr()) || undefined
};
}
async rawResponseHeaders(params) {
return {
headers: await this._object.rawResponseHeaders()
};
}
async sizes(params) {
return {
sizes: await this._object.sizes()
};
}
}
exports.ResponseDispatcher = ResponseDispatcher;
class RouteDispatcher extends _dispatcher.Dispatcher {
static from(scope, route) {
const result = (0, _dispatcher.existingDispatcher)(route);
return result || new RouteDispatcher(scope, route);
}
constructor(scope, route) {
super(scope, route, 'Route', {
// Context route can point to a non-reported request.
request: scope
});
this._type_Route = true;
}
async continue(params, metadata) {
// Used to discriminate between continue in tracing.
await this._object.continue({
url: params.url,
method: params.method,
headers: params.headers,
postData: params.postData,
isFallback: params.isFallback
});
}
async fulfill(params, metadata) {
// Used to discriminate between fulfills in tracing.
await this._object.fulfill(params);
}
async abort(params, metadata) {
// Used to discriminate between abort in tracing.
await this._object.abort(params.errorCode || 'failed');
}
async redirectNavigationRequest(params) {
await this._object.redirectNavigationRequest(params.url);
}
}
exports.RouteDispatcher = RouteDispatcher;
class WebSocketDispatcher extends _dispatcher.Dispatcher {
constructor(scope, webSocket) {
super(scope, webSocket, 'WebSocket', {
url: webSocket.url()
});
this._type_EventTarget = true;
this._type_WebSocket = true;
this.addObjectListener(_network.WebSocket.Events.FrameSent, event => this._dispatchEvent('frameSent', event));
this.addObjectListener(_network.WebSocket.Events.FrameReceived, event => this._dispatchEvent('frameReceived', event));
this.addObjectListener(_network.WebSocket.Events.SocketError, error => this._dispatchEvent('socketError', {
error
}));
this.addObjectListener(_network.WebSocket.Events.Close, () => this._dispatchEvent('close', {}));
}
}
exports.WebSocketDispatcher = WebSocketDispatcher;
class APIRequestContextDispatcher extends _dispatcher.Dispatcher {
static from(scope, request) {
const result = (0, _dispatcher.existingDispatcher)(request);
return result || new APIRequestContextDispatcher(scope, request);
}
static fromNullable(scope, request) {
return request ? APIRequestContextDispatcher.from(scope, request) : undefined;
}
constructor(parentScope, request) {
// We will reparent these to the context below.
const tracing = _tracingDispatcher.TracingDispatcher.from(parentScope, request.tracing());
super(parentScope, request, 'APIRequestContext', {
tracing
});
this._type_APIRequestContext = true;
this.adopt(tracing);
}
async storageState(params) {
return this._object.storageState();
}
async dispose(params) {
await this._object.dispose();
}
async fetch(params, metadata) {
const fetchResponse = await this._object.fetch(params, metadata);
return {
response: {
url: fetchResponse.url,
status: fetchResponse.status,
statusText: fetchResponse.statusText,
headers: fetchResponse.headers,
fetchUid: fetchResponse.fetchUid
}
};
}
async fetchResponseBody(params) {
return {
binary: this._object.fetchResponses.get(params.fetchUid)
};
}
async fetchLog(params) {
const log = this._object.fetchLog.get(params.fetchUid) || [];
return {
log
};
}
async disposeAPIResponse(params) {
this._object.disposeResponse(params.fetchUid);
}
}
exports.APIRequestContextDispatcher = APIRequestContextDispatcher;

View File

@@ -0,0 +1,351 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WorkerDispatcher = exports.PageDispatcher = exports.BindingCallDispatcher = void 0;
var _page = require("../page");
var _dispatcher = require("./dispatcher");
var _serializers = require("../../protocol/serializers");
var _frameDispatcher = require("./frameDispatcher");
var _networkDispatchers = require("./networkDispatchers");
var _jsHandleDispatcher = require("./jsHandleDispatcher");
var _elementHandlerDispatcher = require("./elementHandlerDispatcher");
var _artifactDispatcher = require("./artifactDispatcher");
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 PageDispatcher extends _dispatcher.Dispatcher {
static from(parentScope, page) {
return PageDispatcher.fromNullable(parentScope, page);
}
static fromNullable(parentScope, page) {
if (!page) return undefined;
const result = (0, _dispatcher.existingDispatcher)(page);
return result || new PageDispatcher(parentScope, page);
}
constructor(parentScope, page) {
// TODO: theoretically, there could be more than one frame already.
// If we split pageCreated and pageReady, there should be no main frame during pageCreated.
// We will reparent it to the page below using adopt.
const mainFrame = _frameDispatcher.FrameDispatcher.from(parentScope, page.mainFrame());
super(parentScope, page, 'Page', {
mainFrame,
viewportSize: page.viewportSize() || undefined,
isClosed: page.isClosed(),
opener: PageDispatcher.fromNullable(parentScope, page.opener())
});
this._type_EventTarget = true;
this._type_Page = true;
this._page = void 0;
this._subscriptions = new Set();
this.adopt(mainFrame);
this._page = page;
this.addObjectListener(_page.Page.Events.Close, () => {
this._dispatchEvent('close');
this._dispose();
});
this.addObjectListener(_page.Page.Events.Crash, () => this._dispatchEvent('crash'));
this.addObjectListener(_page.Page.Events.Download, download => {
// Artifact can outlive the page, so bind to the context scope.
this._dispatchEvent('download', {
url: download.url,
suggestedFilename: download.suggestedFilename(),
artifact: _artifactDispatcher.ArtifactDispatcher.from(parentScope, download.artifact)
});
});
this.addObjectListener(_page.Page.Events.FileChooser, fileChooser => this._dispatchEvent('fileChooser', {
element: _elementHandlerDispatcher.ElementHandleDispatcher.from(this, fileChooser.element()),
isMultiple: fileChooser.isMultiple()
}));
this.addObjectListener(_page.Page.Events.FrameAttached, frame => this._onFrameAttached(frame));
this.addObjectListener(_page.Page.Events.FrameDetached, frame => this._onFrameDetached(frame));
this.addObjectListener(_page.Page.Events.PageError, error => this._dispatchEvent('pageError', {
error: (0, _serializers.serializeError)(error)
}));
this.addObjectListener(_page.Page.Events.WebSocket, webSocket => this._dispatchEvent('webSocket', {
webSocket: new _networkDispatchers.WebSocketDispatcher(this, webSocket)
}));
this.addObjectListener(_page.Page.Events.Worker, worker => this._dispatchEvent('worker', {
worker: new WorkerDispatcher(this, worker)
}));
this.addObjectListener(_page.Page.Events.Video, artifact => this._dispatchEvent('video', {
artifact: _artifactDispatcher.ArtifactDispatcher.from(parentScope, artifact)
}));
if (page._video) this._dispatchEvent('video', {
artifact: _artifactDispatcher.ArtifactDispatcher.from(this.parentScope(), page._video)
});
// Ensure client knows about all frames.
const frames = page._frameManager.frames();
for (let i = 1; i < frames.length; i++) this._onFrameAttached(frames[i]);
}
page() {
return this._page;
}
async setDefaultNavigationTimeoutNoReply(params, metadata) {
this._page.setDefaultNavigationTimeout(params.timeout);
}
async setDefaultTimeoutNoReply(params, metadata) {
this._page.setDefaultTimeout(params.timeout);
}
async exposeBinding(params, metadata) {
await this._page.exposeBinding(params.name, !!params.needsHandle, (source, ...args) => {
// When reusing the context, we might have some bindings called late enough,
// after context and page dispatchers have been disposed.
if (this._disposed) return;
const binding = new BindingCallDispatcher(this, params.name, !!params.needsHandle, source, args);
this._dispatchEvent('bindingCall', {
binding
});
return binding.promise();
});
}
async setExtraHTTPHeaders(params, metadata) {
await this._page.setExtraHTTPHeaders(params.headers);
}
async reload(params, metadata) {
return {
response: _networkDispatchers.ResponseDispatcher.fromNullable(this.parentScope(), await this._page.reload(metadata, params))
};
}
async goBack(params, metadata) {
return {
response: _networkDispatchers.ResponseDispatcher.fromNullable(this.parentScope(), await this._page.goBack(metadata, params))
};
}
async goForward(params, metadata) {
return {
response: _networkDispatchers.ResponseDispatcher.fromNullable(this.parentScope(), await this._page.goForward(metadata, params))
};
}
async emulateMedia(params, metadata) {
await this._page.emulateMedia({
media: params.media,
colorScheme: params.colorScheme,
reducedMotion: params.reducedMotion,
forcedColors: params.forcedColors
});
}
async setViewportSize(params, metadata) {
await this._page.setViewportSize(params.viewportSize);
}
async addInitScript(params, metadata) {
await this._page.addInitScript(params.source);
}
async setNetworkInterceptionPatterns(params, metadata) {
if (!params.patterns.length) {
await this._page.setClientRequestInterceptor(undefined);
return;
}
const urlMatchers = params.patterns.map(pattern => pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags) : pattern.glob);
await this._page.setClientRequestInterceptor((route, request) => {
const matchesSome = urlMatchers.some(urlMatch => (0, _utils.urlMatches)(this._page._browserContext._options.baseURL, request.url(), urlMatch));
if (!matchesSome) return false;
this._dispatchEvent('route', {
route: _networkDispatchers.RouteDispatcher.from(_networkDispatchers.RequestDispatcher.from(this.parentScope(), request), route)
});
return true;
});
}
async expectScreenshot(params, metadata) {
var _params$screenshotOpt, _params$comparatorOpt;
const mask = (((_params$screenshotOpt = params.screenshotOptions) === null || _params$screenshotOpt === void 0 ? void 0 : _params$screenshotOpt.mask) || []).map(({
frame,
selector
}) => ({
frame: frame._object,
selector
}));
const locator = params.locator ? {
frame: params.locator.frame._object,
selector: params.locator.selector
} : undefined;
return await this._page.expectScreenshot(metadata, {
...params,
locator,
comparatorOptions: {
...params.comparatorOptions,
_comparator: (_params$comparatorOpt = params.comparatorOptions) === null || _params$comparatorOpt === void 0 ? void 0 : _params$comparatorOpt.comparator
},
screenshotOptions: {
...params.screenshotOptions,
mask
}
});
}
async screenshot(params, metadata) {
const mask = (params.mask || []).map(({
frame,
selector
}) => ({
frame: frame._object,
selector
}));
return {
binary: await this._page.screenshot(metadata, {
...params,
mask
})
};
}
async close(params, metadata) {
await this._page.close(metadata, params);
}
async updateSubscription(params) {
if (params.event === 'fileChooser') await this._page.setFileChooserIntercepted(params.enabled);
if (params.enabled) this._subscriptions.add(params.event);else this._subscriptions.delete(params.event);
}
async keyboardDown(params, metadata) {
await this._page.keyboard.down(params.key);
}
async keyboardUp(params, metadata) {
await this._page.keyboard.up(params.key);
}
async keyboardInsertText(params, metadata) {
await this._page.keyboard.insertText(params.text);
}
async keyboardType(params, metadata) {
await this._page.keyboard.type(params.text, params);
}
async keyboardPress(params, metadata) {
await this._page.keyboard.press(params.key, params);
}
async mouseMove(params, metadata) {
await this._page.mouse.move(params.x, params.y, params);
}
async mouseDown(params, metadata) {
await this._page.mouse.down(params);
}
async mouseUp(params, metadata) {
await this._page.mouse.up(params);
}
async mouseClick(params, metadata) {
await this._page.mouse.click(params.x, params.y, params);
}
async mouseWheel(params, metadata) {
await this._page.mouse.wheel(params.deltaX, params.deltaY);
}
async touchscreenTap(params, metadata) {
await this._page.touchscreen.tap(params.x, params.y);
}
async accessibilitySnapshot(params, metadata) {
const rootAXNode = await this._page.accessibility.snapshot({
interestingOnly: params.interestingOnly,
root: params.root ? params.root._elementHandle : undefined
});
return {
rootAXNode: rootAXNode || undefined
};
}
async pdf(params, metadata) {
if (!this._page.pdf) throw new Error('PDF generation is only supported for Headless Chromium');
const buffer = await this._page.pdf(params);
return {
pdf: buffer
};
}
async bringToFront(params, metadata) {
await this._page.bringToFront();
}
async startJSCoverage(params, metadata) {
const coverage = this._page.coverage;
await coverage.startJSCoverage(params);
}
async stopJSCoverage(params, metadata) {
const coverage = this._page.coverage;
return await coverage.stopJSCoverage();
}
async startCSSCoverage(params, metadata) {
const coverage = this._page.coverage;
await coverage.startCSSCoverage(params);
}
async stopCSSCoverage(params, metadata) {
const coverage = this._page.coverage;
return await coverage.stopCSSCoverage();
}
_onFrameAttached(frame) {
this._dispatchEvent('frameAttached', {
frame: _frameDispatcher.FrameDispatcher.from(this, frame)
});
}
_onFrameDetached(frame) {
this._dispatchEvent('frameDetached', {
frame: _frameDispatcher.FrameDispatcher.from(this, frame)
});
}
_onDispose() {
// Avoid protocol calls for the closed page.
if (!this._page.isClosedOrClosingOrCrashed()) this._page.setClientRequestInterceptor(undefined).catch(() => {});
}
}
exports.PageDispatcher = PageDispatcher;
class WorkerDispatcher extends _dispatcher.Dispatcher {
static fromNullable(scope, worker) {
if (!worker) return undefined;
const result = (0, _dispatcher.existingDispatcher)(worker);
return result || new WorkerDispatcher(scope, worker);
}
constructor(scope, worker) {
super(scope, worker, 'Worker', {
url: worker.url()
});
this._type_Worker = true;
this.addObjectListener(_page.Worker.Events.Close, () => this._dispatchEvent('close'));
}
async evaluateExpression(params, metadata) {
return {
value: (0, _jsHandleDispatcher.serializeResult)(await this._object.evaluateExpression(params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
async evaluateExpressionHandle(params, metadata) {
return {
handle: _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(this, await this._object.evaluateExpressionHandle(params.expression, params.isFunction, (0, _jsHandleDispatcher.parseArgument)(params.arg)))
};
}
}
exports.WorkerDispatcher = WorkerDispatcher;
class BindingCallDispatcher extends _dispatcher.Dispatcher {
constructor(scope, name, needsHandle, source, args) {
super(scope, {
guid: 'bindingCall@' + (0, _utils.createGuid)()
}, 'BindingCall', {
frame: _frameDispatcher.FrameDispatcher.from(scope, source.frame),
name,
args: needsHandle ? undefined : args.map(_jsHandleDispatcher.serializeResult),
handle: needsHandle ? _elementHandlerDispatcher.ElementHandleDispatcher.fromJSHandle(scope, args[0]) : undefined
});
this._type_BindingCall = true;
this._resolve = void 0;
this._reject = void 0;
this._promise = void 0;
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
promise() {
return this._promise;
}
async resolve(params, metadata) {
this._resolve((0, _jsHandleDispatcher.parseArgument)(params.result));
}
async reject(params, metadata) {
this._reject((0, _serializers.parseError)(params.error));
}
}
exports.BindingCallDispatcher = BindingCallDispatcher;

View File

@@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PlaywrightDispatcher = void 0;
var _fetch = require("../fetch");
var _socksProxy = require("../../common/socksProxy");
var _androidDispatcher = require("./androidDispatcher");
var _browserTypeDispatcher = require("./browserTypeDispatcher");
var _dispatcher = require("./dispatcher");
var _electronDispatcher = require("./electronDispatcher");
var _localUtilsDispatcher = require("./localUtilsDispatcher");
var _networkDispatchers = require("./networkDispatchers");
var _selectorsDispatcher = require("./selectorsDispatcher");
var _browserDispatcher = require("./browserDispatcher");
var _utils = require("../../utils");
var _eventsHelper = require("../../utils/eventsHelper");
/**
* 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 PlaywrightDispatcher extends _dispatcher.Dispatcher {
constructor(scope, playwright, socksProxy, preLaunchedBrowser, prelaunchedAndroidDevice) {
const descriptors = require('../deviceDescriptors');
const deviceDescriptors = Object.entries(descriptors).map(([name, descriptor]) => ({
name,
descriptor
}));
const browserDispatcher = preLaunchedBrowser ? new _browserDispatcher.ConnectedBrowserDispatcher(scope, preLaunchedBrowser) : undefined;
const android = new _androidDispatcher.AndroidDispatcher(scope, playwright.android);
const prelaunchedAndroidDeviceDispatcher = prelaunchedAndroidDevice ? new _androidDispatcher.AndroidDeviceDispatcher(android, prelaunchedAndroidDevice) : undefined;
super(scope, playwright, 'Playwright', {
chromium: new _browserTypeDispatcher.BrowserTypeDispatcher(scope, playwright.chromium),
firefox: new _browserTypeDispatcher.BrowserTypeDispatcher(scope, playwright.firefox),
webkit: new _browserTypeDispatcher.BrowserTypeDispatcher(scope, playwright.webkit),
android,
electron: new _electronDispatcher.ElectronDispatcher(scope, playwright.electron),
utils: new _localUtilsDispatcher.LocalUtilsDispatcher(scope, playwright),
deviceDescriptors,
selectors: new _selectorsDispatcher.SelectorsDispatcher(scope, (browserDispatcher === null || browserDispatcher === void 0 ? void 0 : browserDispatcher.selectors) || playwright.selectors),
preLaunchedBrowser: browserDispatcher,
preConnectedAndroidDevice: prelaunchedAndroidDeviceDispatcher,
socksSupport: socksProxy ? new SocksSupportDispatcher(scope, socksProxy) : undefined
});
this._type_Playwright = void 0;
this._browserDispatcher = void 0;
this._type_Playwright = true;
this._browserDispatcher = browserDispatcher;
}
async newRequest(params) {
const request = new _fetch.GlobalAPIRequestContext(this._object, params);
return {
request: _networkDispatchers.APIRequestContextDispatcher.from(this.parentScope(), request)
};
}
async cleanup() {
var _this$_browserDispatc;
// Cleanup contexts upon disconnect.
await ((_this$_browserDispatc = this._browserDispatcher) === null || _this$_browserDispatc === void 0 ? void 0 : _this$_browserDispatc.cleanupContexts());
}
}
exports.PlaywrightDispatcher = PlaywrightDispatcher;
class SocksSupportDispatcher extends _dispatcher.Dispatcher {
constructor(scope, socksProxy) {
super(scope, {
guid: 'socksSupport@' + (0, _utils.createGuid)()
}, 'SocksSupport', {});
this._type_SocksSupport = void 0;
this._socksProxy = void 0;
this._socksListeners = void 0;
this._type_SocksSupport = true;
this._socksProxy = socksProxy;
this._socksListeners = [_eventsHelper.eventsHelper.addEventListener(socksProxy, _socksProxy.SocksProxy.Events.SocksRequested, payload => this._dispatchEvent('socksRequested', payload)), _eventsHelper.eventsHelper.addEventListener(socksProxy, _socksProxy.SocksProxy.Events.SocksData, payload => this._dispatchEvent('socksData', payload)), _eventsHelper.eventsHelper.addEventListener(socksProxy, _socksProxy.SocksProxy.Events.SocksClosed, payload => this._dispatchEvent('socksClosed', payload))];
}
async socksConnected(params) {
var _this$_socksProxy;
(_this$_socksProxy = this._socksProxy) === null || _this$_socksProxy === void 0 ? void 0 : _this$_socksProxy.socketConnected(params);
}
async socksFailed(params) {
var _this$_socksProxy2;
(_this$_socksProxy2 = this._socksProxy) === null || _this$_socksProxy2 === void 0 ? void 0 : _this$_socksProxy2.socketFailed(params);
}
async socksData(params) {
var _this$_socksProxy3;
(_this$_socksProxy3 = this._socksProxy) === null || _this$_socksProxy3 === void 0 ? void 0 : _this$_socksProxy3.sendSocketData(params);
}
async socksError(params) {
var _this$_socksProxy4;
(_this$_socksProxy4 = this._socksProxy) === null || _this$_socksProxy4 === void 0 ? void 0 : _this$_socksProxy4.sendSocketError(params);
}
async socksEnd(params) {
var _this$_socksProxy5;
(_this$_socksProxy5 = this._socksProxy) === null || _this$_socksProxy5 === void 0 ? void 0 : _this$_socksProxy5.sendSocketEnd(params);
}
_onDispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._socksListeners);
}
}

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SelectorsDispatcher = void 0;
var _dispatcher = require("./dispatcher");
/**
* 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 SelectorsDispatcher extends _dispatcher.Dispatcher {
constructor(scope, selectors) {
super(scope, selectors, 'Selectors', {});
this._type_Selectors = true;
}
async register(params) {
await this._object.register(params.name, params.source, params.contentScript);
}
async setTestIdAttributeName(params) {
this._object.setTestIdAttributeName(params.testIdAttributeName);
}
}
exports.SelectorsDispatcher = SelectorsDispatcher;

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.StreamDispatcher = void 0;
var _dispatcher = require("./dispatcher");
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 StreamDispatcher extends _dispatcher.Dispatcher {
constructor(scope, stream) {
super(scope, {
guid: 'stream@' + (0, _utils.createGuid)(),
stream
}, 'Stream', {});
// In Node v12.9.0+ we can use readableEnded.
this._type_Stream = true;
this._ended = false;
stream.once('end', () => this._ended = true);
stream.once('error', () => this._ended = true);
}
async read(params) {
const stream = this._object.stream;
if (this._ended) return {
binary: Buffer.from('')
};
if (!stream.readableLength) {
await new Promise((fulfill, reject) => {
stream.once('readable', fulfill);
stream.once('end', fulfill);
stream.once('error', reject);
});
}
const buffer = stream.read(Math.min(stream.readableLength, params.size || stream.readableLength));
return {
binary: buffer || Buffer.from('')
};
}
async close() {
this._object.stream.destroy();
}
}
exports.StreamDispatcher = StreamDispatcher;

View File

@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TracingDispatcher = void 0;
var _artifactDispatcher = require("./artifactDispatcher");
var _dispatcher = require("./dispatcher");
/**
* 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 TracingDispatcher extends _dispatcher.Dispatcher {
static from(scope, tracing) {
const result = (0, _dispatcher.existingDispatcher)(tracing);
return result || new TracingDispatcher(scope, tracing);
}
constructor(scope, tracing) {
super(scope, tracing, 'Tracing', {});
this._type_Tracing = true;
}
async tracingStart(params) {
await this._object.start(params);
}
async tracingStartChunk(params) {
return await this._object.startChunk(params);
}
async tracingStopChunk(params) {
const {
artifact,
entries
} = await this._object.stopChunk(params);
return {
artifact: artifact ? _artifactDispatcher.ArtifactDispatcher.from(this, artifact) : undefined,
entries
};
}
async tracingStop(params) {
await this._object.stop();
}
}
exports.TracingDispatcher = TracingDispatcher;

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WritableStreamDispatcher = void 0;
var _dispatcher = require("./dispatcher");
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 WritableStreamDispatcher extends _dispatcher.Dispatcher {
constructor(scope, stream) {
super(scope, {
guid: 'writableStream@' + (0, _utils.createGuid)(),
stream
}, 'WritableStream', {});
this._type_WritableStream = true;
}
async write(params) {
const stream = this._object.stream;
await new Promise((fulfill, reject) => {
stream.write(params.binary, error => {
if (error) reject(error);else fulfill();
});
});
}
async close() {
const stream = this._object.stream;
await new Promise(fulfill => stream.end(fulfill));
}
path() {
return this._object.stream.path;
}
}
exports.WritableStreamDispatcher = WritableStreamDispatcher;

View File

@@ -0,0 +1,905 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.NonRecoverableDOMError = exports.InjectedScriptPollHandler = exports.FrameExecutionContext = exports.ElementHandle = void 0;
exports.assertDone = assertDone;
exports.isNonRecoverableDOMError = isNonRecoverableDOMError;
exports.kUnableToAdoptErrorMessage = void 0;
exports.throwRetargetableDOMError = throwRetargetableDOMError;
var _utilsBundle = require("../utilsBundle");
var injectedScriptSource = _interopRequireWildcard(require("../generated/injectedScriptSource"));
var _protocolError = require("./protocolError");
var js = _interopRequireWildcard(require("./javascript"));
var _progress = require("./progress");
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 NonRecoverableDOMError extends Error {}
exports.NonRecoverableDOMError = NonRecoverableDOMError;
function isNonRecoverableDOMError(error) {
return error instanceof NonRecoverableDOMError;
}
class FrameExecutionContext extends js.ExecutionContext {
constructor(delegate, frame, world) {
super(frame, delegate, world || 'content-script');
this.frame = void 0;
this._injectedScriptPromise = void 0;
this.world = void 0;
this.frame = frame;
this.world = world;
}
adoptIfNeeded(handle) {
if (handle instanceof ElementHandle && handle._context !== this) return this.frame._page._delegate.adoptElementHandle(handle, this);
return null;
}
async evaluate(pageFunction, arg) {
return js.evaluate(this, true /* returnByValue */, pageFunction, arg);
}
async evaluateHandle(pageFunction, arg) {
return js.evaluate(this, false /* returnByValue */, pageFunction, arg);
}
async evaluateExpression(expression, options, arg) {
return js.evaluateExpression(this, expression, {
...options,
returnByValue: true
}, arg);
}
async evaluateExpressionHandle(expression, options, arg) {
return js.evaluateExpression(this, expression, {
...options,
returnByValue: false
}, arg);
}
createHandle(remoteObject) {
if (this.frame._page._delegate.isElementHandle(remoteObject)) return new ElementHandle(this, remoteObject.objectId);
return super.createHandle(remoteObject);
}
injectedScript() {
if (!this._injectedScriptPromise) {
const custom = [];
const selectorsRegistry = this.frame._page.context().selectors();
for (const [name, {
source
}] of selectorsRegistry._engines) custom.push(`{ name: '${name}', engine: (${source}) }`);
const sdkLanguage = this.frame.attribution.playwright.options.sdkLanguage;
const source = `
(() => {
const module = {};
${injectedScriptSource.source}
return new (module.exports.InjectedScript())(
globalThis,
${(0, _utils.isUnderTest)()},
"${sdkLanguage}",
${JSON.stringify(selectorsRegistry.testIdAttributeName())},
${this.frame._page._delegate.rafCountForStablePosition()},
"${this.frame._page._browserContext._browser.options.name}",
[${custom.join(',\n')}]
);
})();
`;
this._injectedScriptPromise = this.rawEvaluateHandle(source).then(objectId => new js.JSHandle(this, 'object', 'InjectedScript', objectId));
}
return this._injectedScriptPromise;
}
}
exports.FrameExecutionContext = FrameExecutionContext;
class ElementHandle extends js.JSHandle {
constructor(context, objectId) {
super(context, 'node', undefined, objectId);
this.__elementhandle = true;
this._page = void 0;
this._frame = void 0;
this._page = context.frame._page;
this._frame = context.frame;
this._initializePreview().catch(e => {});
}
async _initializePreview() {
const utility = await this._context.injectedScript();
this._setPreview(await utility.evaluate((injected, e) => 'JSHandle@' + injected.previewNode(e), this));
}
asElement() {
return this;
}
async evaluateInUtility(pageFunction, arg) {
try {
const utility = await this._frame._utilityContext();
return await utility.evaluate(pageFunction, [await utility.injectedScript(), this, arg]);
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || (0, _protocolError.isSessionClosedError)(e)) throw e;
return 'error:notconnected';
}
}
async evaluateHandleInUtility(pageFunction, arg) {
try {
const utility = await this._frame._utilityContext();
return await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]);
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || (0, _protocolError.isSessionClosedError)(e)) throw e;
return 'error:notconnected';
}
}
async evaluatePoll(progress, pageFunction, arg) {
try {
const utility = await this._frame._utilityContext();
const poll = await utility.evaluateHandle(pageFunction, [await utility.injectedScript(), this, arg]);
const pollHandler = new InjectedScriptPollHandler(progress, poll);
return await pollHandler.finish();
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || (0, _protocolError.isSessionClosedError)(e)) throw e;
return 'error:notconnected';
}
}
async ownerFrame() {
const frameId = await this._page._delegate.getOwnerFrame(this);
if (!frameId) return null;
const frame = this._page._frameManager.frame(frameId);
if (frame) return frame;
for (const page of this._page._browserContext.pages()) {
const frame = page._frameManager.frame(frameId);
if (frame) return frame;
}
return null;
}
async isIframeElement() {
return this.evaluateInUtility(([injected, node]) => node && (node.nodeName === 'IFRAME' || node.nodeName === 'FRAME'), {});
}
async contentFrame() {
const isFrameElement = throwRetargetableDOMError(await this.isIframeElement());
if (!isFrameElement) return null;
return this._page._delegate.getContentFrame(this);
}
async getAttribute(name) {
return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node, name]) => {
if (node.nodeType !== Node.ELEMENT_NODE) throw injected.createStacklessError('Node is not an element');
const element = node;
return {
value: element.getAttribute(name)
};
}, name)).value;
}
async inputValue() {
return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => {
const element = injected.retarget(node, 'follow-label');
if (!element || element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT') throw injected.createStacklessError('Node is not an <input>, <textarea> or <select> element');
return {
value: element.value
};
}, undefined)).value;
}
async textContent() {
return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => {
return {
value: node.textContent
};
}, undefined)).value;
}
async innerText() {
return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => {
if (node.nodeType !== Node.ELEMENT_NODE) throw injected.createStacklessError('Node is not an element');
if (node.namespaceURI !== 'http://www.w3.org/1999/xhtml') throw injected.createStacklessError('Node is not an HTMLElement');
const element = node;
return {
value: element.innerText
};
}, undefined)).value;
}
async innerHTML() {
return throwRetargetableDOMError(await this.evaluateInUtility(([injected, node]) => {
if (node.nodeType !== Node.ELEMENT_NODE) throw injected.createStacklessError('Node is not an element');
const element = node;
return {
value: element.innerHTML
};
}, undefined)).value;
}
async dispatchEvent(type, eventInit = {}) {
const main = await this._frame._mainContext();
await this._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
return main.evaluate(([injected, node, {
type,
eventInit
}]) => injected.dispatchEvent(node, type, eventInit), [await main.injectedScript(), this, {
type,
eventInit
}]);
});
}
async _scrollRectIntoViewIfNeeded(rect) {
return await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
}
async _waitAndScrollIntoViewIfNeeded(progress, waitForVisible) {
const timeouts = [0, 50, 100, 250];
while (progress.isRunning()) {
assertDone(throwRetargetableDOMError(await this._waitForElementStates(progress, waitForVisible ? ['visible', 'stable'] : ['stable'], false /* force */)));
progress.throwIfAborted(); // Avoid action that has side-effects.
const result = throwRetargetableDOMError(await this._scrollRectIntoViewIfNeeded());
if (result === 'error:notvisible') {
if (!waitForVisible) {
var _timeouts$shift;
// Wait for a timeout to avoid retrying too often when not waiting for visible.
// If we wait for visible, this should be covered by _waitForElementStates instead.
const timeout = (_timeouts$shift = timeouts.shift()) !== null && _timeouts$shift !== void 0 ? _timeouts$shift : 500;
progress.log(` element is not displayed, retrying in ${timeout}ms`);
await new Promise(f => setTimeout(f, timeout));
}
continue;
}
assertDone(result);
return;
}
}
async scrollIntoViewIfNeeded(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this._waitAndScrollIntoViewIfNeeded(progress, false /* waitForVisible */), this._page._timeoutSettings.timeout(options));
}
async _clickablePoint() {
const intersectQuadWithViewport = quad => {
return quad.map(point => ({
x: Math.min(Math.max(point.x, 0), metrics.width),
y: Math.min(Math.max(point.y, 0), metrics.height)
}));
};
const computeQuadArea = quad => {
// Compute sum of all directed areas of adjacent triangles
// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
let area = 0;
for (let i = 0; i < quad.length; ++i) {
const p1 = quad[i];
const p2 = quad[(i + 1) % quad.length];
area += (p1.x * p2.y - p2.x * p1.y) / 2;
}
return Math.abs(area);
};
const [quads, metrics] = await Promise.all([this._page._delegate.getContentQuads(this), this._page.mainFrame()._utilityContext().then(utility => utility.evaluate(() => ({
width: innerWidth,
height: innerHeight
})))]);
if (!quads || !quads.length) return 'error:notvisible';
// Allow 1x1 elements. Compensate for rounding errors by comparing with 0.99 instead.
const filtered = quads.map(quad => intersectQuadWithViewport(quad)).filter(quad => computeQuadArea(quad) > 0.99);
if (!filtered.length) return 'error:notinviewport';
// Return the middle point of the first quad.
const result = {
x: 0,
y: 0
};
for (const point of filtered[0]) {
result.x += point.x / 4;
result.y += point.y / 4;
}
compensateHalfIntegerRoundingError(result);
return result;
}
async _offsetPoint(offset) {
const [box, border] = await Promise.all([this.boundingBox(), this.evaluateInUtility(([injected, node]) => injected.getElementBorderWidth(node), {}).catch(e => {})]);
if (!box || !border) return 'error:notvisible';
if (border === 'error:notconnected') return border;
// Make point relative to the padding box to align with offsetX/offsetY.
return {
x: box.x + border.left + offset.x,
y: box.y + border.top + offset.y
};
}
async _retryPointerAction(progress, actionName, waitForEnabled, action, options) {
let retry = 0;
// We progressively wait longer between retries, up to 500ms.
const waitTime = [0, 20, 100, 100, 500];
// By default, we scroll with protocol method to reveal the action point.
// However, that might not work to scroll from under position:sticky elements
// that overlay the target element. To fight this, we cycle through different
// scroll alignments. This works in most scenarios.
const scrollOptions = [undefined, {
block: 'end',
inline: 'end'
}, {
block: 'center',
inline: 'center'
}, {
block: 'start',
inline: 'start'
}];
while (progress.isRunning()) {
if (retry) {
progress.log(`retrying ${actionName} action${options.trial ? ' (trial run)' : ''}, attempt #${retry}`);
const timeout = waitTime[Math.min(retry - 1, waitTime.length - 1)];
if (timeout) {
progress.log(` waiting ${timeout}ms`);
const result = await this.evaluateInUtility(([injected, node, timeout]) => new Promise(f => setTimeout(f, timeout)), timeout);
if (result === 'error:notconnected') return result;
}
} else {
progress.log(`attempting ${actionName} action${options.trial ? ' (trial run)' : ''}`);
}
const forceScrollOptions = scrollOptions[retry % scrollOptions.length];
const result = await this._performPointerAction(progress, actionName, waitForEnabled, action, forceScrollOptions, options);
++retry;
if (result === 'error:notvisible') {
if (options.force) throw new NonRecoverableDOMError('Element is not visible');
progress.log(' element is not visible');
continue;
}
if (result === 'error:notinviewport') {
if (options.force) throw new NonRecoverableDOMError('Element is outside of the viewport');
progress.log(' element is outside of the viewport');
continue;
}
if (typeof result === 'object' && 'hitTargetDescription' in result) {
progress.log(` ${result.hitTargetDescription} intercepts pointer events`);
continue;
}
return result;
}
return 'done';
}
async _performPointerAction(progress, actionName, waitForEnabled, action, forceScrollOptions, options) {
const {
force = false,
position
} = options;
if (options.__testHookBeforeStable) await options.__testHookBeforeStable();
const result = await this._waitForElementStates(progress, waitForEnabled ? ['visible', 'enabled', 'stable'] : ['visible', 'stable'], force);
if (result !== 'done') return result;
if (options.__testHookAfterStable) await options.__testHookAfterStable();
progress.log(' scrolling into view if needed');
progress.throwIfAborted(); // Avoid action that has side-effects.
if (forceScrollOptions) {
const scrolled = await this.evaluateInUtility(([injected, node, options]) => {
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) node.scrollIntoView(options);
}, forceScrollOptions);
if (scrolled === 'error:notconnected') return scrolled;
} else {
const scrolled = await this._scrollRectIntoViewIfNeeded(position ? {
x: position.x,
y: position.y,
width: 0,
height: 0
} : undefined);
if (scrolled !== 'done') return scrolled;
}
progress.log(' done scrolling');
const maybePoint = position ? await this._offsetPoint(position) : await this._clickablePoint();
if (typeof maybePoint === 'string') return maybePoint;
const point = roundPoint(maybePoint);
progress.metadata.point = point;
await progress.beforeInputAction(this);
let hitTargetInterceptionHandle;
if (!options.force) {
if (options.__testHookBeforeHitTarget) await options.__testHookBeforeHitTarget();
const frameCheckResult = await this._checkFrameIsHitTarget(point);
if (frameCheckResult === 'error:notconnected' || 'hitTargetDescription' in frameCheckResult) return frameCheckResult;
const hitPoint = frameCheckResult.framePoint;
const actionType = actionName === 'move and up' ? 'drag' : actionName === 'hover' || actionName === 'tap' ? actionName : 'mouse';
const handle = await this.evaluateHandleInUtility(([injected, node, {
actionType,
hitPoint,
trial
}]) => injected.setupHitTargetInterceptor(node, actionType, hitPoint, trial), {
actionType,
hitPoint,
trial: !!options.trial
});
if (handle === 'error:notconnected') return handle;
if (!handle._objectId) {
const error = handle.rawValue();
if (error === 'error:notconnected') return error;
return {
hitTargetDescription: error
};
}
hitTargetInterceptionHandle = handle;
progress.cleanupWhenAborted(() => {
// Do not await here, just in case the renderer is stuck (e.g. on alert)
// and we won't be able to cleanup.
hitTargetInterceptionHandle.evaluate(h => h.stop()).catch(e => {});
hitTargetInterceptionHandle.dispose();
});
}
const actionResult = await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
if (options.__testHookBeforePointerAction) await options.__testHookBeforePointerAction();
progress.throwIfAborted(); // Avoid action that has side-effects.
let restoreModifiers;
if (options && options.modifiers) restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
progress.log(` performing ${actionName} action`);
await action(point);
if (restoreModifiers) await this._page.keyboard._ensureModifiers(restoreModifiers);
if (hitTargetInterceptionHandle) {
const stopHitTargetInterception = hitTargetInterceptionHandle.evaluate(h => h.stop()).catch(e => 'done').finally(() => {
var _hitTargetInterceptio;
(_hitTargetInterceptio = hitTargetInterceptionHandle) === null || _hitTargetInterceptio === void 0 ? void 0 : _hitTargetInterceptio.dispose();
});
if (!options.noWaitAfter) {
// When noWaitAfter is passed, we do not want to accidentally stall on
// non-committed navigation blocking the evaluate.
const hitTargetResult = await stopHitTargetInterception;
if (hitTargetResult !== 'done') return hitTargetResult;
}
}
progress.log(` ${options.trial ? 'trial ' : ''}${actionName} action done`);
progress.log(' waiting for scheduled navigations to finish');
if (options.__testHookAfterPointerAction) await options.__testHookAfterPointerAction();
return 'done';
}, 'input');
if (actionResult !== 'done') return actionResult;
progress.log(' navigations have finished');
return 'done';
}
async hover(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._hover(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
_hover(progress, options) {
return this._retryPointerAction(progress, 'hover', false /* waitForEnabled */, point => this._page.mouse.move(point.x, point.y), options);
}
async click(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._click(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
_click(progress, options) {
return this._retryPointerAction(progress, 'click', true /* waitForEnabled */, point => this._page.mouse.click(point.x, point.y, options), options);
}
async dblclick(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._dblclick(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
_dblclick(progress, options) {
return this._retryPointerAction(progress, 'dblclick', true /* waitForEnabled */, point => this._page.mouse.dblclick(point.x, point.y, options), options);
}
async tap(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._tap(progress, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
_tap(progress, options) {
return this._retryPointerAction(progress, 'tap', true /* waitForEnabled */, point => this._page.touchscreen.tap(point.x, point.y), options);
}
async selectOption(metadata, elements, values, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._selectOption(progress, elements, values, options);
return throwRetargetableDOMError(result);
}, this._page._timeoutSettings.timeout(options));
}
async _selectOption(progress, elements, values, options) {
const optionsToSelect = [...elements, ...values];
await progress.beforeInputAction(this);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
progress.log(' selecting specified option(s)');
const result = await this.evaluatePoll(progress, ([injected, node, {
optionsToSelect,
force
}]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible', 'enabled'], force, injected.selectOptions.bind(injected, optionsToSelect));
}, {
optionsToSelect,
force: options.force
});
return result;
});
}
async fill(metadata, value, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._fill(progress, value, options);
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async _fill(progress, value, options) {
progress.log(`elementHandle.fill("${value}")`);
await progress.beforeInputAction(this);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.log(' waiting for element to be visible, enabled and editable');
const filled = await this.evaluatePoll(progress, ([injected, node, {
value,
force
}]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible', 'enabled', 'editable'], force, injected.fill.bind(injected, value));
}, {
value,
force: options.force
});
progress.throwIfAborted(); // Avoid action that has side-effects.
if (filled === 'error:notconnected') return filled;
progress.log(' element is visible, enabled and editable');
if (filled === 'needsinput') {
progress.throwIfAborted(); // Avoid action that has side-effects.
if (value) await this._page.keyboard.insertText(value);else await this._page.keyboard.press('Delete');
} else {
assertDone(filled);
}
return 'done';
}, 'input');
}
async selectText(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
progress.throwIfAborted(); // Avoid action that has side-effects.
const result = await this.evaluatePoll(progress, ([injected, node, force]) => {
return injected.waitForElementStatesAndPerformAction(node, ['visible'], force, injected.selectText.bind(injected));
}, options.force);
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async setInputFiles(metadata, items, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._setInputFiles(progress, items, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async _setInputFiles(progress, items, options) {
const {
files,
localPaths
} = items;
let filePayloads;
if (files) {
filePayloads = [];
for (const payload of files) {
filePayloads.push({
name: payload.name,
mimeType: payload.mimeType || _utilsBundle.mime.getType(payload.name) || 'application/octet-stream',
buffer: payload.buffer.toString('base64')
});
}
}
const multiple = files && files.length > 1 || localPaths && localPaths.length > 1;
const result = await this.evaluateHandleInUtility(([injected, node, multiple]) => {
const element = injected.retarget(node, 'follow-label');
if (!element) return;
if (element.tagName !== 'INPUT') throw injected.createStacklessError('Node is not an HTMLInputElement');
if (multiple && !element.multiple) throw injected.createStacklessError('Non-multiple file input can only accept single file');
return element;
}, multiple);
if (result === 'error:notconnected' || !result.asElement()) return 'error:notconnected';
const retargeted = result.asElement();
await progress.beforeInputAction(this);
await this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
progress.throwIfAborted(); // Avoid action that has side-effects.
if (localPaths) await this._page._delegate.setInputFilePaths(retargeted, localPaths);else await this._page._delegate.setInputFiles(retargeted, filePayloads);
});
return 'done';
}
async focus(metadata) {
const controller = new _progress.ProgressController(metadata, this);
await controller.run(async progress => {
const result = await this._focus(progress);
return assertDone(throwRetargetableDOMError(result));
}, 0);
}
async _focus(progress, resetSelectionIfNotFocused) {
progress.throwIfAborted(); // Avoid action that has side-effects.
return await this.evaluateInUtility(([injected, node, resetSelectionIfNotFocused]) => injected.focusNode(node, resetSelectionIfNotFocused), resetSelectionIfNotFocused);
}
async _blur(progress) {
progress.throwIfAborted(); // Avoid action that has side-effects.
return await this.evaluateInUtility(([injected, node]) => injected.blurNode(node), {});
}
async type(metadata, text, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._type(progress, text, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async _type(progress, text, options) {
progress.log(`elementHandle.type("${text}")`);
await progress.beforeInputAction(this);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
if (result !== 'done') return result;
progress.throwIfAborted(); // Avoid action that has side-effects.
await this._page.keyboard.type(text, options);
return 'done';
}, 'input');
}
async press(metadata, key, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._press(progress, key, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async _press(progress, key, options) {
progress.log(`elementHandle.press("${key}")`);
await progress.beforeInputAction(this);
return this._page._frameManager.waitForSignalsCreatedBy(progress, options.noWaitAfter, async () => {
const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
if (result !== 'done') return result;
progress.throwIfAborted(); // Avoid action that has side-effects.
await this._page.keyboard.press(key, options);
return 'done';
}, 'input');
}
async check(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._setChecked(progress, true, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async uncheck(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
const result = await this._setChecked(progress, false, options);
return assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async _setChecked(progress, state, options) {
const isChecked = async () => {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {});
return throwRetargetableDOMError(result);
};
if ((await isChecked()) === state) return 'done';
const result = await this._click(progress, options);
if (result !== 'done') return result;
if (options.trial) return 'done';
if ((await isChecked()) !== state) throw new NonRecoverableDOMError('Clicking the checkbox did not change its state');
return 'done';
}
async boundingBox() {
return this._page._delegate.getBoundingBox(this);
}
async screenshot(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this._page._screenshotter.screenshotElement(progress, this, options), this._page._timeoutSettings.timeout(options));
}
async querySelector(selector, options) {
return this._frame.selectors.query(selector, options, this);
}
async querySelectorAll(selector) {
return this._frame.selectors.queryAll(selector, this);
}
async evalOnSelector(selector, strict, expression, isFunction, arg) {
const handle = await this._frame.selectors.query(selector, {
strict
}, this);
if (!handle) throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await handle.evaluateExpression(expression, {
isFunction
}, arg);
handle.dispose();
return result;
}
async evalOnSelectorAll(selector, expression, isFunction, arg) {
const arrayHandle = await this._frame.selectors.queryArrayInMainWorld(selector, this);
const result = await arrayHandle.evaluateExpression(expression, {
isFunction
}, arg);
arrayHandle.dispose();
return result;
}
async isVisible() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'visible'), {});
if (result === 'error:notconnected') return false;
return result;
}
async isHidden() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'hidden'), {});
return throwRetargetableDOMError(result);
}
async isEnabled() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'enabled'), {});
return throwRetargetableDOMError(result);
}
async isDisabled() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'disabled'), {});
return throwRetargetableDOMError(result);
}
async isEditable() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'editable'), {});
return throwRetargetableDOMError(result);
}
async isChecked() {
const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {});
return throwRetargetableDOMError(result);
}
async waitForElementState(metadata, state, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(async progress => {
progress.log(` waiting for element to be ${state}`);
const result = await this.evaluatePoll(progress, ([injected, node, state]) => {
return injected.waitForElementStatesAndPerformAction(node, [state], false, () => 'done');
}, state);
assertDone(throwRetargetableDOMError(result));
}, this._page._timeoutSettings.timeout(options));
}
async waitForSelector(metadata, selector, options = {}) {
return this._frame.waitForSelector(metadata, selector, options, this);
}
async _adoptTo(context) {
if (this._context !== context) {
const adopted = await this._page._delegate.adoptElementHandle(this, context);
this.dispose();
return adopted;
}
return this;
}
async _waitForElementStates(progress, states, force) {
const title = joinWithAnd(states);
progress.log(` waiting for element to be ${title}`);
const result = await this.evaluatePoll(progress, ([injected, node, {
states,
force
}]) => {
return injected.waitForElementStatesAndPerformAction(node, states, force, () => 'done');
}, {
states,
force
});
if (result === 'error:notconnected') return result;
progress.log(` element is ${title}`);
return result;
}
async _checkFrameIsHitTarget(point) {
let frame = this._frame;
const data = [];
while (frame.parentFrame()) {
const frameElement = await frame.frameElement();
const box = await frameElement.boundingBox();
const style = await frameElement.evaluateInUtility(([injected, iframe]) => injected.describeIFrameStyle(iframe), {}).catch(e => 'error:notconnected');
if (!box || style === 'error:notconnected') return 'error:notconnected';
if (style === 'transformed') {
// We cannot translate coordinates when iframe has any transform applied.
// The best we can do right now is to skip the hitPoint check,
// and solely rely on the event interceptor.
return {
framePoint: undefined
};
}
// Translate from viewport coordinates to frame coordinates.
const pointInFrame = {
x: point.x - box.x - style.left,
y: point.y - box.y - style.top
};
data.push({
frame,
frameElement,
pointInFrame
});
frame = frame.parentFrame();
}
// Add main frame.
data.push({
frame,
frameElement: null,
pointInFrame: point
});
for (let i = data.length - 1; i > 0; i--) {
const element = data[i - 1].frameElement;
const point = data[i].pointInFrame;
// Hit target in the parent frame should hit the child frame element.
const hitTargetResult = await element.evaluateInUtility(([injected, element, hitPoint]) => {
return injected.expectHitTarget(hitPoint, element);
}, point);
if (hitTargetResult !== 'done') return hitTargetResult;
}
return {
framePoint: data[0].pointInFrame
};
}
}
// Handles an InjectedScriptPoll running in injected script:
// - streams logs into progress;
// - cancels the poll when progress cancels.
exports.ElementHandle = ElementHandle;
class InjectedScriptPollHandler {
constructor(progress, poll) {
this._progress = void 0;
this._poll = void 0;
this._progress = progress;
this._poll = poll;
// Ensure we cancel the poll before progress aborts and returns:
// - no unnecessary work in the page;
// - no possible side effects after progress promsie rejects.
this._progress.cleanupWhenAborted(() => this.cancel());
this._streamLogs();
}
async _streamLogs() {
while (this._poll && this._progress.isRunning()) {
const log = await this._poll.evaluate(poll => poll.takeNextLogs()).catch(e => []);
if (!this._poll || !this._progress.isRunning()) return;
for (const entry of log) this._progress.logEntry(entry);
}
}
async finishHandle() {
try {
const result = await this._poll.evaluateHandle(poll => poll.run());
await this._finishInternal();
return result;
} finally {
await this.cancel();
}
}
async finish() {
try {
const result = await this._poll.evaluate(poll => poll.run());
await this._finishInternal();
return result;
} finally {
await this.cancel();
}
}
async _finishInternal() {
if (!this._poll) return;
// Retrieve all the logs before continuing.
const log = await this._poll.evaluate(poll => poll.takeLastLogs()).catch(e => []);
for (const entry of log) this._progress.logEntry(entry);
}
async cancel() {
if (!this._poll) return;
const copy = this._poll;
this._poll = null;
await copy.evaluate(p => p.cancel()).catch(e => {});
copy.dispose();
}
}
exports.InjectedScriptPollHandler = InjectedScriptPollHandler;
function throwRetargetableDOMError(result) {
if (result === 'error:notconnected') throw new Error('Element is not attached to the DOM');
return result;
}
function assertDone(result) {
// This function converts 'done' to void and ensures typescript catches unhandled errors.
}
function roundPoint(point) {
return {
x: (point.x * 100 | 0) / 100,
y: (point.y * 100 | 0) / 100
};
}
function compensateHalfIntegerRoundingError(point) {
// Firefox internally uses integer coordinates, so 8.5 is converted to 9 when clicking.
//
// This does not work nicely for small elements. For example, 1x1 square with corners
// (8;8) and (9;9) is targeted when clicking at (8;8) but not when clicking at (9;9).
// So, clicking at (8.5;8.5) will effectively click at (9;9) and miss the target.
//
// Therefore, we skew half-integer values from the interval (8.49, 8.51) towards
// (8.47, 8.49) that is rounded towards 8. This means clicking at (8.5;8.5) will
// be replaced with (8.48;8.48) and will effectively click at (8;8).
//
// Other browsers use float coordinates, so this change should not matter.
const remainderX = point.x - Math.floor(point.x);
if (remainderX > 0.49 && remainderX < 0.51) point.x -= 0.02;
const remainderY = point.y - Math.floor(point.y);
if (remainderY > 0.49 && remainderY < 0.51) point.y -= 0.02;
}
function joinWithAnd(strings) {
if (strings.length < 1) return strings.join(', ');
return strings.slice(0, strings.length - 1).join(', ') + ' and ' + strings[strings.length - 1];
}
const kUnableToAdoptErrorMessage = 'Unable to adopt element handle from a different document';
exports.kUnableToAdoptErrorMessage = kUnableToAdoptErrorMessage;

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Download = void 0;
var _path = _interopRequireDefault(require("path"));
var _page = require("./page");
var _utils = require("../utils");
var _artifact = require("./artifact");
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 Download {
constructor(page, downloadsPath, uuid, url, suggestedFilename) {
this.artifact = void 0;
this.url = void 0;
this._page = void 0;
this._suggestedFilename = void 0;
const unaccessibleErrorMessage = !page._browserContext._options.acceptDownloads ? 'Pass { acceptDownloads: true } when you are creating your browser context.' : undefined;
this.artifact = new _artifact.Artifact(page, _path.default.join(downloadsPath, uuid), unaccessibleErrorMessage, () => {
return this._page._browserContext.cancelDownload(uuid);
});
this._page = page;
this.url = url;
this._suggestedFilename = suggestedFilename;
page._browserContext._downloads.add(this);
if (suggestedFilename !== undefined) this._page.emit(_page.Page.Events.Download, this);
}
_filenameSuggested(suggestedFilename) {
(0, _utils.assert)(this._suggestedFilename === undefined);
this._suggestedFilename = suggestedFilename;
this._page.emit(_page.Page.Events.Download, this);
}
suggestedFilename() {
return this._suggestedFilename;
}
}
exports.Download = Download;

View File

@@ -0,0 +1,245 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ElectronApplication = exports.Electron = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _crBrowser = require("../chromium/crBrowser");
var _crConnection = require("../chromium/crConnection");
var _crExecutionContext = require("../chromium/crExecutionContext");
var js = _interopRequireWildcard(require("../javascript"));
var _timeoutSettings = require("../../common/timeoutSettings");
var _utils = require("../../utils");
var _transport = require("../transport");
var _processLauncher = require("../../utils/processLauncher");
var _browserContext = require("../browserContext");
var _progress = require("../progress");
var _helper = require("../helper");
var _eventsHelper = require("../../utils/eventsHelper");
var readline = _interopRequireWildcard(require("readline"));
var _debugLogger = require("../../common/debugLogger");
var _instrumentation = require("../instrumentation");
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 ARTIFACTS_FOLDER = _path.default.join(_os.default.tmpdir(), 'playwright-artifacts-');
class ElectronApplication extends _instrumentation.SdkObject {
constructor(parent, browser, nodeConnection, process) {
super(parent, 'electron-app');
this._browserContext = void 0;
this._nodeConnection = void 0;
this._nodeSession = void 0;
this._nodeExecutionContext = void 0;
this._nodeElectronHandlePromise = void 0;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._process = void 0;
this._process = process;
this._browserContext = browser._defaultContext;
this._browserContext.on(_browserContext.BrowserContext.Events.Close, () => {
// Emit application closed after context closed.
Promise.resolve().then(() => this.emit(ElectronApplication.Events.Close));
});
this._nodeConnection = nodeConnection;
this._nodeSession = nodeConnection.rootSession;
this._nodeElectronHandlePromise = new Promise(f => {
this._nodeSession.on('Runtime.executionContextCreated', async event => {
if (event.context.auxData && event.context.auxData.isDefault) {
this._nodeExecutionContext = new js.ExecutionContext(this, new _crExecutionContext.CRExecutionContext(this._nodeSession, event.context), 'electron');
const source = `process.mainModule.require('electron')`;
f(await this._nodeExecutionContext.rawEvaluateHandle(source).then(objectId => new js.JSHandle(this._nodeExecutionContext, 'object', 'ElectronModule', objectId)));
}
});
});
this._browserContext.setCustomCloseHandler(async () => {
const electronHandle = await this._nodeElectronHandlePromise;
await electronHandle.evaluate(({
app
}) => app.quit()).catch(() => {});
});
}
async initialize() {
await this._nodeSession.send('Runtime.enable', {});
// Delay loading the app until browser is started and the browser targets are configured to auto-attach.
await this._nodeSession.send('Runtime.evaluate', {
expression: '__playwright_run()'
});
}
process() {
return this._process;
}
context() {
return this._browserContext;
}
async close() {
const progressController = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), this);
const closed = progressController.run(progress => _helper.helper.waitForEvent(progress, this, ElectronApplication.Events.Close).promise);
await this._browserContext.close((0, _instrumentation.serverSideCallMetadata)());
this._nodeConnection.close();
await closed;
}
async browserWindow(page) {
// Assume CRPage as Electron is always Chromium.
const targetId = page._delegate._targetId;
const electronHandle = await this._nodeElectronHandlePromise;
return await electronHandle.evaluateHandle(({
BrowserWindow,
webContents
}, targetId) => {
const wc = webContents.fromDevToolsTargetId(targetId);
return BrowserWindow.fromWebContents(wc);
}, targetId);
}
}
exports.ElectronApplication = ElectronApplication;
ElectronApplication.Events = {
Close: 'close'
};
class Electron extends _instrumentation.SdkObject {
constructor(playwright) {
super(playwright, 'electron');
}
async launch(options) {
const {
args = []
} = options;
const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), this);
controller.setLogName('browser');
return controller.run(async progress => {
let app = undefined;
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...args];
if (_os.default.platform() === 'linux') {
const runningAsRoot = process.geteuid && process.geteuid() === 0;
if (runningAsRoot && electronArguments.indexOf('--no-sandbox') === -1) electronArguments.push('--no-sandbox');
}
const artifactsDir = await _fs.default.promises.mkdtemp(ARTIFACTS_FOLDER);
const browserLogsCollector = new _debugLogger.RecentLogsCollector();
const env = options.env ? (0, _processLauncher.envArrayToObject)(options.env) : process.env;
let command;
if (options.executablePath) {
command = options.executablePath;
} else {
try {
// By default we fallback to the Electron App executable path.
// 'electron/index.js' resolves to the actual Electron App.
command = require('electron/index.js');
} catch (error) {
if ((error === null || error === void 0 ? void 0 : error.code) === 'MODULE_NOT_FOUND') {
throw new Error('\n' + (0, _utils.wrapInASCIIBox)(['Electron executablePath not found!', 'Please install it using `npm install -D electron` or set the executablePath to your Electron executable.'].join('\n'), 1));
}
throw error;
}
// Only use our own loader for non-packaged apps.
// Packaged apps might have their own command line handling.
electronArguments.unshift('-r', require.resolve('./loader'));
}
// When debugging Playwright test that runs Electron, NODE_OPTIONS
// will make the debugger attach to Electron's Node. But Playwright
// also needs to attach to drive the automation. Disable external debugging.
delete env.NODE_OPTIONS;
const {
launchedProcess,
gracefullyClose,
kill
} = await (0, _processLauncher.launchProcess)({
command,
args: electronArguments,
env,
log: message => {
progress.log(message);
browserLogsCollector.log(message);
},
stdio: 'pipe',
cwd: options.cwd,
tempDirectories: [artifactsDir],
attemptToGracefullyClose: () => app.close(),
handleSIGINT: true,
handleSIGTERM: true,
handleSIGHUP: true,
onExit: () => {}
});
const waitForXserverError = new Promise(async (resolve, reject) => {
waitForLine(progress, launchedProcess, /Unable to open X display/).then(() => reject(new Error(['Unable to open X display!', `================================`, 'Most likely this is because there is no X server available.', "Use 'xvfb-run' on Linux to launch your tests with an emulated display server.", "For example: 'xvfb-run npm run test:e2e'", `================================`, progress.metadata.log].join('\n')))).catch(() => {});
});
const nodeMatch = await waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/);
const nodeTransport = await _transport.WebSocketTransport.connect(progress, nodeMatch[1]);
const nodeConnection = new _crConnection.CRConnection(nodeTransport, _helper.helper.debugProtocolLogger(), browserLogsCollector);
// Immediately release exiting process under debug.
waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./).then(() => {
nodeTransport.close();
}).catch(() => {});
const chromeMatch = await Promise.race([waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/), waitForXserverError]);
const chromeTransport = await _transport.WebSocketTransport.connect(progress, chromeMatch[1]);
const browserProcess = {
onclose: undefined,
process: launchedProcess,
close: gracefullyClose,
kill
};
const contextOptions = {
...options,
noDefaultViewport: true
};
const browserOptions = {
name: 'electron',
isChromium: true,
headful: true,
persistent: contextOptions,
browserProcess,
protocolLogger: _helper.helper.debugProtocolLogger(),
browserLogsCollector,
artifactsDir,
downloadsPath: artifactsDir,
tracesDir: artifactsDir,
originalLaunchOptions: {}
};
(0, _browserContext.validateBrowserContextOptions)(contextOptions, browserOptions);
const browser = await _crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions);
app = new ElectronApplication(this, browser, nodeConnection, launchedProcess);
await app.initialize();
return app;
}, _timeoutSettings.TimeoutSettings.timeout(options));
}
}
exports.Electron = Electron;
function waitForLine(progress, process, regex) {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({
input: process.stderr
});
const failError = new Error('Process failed to launch!');
const listeners = [_eventsHelper.eventsHelper.addEventListener(rl, 'line', onLine), _eventsHelper.eventsHelper.addEventListener(rl, 'close', reject.bind(null, failError)), _eventsHelper.eventsHelper.addEventListener(process, 'exit', reject.bind(null, failError)),
// It is Ok to remove error handler because we did not create process and there is another listener.
_eventsHelper.eventsHelper.addEventListener(process, 'error', reject.bind(null, failError))];
progress.cleanupWhenAborted(cleanup);
function onLine(line) {
const match = line.match(regex);
if (!match) return;
cleanup();
resolve(match);
}
function cleanup() {
_eventsHelper.eventsHelper.removeEventListeners(listeners);
}
});
}

View File

@@ -0,0 +1,55 @@
"use strict";
/**
* 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 {
app
} = require('electron');
const {
chromiumSwitches
} = require('../chromium/chromiumSwitches');
// [Electron, -r, loader.js, --inspect=0, --remote-debugging-port=0, ...args]
process.argv.splice(1, 4);
for (const arg of chromiumSwitches) {
const match = arg.match(/--([^=]*)=?(.*)/);
app.commandLine.appendSwitch(match[1], match[2]);
}
// Defer ready event.
const originalWhenReady = app.whenReady();
const originalEmit = app.emit.bind(app);
let readyEventArgs;
app.emit = (event, ...args) => {
if (event === 'ready') {
readyEventArgs = args;
return app.listenerCount('ready') > 0;
}
return originalEmit(event, ...args);
};
let isReady = false;
let whenReadyCallback;
const whenReadyPromise = new Promise(f => whenReadyCallback = f);
app.isReady = () => isReady;
app.whenReady = () => whenReadyPromise;
globalThis.__playwright_run = async () => {
// Wait for app to be ready to avoid browser initialization races.
const event = await originalWhenReady;
isReady = true;
whenReadyCallback(event);
originalEmit('ready', ...readyEventArgs);
};

View File

@@ -0,0 +1,601 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GlobalAPIRequestContext = exports.BrowserContextAPIRequestContext = exports.APIRequestContext = void 0;
var http = _interopRequireWildcard(require("http"));
var https = _interopRequireWildcard(require("https"));
var _stream = require("stream");
var _url = _interopRequireDefault(require("url"));
var _zlib = _interopRequireDefault(require("zlib"));
var _timeoutSettings = require("../common/timeoutSettings");
var _userAgent = require("../utils/userAgent");
var _utils = require("../utils");
var _utilsBundle = require("../utilsBundle");
var _browserContext = require("./browserContext");
var _cookieStore = require("./cookieStore");
var _formData = require("./formData");
var _happyEyeballs = require("../utils/happy-eyeballs");
var _instrumentation = require("./instrumentation");
var _progress = require("./progress");
var _tracing = require("./trace/recorder/tracing");
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 (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 APIRequestContext extends _instrumentation.SdkObject {
static findResponseBody(guid) {
for (const request of APIRequestContext.allInstances) {
const body = request.fetchResponses.get(guid);
if (body) return body;
}
return undefined;
}
constructor(parent) {
super(parent, 'request-context');
this.fetchResponses = new Map();
this.fetchLog = new Map();
this._activeProgressControllers = new Set();
APIRequestContext.allInstances.add(this);
}
_disposeImpl() {
APIRequestContext.allInstances.delete(this);
this.fetchResponses.clear();
this.fetchLog.clear();
this.emit(APIRequestContext.Events.Dispose);
}
disposeResponse(fetchUid) {
this.fetchResponses.delete(fetchUid);
this.fetchLog.delete(fetchUid);
}
_storeResponseBody(body) {
const uid = (0, _utils.createGuid)();
this.fetchResponses.set(uid, body);
return uid;
}
async fetch(params, metadata) {
var _params$method;
const defaults = this._defaultOptions();
const headers = {
'user-agent': defaults.userAgent,
'accept': '*/*',
'accept-encoding': 'gzip,deflate,br'
};
if (defaults.extraHTTPHeaders) {
for (const {
name,
value
} of defaults.extraHTTPHeaders) setHeader(headers, name, value);
}
if (params.headers) {
for (const {
name,
value
} of params.headers) setHeader(headers, name, value);
}
const requestUrl = new URL(params.url, defaults.baseURL);
if (params.params) {
for (const {
name,
value
} of params.params) requestUrl.searchParams.set(name, value);
}
const method = ((_params$method = params.method) === null || _params$method === void 0 ? void 0 : _params$method.toUpperCase()) || 'GET';
const proxy = defaults.proxy;
let agent;
if (proxy && proxy.server !== 'per-context' && !shouldBypassProxy(requestUrl, proxy.bypass)) {
var _proxyOpts$protocol;
const proxyOpts = _url.default.parse(proxy.server);
if ((_proxyOpts$protocol = proxyOpts.protocol) !== null && _proxyOpts$protocol !== void 0 && _proxyOpts$protocol.startsWith('socks')) {
agent = new _utilsBundle.SocksProxyAgent({
host: proxyOpts.hostname,
port: proxyOpts.port || undefined
});
} else {
if (proxy.username) proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
agent = new _utilsBundle.HttpsProxyAgent(proxyOpts);
}
}
const timeout = defaults.timeoutSettings.timeout(params);
const deadline = timeout && (0, _utils.monotonicTime)() + timeout;
const options = {
method,
headers,
agent,
maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects,
timeout,
deadline,
__testHookLookup: params.__testHookLookup
};
// rejectUnauthorized = undefined is treated as true in node 12.
if (params.ignoreHTTPSErrors || defaults.ignoreHTTPSErrors) options.rejectUnauthorized = false;
const postData = serializePostData(params, headers);
if (postData) setHeader(headers, 'content-length', String(postData.byteLength));
const controller = new _progress.ProgressController(metadata, this);
const fetchResponse = await controller.run(progress => {
return this._sendRequest(progress, requestUrl, options, postData);
});
const fetchUid = this._storeResponseBody(fetchResponse.body);
this.fetchLog.set(fetchUid, controller.metadata.log);
if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) throw new Error(`${fetchResponse.status} ${fetchResponse.statusText}`);
return {
...fetchResponse,
fetchUid
};
}
_parseSetCookieHeader(responseUrl, setCookie) {
if (!setCookie) return [];
const url = new URL(responseUrl);
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
const defaultPath = '/' + url.pathname.substr(1).split('/').slice(0, -1).join('/');
const cookies = [];
for (const header of setCookie) {
// Decode cookie value?
const cookie = parseCookie(header);
if (!cookie) continue;
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
if (!cookie.domain) cookie.domain = url.hostname;else (0, _utils.assert)(cookie.domain.startsWith('.') || !cookie.domain.includes('.'));
if (!(0, _cookieStore.domainMatches)(url.hostname, cookie.domain)) continue;
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
if (!cookie.path || !cookie.path.startsWith('/')) cookie.path = defaultPath;
cookies.push(cookie);
}
return cookies;
}
async _updateRequestCookieHeader(url, headers) {
if (getHeader(headers, 'cookie') !== undefined) return;
const cookies = await this._cookies(url);
if (cookies.length) {
const valueArray = cookies.map(c => `${c.name}=${c.value}`);
setHeader(headers, 'cookie', valueArray.join('; '));
}
}
async _sendRequest(progress, url, options, postData) {
var _getHeader;
await this._updateRequestCookieHeader(url, options.headers);
const requestCookies = ((_getHeader = getHeader(options.headers, 'cookie')) === null || _getHeader === void 0 ? void 0 : _getHeader.split(';').map(p => {
const [name, value] = p.split('=').map(v => v.trim());
return {
name,
value
};
})) || [];
const requestEvent = {
url,
method: options.method,
headers: options.headers,
cookies: requestCookies,
postData
};
this.emit(APIRequestContext.Events.Request, requestEvent);
return new Promise((fulfill, reject) => {
const requestConstructor = (url.protocol === 'https:' ? https : http).request;
// If we have a proxy agent already, do not override it.
const agent = options.agent || (url.protocol === 'https:' ? _happyEyeballs.httpsHappyEyeballsAgent : _happyEyeballs.httpHappyEyeballsAgent);
const requestOptions = {
...options,
agent
};
const request = requestConstructor(url, requestOptions, async response => {
const notifyRequestFinished = body => {
const requestFinishedEvent = {
requestEvent,
httpVersion: response.httpVersion,
statusCode: response.statusCode || 0,
statusMessage: response.statusMessage || '',
headers: response.headers,
rawHeaders: response.rawHeaders,
cookies,
body
};
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
};
progress.log(`${response.statusCode} ${response.statusMessage}`);
for (const [name, value] of Object.entries(response.headers)) progress.log(` ${name}: ${value}`);
const cookies = this._parseSetCookieHeader(response.url || url.toString(), response.headers['set-cookie']);
if (cookies.length) await this._addCookies(cookies);
if (redirectStatus.includes(response.statusCode) && options.maxRedirects >= 0) {
if (!options.maxRedirects) {
reject(new Error('Max redirect count exceeded'));
request.destroy();
return;
}
const headers = {
...options.headers
};
removeHeader(headers, `cookie`);
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
const status = response.statusCode;
let method = options.method;
if ((status === 301 || status === 302) && method === 'POST' || status === 303 && !['GET', 'HEAD'].includes(method)) {
method = 'GET';
postData = undefined;
removeHeader(headers, `content-encoding`);
removeHeader(headers, `content-language`);
removeHeader(headers, `content-length`);
removeHeader(headers, `content-location`);
removeHeader(headers, `content-type`);
}
const redirectOptions = {
method,
headers,
agent: options.agent,
maxRedirects: options.maxRedirects - 1,
timeout: options.timeout,
deadline: options.deadline,
__testHookLookup: options.__testHookLookup
};
// rejectUnauthorized = undefined is treated as true in node 12.
if (options.rejectUnauthorized === false) redirectOptions.rejectUnauthorized = false;
// HTTP-redirect fetch step 4: If locationURL is null, then return response.
if (response.headers.location) {
let locationURL;
try {
locationURL = new URL(response.headers.location, url);
} catch (error) {
reject(new Error(`uri requested responds with an invalid redirect URL: ${response.headers.location}`));
request.destroy();
return;
}
notifyRequestFinished();
fulfill(this._sendRequest(progress, locationURL, redirectOptions, postData));
request.destroy();
return;
}
}
if (response.statusCode === 401 && !getHeader(options.headers, 'authorization')) {
const auth = response.headers['www-authenticate'];
const credentials = this._getHttpCredentials(url);
if (auth !== null && auth !== void 0 && auth.trim().startsWith('Basic') && credentials) {
const {
username,
password
} = credentials;
const encoded = Buffer.from(`${username || ''}:${password || ''}`).toString('base64');
setHeader(options.headers, 'authorization', `Basic ${encoded}`);
notifyRequestFinished();
fulfill(this._sendRequest(progress, url, options, postData));
request.destroy();
return;
}
}
response.on('aborted', () => reject(new Error('aborted')));
const chunks = [];
const notifyBodyFinished = () => {
const body = Buffer.concat(chunks);
notifyRequestFinished(body);
fulfill({
url: response.url || url.toString(),
status: response.statusCode || 0,
statusText: response.statusMessage || '',
headers: toHeadersArray(response.rawHeaders),
body
});
};
let body = response;
let transform;
const encoding = response.headers['content-encoding'];
if (encoding === 'gzip' || encoding === 'x-gzip') {
transform = _zlib.default.createGunzip({
flush: _zlib.default.constants.Z_SYNC_FLUSH,
finishFlush: _zlib.default.constants.Z_SYNC_FLUSH
});
} else if (encoding === 'br') {
transform = _zlib.default.createBrotliDecompress();
} else if (encoding === 'deflate') {
transform = _zlib.default.createInflate();
}
if (transform) {
// Brotli and deflate decompressors throw if the input stream is empty.
const emptyStreamTransform = new SafeEmptyStreamTransform(notifyBodyFinished);
body = (0, _stream.pipeline)(response, emptyStreamTransform, transform, e => {
if (e) reject(new Error(`failed to decompress '${encoding}' encoding: ${e.message}`));
});
body.on('error', e => reject(new Error(`failed to decompress '${encoding}' encoding: ${e}`)));
} else {
body.on('error', reject);
}
body.on('data', chunk => chunks.push(chunk));
body.on('end', notifyBodyFinished);
});
request.on('error', reject);
const disposeListener = () => {
reject(new Error('Request context disposed.'));
request.destroy();
};
this.on(APIRequestContext.Events.Dispose, disposeListener);
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
progress.log(`${options.method} ${url.toString()}`);
if (options.headers) {
for (const [name, value] of Object.entries(options.headers)) progress.log(` ${name}: ${value}`);
}
if (options.deadline) {
const rejectOnTimeout = () => {
reject(new Error(`Request timed out after ${options.timeout}ms`));
request.destroy();
};
const remaining = options.deadline - (0, _utils.monotonicTime)();
if (remaining <= 0) {
rejectOnTimeout();
return;
}
request.setTimeout(remaining, rejectOnTimeout);
}
if (postData) request.write(postData);
request.end();
});
}
_getHttpCredentials(url) {
var _this$_defaultOptions, _this$_defaultOptions2, _this$_defaultOptions3;
if (!((_this$_defaultOptions = this._defaultOptions().httpCredentials) !== null && _this$_defaultOptions !== void 0 && _this$_defaultOptions.origin) || url.origin.toLowerCase() === ((_this$_defaultOptions2 = this._defaultOptions().httpCredentials) === null || _this$_defaultOptions2 === void 0 ? void 0 : (_this$_defaultOptions3 = _this$_defaultOptions2.origin) === null || _this$_defaultOptions3 === void 0 ? void 0 : _this$_defaultOptions3.toLowerCase())) return this._defaultOptions().httpCredentials;
return undefined;
}
}
exports.APIRequestContext = APIRequestContext;
APIRequestContext.Events = {
Dispose: 'dispose',
Request: 'request',
RequestFinished: 'requestfinished'
};
APIRequestContext.allInstances = new Set();
class SafeEmptyStreamTransform extends _stream.Transform {
constructor(onEmptyStreamCallback) {
super();
this._receivedSomeData = false;
this._onEmptyStreamCallback = void 0;
this._onEmptyStreamCallback = onEmptyStreamCallback;
}
_transform(chunk, encoding, callback) {
this._receivedSomeData = true;
callback(null, chunk);
}
_flush(callback) {
if (this._receivedSomeData) callback(null);else this._onEmptyStreamCallback();
}
}
class BrowserContextAPIRequestContext extends APIRequestContext {
constructor(context) {
super(context);
this._context = void 0;
this._context = context;
context.once(_browserContext.BrowserContext.Events.Close, () => this._disposeImpl());
}
tracing() {
return this._context.tracing;
}
async dispose() {
this.fetchResponses.clear();
}
_defaultOptions() {
return {
userAgent: this._context._options.userAgent || this._context._browser.userAgent(),
extraHTTPHeaders: this._context._options.extraHTTPHeaders,
httpCredentials: this._context._options.httpCredentials,
proxy: this._context._options.proxy || this._context._browser.options.proxy,
timeoutSettings: this._context._timeoutSettings,
ignoreHTTPSErrors: this._context._options.ignoreHTTPSErrors,
baseURL: this._context._options.baseURL
};
}
async _addCookies(cookies) {
await this._context.addCookies(cookies);
}
async _cookies(url) {
return await this._context.cookies(url.toString());
}
async storageState() {
return this._context.storageState();
}
}
exports.BrowserContextAPIRequestContext = BrowserContextAPIRequestContext;
class GlobalAPIRequestContext extends APIRequestContext {
constructor(playwright, options) {
super(playwright);
this._cookieStore = new _cookieStore.CookieStore();
this._options = void 0;
this._origins = void 0;
this._tracing = void 0;
this.attribution.context = this;
const timeoutSettings = new _timeoutSettings.TimeoutSettings();
if (options.timeout !== undefined) timeoutSettings.setDefaultTimeout(options.timeout);
const proxy = options.proxy;
if (proxy !== null && proxy !== void 0 && proxy.server) {
let url = proxy === null || proxy === void 0 ? void 0 : proxy.server.trim();
if (!/^\w+:\/\//.test(url)) url = 'http://' + url;
proxy.server = url;
}
if (options.storageState) {
this._origins = options.storageState.origins;
this._cookieStore.addCookies(options.storageState.cookies);
}
this._options = {
baseURL: options.baseURL,
userAgent: options.userAgent || (0, _userAgent.getUserAgent)(),
extraHTTPHeaders: options.extraHTTPHeaders,
ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
httpCredentials: options.httpCredentials,
proxy,
timeoutSettings
};
this._tracing = new _tracing.Tracing(this, options.tracesDir);
}
tracing() {
return this._tracing;
}
async dispose() {
await this._tracing.dispose();
await this._tracing.deleteTmpTracesDir();
this._disposeImpl();
}
_defaultOptions() {
return this._options;
}
async _addCookies(cookies) {
this._cookieStore.addCookies(cookies);
}
async _cookies(url) {
return this._cookieStore.cookies(url);
}
async storageState() {
return {
cookies: this._cookieStore.allCookies(),
origins: this._origins || []
};
}
}
exports.GlobalAPIRequestContext = GlobalAPIRequestContext;
function toHeadersArray(rawHeaders) {
const result = [];
for (let i = 0; i < rawHeaders.length; i += 2) result.push({
name: rawHeaders[i],
value: rawHeaders[i + 1]
});
return result;
}
const redirectStatus = [301, 302, 303, 307, 308];
function parseCookie(header) {
const pairs = header.split(';').filter(s => s.trim().length > 0).map(p => {
let key = '';
let value = '';
const separatorPos = p.indexOf('=');
if (separatorPos === -1) {
// If only a key is specified, the value is left undefined.
key = p.trim();
} else {
// Otherwise we assume that the key is the element before the first `=`
key = p.slice(0, separatorPos).trim();
// And the value is the rest of the string.
value = p.slice(separatorPos + 1).trim();
}
return [key, value];
});
if (!pairs.length) return null;
const [name, value] = pairs[0];
const cookie = {
name,
value,
domain: '',
path: '',
expires: -1,
httpOnly: false,
secure: false,
// From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
// The cookie-sending behavior if SameSite is not specified is SameSite=Lax.
sameSite: 'Lax'
};
for (let i = 1; i < pairs.length; i++) {
const [name, value] = pairs[i];
switch (name.toLowerCase()) {
case 'expires':
const expiresMs = +new Date(value);
if (isFinite(expiresMs)) cookie.expires = expiresMs / 1000;
break;
case 'max-age':
const maxAgeSec = parseInt(value, 10);
if (isFinite(maxAgeSec)) cookie.expires = Date.now() / 1000 + maxAgeSec;
break;
case 'domain':
cookie.domain = value.toLocaleLowerCase() || '';
if (cookie.domain && !cookie.domain.startsWith('.') && cookie.domain.includes('.')) cookie.domain = '.' + cookie.domain;
break;
case 'path':
cookie.path = value || '';
break;
case 'secure':
cookie.secure = true;
break;
case 'httponly':
cookie.httpOnly = true;
break;
case 'samesite':
switch (value.toLowerCase()) {
case 'none':
cookie.sameSite = 'None';
break;
case 'lax':
cookie.sameSite = 'Lax';
break;
case 'strict':
cookie.sameSite = 'Strict';
break;
}
break;
}
}
return cookie;
}
function isJsonParsable(value) {
if (typeof value !== 'string') return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError) return false;else throw e;
}
}
function serializePostData(params, headers) {
(0, _utils.assert)((params.postData ? 1 : 0) + (params.jsonData ? 1 : 0) + (params.formData ? 1 : 0) + (params.multipartData ? 1 : 0) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
if (params.jsonData !== undefined) {
const json = isJsonParsable(params.jsonData) ? params.jsonData : JSON.stringify(params.jsonData);
setHeader(headers, 'content-type', 'application/json', true);
return Buffer.from(json, 'utf8');
} else if (params.formData) {
const searchParams = new URLSearchParams();
for (const {
name,
value
} of params.formData) searchParams.append(name, value);
setHeader(headers, 'content-type', 'application/x-www-form-urlencoded', true);
return Buffer.from(searchParams.toString(), 'utf8');
} else if (params.multipartData) {
const formData = new _formData.MultipartFormData();
for (const field of params.multipartData) {
if (field.file) formData.addFileField(field.name, field.file);else if (field.value) formData.addField(field.name, field.value);
}
setHeader(headers, 'content-type', formData.contentTypeHeader(), true);
return formData.finish();
} else if (params.postData !== undefined) {
setHeader(headers, 'content-type', 'application/octet-stream', true);
return params.postData;
}
return undefined;
}
function setHeader(headers, name, value, keepExisting = false) {
const existing = Object.entries(headers).find(pair => pair[0].toLowerCase() === name.toLowerCase());
if (!existing) headers[name] = value;else if (!keepExisting) headers[existing[0]] = value;
}
function getHeader(headers, name) {
const existing = Object.entries(headers).find(pair => pair[0].toLowerCase() === name.toLowerCase());
return existing ? existing[1] : undefined;
}
function removeHeader(headers, name) {
delete headers[name];
}
function shouldBypassProxy(url, bypass) {
if (!bypass) return false;
const domains = bypass.split(',').map(s => {
s = s.trim();
if (!s.startsWith('.')) s = '.' + s;
return s;
});
const domain = '.' + url.hostname;
return domains.some(d => domain.endsWith(d));
}

View File

@@ -0,0 +1,42 @@
"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;
}
}
exports.FileChooser = FileChooser;

View File

@@ -0,0 +1,215 @@
"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(session, needle) {
const objectId = needle ? needle._objectId : undefined;
const {
tree
} = await session.send('Accessibility.getFullAXTree', {
objectId
});
const axNode = new FFAXNode(tree);
return {
tree: axNode,
needle: needle ? axNode._findNeedle() : null
};
}
const FFRoleToARIARole = new Map(Object.entries({
'pushbutton': 'button',
'checkbutton': 'checkbox',
'editcombobox': 'combobox',
'content deletion': 'deletion',
'footnote': 'doc-footnote',
'non-native document': 'document',
'grouping': 'group',
'graphic': 'img',
'content insertion': 'insertion',
'animation': 'marquee',
'flat equation': 'math',
'menupopup': 'menu',
'check menu item': 'menuitemcheckbox',
'radio menu item': 'menuitemradio',
'listbox option': 'option',
'radiobutton': 'radio',
'statusbar': 'status',
'pagetab': 'tab',
'pagetablist': 'tablist',
'propertypage': 'tabpanel',
'entry': 'textbox',
'outline': 'tree',
'tree table': 'treegrid',
'outlineitem': 'treeitem'
}));
class FFAXNode {
constructor(payload) {
this._children = void 0;
this._payload = void 0;
this._editable = void 0;
this._richlyEditable = void 0;
this._focusable = void 0;
this._expanded = void 0;
this._name = void 0;
this._role = void 0;
this._cachedHasFocusableChild = void 0;
this._payload = payload;
this._children = (payload.children || []).map(x => new FFAXNode(x));
this._editable = !!payload.editable;
this._richlyEditable = this._editable && payload.tag !== 'textarea' && payload.tag !== 'input';
this._focusable = !!payload.focusable;
this._expanded = !!payload.expanded;
this._name = this._payload.name;
this._role = this._payload.role;
}
_isPlainTextField() {
if (this._richlyEditable) return false;
if (this._editable) return true;
return this._role === 'entry';
}
_isTextOnlyObject() {
const role = this._role;
return role === 'text leaf' || role === 'text' || 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;
}
_findNeedle() {
if (this._payload.foundObject) return this;
for (const child of this._children) {
const found = child._findNeedle();
if (found) return found;
}
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 'graphic':
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 !== 'document' && this._name) return true;
if (this._role === 'heading' && this._name) return true;
return false;
}
isControl() {
switch (this._role) {
case 'checkbutton':
case 'check menu item':
case 'check rich option':
case 'combobox':
case 'combobox option':
case 'color chooser':
case 'listbox':
case 'listbox option':
case 'listbox rich option':
case 'popup menu':
case 'menupopup':
case 'menuitem':
case 'menubar':
case 'button':
case 'pushbutton':
case 'radiobutton':
case 'radio menuitem':
case 'scrollbar':
case 'slider':
case 'spinbutton':
case 'switch':
case 'pagetab':
case 'entry':
case 'tree table':
return true;
default:
return false;
}
}
isInteresting(insideControl) {
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.trim();
}
serialize() {
const node = {
role: FFRoleToARIARole.get(this._role) || this._role,
name: this._name || ''
};
const userStringProperties = ['name', 'description', 'roledescription', 'valuetext', 'keyshortcuts'];
for (const userStringProperty of userStringProperties) {
if (!(userStringProperty in this._payload)) continue;
node[userStringProperty] = this._payload[userStringProperty];
}
const booleanProperties = ['disabled', 'expanded', 'focused', 'modal', 'multiline', 'multiselectable', 'readonly', 'required', 'selected'];
for (const booleanProperty of booleanProperties) {
if (this._role === 'document' && booleanProperty === 'focused') continue; // document focusing is strange
const value = this._payload[booleanProperty];
if (!value) continue;
node[booleanProperty] = value;
}
const numericalProperties = ['level'];
for (const numericalProperty of numericalProperties) {
if (!(numericalProperty in this._payload)) continue;
node[numericalProperty] = this._payload[numericalProperty];
}
const tokenProperties = ['autocomplete', 'haspopup', 'invalid', 'orientation'];
for (const tokenProperty of tokenProperties) {
const value = this._payload[tokenProperty];
if (!value || value === 'false') continue;
node[tokenProperty] = value;
}
const axNode = node;
axNode.valueString = this._payload.value;
if ('checked' in this._payload) axNode.checked = this._payload.checked === true ? 'checked' : this._payload.checked === 'mixed' ? 'mixed' : 'unchecked';
if ('pressed' in this._payload) axNode.pressed = this._payload.pressed === true ? 'pressed' : 'released';
return axNode;
}
}

View File

@@ -0,0 +1,439 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFBrowserContext = exports.FFBrowser = void 0;
var _errors = require("../../common/errors");
var _utils = require("../../utils");
var _browser = require("../browser");
var _browserContext = require("../browserContext");
var network = _interopRequireWildcard(require("../network"));
var _ffConnection = require("./ffConnection");
var _ffPage = require("./ffPage");
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 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 FFBrowser extends _browser.Browser {
static async connect(parent, transport, options) {
var _options$originalLaun;
const connection = new _ffConnection.FFConnection(transport, options.protocolLogger, options.browserLogsCollector);
const browser = new FFBrowser(parent, connection, options);
if (options.__testHookOnConnectToBrowser) await options.__testHookOnConnectToBrowser();
let firefoxUserPrefs = options.persistent ? {} : (_options$originalLaun = options.originalLaunchOptions.firefoxUserPrefs) !== null && _options$originalLaun !== void 0 ? _options$originalLaun : {};
if (Object.keys(kBandaidFirefoxUserPrefs).length) firefoxUserPrefs = {
...kBandaidFirefoxUserPrefs,
...firefoxUserPrefs
};
const promises = [connection.send('Browser.enable', {
attachToDefaultContext: !!options.persistent,
userPrefs: Object.entries(firefoxUserPrefs).map(([name, value]) => ({
name,
value
}))
}), browser._initVersion()];
if (options.persistent) {
browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent);
promises.push(browser._defaultContext._initialize());
}
if (options.proxy) promises.push(browser._connection.send('Browser.setBrowserProxy', toJugglerProxyOptions(options.proxy)));
await Promise.all(promises);
return browser;
}
constructor(parent, connection, options) {
super(parent, options);
this._connection = void 0;
this._ffPages = void 0;
this._contexts = void 0;
this._version = '';
this._userAgent = '';
this._connection = connection;
this._ffPages = new Map();
this._contexts = new Map();
this._connection.on(_ffConnection.ConnectionEvents.Disconnected, () => this._onDisconnect());
this._connection.on('Browser.attachedToTarget', this._onAttachedToTarget.bind(this));
this._connection.on('Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this));
this._connection.on('Browser.downloadCreated', this._onDownloadCreated.bind(this));
this._connection.on('Browser.downloadFinished', this._onDownloadFinished.bind(this));
this._connection.on('Browser.videoRecordingFinished', this._onVideoRecordingFinished.bind(this));
}
async _initVersion() {
const result = await this._connection.send('Browser.getInfo');
this._version = result.version.substring(result.version.indexOf('/') + 1);
this._userAgent = result.userAgent;
}
isConnected() {
return !this._connection._closed;
}
async doCreateNewContext(options) {
if (options.isMobile) throw new Error('options.isMobile is not supported in Firefox');
const {
browserContextId
} = await this._connection.send('Browser.createBrowserContext', {
removeOnDetach: true
});
const context = new FFBrowserContext(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;
}
_onDetachedFromTarget(payload) {
const ffPage = this._ffPages.get(payload.targetId);
this._ffPages.delete(payload.targetId);
ffPage.didClose();
}
_onAttachedToTarget(payload) {
const {
targetId,
browserContextId,
openerId,
type
} = payload.targetInfo;
(0, _utils.assert)(type === 'page');
const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
(0, _utils.assert)(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);
const session = this._connection.createSession(payload.sessionId);
const opener = openerId ? this._ffPages.get(openerId) : null;
const ffPage = new _ffPage.FFPage(session, context, opener);
this._ffPages.set(targetId, ffPage);
}
_onDownloadCreated(payload) {
const ffPage = this._ffPages.get(payload.pageTargetId);
if (!ffPage) return;
// Abort the navigation that turned into download.
ffPage._page._frameManager.frameAbortedNavigation(payload.frameId, 'Download is starting');
let originPage = ffPage._initializedPage;
// If it's a new window download, report it on the opener page.
if (!originPage) {
// Resume the page creation with an error. The page will automatically close right
// after the download begins.
ffPage._markAsError(new Error('Starting new page download'));
if (ffPage._opener) originPage = ffPage._opener._initializedPage;
}
if (!originPage) return;
this._downloadCreated(originPage, payload.uuid, payload.url, payload.suggestedFileName);
}
_onDownloadFinished(payload) {
const error = payload.canceled ? 'canceled' : payload.error;
this._downloadFinished(payload.uuid, error);
}
_onVideoRecordingFinished(payload) {
var _this$_takeVideo;
(_this$_takeVideo = this._takeVideo(payload.screencastId)) === null || _this$_takeVideo === void 0 ? void 0 : _this$_takeVideo.reportFinished();
}
_onDisconnect() {
for (const video of this._idToVideo.values()) video.artifact.reportFinished(_errors.kBrowserClosedError);
this._idToVideo.clear();
this._didClose();
}
}
exports.FFBrowser = FFBrowser;
class FFBrowserContext extends _browserContext.BrowserContext {
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
}
async _initialize() {
(0, _utils.assert)(!this._ffPages().length);
const browserContextId = this._browserContextId;
const promises = [super._initialize()];
promises.push(this._browser._connection.send('Browser.setDownloadOptions', {
browserContextId,
downloadOptions: {
behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel',
downloadsDir: this._browser.options.downloadsPath
}
}));
if (this._options.viewport) {
const viewport = {
viewportSize: {
width: this._options.viewport.width,
height: this._options.viewport.height
},
deviceScaleFactor: this._options.deviceScaleFactor || 1
};
promises.push(this._browser._connection.send('Browser.setDefaultViewport', {
browserContextId,
viewport
}));
}
if (this._options.hasTouch) promises.push(this._browser._connection.send('Browser.setTouchOverride', {
browserContextId,
hasTouch: true
}));
if (this._options.userAgent) promises.push(this._browser._connection.send('Browser.setUserAgentOverride', {
browserContextId,
userAgent: this._options.userAgent
}));
if (this._options.bypassCSP) promises.push(this._browser._connection.send('Browser.setBypassCSP', {
browserContextId,
bypassCSP: true
}));
if (this._options.ignoreHTTPSErrors) promises.push(this._browser._connection.send('Browser.setIgnoreHTTPSErrors', {
browserContextId,
ignoreHTTPSErrors: true
}));
if (this._options.javaScriptEnabled === false) promises.push(this._browser._connection.send('Browser.setJavaScriptDisabled', {
browserContextId,
javaScriptDisabled: true
}));
if (this._options.locale) promises.push(this._browser._connection.send('Browser.setLocaleOverride', {
browserContextId,
locale: this._options.locale
}));
if (this._options.timezoneId) promises.push(this._browser._connection.send('Browser.setTimezoneOverride', {
browserContextId,
timezoneId: this._options.timezoneId
}));
if (this._options.extraHTTPHeaders || this._options.locale) promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []));
if (this._options.httpCredentials) promises.push(this.setHTTPCredentials(this._options.httpCredentials));
if (this._options.geolocation) promises.push(this.setGeolocation(this._options.geolocation));
if (this._options.offline) promises.push(this.setOffline(this._options.offline));
if (this._options.colorScheme !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setColorScheme', {
browserContextId,
colorScheme: this._options.colorScheme !== undefined ? this._options.colorScheme : 'light'
}));
}
if (this._options.reducedMotion !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setReducedMotion', {
browserContextId,
reducedMotion: this._options.reducedMotion !== undefined ? this._options.reducedMotion : 'no-preference'
}));
}
if (this._options.forcedColors !== 'no-override') {
promises.push(this._browser._connection.send('Browser.setForcedColors', {
browserContextId,
forcedColors: this._options.forcedColors !== undefined ? this._options.forcedColors : 'none'
}));
}
if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => {
return this._browser._connection.send('Browser.setVideoRecordingOptions', {
// validateBrowserContextOptions ensures correct video size.
options: {
...this._options.recordVideo.size,
dir: this._options.recordVideo.dir
},
browserContextId: this._browserContextId
});
}));
}
if (this._options.proxy) {
promises.push(this._browser._connection.send('Browser.setContextProxy', {
browserContextId: this._browserContextId,
...toJugglerProxyOptions(this._options.proxy)
}));
}
await Promise.all(promises);
}
_ffPages() {
return Array.from(this._browser._ffPages.values()).filter(ffPage => ffPage._browserContext === this);
}
pages() {
return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull);
}
async newPageDelegate() {
(0, _browserContext.assertBrowserContextIsNotOwned)(this);
const {
targetId
} = await this._browser._connection.send('Browser.newPage', {
browserContextId: this._browserContextId
}).catch(e => {
if (e.message.includes('Failed to override timezone')) throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
throw e;
});
return this._browser._ffPages.get(targetId);
}
async doGetCookies(urls) {
const {
cookies
} = await this._browser._connection.send('Browser.getCookies', {
browserContextId: this._browserContextId
});
return network.filterCookies(cookies.map(c => {
const copy = {
...c
};
delete copy.size;
delete copy.session;
return copy;
}), urls);
}
async addCookies(cookies) {
const cc = network.rewriteCookies(cookies).map(c => ({
...c,
expires: c.expires && c.expires !== -1 ? c.expires : undefined
}));
await this._browser._connection.send('Browser.setCookies', {
browserContextId: this._browserContextId,
cookies: cc
});
}
async clearCookies() {
await this._browser._connection.send('Browser.clearCookies', {
browserContextId: this._browserContextId
});
}
async doGrantPermissions(origin, permissions) {
const webPermissionToProtocol = new Map([['geolocation', 'geo'], ['persistent-storage', 'persistent-storage'], ['push', 'push'], ['notifications', 'desktop-notification']]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission) throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._browser._connection.send('Browser.grantPermissions', {
origin: origin,
browserContextId: this._browserContextId,
permissions: filtered
});
}
async doClearPermissions() {
await this._browser._connection.send('Browser.resetPermissions', {
browserContextId: this._browserContextId
});
}
async setGeolocation(geolocation) {
(0, _browserContext.verifyGeolocation)(geolocation);
this._options.geolocation = geolocation;
await this._browser._connection.send('Browser.setGeolocationOverride', {
browserContextId: this._browserContextId,
geolocation: geolocation || null
});
}
async setExtraHTTPHeaders(headers) {
this._options.extraHTTPHeaders = headers;
let allHeaders = this._options.extraHTTPHeaders;
if (this._options.locale) allHeaders = network.mergeHeaders([allHeaders, network.singleHeader('Accept-Language', this._options.locale)]);
await this._browser._connection.send('Browser.setExtraHTTPHeaders', {
browserContextId: this._browserContextId,
headers: allHeaders
});
}
async setUserAgent(userAgent) {
await this._browser._connection.send('Browser.setUserAgentOverride', {
browserContextId: this._browserContextId,
userAgent: userAgent || null
});
}
async setOffline(offline) {
this._options.offline = offline;
await this._browser._connection.send('Browser.setOnlineOverride', {
browserContextId: this._browserContextId,
override: offline ? 'offline' : 'online'
});
}
async doSetHTTPCredentials(httpCredentials) {
this._options.httpCredentials = httpCredentials;
await this._browser._connection.send('Browser.setHTTPCredentials', {
browserContextId: this._browserContextId,
credentials: httpCredentials || null
});
}
async doAddInitScript(source) {
await this._browser._connection.send('Browser.setInitScripts', {
browserContextId: this._browserContextId,
scripts: this.initScripts.map(script => ({
script
}))
});
}
async doRemoveInitScripts() {
await this._browser._connection.send('Browser.setInitScripts', {
browserContextId: this._browserContextId,
scripts: []
});
}
async doExposeBinding(binding) {
await this._browser._connection.send('Browser.addBinding', {
browserContextId: this._browserContextId,
name: binding.name,
script: binding.source
});
}
async doRemoveExposedBindings() {
// TODO: implement me.
// This is not a critical problem, what ends up happening is
// an old binding will be restored upon page reload and will point nowhere.
}
async doUpdateRequestInterception() {
await this._browser._connection.send('Browser.setRequestInterception', {
browserContextId: this._browserContextId,
enabled: !!this._requestInterceptor
});
}
onClosePersistent() {}
async clearCache() {
// Clearing only the context cache does not work: https://bugzilla.mozilla.org/show_bug.cgi?id=1819147
await this._browser._connection.send('Browser.clearCache');
}
async doClose() {
if (!this._browserContextId) {
if (this._options.recordVideo) {
await this._browser._connection.send('Browser.setVideoRecordingOptions', {
options: undefined,
browserContextId: this._browserContextId
});
}
// Closing persistent context should close the browser.
await this._browser.close();
} else {
await this._browser._connection.send('Browser.removeBrowserContext', {
browserContextId: this._browserContextId
});
this._browser._contexts.delete(this._browserContextId);
}
}
async cancelDownload(uuid) {
await this._browser._connection.send('Browser.cancelDownload', {
uuid
});
}
}
exports.FFBrowserContext = FFBrowserContext;
function toJugglerProxyOptions(proxy) {
const proxyServer = new URL(proxy.server);
let port = parseInt(proxyServer.port, 10);
let type = 'http';
if (proxyServer.protocol === 'socks5:') type = 'socks';else if (proxyServer.protocol === 'socks4:') type = 'socks4';else if (proxyServer.protocol === 'https:') type = 'https';
if (proxyServer.port === '') {
if (proxyServer.protocol === 'http:') port = 80;else if (proxyServer.protocol === 'https:') port = 443;
}
return {
type,
bypass: proxy.bypass ? proxy.bypass.split(',').map(domain => domain.trim()) : [],
host: proxyServer.hostname,
port,
username: proxy.username,
password: proxy.password
};
}
// Prefs for quick fixes that didn't make it to the build.
// Should all be moved to `playwright.cfg`.
const kBandaidFirefoxUserPrefs = {};

View File

@@ -0,0 +1,223 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.kBrowserCloseMessageId = exports.FFSessionEvents = exports.FFSession = exports.FFConnection = exports.ConnectionEvents = void 0;
var _events = require("events");
var _utils = require("../../utils");
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('Disconnected')
};
// FFPlaywright uses this special id to issue Browser.close command which we
// should ignore.
exports.ConnectionEvents = ConnectionEvents;
const kBrowserCloseMessageId = -9999;
exports.kBrowserCloseMessageId = kBrowserCloseMessageId;
class FFConnection extends _events.EventEmitter {
constructor(transport, protocolLogger, browserLogsCollector) {
super();
this._lastId = void 0;
this._callbacks = void 0;
this._transport = void 0;
this._protocolLogger = void 0;
this._browserLogsCollector = void 0;
this._sessions = void 0;
this._closed = void 0;
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.setMaxListeners(0);
this._transport = transport;
this._protocolLogger = protocolLogger;
this._browserLogsCollector = browserLogsCollector;
this._lastId = 0;
this._callbacks = new Map();
this._sessions = new Map();
this._closed = false;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
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);
}
async send(method, params) {
this._checkClosed(method);
const id = this.nextMessageId();
this._rawSend({
id,
method,
params
});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {
resolve,
reject,
error: new _protocolError.ProtocolError(false),
method
});
});
}
nextMessageId() {
return ++this._lastId;
}
_checkClosed(method) {
if (this._closed) throw new _protocolError.ProtocolError(true, `${method}): Browser closed.` + _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs()));
}
_rawSend(message) {
this._protocolLogger('send', message);
this._transport.send(message);
}
async _onMessage(message) {
this._protocolLogger('receive', message);
if (message.id === kBrowserCloseMessageId) return;
if (message.sessionId) {
const session = this._sessions.get(message.sessionId);
if (session) session.dispatchMessage(message);
} else if (message.id) {
const callback = this._callbacks.get(message.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
if (callback) {
this._callbacks.delete(message.id);
if (message.error) callback.reject(createProtocolError(callback.error, callback.method, message.error));else callback.resolve(message.result);
}
} else {
Promise.resolve().then(() => this.emit(message.method, message.params));
}
}
_onClose() {
this._closed = true;
this._transport.onmessage = undefined;
this._transport.onclose = undefined;
const formattedBrowserLogs = _helper.helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
for (const session of this._sessions.values()) session.dispose();
this._sessions.clear();
for (const callback of this._callbacks.values()) {
const error = (0, _stackTrace.rewriteErrorMessage)(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs);
error.sessionClosed = true;
callback.reject(error);
}
this._callbacks.clear();
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
}
close() {
if (!this._closed) this._transport.close();
}
createSession(sessionId) {
const session = new FFSession(this, sessionId, message => this._rawSend({
...message,
sessionId
}));
this._sessions.set(sessionId, session);
return session;
}
}
exports.FFConnection = FFConnection;
const FFSessionEvents = {
Disconnected: Symbol('Disconnected')
};
exports.FFSessionEvents = FFSessionEvents;
class FFSession extends _events.EventEmitter {
constructor(connection, sessionId, rawSend) {
super();
this._connection = void 0;
this._disposed = false;
this._callbacks = void 0;
this._sessionId = void 0;
this._rawSend = void 0;
this._crashed = false;
this.on = void 0;
this.addListener = void 0;
this.off = void 0;
this.removeListener = void 0;
this.once = void 0;
this.setMaxListeners(0);
this._callbacks = new Map();
this._connection = connection;
this._sessionId = sessionId;
this._rawSend = rawSend;
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');
this._connection._checkClosed(method);
if (this._disposed) throw new _protocolError.ProtocolError(true, 'Target closed');
const id = this._connection.nextMessageId();
this._rawSend({
method,
params,
id
});
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));
}
dispatchMessage(object) {
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 {
(0, _utils.assert)(!object.id);
Promise.resolve().then(() => this.emit(object.method, object.params));
}
}
dispose() {
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject((0, _stackTrace.rewriteErrorMessage)(callback.error, 'Target closed'));
}
this._callbacks.clear();
this._disposed = true;
this._connection._sessions.delete(this._sessionId);
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
}
}
exports.FFSession = FFSession;
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,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFExecutionContext = void 0;
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 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.
*/
class FFExecutionContext {
constructor(session, executionContextId) {
this._session = void 0;
this._executionContextId = void 0;
this._session = session;
this._executionContextId = executionContextId;
}
async rawEvaluateJSON(expression) {
const payload = await this._session.send('Runtime.evaluate', {
expression,
returnByValue: true,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
return payload.result.value;
}
async rawEvaluateHandle(expression) {
const payload = await this._session.send('Runtime.evaluate', {
expression,
returnByValue: false,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
return payload.result.objectId;
}
rawCallFunctionNoReply(func, ...args) {
this._session.send('Runtime.callFunction', {
functionDeclaration: func.toString(),
args: args.map(a => a instanceof js.JSHandle ? {
objectId: a._objectId
} : {
value: a
}),
returnByValue: true,
executionContextId: this._executionContextId
}).catch(() => {});
}
async evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
const payload = await this._session.send('Runtime.callFunction', {
functionDeclaration: expression,
args: [{
objectId: utilityScript._objectId,
value: undefined
}, ...values.map(value => ({
value
})), ...objectIds.map(objectId => ({
objectId,
value: undefined
}))],
returnByValue,
executionContextId: this._executionContextId
}).catch(rewriteError);
checkException(payload.exceptionDetails);
if (returnByValue) return (0, _utilityScriptSerializers.parseEvaluationResultValue)(payload.result.value);
return utilityScript._context.createHandle(payload.result);
}
async getProperties(context, objectId) {
const response = await this._session.send('Runtime.getObjectProperties', {
executionContextId: this._executionContextId,
objectId
});
const result = new Map();
for (const property of response.properties) 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 this._session.send('Runtime.disposeObject', {
executionContextId: this._executionContextId,
objectId
});
}
objectCount(objectId) {
throw new Error('Method not implemented in Firefox.');
}
}
exports.FFExecutionContext = FFExecutionContext;
function checkException(exceptionDetails) {
if (!exceptionDetails) return;
if (exceptionDetails.value) throw new js.JavaScriptErrorInEvaluate(JSON.stringify(exceptionDetails.value));else throw new js.JavaScriptErrorInEvaluate(exceptionDetails.text + (exceptionDetails.stack ? '\n' + exceptionDetails.stack : ''));
}
function rewriteError(error) {
if (error.message.includes('cyclic object value') || error.message.includes('Object is not serializable')) return {
result: {
type: 'undefined',
value: 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 (object.unserializableValue) return String(object.unserializableValue);
if (object.type === 'symbol') return 'Symbol()';
if (object.subtype === 'regexp') return 'RegExp';
if (object.subtype === 'weakmap') return 'WeakMap';
if (object.subtype === 'weakset') return 'WeakSet';
if (object.subtype) return object.subtype[0].toUpperCase() + object.subtype.slice(1);
if ('value' in object) return String(object.value);
}

View File

@@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RawTouchscreenImpl = exports.RawMouseImpl = exports.RawKeyboardImpl = 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 toModifiersMask(modifiers) {
let mask = 0;
if (modifiers.has('Alt')) mask |= 1;
if (modifiers.has('Control')) mask |= 2;
if (modifiers.has('Shift')) mask |= 4;
if (modifiers.has('Meta')) mask |= 8;
return mask;
}
function toButtonNumber(button) {
if (button === 'left') return 0;
if (button === 'middle') return 1;
if (button === 'right') return 2;
return 0;
}
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;
}
class RawKeyboardImpl {
constructor(client) {
this._client = void 0;
this._client = client;
}
async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
if (code === 'MetaLeft') code = 'OSLeft';
if (code === 'MetaRight') code = 'OSRight';
// Firefox will figure out Enter by itself
if (text === '\r') text = '';
await this._client.send('Page.dispatchKeyEvent', {
type: 'keydown',
keyCode: keyCodeWithoutLocation,
code,
key,
repeat: autoRepeat,
location,
text
});
}
async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
if (code === 'MetaLeft') code = 'OSLeft';
if (code === 'MetaRight') code = 'OSRight';
await this._client.send('Page.dispatchKeyEvent', {
type: 'keyup',
key,
keyCode: keyCodeWithoutLocation,
code,
location,
repeat: false
});
}
async sendText(text) {
await this._client.send('Page.insertText', {
text
});
}
}
exports.RawKeyboardImpl = RawKeyboardImpl;
class RawMouseImpl {
constructor(client) {
this._client = void 0;
this._page = void 0;
this._client = client;
}
async move(x, y, button, buttons, modifiers, forClick) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mousemove',
button: 0,
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers)
});
}
async down(x, y, button, buttons, modifiers, clickCount) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mousedown',
button: toButtonNumber(button),
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers),
clickCount
});
}
async up(x, y, button, buttons, modifiers, clickCount) {
await this._client.send('Page.dispatchMouseEvent', {
type: 'mouseup',
button: toButtonNumber(button),
buttons: toButtonsMask(buttons),
x: Math.floor(x),
y: Math.floor(y),
modifiers: toModifiersMask(modifiers),
clickCount
});
}
async wheel(x, y, buttons, modifiers, deltaX, deltaY) {
// Wheel events hit the compositor first, so wait one frame for it to be synced.
await this._page.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, {
world: 'utility'
});
await this._client.send('Page.dispatchWheelEvent', {
deltaX,
deltaY,
x: Math.floor(x),
y: Math.floor(y),
deltaZ: 0,
modifiers: toModifiersMask(modifiers)
});
}
setPage(page) {
this._page = page;
}
}
exports.RawMouseImpl = RawMouseImpl;
class RawTouchscreenImpl {
constructor(client) {
this._client = void 0;
this._client = client;
}
async tap(x, y, modifiers) {
await this._client.send('Page.dispatchTapEvent', {
x,
y,
modifiers: toModifiersMask(modifiers)
});
}
}
exports.RawTouchscreenImpl = RawTouchscreenImpl;

View File

@@ -0,0 +1,231 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FFNetworkManager = void 0;
var _eventsHelper = require("../../utils/eventsHelper");
var network = _interopRequireWildcard(require("../network"));
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 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.
*/
class FFNetworkManager {
constructor(session, page) {
this._session = void 0;
this._requests = void 0;
this._page = void 0;
this._eventListeners = void 0;
this._session = session;
this._requests = new Map();
this._page = page;
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestFinished', this._onRequestFinished.bind(this)), _eventsHelper.eventsHelper.addEventListener(session, 'Network.requestFailed', this._onRequestFailed.bind(this))];
}
dispose() {
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
}
async setRequestInterception(enabled) {
await this._session.send('Network.setRequestInterception', {
enabled
});
}
_onRequestWillBeSent(event) {
const redirectedFrom = event.redirectedFrom ? this._requests.get(event.redirectedFrom) || null : null;
const frame = redirectedFrom ? redirectedFrom.request.frame() : event.frameId ? this._page._frameManager.frame(event.frameId) : null;
if (!frame) return;
if (redirectedFrom) this._requests.delete(redirectedFrom._id);
const request = new InterceptableRequest(frame, redirectedFrom, event);
let route;
if (event.isIntercepted) route = new FFRouteImpl(this._session, request);
this._requests.set(request._id, request);
this._page._frameManager.requestStarted(request.request, route);
}
_onResponseReceived(event) {
var _event$securityDetail, _event$securityDetail2, _event$securityDetail3, _event$securityDetail4, _event$securityDetail5;
const request = this._requests.get(event.requestId);
if (!request) return;
const getResponseBody = async () => {
const response = await this._session.send('Network.getResponseBody', {
requestId: request._id
});
if (response.evicted) throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
return Buffer.from(response.base64body, 'base64');
};
const startTime = event.timing.startTime;
function relativeToStart(time) {
if (!time) return -1;
return (time - startTime) / 1000;
}
const timing = {
startTime: startTime / 1000,
domainLookupStart: relativeToStart(event.timing.domainLookupStart),
domainLookupEnd: relativeToStart(event.timing.domainLookupEnd),
connectStart: relativeToStart(event.timing.connectStart),
secureConnectionStart: relativeToStart(event.timing.secureConnectionStart),
connectEnd: relativeToStart(event.timing.connectEnd),
requestStart: relativeToStart(event.timing.requestStart),
responseStart: relativeToStart(event.timing.responseStart)
};
const response = new network.Response(request.request, event.status, event.statusText, parseMultivalueHeaders(event.headers), timing, getResponseBody, event.fromServiceWorker);
if (event !== null && event !== void 0 && event.remoteIPAddress && typeof (event === null || event === void 0 ? void 0 : event.remotePort) === 'number') {
response._serverAddrFinished({
ipAddress: event.remoteIPAddress,
port: event.remotePort
});
} else {
response._serverAddrFinished();
}
response._securityDetailsFinished({
protocol: event === null || event === void 0 ? void 0 : (_event$securityDetail = event.securityDetails) === null || _event$securityDetail === void 0 ? void 0 : _event$securityDetail.protocol,
subjectName: event === null || event === void 0 ? void 0 : (_event$securityDetail2 = event.securityDetails) === null || _event$securityDetail2 === void 0 ? void 0 : _event$securityDetail2.subjectName,
issuer: event === null || event === void 0 ? void 0 : (_event$securityDetail3 = event.securityDetails) === null || _event$securityDetail3 === void 0 ? void 0 : _event$securityDetail3.issuer,
validFrom: event === null || event === void 0 ? void 0 : (_event$securityDetail4 = event.securityDetails) === null || _event$securityDetail4 === void 0 ? void 0 : _event$securityDetail4.validFrom,
validTo: event === null || event === void 0 ? void 0 : (_event$securityDetail5 = event.securityDetails) === null || _event$securityDetail5 === void 0 ? void 0 : _event$securityDetail5.validTo
});
// "raw" headers are the same as "provisional" headers in Firefox.
response.setRawResponseHeaders(null);
// Headers size are not available in Firefox.
response.setResponseHeadersSize(null);
this._page._frameManager.requestReceivedResponse(response);
}
_onRequestFinished(event) {
const request = this._requests.get(event.requestId);
if (!request) return;
const response = request.request._existingResponse();
response.setTransferSize(event.transferSize);
response.setEncodedBodySize(event.encodedBodySize);
// Keep redirected requests in the map for future reference as redirectedFrom.
const isRedirected = response.status() >= 300 && response.status() <= 399;
const responseEndTime = event.responseEndTime ? event.responseEndTime / 1000 - response.timing().startTime : -1;
if (isRedirected) {
response._requestFinished(responseEndTime);
} else {
this._requests.delete(request._id);
response._requestFinished(responseEndTime);
}
if (event.protocolVersion) response._setHttpVersion(event.protocolVersion);
this._page._frameManager.reportRequestFinished(request.request, response);
}
_onRequestFailed(event) {
const request = this._requests.get(event.requestId);
if (!request) return;
this._requests.delete(request._id);
const response = request.request._existingResponse();
if (response) {
response.setTransferSize(null);
response.setEncodedBodySize(null);
response._requestFinished(-1);
}
request.request._setFailureText(event.errorCode);
this._page._frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED');
}
}
exports.FFNetworkManager = FFNetworkManager;
const causeToResourceType = {
TYPE_INVALID: 'other',
TYPE_OTHER: 'other',
TYPE_SCRIPT: 'script',
TYPE_IMAGE: 'image',
TYPE_STYLESHEET: 'stylesheet',
TYPE_OBJECT: 'other',
TYPE_DOCUMENT: 'document',
TYPE_SUBDOCUMENT: 'document',
TYPE_REFRESH: 'document',
TYPE_XBL: 'other',
TYPE_PING: 'other',
TYPE_XMLHTTPREQUEST: 'xhr',
TYPE_OBJECT_SUBREQUEST: 'other',
TYPE_DTD: 'other',
TYPE_FONT: 'font',
TYPE_MEDIA: 'media',
TYPE_WEBSOCKET: 'websocket',
TYPE_CSP_REPORT: 'other',
TYPE_XSLT: 'other',
TYPE_BEACON: 'other',
TYPE_FETCH: 'fetch',
TYPE_IMAGESET: 'images',
TYPE_WEB_MANIFEST: 'manifest'
};
const internalCauseToResourceType = {
TYPE_INTERNAL_EVENTSOURCE: 'eventsource'
};
class InterceptableRequest {
constructor(frame, redirectedFrom, payload) {
this.request = void 0;
this._id = void 0;
this._redirectedTo = void 0;
this._id = payload.requestId;
if (redirectedFrom) redirectedFrom._redirectedTo = this;
let postDataBuffer = null;
if (payload.postData) postDataBuffer = Buffer.from(payload.postData, 'base64');
this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, payload.navigationId, payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
// "raw" headers are the same as "provisional" headers in Firefox.
this.request.setRawRequestHeaders(null);
}
_finalRequest() {
let request = this;
while (request._redirectedTo) request = request._redirectedTo;
return request;
}
}
class FFRouteImpl {
constructor(session, request) {
this._request = void 0;
this._session = void 0;
this._session = session;
this._request = request;
}
async continue(request, overrides) {
await this._session.sendMayFail('Network.resumeInterceptedRequest', {
requestId: this._request._id,
url: overrides.url,
method: overrides.method,
headers: overrides.headers,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
});
}
async fulfill(response) {
const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
requestId: this._request._id,
status: response.status,
statusText: network.STATUS_TEXTS[String(response.status)] || '',
headers: response.headers,
base64body
});
}
async abort(errorCode) {
await this._session.sendMayFail('Network.abortInterceptedRequest', {
requestId: this._request._id,
errorCode
});
}
}
function parseMultivalueHeaders(headers) {
const result = [];
for (const header of headers) {
const separator = header.name.toLowerCase() === 'set-cookie' ? '\n' : ',';
const tokens = header.value.split(separator).map(s => s.trim());
for (const token of tokens) result.push({
name: header.name,
value: token
});
}
return result;
}

View File

@@ -0,0 +1,560 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UTILITY_WORLD_NAME = exports.FFPage = void 0;
var dialog = _interopRequireWildcard(require("../dialog"));
var dom = _interopRequireWildcard(require("../dom"));
var _eventsHelper = require("../../utils/eventsHelper");
var _page = require("../page");
var _ffAccessibility = require("./ffAccessibility");
var _ffConnection = require("./ffConnection");
var _ffExecutionContext = require("./ffExecutionContext");
var _ffInput = require("./ffInput");
var _ffNetworkManager = require("./ffNetworkManager");
var _stackTrace = require("../../utils/stackTrace");
var _debugLogger = require("../../common/debugLogger");
var _manualPromise = require("../../utils/manualPromise");
var _browserContext = require("../browserContext");
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 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 UTILITY_WORLD_NAME = '__playwright_utility_world__';
exports.UTILITY_WORLD_NAME = UTILITY_WORLD_NAME;
class FFPage {
constructor(session, browserContext, opener) {
this.cspErrorsAsynchronousForInlineScipts = true;
this.rawMouse = void 0;
this.rawKeyboard = void 0;
this.rawTouchscreen = void 0;
this._session = void 0;
this._page = void 0;
this._networkManager = void 0;
this._browserContext = void 0;
this._pagePromise = new _manualPromise.ManualPromise();
this._initializedPage = null;
this._initializationFailed = false;
this._opener = void 0;
this._contextIdToContext = void 0;
this._eventListeners = void 0;
this._workers = new Map();
this._screencastId = void 0;
this._initScripts = [];
this._session = session;
this._opener = opener;
this.rawKeyboard = new _ffInput.RawKeyboardImpl(session);
this.rawMouse = new _ffInput.RawMouseImpl(session);
this.rawTouchscreen = new _ffInput.RawTouchscreenImpl(session);
this._contextIdToContext = new Map();
this._browserContext = browserContext;
this._page = new _page.Page(this, browserContext);
this.rawMouse.setPage(this._page);
this._networkManager = new _ffNetworkManager.FFNetworkManager(session, this._page);
this._page.on(_page.Page.Events.FrameDetached, frame => this._removeContextsForFrame(frame));
// TODO: remove Page.willOpenNewWindowAsynchronously from the protocol.
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.executionContextsCleared', this._onExecutionContextsCleared.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerCreated', this._onWorkerCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.workerDestroyed', this._onWorkerDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.dispatchMessageFromWorker', this._onDispatchMessageFromWorker.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.crashed', this._onCrashed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.videoRecordingStarted', this._onVideoRecordingStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketCreated', this._onWebSocketCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketClosed', this._onWebSocketClosed.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameReceived', this._onWebSocketFrameReceived.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.webSocketFrameSent', this._onWebSocketFrameSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(this._session, 'Page.screencastFrame', this._onScreencastFrame.bind(this))];
session.once(_ffConnection.FFSessionEvents.Disconnected, () => {
this._markAsError(new Error('Page closed'));
this._page._didDisconnect();
});
this._session.once('Page.ready', async () => {
await this._page.initOpener(this._opener);
if (this._initializationFailed) return;
// Note: it is important to call |reportAsNew| before resolving pageOrError promise,
// so that anyone who awaits pageOrError got a ready and reported page.
this._initializedPage = this._page;
this._page.reportAsNew();
this._pagePromise.resolve(this._page);
});
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
this.addInitScript('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
}
potentiallyUninitializedPage() {
return this._page;
}
async _markAsError(error) {
// Same error may be report twice: channer disconnected and session.send fails.
if (this._initializationFailed) return;
this._initializationFailed = true;
if (!this._initializedPage) {
await this._page.initOpener(this._opener);
this._page.reportAsNew(error);
this._pagePromise.resolve(error);
}
}
async pageOrError() {
return this._pagePromise;
}
_onWebSocketCreated(event) {
this._page._frameManager.onWebSocketCreated(webSocketId(event.frameId, event.wsid), event.requestURL);
this._page._frameManager.onWebSocketRequest(webSocketId(event.frameId, event.wsid));
}
_onWebSocketClosed(event) {
if (event.error) this._page._frameManager.webSocketError(webSocketId(event.frameId, event.wsid), event.error);
this._page._frameManager.webSocketClosed(webSocketId(event.frameId, event.wsid));
}
_onWebSocketFrameReceived(event) {
this._page._frameManager.webSocketFrameReceived(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
}
_onWebSocketFrameSent(event) {
this._page._frameManager.onWebSocketFrameSent(webSocketId(event.frameId, event.wsid), event.opcode, event.data);
}
_onExecutionContextCreated(payload) {
const {
executionContextId,
auxData
} = payload;
const frame = this._page._frameManager.frame(auxData.frameId);
if (!frame) return;
const delegate = new _ffExecutionContext.FFExecutionContext(this._session, executionContextId);
let worldName = null;
if (auxData.name === UTILITY_WORLD_NAME) worldName = 'utility';else if (!auxData.name) worldName = 'main';
const context = new dom.FrameExecutionContext(delegate, frame, worldName);
context[contextDelegateSymbol] = delegate;
if (worldName) frame._contextCreated(worldName, context);
this._contextIdToContext.set(executionContextId, context);
}
_onExecutionContextDestroyed(payload) {
const {
executionContextId
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
this._contextIdToContext.delete(executionContextId);
context.frame._contextDestroyed(context);
}
_onExecutionContextsCleared() {
for (const executionContextId of Array.from(this._contextIdToContext.keys())) this._onExecutionContextDestroyed({
executionContextId
});
}
_removeContextsForFrame(frame) {
for (const [contextId, context] of this._contextIdToContext) {
if (context.frame === frame) this._contextIdToContext.delete(contextId);
}
}
_onLinkClicked(phase) {
if (phase === 'before') this._page._frameManager.frameWillPotentiallyRequestNavigation();else this._page._frameManager.frameDidPotentiallyRequestNavigation();
}
_onNavigationStarted(params) {
this._page._frameManager.frameRequestedNavigation(params.frameId, params.navigationId);
}
_onNavigationAborted(params) {
this._page._frameManager.frameAbortedNavigation(params.frameId, params.errorText, params.navigationId);
}
_onNavigationCommitted(params) {
for (const [workerId, worker] of this._workers) {
if (worker.frameId === params.frameId) this._onWorkerDestroyed({
workerId
});
}
this._page._frameManager.frameCommittedNewDocumentNavigation(params.frameId, params.url, params.name || '', params.navigationId || '', false);
}
_onSameDocumentNavigation(params) {
this._page._frameManager.frameCommittedSameDocumentNavigation(params.frameId, params.url);
}
_onFrameAttached(params) {
this._page._frameManager.frameAttached(params.frameId, params.parentFrameId);
}
_onFrameDetached(params) {
this._page._frameManager.frameDetached(params.frameId);
}
_onEventFired(payload) {
const {
frameId,
name
} = payload;
if (name === 'load') this._page._frameManager.frameLifecycleEvent(frameId, 'load');
if (name === 'DOMContentLoaded') this._page._frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
}
_onUncaughtError(params) {
const {
name,
message
} = (0, _stackTrace.splitErrorMessage)(params.message);
const error = new Error(message);
error.stack = params.message + '\n' + params.stack.split('\n').filter(Boolean).map(a => a.replace(/([^@]*)@(.*)/, ' at $1 ($2)')).join('\n');
error.name = name;
this._page.firePageError(error);
}
_onConsole(payload) {
const {
type,
args,
executionContextId,
location
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
}
_onDialogOpened(params) {
this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, params.type, params.message, async (accept, promptText) => {
await this._session.sendMayFail('Page.handleDialog', {
dialogId: params.dialogId,
accept,
promptText
});
}, params.defaultValue));
}
async _onBindingCalled(event) {
const pageOrError = await this.pageOrError();
if (!(pageOrError instanceof Error)) {
const context = this._contextIdToContext.get(event.executionContextId);
if (context) await this._page._onBindingCalled(event.payload, context);
}
}
async _onFileChooserOpened(payload) {
const {
executionContextId,
element
} = payload;
const context = this._contextIdToContext.get(executionContextId);
if (!context) return;
const handle = context.createHandle(element).asElement();
await this._page._onFileChooserOpened(handle);
}
async _onWorkerCreated(event) {
const workerId = event.workerId;
const worker = new _page.Worker(this._page, event.url);
const workerSession = new _ffConnection.FFSession(this._session._connection, workerId, message => {
this._session.send('Page.sendMessageToWorker', {
frameId: event.frameId,
workerId: workerId,
message: JSON.stringify(message)
}).catch(e => {
workerSession.dispatchMessage({
id: message.id,
method: '',
params: {},
error: {
message: e.message,
data: undefined
}
});
});
});
this._workers.set(workerId, {
session: workerSession,
frameId: event.frameId
});
this._page._addWorker(workerId, worker);
workerSession.once('Runtime.executionContextCreated', event => {
worker._createExecutionContext(new _ffExecutionContext.FFExecutionContext(workerSession, event.executionContextId));
});
workerSession.on('Runtime.console', event => {
const {
type,
args,
location
} = event;
const context = worker._existingExecutionContext;
this._page._addConsoleMessage(type, args.map(arg => context.createHandle(arg)), location);
});
// Note: we receive worker exceptions directly from the page.
}
_onWorkerDestroyed(event) {
const workerId = event.workerId;
const worker = this._workers.get(workerId);
if (!worker) return;
worker.session.dispose();
this._workers.delete(workerId);
this._page._removeWorker(workerId);
}
async _onDispatchMessageFromWorker(event) {
const worker = this._workers.get(event.workerId);
if (!worker) return;
worker.session.dispatchMessage(JSON.parse(event.message));
}
async _onCrashed(event) {
this._session.markAsCrashed();
this._page._didCrash();
}
_onVideoRecordingStarted(event) {
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError());
}
async exposeBinding(binding) {
await this._session.send('Page.addBinding', {
name: binding.name,
script: binding.source
});
}
async removeExposedBindings() {
// TODO: implement me.
}
didClose() {
this._session.dispose();
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
this._networkManager.dispose();
this._page._didClose();
}
async navigateFrame(frame, url, referer) {
const response = await this._session.send('Page.navigate', {
url,
referer,
frameId: frame._id
});
return {
newDocumentId: response.navigationId || undefined
};
}
async updateExtraHTTPHeaders() {
await this._session.send('Network.setExtraHTTPHeaders', {
headers: this._page.extraHTTPHeaders() || []
});
}
async updateEmulatedViewportSize() {
const viewportSize = this._page.viewportSize();
await this._session.send('Page.setViewportSize', {
viewportSize
});
}
async bringToFront() {
await this._session.send('Page.bringToFront', {});
}
async updateEmulateMedia() {
const emulatedMedia = this._page.emulatedMedia();
const colorScheme = emulatedMedia.colorScheme === 'no-override' ? undefined : emulatedMedia.colorScheme;
const reducedMotion = emulatedMedia.reducedMotion === 'no-override' ? undefined : emulatedMedia.reducedMotion;
const forcedColors = emulatedMedia.forcedColors === 'no-override' ? undefined : emulatedMedia.forcedColors;
await this._session.send('Page.setEmulatedMedia', {
// Empty string means reset.
type: emulatedMedia.media === 'no-override' ? '' : emulatedMedia.media,
colorScheme,
reducedMotion,
forcedColors
});
}
async updateRequestInterception() {
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
}
async updateFileChooserInterception() {
const enabled = this._page.fileChooserIntercepted();
await this._session.send('Page.setInterceptFileChooserDialog', {
enabled
}).catch(() => {}); // target can be closed.
}
async reload() {
await this._session.send('Page.reload');
}
async goBack() {
const {
success
} = await this._session.send('Page.goBack', {
frameId: this._page.mainFrame()._id
});
return success;
}
async goForward() {
const {
success
} = await this._session.send('Page.goForward', {
frameId: this._page.mainFrame()._id
});
return success;
}
async addInitScript(script, worldName) {
this._initScripts.push({
script,
worldName
});
await this._session.send('Page.setInitScripts', {
scripts: this._initScripts
});
}
async removeInitScripts() {
this._initScripts = [];
await this._session.send('Page.setInitScripts', {
scripts: []
});
}
async closePage(runBeforeUnload) {
await this._session.send('Page.close', {
runBeforeUnload
});
}
async setBackgroundColor(color) {
if (color) throw new Error('Not implemented');
}
async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) {
if (!documentRect) {
const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({
x: window.scrollX,
y: window.scrollY
}));
documentRect = {
x: viewportRect.x + scrollOffset.x,
y: viewportRect.y + scrollOffset.y,
width: viewportRect.width,
height: viewportRect.height
};
}
progress.throwIfAborted();
const {
data
} = await this._session.send('Page.screenshot', {
mimeType: 'image/' + format,
clip: documentRect,
quality,
omitDeviceScaleFactor: scale === 'css'
});
return Buffer.from(data, 'base64');
}
async getContentFrame(handle) {
const {
contentFrameId
} = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
if (!contentFrameId) return null;
return this._page._frameManager.frame(contentFrameId);
}
async getOwnerFrame(handle) {
const {
ownerFrameId
} = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
return ownerFrameId || null;
}
isElementHandle(remoteObject) {
return remoteObject.subtype === 'node';
}
async getBoundingBox(handle) {
const quads = await this.getContentQuads(handle);
if (!quads || !quads.length) return null;
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (const quad of quads) {
for (const point of quad) {
minX = Math.min(minX, point.x);
maxX = Math.max(maxX, point.x);
minY = Math.min(minY, point.y);
maxY = Math.max(maxY, point.y);
}
}
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
async scrollRectIntoViewIfNeeded(handle, rect) {
return await this._session.send('Page.scrollIntoViewIfNeeded', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
rect
}).then(() => 'done').catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document')) return 'error:notconnected';
if (e instanceof Error && e.message.includes('Node does not have a layout object')) return 'error:notvisible';
throw e;
});
}
async setScreencastOptions(options) {
if (options) {
const {
screencastId
} = await this._session.send('Page.startScreencast', options);
this._screencastId = screencastId;
} else {
await this._session.send('Page.stopScreencast');
}
}
_onScreencastFrame(event) {
if (!this._screencastId) return;
const screencastId = this._screencastId;
this._page.throttleScreencastFrameAck(() => {
this._session.send('Page.screencastFrameAck', {
screencastId
}).catch(e => _debugLogger.debugLogger.log('error', e));
});
const buffer = Buffer.from(event.data, 'base64');
this._page.emit(_page.Page.Events.ScreencastFrame, {
buffer,
width: event.deviceWidth,
height: event.deviceHeight
});
}
rafCountForStablePosition() {
return 1;
}
async getContentQuads(handle) {
const result = await this._session.sendMayFail('Page.getContentQuads', {
frameId: handle._context.frame._id,
objectId: handle._objectId
});
if (!result) return null;
return result.quads.map(quad => [quad.p1, quad.p2, quad.p3, quad.p4]);
}
async setInputFiles(handle, files) {
await handle.evaluateInUtility(([injected, node, files]) => injected.setInputFiles(node, files), files);
}
async setInputFilePaths(handle, files) {
await Promise.all([this._session.send('Page.setFileInputFiles', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
files
}), handle.dispatchEvent('input'), handle.dispatchEvent('change')]);
}
async adoptElementHandle(handle, to) {
const result = await this._session.send('Page.adoptNode', {
frameId: handle._context.frame._id,
objectId: handle._objectId,
executionContextId: to[contextDelegateSymbol]._executionContextId
});
if (!result.remoteObject) throw new Error(dom.kUnableToAdoptErrorMessage);
return to.createHandle(result.remoteObject);
}
async getAccessibilityTree(needle) {
return (0, _ffAccessibility.getAccessibilityTree)(this._session, needle);
}
async inputActionEpilogue() {}
async resetForReuse() {
// Firefox sometimes keeps the last mouse position in the page,
// which affects things like hovered state.
// See https://github.com/microsoft/playwright/issues/22432.
// Move mouse to (-1, -1) to avoid anything being hovered.
await this.rawMouse.move(-1, -1, 'none', new Set(), new Set(), false);
}
async getFrameElement(frame) {
const parent = frame.parentFrame();
if (!parent) throw new Error('Frame has been detached.');
const context = await parent._mainContext();
const result = await this._session.send('Page.adoptNode', {
frameId: frame._id,
executionContextId: context[contextDelegateSymbol]._executionContextId
});
if (!result.remoteObject) throw new Error('Frame has been detached.');
return context.createHandle(result.remoteObject);
}
}
exports.FFPage = FFPage;
function webSocketId(frameId, wsid) {
return `${frameId}---${wsid}`;
}
const contextDelegateSymbol = Symbol('delegate');

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Firefox = void 0;
var os = _interopRequireWildcard(require("os"));
var _path = _interopRequireDefault(require("path"));
var _ffBrowser = require("./ffBrowser");
var _ffConnection = require("./ffConnection");
var _browserType = require("../browserType");
var _stackTrace = require("../../utils/stackTrace");
var _utils = require("../../utils");
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 Firefox extends _browserType.BrowserType {
constructor(parent) {
super(parent, 'firefox');
}
_connectToTransport(transport, options) {
return _ffBrowser.FFBrowser.connect(this.attribution.playwright, transport, options);
}
_rewriteStartupError(error) {
if (error.message.includes('no DISPLAY environment variable specified')) return (0, _stackTrace.rewriteErrorMessage)(error, '\n' + (0, _utils.wrapInASCIIBox)(_browserType.kNoXServerRunningError, 1));
return error;
}
_amendEnvironment(env, userDataDir, executable, browserArguments) {
if (!_path.default.isAbsolute(os.homedir())) throw new Error(`Cannot launch Firefox with relative home directory. Did you set ${os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'} to a relative path?`);
if (os.platform() === 'linux') {
// Always remove SNAP_NAME and SNAP_INSTANCE_NAME env variables since they
// confuse Firefox: in our case, builds never come from SNAP.
// See https://github.com/microsoft/playwright/issues/20555
return {
...env,
SNAP_NAME: undefined,
SNAP_INSTANCE_NAME: undefined
};
}
return env;
}
_attemptToGracefullyCloseBrowser(transport) {
const message = {
method: 'Browser.close',
params: {},
id: _ffConnection.kBrowserCloseMessageId
};
transport.send(message);
}
_defaultArgs(options, isPersistent, userDataDir) {
const {
args = [],
headless
} = options;
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
if (userDataDirArg) throw new Error('Pass userDataDir parameter to `browserType.launchPersistentContext(userDataDir, ...)` instead of specifying --profile argument');
if (args.find(arg => arg.startsWith('-juggler'))) throw new Error('Use the port parameter instead of -juggler argument');
const firefoxArguments = ['-no-remote'];
if (headless) {
firefoxArguments.push('-headless');
} else {
firefoxArguments.push('-wait-for-browser');
firefoxArguments.push('-foreground');
}
firefoxArguments.push(`-profile`, userDataDir);
firefoxArguments.push('-juggler-pipe');
firefoxArguments.push(...args);
if (isPersistent) firefoxArguments.push('about:blank');else firefoxArguments.push('-silent');
return firefoxArguments;
}
}
exports.Firefox = Firefox;

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MultipartFormData = void 0;
var _utilsBundle = require("../utilsBundle");
/**
* 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 MultipartFormData {
constructor() {
this._boundary = void 0;
this._chunks = [];
this._boundary = generateUniqueBoundaryString();
}
contentTypeHeader() {
return `multipart/form-data; boundary=${this._boundary}`;
}
addField(name, value) {
this._beginMultiPartHeader(name);
this._finishMultiPartHeader();
this._chunks.push(Buffer.from(value));
this._finishMultiPartField();
}
addFileField(name, value) {
this._beginMultiPartHeader(name);
this._chunks.push(Buffer.from(`; filename="${value.name}"`));
this._chunks.push(Buffer.from(`\r\ncontent-type: ${value.mimeType || _utilsBundle.mime.getType(value.name) || 'application/octet-stream'}`));
this._finishMultiPartHeader();
this._chunks.push(value.buffer);
this._finishMultiPartField();
}
finish() {
this._addBoundary(true);
return Buffer.concat(this._chunks);
}
_beginMultiPartHeader(name) {
this._addBoundary();
this._chunks.push(Buffer.from(`content-disposition: form-data; name="${name}"`));
}
_finishMultiPartHeader() {
this._chunks.push(Buffer.from(`\r\n\r\n`));
}
_finishMultiPartField() {
this._chunks.push(Buffer.from(`\r\n`));
}
_addBoundary(isLastBoundary) {
this._chunks.push(Buffer.from('--' + this._boundary));
if (isLastBoundary) this._chunks.push(Buffer.from('--'));
this._chunks.push(Buffer.from('\r\n'));
}
}
exports.MultipartFormData = MultipartFormData;
const alphaNumericEncodingMap = [0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42];
// See generateUniqueBoundaryString() in WebKit
function generateUniqueBoundaryString() {
const charCodes = [];
for (let i = 0; i < 16; i++) charCodes.push(alphaNumericEncodingMap[Math.floor(Math.random() * alphaNumericEncodingMap.length)]);
return '----WebKitFormBoundary' + String.fromCharCode(...charCodes);
}

View File

@@ -0,0 +1,162 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FrameSelectors = void 0;
var _selectorParser = require("../utils/isomorphic/selectorParser");
/**
* 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 FrameSelectors {
constructor(frame) {
this.frame = void 0;
this.frame = frame;
}
_parseSelector(selector, options) {
const strict = typeof (options === null || options === void 0 ? void 0 : options.strict) === 'boolean' ? options.strict : !!this.frame._page.context()._options.strictSelectors;
return this.frame._page.context().selectors().parseSelector(selector, strict);
}
async query(selector, options, scope) {
const resolved = await this.resolveInjectedForSelector(selector, options, scope);
// Be careful, |this.frame| can be different from |resolved.frame|.
if (!resolved) return null;
const handle = await resolved.injected.evaluateHandle((injected, {
info,
scope
}) => {
return injected.querySelector(info.parsed, scope || document, info.strict);
}, {
info: resolved.info,
scope: resolved.scope
});
const elementHandle = handle.asElement();
if (!elementHandle) {
handle.dispose();
return null;
}
return adoptIfNeeded(elementHandle, await resolved.frame._mainContext());
}
async queryArrayInMainWorld(selector, scope) {
const resolved = await this.resolveInjectedForSelector(selector, {
mainWorld: true
}, scope);
// Be careful, |this.frame| can be different from |resolved.frame|.
if (!resolved) throw new Error(`Error: failed to find frame for selector "${selector}"`);
return await resolved.injected.evaluateHandle((injected, {
info,
scope
}) => {
return injected.querySelectorAll(info.parsed, scope || document);
}, {
info: resolved.info,
scope: resolved.scope
});
}
async queryCount(selector) {
const resolved = await this.resolveInjectedForSelector(selector);
// Be careful, |this.frame| can be different from |resolved.frame|.
if (!resolved) throw new Error(`Error: failed to find frame for selector "${selector}"`);
return await resolved.injected.evaluate((injected, {
info
}) => {
return injected.querySelectorAll(info.parsed, document).length;
}, {
info: resolved.info
});
}
async queryAll(selector, scope) {
const resolved = await this.resolveInjectedForSelector(selector, {}, scope);
// Be careful, |this.frame| can be different from |resolved.frame|.
if (!resolved) return [];
const arrayHandle = await resolved.injected.evaluateHandle((injected, {
info,
scope
}) => {
return injected.querySelectorAll(info.parsed, scope || document);
}, {
info: resolved.info,
scope: resolved.scope
});
const properties = await arrayHandle.getProperties();
arrayHandle.dispose();
// Note: adopting elements one by one may be slow. If we encounter the issue here,
// we might introduce 'useMainContext' option or similar to speed things up.
const targetContext = await resolved.frame._mainContext();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle) result.push(adoptIfNeeded(elementHandle, targetContext));else property.dispose();
}
return Promise.all(result);
}
async resolveFrameForSelector(selector, options = {}, scope) {
let frame = this.frame;
const frameChunks = (0, _selectorParser.splitSelectorByFrame)(selector);
for (let i = 0; i < frameChunks.length - 1; ++i) {
const info = this._parseSelector(frameChunks[i], options);
const context = await frame._context(info.world);
const injectedScript = await context.injectedScript();
const handle = await injectedScript.evaluateHandle((injected, {
info,
scope,
selectorString
}) => {
const element = injected.querySelector(info.parsed, scope || document, info.strict);
if (element && element.nodeName !== 'IFRAME' && element.nodeName !== 'FRAME') throw injected.createStacklessError(`Selector "${selectorString}" resolved to ${injected.previewNode(element)}, <iframe> was expected`);
return element;
}, {
info,
scope: i === 0 ? scope : undefined,
selectorString: (0, _selectorParser.stringifySelector)(info.parsed)
});
const element = handle.asElement();
if (!element) return null;
const maybeFrame = await frame._page._delegate.getContentFrame(element);
element.dispose();
if (!maybeFrame) return null;
frame = maybeFrame;
}
// If we end up in the different frame, we should start from the frame root, so throw away the scope.
if (frame !== this.frame) scope = undefined;
return {
frame,
info: frame.selectors._parseSelector(frameChunks[frameChunks.length - 1], options),
scope
};
}
async resolveInjectedForSelector(selector, options, scope) {
const resolved = await this.resolveFrameForSelector(selector, options, scope);
// Be careful, |this.frame| can be different from |resolved.frame|.
if (!resolved) return;
const context = await resolved.frame._context(options !== null && options !== void 0 && options.mainWorld ? 'main' : resolved.info.world);
const injected = await context.injectedScript();
return {
injected,
info: resolved.info,
frame: resolved.frame,
scope: resolved.scope
};
}
}
exports.FrameSelectors = FrameSelectors;
async function adoptIfNeeded(handle, context) {
if (handle._context === context) return handle;
const adopted = handle._page._delegate.adoptElementHandle(handle, context);
handle.dispose();
return adopted;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HarRecorder = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _artifact = require("../artifact");
var _harTracer = require("./harTracer");
var _zipBundle = require("../../zipBundle");
var _manualPromise = require("../../utils/manualPromise");
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 HarRecorder {
constructor(context, page, options) {
this._artifact = void 0;
this._isFlushed = false;
this._tracer = void 0;
this._entries = [];
this._zipFile = null;
this._writtenZipEntries = new Set();
this._artifact = new _artifact.Artifact(context, _path.default.join(context._browser.options.artifactsDir, `${(0, _utils.createGuid)()}.har`));
const urlFilterRe = options.urlRegexSource !== undefined && options.urlRegexFlags !== undefined ? new RegExp(options.urlRegexSource, options.urlRegexFlags) : undefined;
const expectsZip = options.path.endsWith('.zip');
const content = options.content || (expectsZip ? 'attach' : 'embed');
this._tracer = new _harTracer.HarTracer(context, page, this, {
content,
slimMode: options.mode === 'minimal',
includeTraceInfo: false,
recordRequestOverrides: true,
waitForContentOnStop: true,
skipScripts: false,
urlFilter: urlFilterRe !== null && urlFilterRe !== void 0 ? urlFilterRe : options.urlGlob
});
this._zipFile = content === 'attach' || expectsZip ? new _zipBundle.yazl.ZipFile() : null;
this._tracer.start();
}
onEntryStarted(entry) {
this._entries.push(entry);
}
onEntryFinished(entry) {}
onContentBlob(sha1, buffer) {
if (!this._zipFile || this._writtenZipEntries.has(sha1)) return;
this._writtenZipEntries.add(sha1);
this._zipFile.addBuffer(buffer, sha1);
}
async flush() {
if (this._isFlushed) return;
this._isFlushed = true;
await this._tracer.flush();
const log = this._tracer.stop();
log.entries = this._entries;
const harFileContent = jsonStringify({
log
});
if (this._zipFile) {
const result = new _manualPromise.ManualPromise();
this._zipFile.on('error', error => result.reject(error));
this._zipFile.addBuffer(Buffer.from(harFileContent, 'utf-8'), 'har.har');
this._zipFile.end();
this._zipFile.outputStream.pipe(_fs.default.createWriteStream(this._artifact.localPath())).on('close', () => {
result.resolve();
});
await result;
} else {
await _fs.default.promises.writeFile(this._artifact.localPath(), harFileContent);
}
}
async export() {
await this.flush();
this._artifact.reportFinished();
return this._artifact;
}
}
exports.HarRecorder = HarRecorder;
function jsonStringify(object) {
const tokens = [];
innerJsonStringify(object, tokens, '', false, undefined);
return tokens.join('');
}
function innerJsonStringify(object, tokens, indent, flat, parentKey) {
if (typeof object !== 'object' || object === null) {
tokens.push(JSON.stringify(object));
return;
}
const isArray = Array.isArray(object);
if (!isArray && object.constructor.name !== 'Object') {
tokens.push(JSON.stringify(object));
return;
}
const entries = isArray ? object : Object.entries(object).filter(e => e[1] !== undefined);
if (!entries.length) {
tokens.push(isArray ? `[]` : `{}`);
return;
}
const childIndent = `${indent} `;
let brackets;
if (isArray) brackets = flat ? {
open: '[',
close: ']'
} : {
open: `[\n${childIndent}`,
close: `\n${indent}]`
};else brackets = flat ? {
open: '{ ',
close: ' }'
} : {
open: `{\n${childIndent}`,
close: `\n${indent}}`
};
tokens.push(brackets.open);
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (i) tokens.push(flat ? `, ` : `,\n${childIndent}`);
if (!isArray) tokens.push(`${JSON.stringify(entry[0])}: `);
const key = isArray ? undefined : entry[0];
const flatten = flat || key === 'timings' || parentKey === 'headers';
innerJsonStringify(isArray ? entry : entry[1], tokens, childIndent, flatten, key);
}
tokens.push(brackets.close);
}

View File

@@ -0,0 +1,526 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.HarTracer = void 0;
var _browserContext = require("../browserContext");
var _fetch = require("../fetch");
var _helper = require("../helper");
var network = _interopRequireWildcard(require("../network"));
var _utils = require("../../utils");
var _eventsHelper = require("../../utils/eventsHelper");
var _utilsBundle = require("../../utilsBundle");
var _manualPromise = require("../../utils/manualPromise");
var _userAgent = require("../../utils/userAgent");
var _network2 = require("../../utils/network");
var _frames = require("../frames");
var _mimeType = require("../../utils/mimeType");
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.
*/
const FALLBACK_HTTP_VERSION = 'HTTP/1.1';
class HarTracer {
constructor(context, page, delegate, options) {
this._context = void 0;
this._barrierPromises = new Set();
this._delegate = void 0;
this._options = void 0;
this._pageEntries = new Map();
this._eventListeners = [];
this._started = false;
this._entrySymbol = void 0;
this._baseURL = void 0;
this._page = void 0;
this._context = context;
this._page = page;
this._delegate = delegate;
this._options = options;
if (options.slimMode) {
options.omitSecurityDetails = true;
options.omitCookies = true;
options.omitTiming = true;
options.omitServerIP = true;
options.omitSizes = true;
options.omitPages = true;
}
this._entrySymbol = Symbol('requestHarEntry');
this._baseURL = context instanceof _fetch.APIRequestContext ? context._defaultOptions().baseURL : context._options.baseURL;
}
start() {
if (this._started) return;
this._started = true;
const apiRequest = this._context instanceof _fetch.APIRequestContext ? this._context : this._context.fetchRequest;
this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(apiRequest, _fetch.APIRequestContext.Events.Request, event => this._onAPIRequest(event)), _eventsHelper.eventsHelper.addEventListener(apiRequest, _fetch.APIRequestContext.Events.RequestFinished, event => this._onAPIRequestFinished(event))];
if (this._context instanceof _browserContext.BrowserContext) {
this._eventListeners.push(_eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.Page, page => this._createPageEntryIfNeeded(page)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.Request, request => this._onRequest(request)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.RequestFinished, ({
request,
response
}) => this._onRequestFinished(request, response).catch(() => {})), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.Response, response => this._onResponse(response)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.RequestAborted, request => this._onRequestAborted(request)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.RequestFulfilled, request => this._onRequestFulfilled(request)), _eventsHelper.eventsHelper.addEventListener(this._context, _browserContext.BrowserContext.Events.RequestContinued, request => this._onRequestContinued(request)));
}
}
_shouldIncludeEntryWithUrl(urlString) {
return !this._options.urlFilter || (0, _network2.urlMatches)(this._baseURL, urlString, this._options.urlFilter);
}
_entryForRequest(request) {
return request[this._entrySymbol];
}
_createPageEntryIfNeeded(page) {
if (!page) return;
if (this._options.omitPages) return;
if (this._page && page !== this._page) return;
let pageEntry = this._pageEntries.get(page);
if (!pageEntry) {
pageEntry = {
startedDateTime: new Date(),
id: page.guid,
title: '',
pageTimings: this._options.omitTiming ? {} : {
onContentLoad: -1,
onLoad: -1
}
};
page.mainFrame().on(_frames.Frame.Events.AddLifecycle, event => {
if (event === 'load') this._onLoad(page, pageEntry);
if (event === 'domcontentloaded') this._onDOMContentLoaded(page, pageEntry);
});
this._pageEntries.set(page, pageEntry);
}
return pageEntry;
}
_onDOMContentLoaded(page, pageEntry) {
const promise = page.mainFrame().evaluateExpression(String(() => {
return {
title: document.title,
domContentLoaded: performance.timing.domContentLoadedEventStart
};
}), {
isFunction: true,
world: 'utility'
}).then(result => {
pageEntry.title = result.title;
if (!this._options.omitTiming) pageEntry.pageTimings.onContentLoad = result.domContentLoaded;
}).catch(() => {});
this._addBarrier(page, promise);
}
_onLoad(page, pageEntry) {
const promise = page.mainFrame().evaluateExpression(String(() => {
return {
title: document.title,
loaded: performance.timing.loadEventStart
};
}), {
isFunction: true,
world: 'utility'
}).then(result => {
pageEntry.title = result.title;
if (!this._options.omitTiming) pageEntry.pageTimings.onLoad = result.loaded;
}).catch(() => {});
this._addBarrier(page, promise);
}
_addBarrier(target, promise) {
if (!target) return null;
if (!this._options.waitForContentOnStop) return;
const race = Promise.race([new Promise(f => target.on('close', () => {
this._barrierPromises.delete(race);
f();
})), promise]);
this._barrierPromises.add(race);
race.then(() => this._barrierPromises.delete(race));
}
_onAPIRequest(event) {
var _event$postData;
if (!this._shouldIncludeEntryWithUrl(event.url.toString())) return;
const harEntry = createHarEntry(event.method, event.url, undefined, this._options);
harEntry._apiRequest = true;
if (!this._options.omitCookies) harEntry.request.cookies = event.cookies;
harEntry.request.headers = Object.entries(event.headers).map(([name, value]) => ({
name,
value
}));
harEntry.request.postData = this._postDataForBuffer(event.postData || null, event.headers['content-type'], this._options.content);
if (!this._options.omitSizes) harEntry.request.bodySize = ((_event$postData = event.postData) === null || _event$postData === void 0 ? void 0 : _event$postData.length) || 0;
event[this._entrySymbol] = harEntry;
if (this._started) this._delegate.onEntryStarted(harEntry);
}
_onAPIRequestFinished(event) {
const harEntry = this._entryForRequest(event.requestEvent);
if (!harEntry) return;
harEntry.response.status = event.statusCode;
harEntry.response.statusText = event.statusMessage;
harEntry.response.httpVersion = event.httpVersion;
harEntry.response.redirectURL = event.headers.location || '';
for (let i = 0; i < event.rawHeaders.length; i += 2) {
harEntry.response.headers.push({
name: event.rawHeaders[i],
value: event.rawHeaders[i + 1]
});
}
harEntry.response.cookies = this._options.omitCookies ? [] : event.cookies.map(c => {
return {
...c,
expires: c.expires === -1 ? undefined : new Date(c.expires)
};
});
const content = harEntry.response.content;
const contentType = event.headers['content-type'];
if (contentType) content.mimeType = contentType;
this._storeResponseContent(event.body, content, 'other');
if (this._started) this._delegate.onEntryFinished(harEntry);
}
_onRequest(request) {
var _request$frame, _request$frame2;
if (!this._shouldIncludeEntryWithUrl(request.url())) return;
const page = (_request$frame = request.frame()) === null || _request$frame === void 0 ? void 0 : _request$frame._page;
if (this._page && page !== this._page) return;
const url = network.parsedURL(request.url());
if (!url) return;
const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(request.method(), url, (_request$frame2 = request.frame()) === null || _request$frame2 === void 0 ? void 0 : _request$frame2.guid, this._options);
if (pageEntry) harEntry.pageref = pageEntry.id;
this._recordRequestHeadersAndCookies(harEntry, request.headers());
harEntry.request.postData = this._postDataForRequest(request, this._options.content);
if (!this._options.omitSizes) harEntry.request.bodySize = request.bodySize();
if (request.redirectedFrom()) {
const fromEntry = this._entryForRequest(request.redirectedFrom());
if (fromEntry) fromEntry.response.redirectURL = request.url();
}
request[this._entrySymbol] = harEntry;
(0, _utils.assert)(this._started);
this._delegate.onEntryStarted(harEntry);
}
_recordRequestHeadersAndCookies(harEntry, headers) {
if (!this._options.omitCookies) {
harEntry.request.cookies = [];
for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie')) harEntry.request.cookies.push(...header.value.split(';').map(parseCookie));
}
harEntry.request.headers = headers;
}
_recordRequestOverrides(harEntry, request) {
if (!request._hasOverrides() || !this._options.recordRequestOverrides) return;
harEntry.request.method = request.method();
harEntry.request.url = request.url();
harEntry.request.postData = this._postDataForRequest(request, this._options.content);
this._recordRequestHeadersAndCookies(harEntry, request.headers());
}
async _onRequestFinished(request, response) {
var _request$frame3;
if (!response) return;
const harEntry = this._entryForRequest(request);
if (!harEntry) return;
const page = (_request$frame3 = request.frame()) === null || _request$frame3 === void 0 ? void 0 : _request$frame3._page;
// In WebKit security details and server ip are reported in Network.loadingFinished, so we populate
// it here to not hang in case of long chunked responses, see https://github.com/microsoft/playwright/issues/21182.
if (!this._options.omitServerIP) {
this._addBarrier(page || request.serviceWorker(), response.serverAddr().then(server => {
if (server !== null && server !== void 0 && server.ipAddress) harEntry.serverIPAddress = server.ipAddress;
if (server !== null && server !== void 0 && server.port) harEntry._serverPort = server.port;
}));
}
if (!this._options.omitSecurityDetails) {
this._addBarrier(page || request.serviceWorker(), response.securityDetails().then(details => {
if (details) harEntry._securityDetails = details;
}));
}
const httpVersion = response.httpVersion();
harEntry.request.httpVersion = httpVersion;
harEntry.response.httpVersion = httpVersion;
const compressionCalculationBarrier = this._options.omitSizes ? undefined : {
_encodedBodySize: -1,
_decodedBodySize: -1,
barrier: new _manualPromise.ManualPromise(),
_check: function () {
if (this._encodedBodySize !== -1 && this._decodedBodySize !== -1) {
harEntry.response.content.compression = Math.max(0, this._decodedBodySize - this._encodedBodySize);
this.barrier.resolve();
}
},
setEncodedBodySize: function (encodedBodySize) {
this._encodedBodySize = encodedBodySize;
this._check();
},
setDecodedBodySize: function (decodedBodySize) {
this._decodedBodySize = decodedBodySize;
this._check();
}
};
if (compressionCalculationBarrier) this._addBarrier(page || request.serviceWorker(), compressionCalculationBarrier.barrier);
const promise = response.body().then(buffer => {
if (this._options.skipScripts && request.resourceType() === 'script') {
compressionCalculationBarrier === null || compressionCalculationBarrier === void 0 ? void 0 : compressionCalculationBarrier.setDecodedBodySize(0);
return;
}
const content = harEntry.response.content;
compressionCalculationBarrier === null || compressionCalculationBarrier === void 0 ? void 0 : compressionCalculationBarrier.setDecodedBodySize(buffer.length);
this._storeResponseContent(buffer, content, request.resourceType());
}).catch(() => {
compressionCalculationBarrier === null || compressionCalculationBarrier === void 0 ? void 0 : compressionCalculationBarrier.setDecodedBodySize(0);
}).then(() => {
if (this._started) this._delegate.onEntryFinished(harEntry);
});
this._addBarrier(page || request.serviceWorker(), promise);
// Respose end timing is only available after the response event was received.
const timing = response.timing();
harEntry.timings.receive = response.request()._responseEndTiming !== -1 ? _helper.helper.millisToRoundishMillis(response.request()._responseEndTiming - timing.responseStart) : -1;
this._computeHarEntryTotalTime(harEntry);
if (!this._options.omitSizes) {
this._addBarrier(page || request.serviceWorker(), response.sizes().then(sizes => {
harEntry.response.bodySize = sizes.responseBodySize;
harEntry.response.headersSize = sizes.responseHeadersSize;
harEntry.response._transferSize = sizes.transferSize;
harEntry.request.headersSize = sizes.requestHeadersSize;
compressionCalculationBarrier === null || compressionCalculationBarrier === void 0 ? void 0 : compressionCalculationBarrier.setEncodedBodySize(sizes.responseBodySize);
}));
}
}
async _onRequestFailed(request) {
const harEntry = this._entryForRequest(request);
if (!harEntry) return;
if (request._failureText !== null) harEntry.response._failureText = request._failureText;
this._recordRequestOverrides(harEntry, request);
if (this._started) this._delegate.onEntryFinished(harEntry);
}
_onRequestAborted(request) {
const harEntry = this._entryForRequest(request);
if (harEntry) harEntry._wasAborted = true;
}
_onRequestFulfilled(request) {
const harEntry = this._entryForRequest(request);
if (harEntry) harEntry._wasFulfilled = true;
}
_onRequestContinued(request) {
const harEntry = this._entryForRequest(request);
if (harEntry) harEntry._wasContinued = true;
}
_storeResponseContent(buffer, content, resourceType) {
if (!buffer) {
content.size = 0;
return;
}
if (!this._options.omitSizes) content.size = buffer.length;
if (this._options.content === 'embed') {
// Sometimes, we can receive a font/media file with textual mime type. Browser
// still interprets them correctly, but the 'content-type' header is obviously wrong.
if ((0, _mimeType.isTextualMimeType)(content.mimeType) && resourceType !== 'font') {
content.text = buffer.toString();
} else {
content.text = buffer.toString('base64');
content.encoding = 'base64';
}
} else if (this._options.content === 'attach') {
const sha1 = (0, _utils.calculateSha1)(buffer) + '.' + (_utilsBundle.mime.getExtension(content.mimeType) || 'dat');
if (this._options.includeTraceInfo) content._sha1 = sha1;else content._file = sha1;
if (this._started) this._delegate.onContentBlob(sha1, buffer);
}
}
_onResponse(response) {
var _response$frame;
const harEntry = this._entryForRequest(response.request());
if (!harEntry) return;
const page = (_response$frame = response.frame()) === null || _response$frame === void 0 ? void 0 : _response$frame._page;
const pageEntry = this._createPageEntryIfNeeded(page);
const request = response.request();
harEntry.response = {
status: response.status(),
statusText: response.statusText(),
httpVersion: response.httpVersion(),
// These are bad values that will be overwritten bellow.
cookies: [],
headers: [],
content: {
size: -1,
mimeType: 'x-unknown'
},
headersSize: -1,
bodySize: -1,
redirectURL: '',
_transferSize: this._options.omitSizes ? undefined : -1
};
if (!this._options.omitTiming) {
const timing = response.timing();
if (pageEntry && pageEntry.startedDateTime.valueOf() > timing.startTime) pageEntry.startedDateTime = new Date(timing.startTime);
const dns = timing.domainLookupEnd !== -1 ? _helper.helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1;
const connect = timing.connectEnd !== -1 ? _helper.helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1;
const ssl = timing.connectEnd !== -1 ? _helper.helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1;
const wait = timing.responseStart !== -1 ? _helper.helper.millisToRoundishMillis(timing.responseStart - timing.requestStart) : -1;
const receive = -1;
harEntry.timings = {
dns,
connect,
ssl,
send: 0,
wait,
receive
};
this._computeHarEntryTotalTime(harEntry);
}
this._recordRequestOverrides(harEntry, request);
this._addBarrier(page || request.serviceWorker(), request.rawRequestHeaders().then(headers => {
this._recordRequestHeadersAndCookies(harEntry, headers);
}));
// Record available headers including redirect location in case the tracing is stopped before
// reponse extra info is received (in Chromium).
this._recordResponseHeaders(harEntry, response.headers());
this._addBarrier(page || request.serviceWorker(), response.rawResponseHeaders().then(headers => {
this._recordResponseHeaders(harEntry, headers);
}));
}
_recordResponseHeaders(harEntry, headers) {
if (!this._options.omitCookies) {
harEntry.response.cookies = headers.filter(header => header.name.toLowerCase() === 'set-cookie').map(header => parseCookie(header.value));
}
harEntry.response.headers = headers;
const contentType = headers.find(header => header.name.toLowerCase() === 'content-type');
if (contentType) harEntry.response.content.mimeType = contentType.value;
}
_computeHarEntryTotalTime(harEntry) {
harEntry.time = [harEntry.timings.dns, harEntry.timings.connect, harEntry.timings.ssl, harEntry.timings.wait, harEntry.timings.receive].reduce((pre, cur) => (cur || -1) > 0 ? cur + pre : pre, 0);
}
async flush() {
await Promise.all(this._barrierPromises);
}
stop() {
this._started = false;
_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
this._barrierPromises.clear();
const context = this._context instanceof _browserContext.BrowserContext ? this._context : undefined;
const log = {
version: '1.2',
creator: {
name: 'Playwright',
version: (0, _userAgent.getPlaywrightVersion)()
},
browser: {
name: (context === null || context === void 0 ? void 0 : context._browser.options.name) || '',
version: (context === null || context === void 0 ? void 0 : context._browser.version()) || ''
},
pages: this._pageEntries.size ? Array.from(this._pageEntries.values()) : undefined,
entries: []
};
if (!this._options.omitTiming) {
for (const pageEntry of log.pages || []) {
if (typeof pageEntry.pageTimings.onContentLoad === 'number' && pageEntry.pageTimings.onContentLoad >= 0) pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf();else pageEntry.pageTimings.onContentLoad = -1;
if (typeof pageEntry.pageTimings.onLoad === 'number' && pageEntry.pageTimings.onLoad >= 0) pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf();else pageEntry.pageTimings.onLoad = -1;
}
}
this._pageEntries.clear();
return log;
}
_postDataForRequest(request, content) {
const postData = request.postDataBuffer();
if (!postData) return;
const contentType = request.headerValue('content-type');
return this._postDataForBuffer(postData, contentType, content);
}
_postDataForBuffer(postData, contentType, content) {
var _contentType;
if (!postData) return;
(_contentType = contentType) !== null && _contentType !== void 0 ? _contentType : contentType = 'application/octet-stream';
const result = {
mimeType: contentType,
text: '',
params: []
};
if (content === 'embed' && contentType !== 'application/octet-stream') result.text = postData.toString();
if (content === 'attach') {
const sha1 = (0, _utils.calculateSha1)(postData) + '.' + (_utilsBundle.mime.getExtension(contentType) || 'dat');
if (this._options.includeTraceInfo) result._sha1 = sha1;else result._file = sha1;
this._delegate.onContentBlob(sha1, postData);
}
if (contentType === 'application/x-www-form-urlencoded') {
const parsed = new URLSearchParams(postData.toString());
for (const [name, value] of parsed.entries()) result.params.push({
name,
value
});
}
return result;
}
}
exports.HarTracer = HarTracer;
function createHarEntry(method, url, frameref, options) {
const harEntry = {
_frameref: options.includeTraceInfo ? frameref : undefined,
_monotonicTime: options.includeTraceInfo ? (0, _utils.monotonicTime)() : undefined,
startedDateTime: new Date(),
time: -1,
request: {
method: method,
url: url.toString(),
httpVersion: FALLBACK_HTTP_VERSION,
cookies: [],
headers: [],
queryString: [...url.searchParams].map(e => ({
name: e[0],
value: e[1]
})),
headersSize: -1,
bodySize: -1
},
response: {
status: -1,
statusText: '',
httpVersion: FALLBACK_HTTP_VERSION,
cookies: [],
headers: [],
content: {
size: -1,
mimeType: 'x-unknown'
},
headersSize: -1,
bodySize: -1,
redirectURL: '',
_transferSize: options.omitSizes ? undefined : -1
},
cache: {},
timings: {
send: -1,
wait: -1,
receive: -1
}
};
return harEntry;
}
function parseCookie(c) {
const cookie = {
name: '',
value: ''
};
let first = true;
for (const pair of c.split(/; */)) {
const indexOfEquals = pair.indexOf('=');
const name = indexOfEquals !== -1 ? pair.substr(0, indexOfEquals).trim() : pair.trim();
const value = indexOfEquals !== -1 ? pair.substr(indexOfEquals + 1, pair.length).trim() : '';
if (first) {
first = false;
cookie.name = name;
cookie.value = value;
continue;
}
if (name === 'Domain') cookie.domain = value;
if (name === 'Expires') cookie.expires = new Date(value);
if (name === 'HttpOnly') cookie.httpOnly = true;
if (name === 'Max-Age') cookie.expires = new Date(Date.now() + +value * 1000);
if (name === 'Path') cookie.path = value;
if (name === 'SameSite') cookie.sameSite = value;
if (name === 'Secure') cookie.secure = true;
}
return cookie;
}

View File

@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.helper = void 0;
var _debugLogger = require("../common/debugLogger");
var _eventsHelper = require("../utils/eventsHelper");
/**
* 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 MAX_LOG_LENGTH = process.env.MAX_LOG_LENGTH ? +process.env.MAX_LOG_LENGTH : Infinity;
class Helper {
static completeUserURL(urlString) {
if (urlString.startsWith('localhost') || urlString.startsWith('127.0.0.1')) urlString = 'http://' + urlString;
return urlString;
}
static enclosingIntRect(rect) {
const x = Math.floor(rect.x + 1e-3);
const y = Math.floor(rect.y + 1e-3);
const x2 = Math.ceil(rect.x + rect.width - 1e-3);
const y2 = Math.ceil(rect.y + rect.height - 1e-3);
return {
x,
y,
width: x2 - x,
height: y2 - y
};
}
static enclosingIntSize(size) {
return {
width: Math.floor(size.width + 1e-3),
height: Math.floor(size.height + 1e-3)
};
}
static getViewportSizeFromWindowFeatures(features) {
const widthString = features.find(f => f.startsWith('width='));
const heightString = features.find(f => f.startsWith('height='));
const width = widthString ? parseInt(widthString.substring(6), 10) : NaN;
const height = heightString ? parseInt(heightString.substring(7), 10) : NaN;
if (!Number.isNaN(width) && !Number.isNaN(height)) return {
width,
height
};
return null;
}
static waitForEvent(progress, emitter, event, predicate) {
const listeners = [];
const promise = new Promise((resolve, reject) => {
listeners.push(_eventsHelper.eventsHelper.addEventListener(emitter, event, eventArg => {
try {
if (predicate && !predicate(eventArg)) return;
_eventsHelper.eventsHelper.removeEventListeners(listeners);
resolve(eventArg);
} catch (e) {
_eventsHelper.eventsHelper.removeEventListeners(listeners);
reject(e);
}
}));
});
const dispose = () => _eventsHelper.eventsHelper.removeEventListeners(listeners);
if (progress) progress.cleanupWhenAborted(dispose);
return {
promise,
dispose
};
}
static secondsToRoundishMillis(value) {
return (value * 1000000 | 0) / 1000;
}
static millisToRoundishMillis(value) {
return (value * 1000 | 0) / 1000;
}
static debugProtocolLogger(protocolLogger) {
return (direction, message) => {
if (protocolLogger) protocolLogger(direction, message);
if (_debugLogger.debugLogger.isEnabled('protocol')) {
let text = JSON.stringify(message);
if (text.length > MAX_LOG_LENGTH) text = text.substring(0, MAX_LOG_LENGTH / 2) + ' <<<<<( LOG TRUNCATED )>>>>> ' + text.substring(text.length - MAX_LOG_LENGTH / 2);
_debugLogger.debugLogger.log('protocol', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + text);
}
};
}
static formatBrowserLogs(logs) {
if (!logs.length) return '';
return '\n' + '='.repeat(20) + ' Browser output: ' + '='.repeat(20) + '\n' + logs.join('\n');
}
}
const helper = Helper;
exports.helper = helper;

View File

@@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "DispatcherConnection", {
enumerable: true,
get: function () {
return _dispatcher.DispatcherConnection;
}
});
Object.defineProperty(exports, "PlaywrightDispatcher", {
enumerable: true,
get: function () {
return _playwrightDispatcher.PlaywrightDispatcher;
}
});
Object.defineProperty(exports, "Registry", {
enumerable: true,
get: function () {
return _registry.Registry;
}
});
Object.defineProperty(exports, "RootDispatcher", {
enumerable: true,
get: function () {
return _dispatcher.RootDispatcher;
}
});
Object.defineProperty(exports, "createPlaywright", {
enumerable: true,
get: function () {
return _playwright.createPlaywright;
}
});
Object.defineProperty(exports, "installBrowsersForNpmInstall", {
enumerable: true,
get: function () {
return _registry.installBrowsersForNpmInstall;
}
});
Object.defineProperty(exports, "installDefaultBrowsersForNpmInstall", {
enumerable: true,
get: function () {
return _registry.installDefaultBrowsersForNpmInstall;
}
});
Object.defineProperty(exports, "openTraceViewerApp", {
enumerable: true,
get: function () {
return _traceViewer.openTraceViewerApp;
}
});
Object.defineProperty(exports, "registry", {
enumerable: true,
get: function () {
return _registry.registry;
}
});
Object.defineProperty(exports, "registryDirectory", {
enumerable: true,
get: function () {
return _registry.registryDirectory;
}
});
Object.defineProperty(exports, "serverSideCallMetadata", {
enumerable: true,
get: function () {
return _instrumentation.serverSideCallMetadata;
}
});
Object.defineProperty(exports, "showTraceViewer", {
enumerable: true,
get: function () {
return _traceViewer.showTraceViewer;
}
});
Object.defineProperty(exports, "writeDockerVersion", {
enumerable: true,
get: function () {
return _registry.writeDockerVersion;
}
});
var _registry = require("./registry");
var _dispatcher = require("./dispatchers/dispatcher");
var _playwrightDispatcher = require("./dispatchers/playwrightDispatcher");
var _playwright = require("./playwright");
var _traceViewer = require("./trace/viewer/traceViewer");
var _instrumentation = require("./instrumentation");

View File

@@ -0,0 +1,289 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.keypadLocation = exports.Touchscreen = exports.Mouse = exports.Keyboard = void 0;
var _utils = require("../utils");
var keyboardLayout = _interopRequireWildcard(require("./usKeyboardLayout"));
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.
*/
const keypadLocation = keyboardLayout.keypadLocation;
exports.keypadLocation = keypadLocation;
const kModifiers = ['Alt', 'Control', 'Meta', 'Shift'];
class Keyboard {
constructor(raw, page) {
this._pressedModifiers = new Set();
this._pressedKeys = new Set();
this._raw = void 0;
this._page = void 0;
this._raw = raw;
this._page = page;
}
async down(key) {
const description = this._keyDescriptionForString(key);
const autoRepeat = this._pressedKeys.has(description.code);
this._pressedKeys.add(description.code);
if (kModifiers.includes(description.key)) this._pressedModifiers.add(description.key);
const text = description.text;
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
}
_keyDescriptionForString(keyString) {
let description = usKeyboardLayout.get(keyString);
(0, _utils.assert)(description, `Unknown key: "${keyString}"`);
const shift = this._pressedModifiers.has('Shift');
description = shift && description.shifted ? description.shifted : description;
// if any modifiers besides shift are pressed, no text should be sent
if (this._pressedModifiers.size > 1 || !this._pressedModifiers.has('Shift') && this._pressedModifiers.size === 1) return {
...description,
text: ''
};
return description;
}
async up(key) {
const description = this._keyDescriptionForString(key);
if (kModifiers.includes(description.key)) this._pressedModifiers.delete(description.key);
this._pressedKeys.delete(description.code);
await this._raw.keyup(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location);
}
async insertText(text) {
await this._raw.sendText(text);
}
async type(text, options) {
const delay = options && options.delay || undefined;
for (const char of text) {
if (usKeyboardLayout.has(char)) {
await this.press(char, {
delay
});
} else {
if (delay) await new Promise(f => setTimeout(f, delay));
await this.insertText(char);
}
}
}
async press(key, options = {}) {
function split(keyString) {
const keys = [];
let building = '';
for (const char of keyString) {
if (char === '+' && building) {
keys.push(building);
building = '';
} else {
building += char;
}
}
keys.push(building);
return keys;
}
const tokens = split(key);
const promises = [];
key = tokens[tokens.length - 1];
for (let i = 0; i < tokens.length - 1; ++i) promises.push(this.down(tokens[i]));
promises.push(this.down(key));
if (options.delay) {
await Promise.all(promises);
await new Promise(f => setTimeout(f, options.delay));
}
promises.push(this.up(key));
for (let i = tokens.length - 2; i >= 0; --i) promises.push(this.up(tokens[i]));
await Promise.all(promises);
}
async _ensureModifiers(modifiers) {
for (const modifier of modifiers) {
if (!kModifiers.includes(modifier)) throw new Error('Unknown modifier ' + modifier);
}
const restore = Array.from(this._pressedModifiers);
const promises = [];
for (const key of kModifiers) {
const needDown = modifiers.includes(key);
const isDown = this._pressedModifiers.has(key);
if (needDown && !isDown) promises.push(this.down(key));else if (!needDown && isDown) promises.push(this.up(key));
}
await Promise.all(promises);
return restore;
}
_modifiers() {
return this._pressedModifiers;
}
}
exports.Keyboard = Keyboard;
class Mouse {
constructor(raw, page) {
this._keyboard = void 0;
this._x = 0;
this._y = 0;
this._lastButton = 'none';
this._buttons = new Set();
this._raw = void 0;
this._page = void 0;
this._raw = raw;
this._page = page;
this._keyboard = this._page.keyboard;
}
async move(x, y, options = {}) {
const {
steps = 1
} = options;
const fromX = this._x;
const fromY = this._y;
this._x = x;
this._y = y;
for (let i = 1; i <= steps; i++) {
const middleX = fromX + (x - fromX) * (i / steps);
const middleY = fromY + (y - fromY) * (i / steps);
await this._raw.move(middleX, middleY, this._lastButton, this._buttons, this._keyboard._modifiers(), !!options.forClick);
}
}
async down(options = {}) {
const {
button = 'left',
clickCount = 1
} = options;
this._lastButton = button;
this._buttons.add(button);
await this._raw.down(this._x, this._y, this._lastButton, this._buttons, this._keyboard._modifiers(), clickCount);
}
async up(options = {}) {
const {
button = 'left',
clickCount = 1
} = options;
this._lastButton = 'none';
this._buttons.delete(button);
await this._raw.up(this._x, this._y, button, this._buttons, this._keyboard._modifiers(), clickCount);
}
async click(x, y, options = {}) {
const {
delay = null,
clickCount = 1
} = options;
if (delay) {
this.move(x, y, {
forClick: true
});
for (let cc = 1; cc <= clickCount; ++cc) {
await this.down({
...options,
clickCount: cc
});
await new Promise(f => setTimeout(f, delay));
await this.up({
...options,
clickCount: cc
});
if (cc < clickCount) await new Promise(f => setTimeout(f, delay));
}
} else {
const promises = [];
promises.push(this.move(x, y, {
forClick: true
}));
for (let cc = 1; cc <= clickCount; ++cc) {
promises.push(this.down({
...options,
clickCount: cc
}));
promises.push(this.up({
...options,
clickCount: cc
}));
}
await Promise.all(promises);
}
}
async dblclick(x, y, options = {}) {
await this.click(x, y, {
...options,
clickCount: 2
});
}
async wheel(deltaX, deltaY) {
await this._raw.wheel(this._x, this._y, this._buttons, this._keyboard._modifiers(), deltaX, deltaY);
}
}
exports.Mouse = Mouse;
const aliases = new Map([['ShiftLeft', ['Shift']], ['ControlLeft', ['Control']], ['AltLeft', ['Alt']], ['MetaLeft', ['Meta']], ['Enter', ['\n', '\r']]]);
const usKeyboardLayout = buildLayoutClosure(keyboardLayout.USKeyboardLayout);
function buildLayoutClosure(layout) {
const result = new Map();
for (const code in layout) {
const definition = layout[code];
const description = {
key: definition.key || '',
keyCode: definition.keyCode || 0,
keyCodeWithoutLocation: definition.keyCodeWithoutLocation || definition.keyCode || 0,
code,
text: definition.text || '',
location: definition.location || 0
};
if (definition.key.length === 1) description.text = description.key;
// Generate shifted definition.
let shiftedDescription;
if (definition.shiftKey) {
(0, _utils.assert)(definition.shiftKey.length === 1);
shiftedDescription = {
...description
};
shiftedDescription.key = definition.shiftKey;
shiftedDescription.text = definition.shiftKey;
if (definition.shiftKeyCode) shiftedDescription.keyCode = definition.shiftKeyCode;
}
// Map from code: Digit3 -> { ... descrption, shifted }
result.set(code, {
...description,
shifted: shiftedDescription
});
// Map from aliases: Shift -> non-shiftable definition
if (aliases.has(code)) {
for (const alias of aliases.get(code)) result.set(alias, description);
}
// Do not use numpad when converting keys to codes.
if (definition.location) continue;
// Map from key, no shifted
if (description.key.length === 1) result.set(description.key, description);
// Map from shiftKey, no shifted
if (shiftedDescription) result.set(shiftedDescription.key, {
...shiftedDescription,
shifted: undefined
});
}
return result;
}
class Touchscreen {
constructor(raw, page) {
this._raw = void 0;
this._page = void 0;
this._raw = raw;
this._page = page;
}
async tap(x, y) {
if (!this._page._browserContext._options.hasTouch) throw new Error('hasTouch must be enabled on the browser context before using the touchscreen.');
await this._raw.tap(x, y, this._page.keyboard._modifiers());
}
}
exports.Touchscreen = Touchscreen;

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SdkObject = void 0;
exports.createInstrumentation = createInstrumentation;
exports.kTestSdkObjects = void 0;
exports.serverSideCallMetadata = serverSideCallMetadata;
var _events = require("events");
var _utils = require("../utils");
/**
* 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.
*/
const kTestSdkObjects = new WeakSet();
exports.kTestSdkObjects = kTestSdkObjects;
class SdkObject extends _events.EventEmitter {
constructor(parent, guidPrefix, guid) {
super();
this.guid = void 0;
this.attribution = void 0;
this.instrumentation = void 0;
this.guid = guid || `${guidPrefix || ''}@${(0, _utils.createGuid)()}`;
this.setMaxListeners(0);
this.attribution = {
...parent.attribution
};
this.instrumentation = parent.instrumentation;
if (process.env._PW_INTERNAL_COUNT_SDK_OBJECTS) kTestSdkObjects.add(this);
}
}
exports.SdkObject = SdkObject;
function createInstrumentation() {
const listeners = new Map();
return new Proxy({}, {
get: (obj, prop) => {
if (typeof prop !== 'string') return obj[prop];
if (prop === 'addListener') return (listener, context) => listeners.set(listener, context);
if (prop === 'removeListener') return listener => listeners.delete(listener);
if (!prop.startsWith('on')) return obj[prop];
return async (sdkObject, ...params) => {
for (const [listener, context] of listeners) {
var _prop, _ref;
if (!context || sdkObject.attribution.context === context) await ((_prop = (_ref = listener)[prop]) === null || _prop === void 0 ? void 0 : _prop.call(_ref, sdkObject, ...params));
}
};
}
});
}
function serverSideCallMetadata() {
return {
id: '',
startTime: 0,
endTime: 0,
wallTime: Date.now(),
type: 'Internal',
method: '',
params: {},
log: [],
isServerSide: true
};
}

View File

@@ -0,0 +1,205 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.serializeAsCallArgument = exports.parseEvaluationResultValue = void 0;
exports.source = source;
/**
* 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 source() {
function isRegExp(obj) {
try {
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
} catch (error) {
return false;
}
}
function isDate(obj) {
try {
return obj instanceof Date || Object.prototype.toString.call(obj) === '[object Date]';
} catch (error) {
return false;
}
}
function isURL(obj) {
try {
return obj instanceof URL || Object.prototype.toString.call(obj) === '[object URL]';
} catch (error) {
return false;
}
}
function isError(obj) {
try {
var _Object$getPrototypeO;
return obj instanceof Error || obj && ((_Object$getPrototypeO = Object.getPrototypeOf(obj)) === null || _Object$getPrototypeO === void 0 ? void 0 : _Object$getPrototypeO.name) === 'Error';
} catch (error) {
return false;
}
}
function parseEvaluationResultValue(value, handles = [], refs = new Map()) {
if (Object.is(value, undefined)) return undefined;
if (typeof value === 'object' && value) {
if ('ref' in value) return refs.get(value.ref);
if ('v' in value) {
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;
return undefined;
}
if ('d' in value) return new Date(value.d);
if ('u' in value) return new URL(value.u);
if ('r' in value) return new RegExp(value.r.p, value.r.f);
if ('a' in value) {
const result = [];
refs.set(value.id, result);
for (const a of value.a) result.push(parseEvaluationResultValue(a, handles, refs));
return result;
}
if ('o' in value) {
const result = {};
refs.set(value.id, result);
for (const {
k,
v
} of value.o) result[k] = parseEvaluationResultValue(v, handles, refs);
return result;
}
if ('h' in value) return handles[value.h];
}
return value;
}
function serializeAsCallArgument(value, handleSerializer) {
return serialize(value, handleSerializer, {
visited: new Map(),
lastId: 0
});
}
function serialize(value, handleSerializer, visitorInfo) {
if (value && typeof value === 'object') {
if (typeof globalThis.Window === 'function' && value instanceof globalThis.Window) return 'ref: <Window>';
if (typeof globalThis.Document === 'function' && value instanceof globalThis.Document) return 'ref: <Document>';
if (typeof globalThis.Node === 'function' && value instanceof globalThis.Node) return 'ref: <Node>';
}
return innerSerialize(value, handleSerializer, visitorInfo);
}
function innerSerialize(value, handleSerializer, visitorInfo) {
const result = handleSerializer(value);
if ('fallThrough' in result) value = result.fallThrough;else return result;
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 value;
if (typeof value === 'number') return value;
if (typeof value === 'string') return value;
if (isError(value)) {
const error = value;
if ('captureStackTrace' in globalThis.Error) {
// v8
return error.stack || '';
}
return `${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(serialize(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)) {
let item;
try {
item = value[name];
} catch (e) {
continue; // native bindings will throw sometimes
}
if (name === 'toJSON' && typeof item === 'function') o.push({
k: name,
v: {
o: [],
id: 0
}
});else o.push({
k: name,
v: serialize(item, handleSerializer, visitorInfo)
});
}
// If Object.keys().length === 0 we fall back to toJSON if it exists
if (o.length === 0 && value.toJSON && typeof value.toJSON === 'function') return innerSerialize(value.toJSON(), handleSerializer, visitorInfo);
return {
o,
id
};
}
}
return {
parseEvaluationResultValue,
serializeAsCallArgument
};
}
const result = source();
const parseEvaluationResultValue = result.parseEvaluationResultValue;
exports.parseEvaluationResultValue = parseEvaluationResultValue;
const serializeAsCallArgument = result.serializeAsCallArgument;
exports.serializeAsCallArgument = serializeAsCallArgument;

View File

@@ -0,0 +1,305 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JavaScriptErrorInEvaluate = exports.JSHandle = exports.ExecutionContext = void 0;
exports.evaluate = evaluate;
exports.evaluateExpression = evaluateExpression;
exports.isJavaScriptErrorInEvaluate = isJavaScriptErrorInEvaluate;
exports.normalizeEvaluationExpression = normalizeEvaluationExpression;
exports.parseUnserializableValue = parseUnserializableValue;
exports.sparseArrayToString = sparseArrayToString;
var utilityScriptSource = _interopRequireWildcard(require("../generated/utilityScriptSource"));
var _utilityScriptSerializers = require("./isomorphic/utilityScriptSerializers");
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; }
/**
* 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 ExecutionContext extends _instrumentation.SdkObject {
constructor(parent, delegate, worldNameForTest) {
super(parent, 'execution-context');
this._delegate = void 0;
this._utilityScriptPromise = void 0;
this._contextDestroyedRace = new _manualPromise.ScopedRace();
this.worldNameForTest = void 0;
this.worldNameForTest = worldNameForTest;
this._delegate = delegate;
}
contextDestroyed(error) {
this._contextDestroyedRace.scopeClosed(error);
}
async _raceAgainstContextDestroyed(promise) {
return this._contextDestroyedRace.race(promise);
}
rawEvaluateJSON(expression) {
return this._raceAgainstContextDestroyed(this._delegate.rawEvaluateJSON(expression));
}
rawEvaluateHandle(expression) {
return this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(expression));
}
rawCallFunctionNoReply(func, ...args) {
this._delegate.rawCallFunctionNoReply(func, ...args);
}
evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
return this._raceAgainstContextDestroyed(this._delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds));
}
getProperties(context, objectId) {
return this._raceAgainstContextDestroyed(this._delegate.getProperties(context, objectId));
}
createHandle(remoteObject) {
return this._delegate.createHandle(this, remoteObject);
}
releaseHandle(objectId) {
return this._delegate.releaseHandle(objectId);
}
adoptIfNeeded(handle) {
return null;
}
utilityScript() {
if (!this._utilityScriptPromise) {
const source = `
(() => {
const module = {};
${utilityScriptSource.source}
return new (module.exports.UtilityScript())();
})();`;
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', 'UtilityScript', objectId)));
}
return this._utilityScriptPromise;
}
async objectCount(objectId) {
return this._delegate.objectCount(objectId);
}
async doSlowMo() {
// overridden in FrameExecutionContext
}
}
exports.ExecutionContext = ExecutionContext;
class JSHandle extends _instrumentation.SdkObject {
constructor(context, type, preview, objectId, value) {
super(context, 'handle');
this.__jshandle = true;
this._context = void 0;
this._disposed = false;
this._objectId = void 0;
this._value = void 0;
this._objectType = void 0;
this._preview = void 0;
this._previewCallback = void 0;
this._context = context;
this._objectId = objectId;
this._value = value;
this._objectType = type;
this._preview = this._objectId ? preview || `JSHandle@${this._objectType}` : String(value);
if (this._objectId && globalThis.leakedJSHandles) globalThis.leakedJSHandles.set(this, new Error('Leaked JSHandle'));
}
callFunctionNoReply(func, arg) {
this._context.rawCallFunctionNoReply(func, this, arg);
}
async evaluate(pageFunction, arg) {
return evaluate(this._context, true /* returnByValue */, pageFunction, this, arg);
}
async evaluateHandle(pageFunction, arg) {
return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
}
async evaluateExpression(expression, options, arg) {
const value = await evaluateExpression(this._context, expression, {
...options,
returnByValue: true
}, this, arg);
await this._context.doSlowMo();
return value;
}
async evaluateExpressionHandle(expression, options, arg) {
const value = await evaluateExpression(this._context, expression, {
...options,
returnByValue: false
}, this, arg);
await this._context.doSlowMo();
return value;
}
async getProperty(propertyName) {
const objectHandle = await this.evaluateHandle((object, propertyName) => {
const result = {
__proto__: null
};
result[propertyName] = object[propertyName];
return result;
}, propertyName);
const properties = await objectHandle.getProperties();
const result = properties.get(propertyName);
objectHandle.dispose();
return result;
}
async getProperties() {
if (!this._objectId) return new Map();
return this._context.getProperties(this._context, this._objectId);
}
rawValue() {
return this._value;
}
async jsonValue() {
if (!this._objectId) return this._value;
const utilityScript = await this._context.utilityScript();
const script = `(utilityScript, ...args) => utilityScript.jsonValue(...args)`;
return this._context.evaluateWithArguments(script, true, utilityScript, [true], [this._objectId]);
}
asElement() {
return null;
}
dispose() {
if (this._disposed) return;
this._disposed = true;
if (this._objectId) {
this._context.releaseHandle(this._objectId).catch(e => {});
if (globalThis.leakedJSHandles) globalThis.leakedJSHandles.delete(this);
}
}
toString() {
return this._preview;
}
_setPreviewCallback(callback) {
this._previewCallback = callback;
}
preview() {
return this._preview;
}
worldNameForTest() {
return this._context.worldNameForTest;
}
_setPreview(preview) {
this._preview = preview;
if (this._previewCallback) this._previewCallback(preview);
}
async objectCount() {
if (!this._objectId) throw new Error('Can only count objects for a handle that points to the constructor prototype');
return this._context.objectCount(this._objectId);
}
}
exports.JSHandle = JSHandle;
async function evaluate(context, returnByValue, pageFunction, ...args) {
return evaluateExpression(context, String(pageFunction), {
returnByValue,
isFunction: typeof pageFunction === 'function'
}, ...args);
}
async function evaluateExpression(context, expression, options, ...args) {
const utilityScript = await context.utilityScript();
expression = normalizeEvaluationExpression(expression, options.isFunction);
const handles = [];
const toDispose = [];
const pushHandle = handle => {
handles.push(handle);
return handles.length - 1;
};
args = args.map(arg => (0, _utilityScriptSerializers.serializeAsCallArgument)(arg, handle => {
if (handle instanceof JSHandle) {
if (!handle._objectId) return {
fallThrough: handle._value
};
if (handle._disposed) throw new Error('JSHandle is disposed!');
const adopted = context.adoptIfNeeded(handle);
if (adopted === null) return {
h: pushHandle(Promise.resolve(handle))
};
toDispose.push(adopted);
return {
h: pushHandle(adopted)
};
}
return {
fallThrough: handle
};
}));
const utilityScriptObjectIds = [];
for (const handle of await Promise.all(handles)) {
if (handle._context !== context) throw new Error('JSHandles can be evaluated only in the context they were created!');
utilityScriptObjectIds.push(handle._objectId);
}
// See UtilityScript for arguments.
const utilityScriptValues = [options.isFunction, options.returnByValue, options.exposeUtilityScript, expression, args.length, ...args];
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
try {
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScript, utilityScriptValues, utilityScriptObjectIds);
} finally {
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
}
}
function parseUnserializableValue(unserializableValue) {
if (unserializableValue === 'NaN') return NaN;
if (unserializableValue === 'Infinity') return Infinity;
if (unserializableValue === '-Infinity') return -Infinity;
if (unserializableValue === '-0') return -0;
}
function normalizeEvaluationExpression(expression, isFunction) {
expression = expression.trim();
if (isFunction) {
try {
new Function('(' + expression + ')');
} catch (e1) {
// This means we might have a function shorthand. Try another
// time prefixing 'function '.
if (expression.startsWith('async ')) expression = 'async function ' + expression.substring('async '.length);else expression = 'function ' + expression;
try {
new Function('(' + expression + ')');
} catch (e2) {
// We tried hard to serialize, but there's a weird beast here.
throw new Error('Passed function is not well-serializable!');
}
}
}
if (/^(async)?\s*function(\s|\()/.test(expression)) expression = '(' + expression + ')';
return expression;
}
// Error inside the expression evaluation as opposed to a protocol error.
class JavaScriptErrorInEvaluate extends Error {}
exports.JavaScriptErrorInEvaluate = JavaScriptErrorInEvaluate;
function isJavaScriptErrorInEvaluate(error) {
return error instanceof JavaScriptErrorInEvaluate;
}
function sparseArrayToString(entries) {
const arrayEntries = [];
for (const {
name,
value
} of entries) {
const index = +name;
if (isNaN(index) || index < 0) continue;
arrayEntries.push({
index,
value
});
}
arrayEntries.sort((a, b) => a.index - b.index);
let lastIndex = -1;
const tokens = [];
for (const {
index,
value
} of arrayEntries) {
const emptyItems = index - lastIndex - 1;
if (emptyItems === 1) tokens.push(`empty`);else if (emptyItems > 1) tokens.push(`empty x ${emptyItems}`);
tokens.push(String(value));
lastIndex = index;
}
return '[' + tokens.join(', ') + ']';
}

View File

@@ -0,0 +1,140 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.macEditingCommands = 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.
*/
const macEditingCommands = {
'Backspace': 'deleteBackward:',
'Enter': 'insertNewline:',
'NumpadEnter': 'insertNewline:',
'Escape': 'cancelOperation:',
'ArrowUp': 'moveUp:',
'ArrowDown': 'moveDown:',
'ArrowLeft': 'moveLeft:',
'ArrowRight': 'moveRight:',
'F5': 'complete:',
'Delete': 'deleteForward:',
'Home': 'scrollToBeginningOfDocument:',
'End': 'scrollToEndOfDocument:',
'PageUp': 'scrollPageUp:',
'PageDown': 'scrollPageDown:',
'Shift+Backspace': 'deleteBackward:',
'Shift+Enter': 'insertNewline:',
'Shift+NumpadEnter': 'insertNewline:',
'Shift+Escape': 'cancelOperation:',
'Shift+ArrowUp': 'moveUpAndModifySelection:',
'Shift+ArrowDown': 'moveDownAndModifySelection:',
'Shift+ArrowLeft': 'moveLeftAndModifySelection:',
'Shift+ArrowRight': 'moveRightAndModifySelection:',
'Shift+F5': 'complete:',
'Shift+Delete': 'deleteForward:',
'Shift+Home': 'moveToBeginningOfDocumentAndModifySelection:',
'Shift+End': 'moveToEndOfDocumentAndModifySelection:',
'Shift+PageUp': 'pageUpAndModifySelection:',
'Shift+PageDown': 'pageDownAndModifySelection:',
'Shift+Numpad5': 'delete:',
'Control+Tab': 'selectNextKeyView:',
'Control+Enter': 'insertLineBreak:',
'Control+NumpadEnter': 'insertLineBreak:',
'Control+Quote': 'insertSingleQuoteIgnoringSubstitution:',
'Control+KeyA': 'moveToBeginningOfParagraph:',
'Control+KeyB': 'moveBackward:',
'Control+KeyD': 'deleteForward:',
'Control+KeyE': 'moveToEndOfParagraph:',
'Control+KeyF': 'moveForward:',
'Control+KeyH': 'deleteBackward:',
'Control+KeyK': 'deleteToEndOfParagraph:',
'Control+KeyL': 'centerSelectionInVisibleArea:',
'Control+KeyN': 'moveDown:',
'Control+KeyO': ['insertNewlineIgnoringFieldEditor:', 'moveBackward:'],
'Control+KeyP': 'moveUp:',
'Control+KeyT': 'transpose:',
'Control+KeyV': 'pageDown:',
'Control+KeyY': 'yank:',
'Control+Backspace': 'deleteBackwardByDecomposingPreviousCharacter:',
'Control+ArrowUp': 'scrollPageUp:',
'Control+ArrowDown': 'scrollPageDown:',
'Control+ArrowLeft': 'moveToLeftEndOfLine:',
'Control+ArrowRight': 'moveToRightEndOfLine:',
'Shift+Control+Enter': 'insertLineBreak:',
'Shift+Control+NumpadEnter': 'insertLineBreak:',
'Shift+Control+Tab': 'selectPreviousKeyView:',
'Shift+Control+Quote': 'insertDoubleQuoteIgnoringSubstitution:',
'Shift+Control+KeyA': 'moveToBeginningOfParagraphAndModifySelection:',
'Shift+Control+KeyB': 'moveBackwardAndModifySelection:',
'Shift+Control+KeyE': 'moveToEndOfParagraphAndModifySelection:',
'Shift+Control+KeyF': 'moveForwardAndModifySelection:',
'Shift+Control+KeyN': 'moveDownAndModifySelection:',
'Shift+Control+KeyP': 'moveUpAndModifySelection:',
'Shift+Control+KeyV': 'pageDownAndModifySelection:',
'Shift+Control+Backspace': 'deleteBackwardByDecomposingPreviousCharacter:',
'Shift+Control+ArrowUp': 'scrollPageUp:',
'Shift+Control+ArrowDown': 'scrollPageDown:',
'Shift+Control+ArrowLeft': 'moveToLeftEndOfLineAndModifySelection:',
'Shift+Control+ArrowRight': 'moveToRightEndOfLineAndModifySelection:',
'Alt+Backspace': 'deleteWordBackward:',
'Alt+Enter': 'insertNewlineIgnoringFieldEditor:',
'Alt+NumpadEnter': 'insertNewlineIgnoringFieldEditor:',
'Alt+Escape': 'complete:',
'Alt+ArrowUp': ['moveBackward:', 'moveToBeginningOfParagraph:'],
'Alt+ArrowDown': ['moveForward:', 'moveToEndOfParagraph:'],
'Alt+ArrowLeft': 'moveWordLeft:',
'Alt+ArrowRight': 'moveWordRight:',
'Alt+Delete': 'deleteWordForward:',
'Alt+PageUp': 'pageUp:',
'Alt+PageDown': 'pageDown:',
'Shift+Alt+Backspace': 'deleteWordBackward:',
'Shift+Alt+Enter': 'insertNewlineIgnoringFieldEditor:',
'Shift+Alt+NumpadEnter': 'insertNewlineIgnoringFieldEditor:',
'Shift+Alt+Escape': 'complete:',
'Shift+Alt+ArrowUp': 'moveParagraphBackwardAndModifySelection:',
'Shift+Alt+ArrowDown': 'moveParagraphForwardAndModifySelection:',
'Shift+Alt+ArrowLeft': 'moveWordLeftAndModifySelection:',
'Shift+Alt+ArrowRight': 'moveWordRightAndModifySelection:',
'Shift+Alt+Delete': 'deleteWordForward:',
'Shift+Alt+PageUp': 'pageUp:',
'Shift+Alt+PageDown': 'pageDown:',
'Control+Alt+KeyB': 'moveWordBackward:',
'Control+Alt+KeyF': 'moveWordForward:',
'Control+Alt+Backspace': 'deleteWordBackward:',
'Shift+Control+Alt+KeyB': 'moveWordBackwardAndModifySelection:',
'Shift+Control+Alt+KeyF': 'moveWordForwardAndModifySelection:',
'Shift+Control+Alt+Backspace': 'deleteWordBackward:',
'Meta+NumpadSubtract': 'cancel:',
'Meta+Backspace': 'deleteToBeginningOfLine:',
'Meta+ArrowUp': 'moveToBeginningOfDocument:',
'Meta+ArrowDown': 'moveToEndOfDocument:',
'Meta+ArrowLeft': 'moveToLeftEndOfLine:',
'Meta+ArrowRight': 'moveToRightEndOfLine:',
'Shift+Meta+NumpadSubtract': 'cancel:',
'Shift+Meta+Backspace': 'deleteToBeginningOfLine:',
'Shift+Meta+ArrowUp': 'moveToBeginningOfDocumentAndModifySelection:',
'Shift+Meta+ArrowDown': 'moveToEndOfDocumentAndModifySelection:',
'Shift+Meta+ArrowLeft': 'moveToLeftEndOfLineAndModifySelection:',
'Shift+Meta+ArrowRight': 'moveToRightEndOfLineAndModifySelection:',
'Meta+KeyA': 'selectAll:',
'Meta+KeyC': 'copy:',
'Meta+KeyX': 'cut:',
'Meta+KeyV': 'paste:',
'Meta+KeyZ': 'undo:',
'Shift+Meta+KeyZ': 'redo:'
};
exports.macEditingCommands = macEditingCommands;

View File

@@ -0,0 +1,607 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WebSocket = exports.STATUS_TEXTS = exports.Route = exports.Response = exports.Request = void 0;
exports.filterCookies = filterCookies;
exports.mergeHeaders = mergeHeaders;
exports.parsedURL = parsedURL;
exports.rewriteCookies = rewriteCookies;
exports.singleHeader = singleHeader;
exports.stripFragmentFromUrl = stripFragmentFromUrl;
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
var _instrumentation = require("./instrumentation");
var _fetch = require("./fetch");
var _browserContext = require("./browserContext");
/**
* 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 filterCookies(cookies, urls) {
const parsedURLs = urls.map(s => new URL(s));
// Chromiums's cookies are missing sameSite when it is 'None'
return cookies.filter(c => {
if (!parsedURLs.length) return true;
for (const parsedURL of parsedURLs) {
let domain = c.domain;
if (!domain.startsWith('.')) domain = '.' + domain;
if (!('.' + parsedURL.hostname).endsWith(domain)) continue;
if (!parsedURL.pathname.startsWith(c.path)) continue;
if (parsedURL.protocol !== 'https:' && parsedURL.hostname !== 'localhost' && c.secure) continue;
return true;
}
return false;
});
}
// Rollover to 5-digit year:
// 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC)
// 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC)
const kMaxCookieExpiresDateInSeconds = 253402300799;
function rewriteCookies(cookies) {
return cookies.map(c => {
(0, _utils.assert)(c.url || c.domain && c.path, 'Cookie should have a url or a domain/path pair');
(0, _utils.assert)(!(c.url && c.domain), 'Cookie should have either url or domain');
(0, _utils.assert)(!(c.url && c.path), 'Cookie should have either url or path');
(0, _utils.assert)(!(c.expires && c.expires < 0 && c.expires !== -1), 'Cookie should have a valid expires, only -1 or a positive number for the unix timestamp in seconds is allowed');
(0, _utils.assert)(!(c.expires && c.expires > 0 && c.expires > kMaxCookieExpiresDateInSeconds), 'Cookie should have a valid expires, only -1 or a positive number for the unix timestamp in seconds is allowed');
const copy = {
...c
};
if (copy.url) {
(0, _utils.assert)(copy.url !== 'about:blank', `Blank page can not have cookie "${c.name}"`);
(0, _utils.assert)(!copy.url.startsWith('data:'), `Data URL page can not have cookie "${c.name}"`);
const url = new URL(copy.url);
copy.domain = url.hostname;
copy.path = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1);
copy.secure = url.protocol === 'https:';
}
return copy;
});
}
function parsedURL(url) {
try {
return new URL(url);
} catch (e) {
return null;
}
}
function stripFragmentFromUrl(url) {
if (!url.includes('#')) return url;
return url.substring(0, url.indexOf('#'));
}
class Request extends _instrumentation.SdkObject {
constructor(context, frame, serviceWorker, redirectedFrom, documentId, url, resourceType, method, postData, headers) {
super(frame || context, 'request');
this._response = null;
this._redirectedFrom = void 0;
this._redirectedTo = null;
this._documentId = void 0;
this._isFavicon = void 0;
this._failureText = null;
this._url = void 0;
this._resourceType = void 0;
this._method = void 0;
this._postData = void 0;
this._headers = void 0;
this._headersMap = new Map();
this._frame = null;
this._serviceWorker = null;
this._context = void 0;
this._rawRequestHeadersPromise = new _manualPromise.ManualPromise();
this._waitForResponsePromise = new _manualPromise.ManualPromise();
this._responseEndTiming = -1;
this._overrides = void 0;
(0, _utils.assert)(!url.startsWith('data:'), 'Data urls should not fire requests');
this._context = context;
this._frame = frame;
this._serviceWorker = serviceWorker;
this._redirectedFrom = redirectedFrom;
if (redirectedFrom) redirectedFrom._redirectedTo = this;
this._documentId = documentId;
this._url = stripFragmentFromUrl(url);
this._resourceType = resourceType;
this._method = method;
this._postData = postData;
this._headers = headers;
this._updateHeadersMap();
this._isFavicon = url.endsWith('/favicon.ico') || !!(redirectedFrom !== null && redirectedFrom !== void 0 && redirectedFrom._isFavicon);
}
_setFailureText(failureText) {
this._failureText = failureText;
this._waitForResponsePromise.resolve(null);
}
_setOverrides(overrides) {
this._overrides = overrides;
this._updateHeadersMap();
}
_updateHeadersMap() {
for (const {
name,
value
} of this.headers()) this._headersMap.set(name.toLowerCase(), value);
}
_hasOverrides() {
return !!this._overrides;
}
url() {
var _this$_overrides;
return ((_this$_overrides = this._overrides) === null || _this$_overrides === void 0 ? void 0 : _this$_overrides.url) || this._url;
}
resourceType() {
return this._resourceType;
}
method() {
var _this$_overrides2;
return ((_this$_overrides2 = this._overrides) === null || _this$_overrides2 === void 0 ? void 0 : _this$_overrides2.method) || this._method;
}
postDataBuffer() {
var _this$_overrides3;
return ((_this$_overrides3 = this._overrides) === null || _this$_overrides3 === void 0 ? void 0 : _this$_overrides3.postData) || this._postData;
}
headers() {
var _this$_overrides4;
return ((_this$_overrides4 = this._overrides) === null || _this$_overrides4 === void 0 ? void 0 : _this$_overrides4.headers) || this._headers;
}
headerValue(name) {
return this._headersMap.get(name);
}
// "null" means no raw headers available - we'll use provisional headers as raw headers.
setRawRequestHeaders(headers) {
if (!this._rawRequestHeadersPromise.isDone()) this._rawRequestHeadersPromise.resolve(headers || this._headers);
}
async rawRequestHeaders() {
var _this$_overrides5;
return ((_this$_overrides5 = this._overrides) === null || _this$_overrides5 === void 0 ? void 0 : _this$_overrides5.headers) || this._rawRequestHeadersPromise;
}
response() {
return this._waitForResponsePromise;
}
_existingResponse() {
return this._response;
}
_setResponse(response) {
this._response = response;
this._waitForResponsePromise.resolve(response);
}
_finalRequest() {
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
}
frame() {
return this._frame;
}
serviceWorker() {
return this._serviceWorker;
}
isNavigationRequest() {
return !!this._documentId;
}
redirectedFrom() {
return this._redirectedFrom;
}
failure() {
if (this._failureText === null) return null;
return {
errorText: this._failureText
};
}
bodySize() {
var _this$postDataBuffer;
return ((_this$postDataBuffer = this.postDataBuffer()) === null || _this$postDataBuffer === void 0 ? void 0 : _this$postDataBuffer.length) || 0;
}
async requestHeadersSize() {
let headersSize = 4; // 4 = 2 spaces + 2 line breaks (GET /path \r\n)
headersSize += this.method().length;
headersSize += new URL(this.url()).pathname.length;
headersSize += 8; // httpVersion
const headers = await this.rawRequestHeaders();
for (const header of headers) headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
return headersSize;
}
}
exports.Request = Request;
class Route extends _instrumentation.SdkObject {
constructor(request, delegate) {
super(request._frame || request._context, 'route');
this._request = void 0;
this._delegate = void 0;
this._handled = false;
this._request = request;
this._delegate = delegate;
this._request._context.addRouteInFlight(this);
}
request() {
return this._request;
}
async abort(errorCode = 'failed') {
this._startHandling();
await this._delegate.abort(errorCode);
this._request._context.emit(_browserContext.BrowserContext.Events.RequestAborted, this._request);
this._endHandling();
}
async redirectNavigationRequest(url) {
this._startHandling();
(0, _utils.assert)(this._request.isNavigationRequest());
this._request.frame().redirectNavigation(url, this._request._documentId, this._request.headerValue('referer'));
}
async fulfill(overrides) {
this._startHandling();
let body = overrides.body;
let isBase64 = overrides.isBase64 || false;
if (body === undefined) {
if (overrides.fetchResponseUid) {
const buffer = this._request._context.fetchRequest.fetchResponses.get(overrides.fetchResponseUid) || _fetch.APIRequestContext.findResponseBody(overrides.fetchResponseUid);
(0, _utils.assert)(buffer, 'Fetch response has been disposed');
body = buffer.toString('base64');
isBase64 = true;
} else {
body = '';
isBase64 = false;
}
}
const headers = [...(overrides.headers || [])];
this._maybeAddCorsHeaders(headers);
await this._delegate.fulfill({
status: overrides.status || 200,
headers,
body,
isBase64
});
this._request._context.emit(_browserContext.BrowserContext.Events.RequestFulfilled, this._request);
this._endHandling();
}
// See https://github.com/microsoft/playwright/issues/12929
_maybeAddCorsHeaders(headers) {
const origin = this._request.headerValue('origin');
if (!origin) return;
const requestUrl = new URL(this._request.url());
if (!requestUrl.protocol.startsWith('http')) return;
if (requestUrl.origin === origin.trim()) return;
const corsHeader = headers.find(({
name
}) => name === 'access-control-allow-origin');
if (corsHeader) return;
headers.push({
name: 'access-control-allow-origin',
value: origin
});
headers.push({
name: 'access-control-allow-credentials',
value: 'true'
});
headers.push({
name: 'vary',
value: 'Origin'
});
}
async continue(overrides = {}) {
this._startHandling();
if (overrides.url) {
const newUrl = new URL(overrides.url);
const oldUrl = new URL(this._request.url());
if (oldUrl.protocol !== newUrl.protocol) throw new Error('New URL must have same protocol as overridden URL');
}
this._request._setOverrides(overrides);
await this._delegate.continue(this._request, overrides);
if (!overrides.isFallback) this._request._context.emit(_browserContext.BrowserContext.Events.RequestContinued, this._request);
this._endHandling();
}
_startHandling() {
(0, _utils.assert)(!this._handled, 'Route is already handled!');
this._handled = true;
}
_endHandling() {
this._request._context.removeRouteInFlight(this);
}
}
exports.Route = Route;
class Response extends _instrumentation.SdkObject {
constructor(request, status, statusText, headers, timing, getResponseBodyCallback, fromServiceWorker, httpVersion) {
super(request.frame() || request._context, 'response');
this._request = void 0;
this._contentPromise = null;
this._finishedPromise = new _manualPromise.ManualPromise();
this._status = void 0;
this._statusText = void 0;
this._url = void 0;
this._headers = void 0;
this._headersMap = new Map();
this._getResponseBodyCallback = void 0;
this._timing = void 0;
this._serverAddrPromise = new _manualPromise.ManualPromise();
this._securityDetailsPromise = new _manualPromise.ManualPromise();
this._rawResponseHeadersPromise = new _manualPromise.ManualPromise();
this._httpVersion = void 0;
this._fromServiceWorker = void 0;
this._encodedBodySizePromise = new _manualPromise.ManualPromise();
this._transferSizePromise = new _manualPromise.ManualPromise();
this._responseHeadersSizePromise = new _manualPromise.ManualPromise();
this._request = request;
this._timing = timing;
this._status = status;
this._statusText = statusText;
this._url = request.url();
this._headers = headers;
for (const {
name,
value
} of this._headers) this._headersMap.set(name.toLowerCase(), value);
this._getResponseBodyCallback = getResponseBodyCallback;
this._request._setResponse(this);
this._httpVersion = httpVersion;
this._fromServiceWorker = fromServiceWorker;
}
_serverAddrFinished(addr) {
this._serverAddrPromise.resolve(addr);
}
_securityDetailsFinished(securityDetails) {
this._securityDetailsPromise.resolve(securityDetails);
}
_requestFinished(responseEndTiming) {
this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
// Set start time equal to end when request is served from memory cache.
if (this._timing.requestStart === -1) this._timing.requestStart = this._request._responseEndTiming;
this._finishedPromise.resolve();
}
_setHttpVersion(httpVersion) {
this._httpVersion = httpVersion;
}
url() {
return this._url;
}
status() {
return this._status;
}
statusText() {
return this._statusText;
}
headers() {
return this._headers;
}
headerValue(name) {
return this._headersMap.get(name);
}
async rawResponseHeaders() {
return this._rawResponseHeadersPromise;
}
// "null" means no raw headers available - we'll use provisional headers as raw headers.
setRawResponseHeaders(headers) {
if (!this._rawResponseHeadersPromise.isDone()) this._rawResponseHeadersPromise.resolve(headers || this._headers);
}
setTransferSize(size) {
this._transferSizePromise.resolve(size);
}
setEncodedBodySize(size) {
this._encodedBodySizePromise.resolve(size);
}
setResponseHeadersSize(size) {
this._responseHeadersSizePromise.resolve(size);
}
timing() {
return this._timing;
}
async serverAddr() {
return (await this._serverAddrPromise) || null;
}
async securityDetails() {
return (await this._securityDetailsPromise) || null;
}
body() {
if (!this._contentPromise) {
this._contentPromise = this._finishedPromise.then(async () => {
if (this._status >= 300 && this._status <= 399) throw new Error('Response body is unavailable for redirect responses');
return this._getResponseBodyCallback();
});
}
return this._contentPromise;
}
request() {
return this._request;
}
frame() {
return this._request.frame();
}
httpVersion() {
if (!this._httpVersion) return 'HTTP/1.1';
if (this._httpVersion === 'http/1.1') return 'HTTP/1.1';
if (this._httpVersion === 'h2') return 'HTTP/2.0';
return this._httpVersion;
}
fromServiceWorker() {
return this._fromServiceWorker;
}
async responseHeadersSize() {
const availableSize = await this._responseHeadersSizePromise;
if (availableSize !== null) return availableSize;
// Fallback to calculating it manually.
let headersSize = 4; // 4 = 2 spaces + 2 line breaks (HTTP/1.1 200 Ok\r\n)
headersSize += 8; // httpVersion;
headersSize += 3; // statusCode;
headersSize += this.statusText().length;
const headers = await this._rawResponseHeadersPromise;
for (const header of headers) headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
headersSize += 2; // '\r\n'
return headersSize;
}
async sizes() {
const requestHeadersSize = await this._request.requestHeadersSize();
const responseHeadersSize = await this.responseHeadersSize();
let encodedBodySize = await this._encodedBodySizePromise;
if (encodedBodySize === null) {
var _headers$find;
// Fallback to calculating it manually.
const headers = await this._rawResponseHeadersPromise;
const contentLength = (_headers$find = headers.find(h => h.name.toLowerCase() === 'content-length')) === null || _headers$find === void 0 ? void 0 : _headers$find.value;
encodedBodySize = contentLength ? +contentLength : 0;
}
let transferSize = await this._transferSizePromise;
if (transferSize === null) {
// Fallback to calculating it manually.
transferSize = responseHeadersSize + encodedBodySize;
}
return {
requestBodySize: this._request.bodySize(),
requestHeadersSize,
responseBodySize: encodedBodySize,
responseHeadersSize,
transferSize
};
}
}
exports.Response = Response;
class WebSocket extends _instrumentation.SdkObject {
constructor(parent, url) {
super(parent, 'ws');
this._url = void 0;
this._notified = false;
this._url = url;
}
markAsNotified() {
// Sometimes we get "onWebSocketRequest" twice, at least in Chromium.
// Perhaps websocket is restarted because of chrome.webRequest extensions api?
// Or maybe the handshake response was a redirect?
if (this._notified) return false;
this._notified = true;
return true;
}
url() {
return this._url;
}
frameSent(opcode, data) {
this.emit(WebSocket.Events.FrameSent, {
opcode,
data
});
}
frameReceived(opcode, data) {
this.emit(WebSocket.Events.FrameReceived, {
opcode,
data
});
}
error(errorMessage) {
this.emit(WebSocket.Events.SocketError, errorMessage);
}
closed() {
this.emit(WebSocket.Events.Close);
}
}
exports.WebSocket = WebSocket;
WebSocket.Events = {
Close: 'close',
SocketError: 'socketerror',
FrameReceived: 'framereceived',
FrameSent: 'framesent'
};
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
const STATUS_TEXTS = {
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'103': 'Early Hints',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'306': 'Switch Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Too Early',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'510': 'Not Extended',
'511': 'Network Authentication Required'
};
exports.STATUS_TEXTS = STATUS_TEXTS;
function singleHeader(name, value) {
return [{
name,
value
}];
}
function mergeHeaders(headers) {
const lowerCaseToValue = new Map();
const lowerCaseToOriginalCase = new Map();
for (const h of headers) {
if (!h) continue;
for (const {
name,
value
} of h) {
const lower = name.toLowerCase();
lowerCaseToOriginalCase.set(lower, name);
lowerCaseToValue.set(lower, value);
}
}
const result = [];
for (const [lower, value] of lowerCaseToValue) result.push({
name: lowerCaseToOriginalCase.get(lower),
value
});
return result;
}

View File

@@ -0,0 +1,757 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Worker = exports.PageBinding = exports.Page = void 0;
var frames = _interopRequireWildcard(require("./frames"));
var input = _interopRequireWildcard(require("./input"));
var js = _interopRequireWildcard(require("./javascript"));
var network = _interopRequireWildcard(require("./network"));
var _screenshotter = require("./screenshotter");
var _timeoutSettings = require("../common/timeoutSettings");
var _browserContext = require("./browserContext");
var _console = require("./console");
var accessibility = _interopRequireWildcard(require("./accessibility"));
var _fileChooser = require("./fileChooser");
var _progress = require("./progress");
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
var _debugLogger = require("../common/debugLogger");
var _comparators = require("../utils/comparators");
var _instrumentation = require("./instrumentation");
var _selectorParser = require("../utils/isomorphic/selectorParser");
var _utilityScriptSerializers = require("./isomorphic/utilityScriptSerializers");
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 Page extends _instrumentation.SdkObject {
// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
constructor(delegate, browserContext) {
super(browserContext, 'page');
this._closedState = 'open';
this._closedPromise = new _manualPromise.ManualPromise();
this._disconnected = false;
this._initialized = false;
this._eventsToEmitAfterInitialized = [];
this._disconnectedPromise = new _manualPromise.ManualPromise();
this._crashedPromise = new _manualPromise.ManualPromise();
this._browserContext = void 0;
this.keyboard = void 0;
this.mouse = void 0;
this.touchscreen = void 0;
this._timeoutSettings = void 0;
this._delegate = void 0;
this._emulatedSize = void 0;
this._extraHTTPHeaders = void 0;
this._emulatedMedia = {};
this._interceptFileChooser = false;
this._pageBindings = new Map();
this.initScripts = [];
this._screenshotter = void 0;
this._frameManager = void 0;
this.accessibility = void 0;
this._workers = new Map();
this.pdf = void 0;
this.coverage = void 0;
this._clientRequestInterceptor = void 0;
this._serverRequestInterceptor = void 0;
this._ownedContext = void 0;
this._pageIsError = void 0;
this._video = null;
this._opener = void 0;
this._isServerSideOnly = false;
this._frameThrottler = new FrameThrottler(10, 35, 200);
this.attribution.page = this;
this._delegate = delegate;
this._browserContext = browserContext;
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
this.keyboard = new input.Keyboard(delegate.rawKeyboard, this);
this.mouse = new input.Mouse(delegate.rawMouse, this);
this.touchscreen = new input.Touchscreen(delegate.rawTouchscreen, this);
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(browserContext._timeoutSettings);
this._screenshotter = new _screenshotter.Screenshotter(this);
this._frameManager = new frames.FrameManager(this);
if (delegate.pdf) this.pdf = delegate.pdf.bind(delegate);
this.coverage = delegate.coverage ? delegate.coverage() : null;
}
async initOpener(opener) {
if (!opener) return;
const openerPage = await opener.pageOrError();
if (openerPage instanceof Page && !openerPage.isClosed()) this._opener = openerPage;
}
reportAsNew(error = undefined, contextEvent = _browserContext.BrowserContext.Events.Page) {
if (error) {
// Initialization error could have happened because of
// context/browser closure. Just ignore the page.
if (this._browserContext.isClosingOrClosed()) return;
this._setIsError(error);
}
this._initialized = true;
this.emitOnContext(contextEvent, this);
for (const {
event,
args
} of this._eventsToEmitAfterInitialized) this._browserContext.emit(event, ...args);
this._eventsToEmitAfterInitialized = [];
// It may happen that page initialization finishes after Close event has already been sent,
// in that case we fire another Close event to ensure that each reported Page will have
// corresponding Close event after it is reported on the context.
if (this.isClosed()) this.emit(Page.Events.Close);else this.instrumentation.onPageOpen(this);
}
initializedOrUndefined() {
return this._initialized ? this : undefined;
}
emitOnContext(event, ...args) {
if (this._isServerSideOnly) return;
this._browserContext.emit(event, ...args);
}
emitOnContextOnceInitialized(event, ...args) {
if (this._isServerSideOnly) return;
// Some events, like console messages, may come before page is ready.
// In this case, postpone the event until page is initialized,
// and dispatch it to the client later, either on the live Page,
// or on the "errored" Page.
if (this._initialized) this._browserContext.emit(event, ...args);else this._eventsToEmitAfterInitialized.push({
event,
args
});
}
async resetForReuse(metadata) {
this.setDefaultNavigationTimeout(undefined);
this.setDefaultTimeout(undefined);
await this._removeExposedBindings();
await this._removeInitScripts();
await this.setClientRequestInterceptor(undefined);
await this._setServerRequestInterceptor(undefined);
await this.setFileChooserIntercepted(false);
// Re-navigate once init scripts are gone.
await this.mainFrame().goto(metadata, 'about:blank');
this._emulatedSize = undefined;
this._emulatedMedia = {};
this._extraHTTPHeaders = undefined;
this._interceptFileChooser = false;
await Promise.all([this._delegate.updateEmulatedViewportSize(), this._delegate.updateEmulateMedia(), this._delegate.updateFileChooserInterception()]);
await this._delegate.resetForReuse();
}
_didClose() {
this._frameManager.dispose();
this._frameThrottler.dispose();
(0, _utils.assert)(this._closedState !== 'closed', 'Page closed twice');
this._closedState = 'closed';
this.emit(Page.Events.Close);
this._closedPromise.resolve();
this.instrumentation.onPageClose(this);
}
_didCrash() {
this._frameManager.dispose();
this._frameThrottler.dispose();
this.emit(Page.Events.Crash);
this._crashedPromise.resolve(new Error('Page crashed'));
this.instrumentation.onPageClose(this);
}
_didDisconnect() {
this._frameManager.dispose();
this._frameThrottler.dispose();
(0, _utils.assert)(!this._disconnected, 'Page disconnected twice');
this._disconnected = true;
this._disconnectedPromise.resolve(new Error('Page closed'));
}
async _onFileChooserOpened(handle) {
let multiple;
try {
multiple = await handle.evaluate(element => !!element.multiple);
} catch (e) {
// Frame/context may be gone during async processing. Do not throw.
return;
}
if (!this.listenerCount(Page.Events.FileChooser)) {
handle.dispose();
return;
}
const fileChooser = new _fileChooser.FileChooser(this, handle, multiple);
this.emit(Page.Events.FileChooser, fileChooser);
}
context() {
return this._browserContext;
}
opener() {
return this._opener;
}
mainFrame() {
return this._frameManager.mainFrame();
}
frames() {
return this._frameManager.frames();
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
}
async exposeBinding(name, needsHandle, playwrightBinding) {
if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
if (this._browserContext._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered in the browser context`);
const binding = new PageBinding(name, playwrightBinding, needsHandle);
this._pageBindings.set(name, binding);
await this._delegate.exposeBinding(binding);
}
async _removeExposedBindings() {
for (const key of this._pageBindings.keys()) {
if (!key.startsWith('__pw')) this._pageBindings.delete(key);
}
await this._delegate.removeExposedBindings();
}
setExtraHTTPHeaders(headers) {
this._extraHTTPHeaders = headers;
return this._delegate.updateExtraHTTPHeaders();
}
extraHTTPHeaders() {
return this._extraHTTPHeaders;
}
async _onBindingCalled(payload, context) {
if (this._disconnected || this._closedState === 'closed') return;
await PageBinding.dispatch(this, payload, context);
}
_addConsoleMessage(type, args, location, text) {
const message = new _console.ConsoleMessage(this, type, text, args, location);
const intercepted = this._frameManager.interceptConsoleMessage(message);
if (intercepted) {
args.forEach(arg => arg.dispose());
return;
}
this.emitOnContextOnceInitialized(_browserContext.BrowserContext.Events.Console, message);
}
async reload(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
// Note: waitForNavigation may fail before we get response to reload(),
// so we should await it immediately.
const [response] = await Promise.all([
// Reload must be a new document, and should not be confused with a stray pushState.
this.mainFrame()._waitForNavigation(progress, true /* requiresNewDocument */, options), this._delegate.reload()]);
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
async goBack(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
// Note: waitForNavigation may fail before we get response to goBack,
// so we should catch it immediately.
let error;
const waitPromise = this.mainFrame()._waitForNavigation(progress, false /* requiresNewDocument */, options).catch(e => {
error = e;
return null;
});
const result = await this._delegate.goBack();
if (!result) return null;
const response = await waitPromise;
if (error) throw error;
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
async goForward(metadata, options) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
// Note: waitForNavigation may fail before we get response to goForward,
// so we should catch it immediately.
let error;
const waitPromise = this.mainFrame()._waitForNavigation(progress, false /* requiresNewDocument */, options).catch(e => {
error = e;
return null;
});
const result = await this._delegate.goForward();
if (!result) return null;
const response = await waitPromise;
if (error) throw error;
return response;
}), this._timeoutSettings.navigationTimeout(options));
}
async emulateMedia(options) {
if (options.media !== undefined) this._emulatedMedia.media = options.media;
if (options.colorScheme !== undefined) this._emulatedMedia.colorScheme = options.colorScheme;
if (options.reducedMotion !== undefined) this._emulatedMedia.reducedMotion = options.reducedMotion;
if (options.forcedColors !== undefined) this._emulatedMedia.forcedColors = options.forcedColors;
await this._delegate.updateEmulateMedia();
}
emulatedMedia() {
var _contextOptions$color, _contextOptions$reduc, _contextOptions$force;
const contextOptions = this._browserContext._options;
return {
media: this._emulatedMedia.media || 'no-override',
colorScheme: this._emulatedMedia.colorScheme !== undefined ? this._emulatedMedia.colorScheme : (_contextOptions$color = contextOptions.colorScheme) !== null && _contextOptions$color !== void 0 ? _contextOptions$color : 'light',
reducedMotion: this._emulatedMedia.reducedMotion !== undefined ? this._emulatedMedia.reducedMotion : (_contextOptions$reduc = contextOptions.reducedMotion) !== null && _contextOptions$reduc !== void 0 ? _contextOptions$reduc : 'no-preference',
forcedColors: this._emulatedMedia.forcedColors !== undefined ? this._emulatedMedia.forcedColors : (_contextOptions$force = contextOptions.forcedColors) !== null && _contextOptions$force !== void 0 ? _contextOptions$force : 'none'
};
}
async setViewportSize(viewportSize) {
this._emulatedSize = {
viewport: {
...viewportSize
},
screen: {
...viewportSize
}
};
await this._delegate.updateEmulatedViewportSize();
}
viewportSize() {
var _this$emulatedSize;
return ((_this$emulatedSize = this.emulatedSize()) === null || _this$emulatedSize === void 0 ? void 0 : _this$emulatedSize.viewport) || null;
}
emulatedSize() {
if (this._emulatedSize) return this._emulatedSize;
const contextOptions = this._browserContext._options;
return contextOptions.viewport ? {
viewport: contextOptions.viewport,
screen: contextOptions.screen || contextOptions.viewport
} : null;
}
async bringToFront() {
await this._delegate.bringToFront();
}
async addInitScript(source) {
this.initScripts.push(source);
await this._delegate.addInitScript(source);
}
async _removeInitScripts() {
this.initScripts.splice(0, this.initScripts.length);
await this._delegate.removeInitScripts();
}
needsRequestInterception() {
return !!this._clientRequestInterceptor || !!this._serverRequestInterceptor || !!this._browserContext._requestInterceptor;
}
async setClientRequestInterceptor(handler) {
this._clientRequestInterceptor = handler;
await this._delegate.updateRequestInterception();
}
async _setServerRequestInterceptor(handler) {
this._serverRequestInterceptor = handler;
await this._delegate.updateRequestInterception();
}
async expectScreenshot(metadata, options = {}) {
const locator = options.locator;
const rafrafScreenshot = locator ? async (progress, timeout) => {
return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options.screenshotOptions || {});
} : async (progress, timeout) => {
await this.mainFrame().rafrafTimeout(timeout);
return await this._screenshotter.screenshotPage(progress, options.screenshotOptions || {});
};
const comparator = (0, _comparators.getComparator)('image/png');
const controller = new _progress.ProgressController(metadata, this);
if (!options.expected && options.isNot) return {
errorMessage: '"not" matcher requires expected result'
};
try {
const format = (0, _screenshotter.validateScreenshotOptions)(options.screenshotOptions || {});
if (format !== 'png') throw new Error('Only PNG screenshots are supported');
} catch (error) {
return {
errorMessage: error.message
};
}
let intermediateResult = undefined;
const areEqualScreenshots = (actual, expected, previous) => {
const comparatorResult = actual && expected ? comparator(actual, expected, options.comparatorOptions) : undefined;
if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot) return true;
if (comparatorResult) intermediateResult = {
errorMessage: comparatorResult.errorMessage,
diff: comparatorResult.diff,
actual,
previous
};
return false;
};
const callTimeout = this._timeoutSettings.timeout(options);
return controller.run(async progress => {
let actual;
let previous;
const pollIntervals = [0, 100, 250, 500];
progress.log(`${metadata.apiName}${callTimeout ? ` with timeout ${callTimeout}ms` : ''}`);
if (options.expected) progress.log(` verifying given screenshot expectation`);else progress.log(` generating new stable screenshot expectation`);
let isFirstIteration = true;
while (true) {
var _pollIntervals$shift;
progress.throwIfAborted();
if (this.isClosed()) throw new Error('The page has closed');
const screenshotTimeout = (_pollIntervals$shift = pollIntervals.shift()) !== null && _pollIntervals$shift !== void 0 ? _pollIntervals$shift : 1000;
if (screenshotTimeout) progress.log(`waiting ${screenshotTimeout}ms before taking screenshot`);
previous = actual;
actual = await rafrafScreenshot(progress, screenshotTimeout).catch(e => {
progress.log(`failed to take screenshot - ` + e.message);
return undefined;
});
if (!actual) continue;
// Compare against expectation for the first iteration.
const expectation = options.expected && isFirstIteration ? options.expected : previous;
if (areEqualScreenshots(actual, expectation, previous)) break;
if (intermediateResult) progress.log(intermediateResult.errorMessage);
isFirstIteration = false;
}
if (!isFirstIteration) progress.log(`captured a stable screenshot`);
if (!options.expected) return {
actual
};
if (isFirstIteration) {
progress.log(`screenshot matched expectation`);
return {};
}
if (areEqualScreenshots(actual, options.expected, previous)) {
progress.log(`screenshot matched expectation`);
return {};
}
throw new Error(intermediateResult.errorMessage);
}, callTimeout).catch(e => {
// Q: Why not throw upon isSessionClosedError(e) as in other places?
// A: We want user to receive a friendly diff between actual and expected/previous.
if (js.isJavaScriptErrorInEvaluate(e) || (0, _selectorParser.isInvalidSelectorError)(e)) throw e;
return {
log: e.message ? [...metadata.log, e.message] : metadata.log,
...intermediateResult,
errorMessage: e.message
};
});
}
async screenshot(metadata, options = {}) {
const controller = new _progress.ProgressController(metadata, this);
return controller.run(progress => this._screenshotter.screenshotPage(progress, options), this._timeoutSettings.timeout(options));
}
async close(metadata, options) {
if (this._closedState === 'closed') return;
const runBeforeUnload = !!options && !!options.runBeforeUnload;
if (this._closedState !== 'closing') {
this._closedState = 'closing';
(0, _utils.assert)(!this._disconnected, 'Target closed');
// This might throw if the browser context containing the page closes
// while we are trying to close the page.
await this._delegate.closePage(runBeforeUnload).catch(e => _debugLogger.debugLogger.log('error', e));
}
if (!runBeforeUnload) await this._closedPromise;
if (this._ownedContext) await this._ownedContext.close(metadata);
}
_setIsError(error) {
this._pageIsError = error;
this._frameManager.createDummyMainFrameIfNeeded();
}
isClosed() {
return this._closedState === 'closed';
}
isClosedOrClosingOrCrashed() {
return this._closedState !== 'open' || this._crashedPromise.isDone();
}
_addWorker(workerId, worker) {
this._workers.set(workerId, worker);
this.emit(Page.Events.Worker, worker);
}
_removeWorker(workerId) {
const worker = this._workers.get(workerId);
if (!worker) return;
worker.didClose();
this._workers.delete(workerId);
}
_clearWorkers() {
for (const [workerId, worker] of this._workers) {
worker.didClose();
this._workers.delete(workerId);
}
}
async setFileChooserIntercepted(enabled) {
this._interceptFileChooser = enabled;
await this._delegate.updateFileChooserInterception();
}
fileChooserIntercepted() {
return this._interceptFileChooser;
}
frameNavigatedToNewDocument(frame) {
this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame);
const url = frame.url();
if (!url.startsWith('http')) return;
const purl = network.parsedURL(url);
if (purl) this._browserContext.addVisitedOrigin(purl.origin);
}
allBindings() {
return [...this._browserContext._pageBindings.values(), ...this._pageBindings.values()];
}
getBinding(name) {
return this._pageBindings.get(name) || this._browserContext._pageBindings.get(name);
}
setScreencastOptions(options) {
this._delegate.setScreencastOptions(options).catch(e => _debugLogger.debugLogger.log('error', e));
this._frameThrottler.setThrottlingEnabled(!!options);
}
throttleScreencastFrameAck(ack) {
// Don't ack immediately, tracing has smart throttling logic that is implemented here.
this._frameThrottler.ack(ack);
}
temporarlyDisableTracingScreencastThrottling() {
this._frameThrottler.recharge();
}
firePageError(error) {
this.emit(Page.Events.PageError, error);
}
async hideHighlight() {
await Promise.all(this.frames().map(frame => frame.hideHighlight().catch(() => {})));
}
markAsServerSideOnly() {
this._isServerSideOnly = true;
}
}
exports.Page = Page;
Page.Events = {
Close: 'close',
Crash: 'crash',
Download: 'download',
FileChooser: 'filechooser',
// 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',
FrameAttached: 'frameattached',
FrameDetached: 'framedetached',
InternalFrameNavigatedToNewDocument: 'internalframenavigatedtonewdocument',
ScreencastFrame: 'screencastframe',
Video: 'video',
WebSocket: 'websocket',
Worker: 'worker'
};
class Worker extends _instrumentation.SdkObject {
constructor(parent, url) {
super(parent, 'worker');
this._url = void 0;
this._executionContextPromise = void 0;
this._executionContextCallback = void 0;
this._existingExecutionContext = null;
this._url = url;
this._executionContextCallback = () => {};
this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
}
_createExecutionContext(delegate) {
this._existingExecutionContext = new js.ExecutionContext(this, delegate, 'worker');
this._executionContextCallback(this._existingExecutionContext);
}
url() {
return this._url;
}
didClose() {
if (this._existingExecutionContext) this._existingExecutionContext.contextDestroyed(new Error('Worker was closed'));
this.emit(Worker.Events.Close, this);
}
async evaluateExpression(expression, isFunction, arg) {
return js.evaluateExpression(await this._executionContextPromise, expression, {
returnByValue: true,
isFunction
}, arg);
}
async evaluateExpressionHandle(expression, isFunction, arg) {
return js.evaluateExpression(await this._executionContextPromise, expression, {
returnByValue: false,
isFunction
}, arg);
}
}
exports.Worker = Worker;
Worker.Events = {
Close: 'close'
};
class PageBinding {
constructor(name, playwrightFunction, needsHandle) {
this.name = void 0;
this.playwrightFunction = void 0;
this.source = void 0;
this.needsHandle = void 0;
this.name = name;
this.playwrightFunction = playwrightFunction;
this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle}, (${_utilityScriptSerializers.source})())`;
this.needsHandle = needsHandle;
}
static async dispatch(page, payload, context) {
const {
name,
seq,
serializedArgs
} = JSON.parse(payload);
try {
(0, _utils.assert)(context.world);
const binding = page.getBinding(name);
let result;
if (binding.needsHandle) {
const handle = await context.evaluateHandle(takeHandle, {
name,
seq
}).catch(e => null);
result = await binding.playwrightFunction({
frame: context.frame,
page,
context: page._browserContext
}, handle);
} else {
const args = serializedArgs.map(a => (0, _utilityScriptSerializers.parseEvaluationResultValue)(a));
result = await binding.playwrightFunction({
frame: context.frame,
page,
context: page._browserContext
}, ...args);
}
context.evaluate(deliverResult, {
name,
seq,
result
}).catch(e => _debugLogger.debugLogger.log('error', e));
} catch (error) {
if ((0, _utils.isError)(error)) context.evaluate(deliverError, {
name,
seq,
message: error.message,
stack: error.stack
}).catch(e => _debugLogger.debugLogger.log('error', e));else context.evaluate(deliverErrorValue, {
name,
seq,
error
}).catch(e => _debugLogger.debugLogger.log('error', e));
}
function takeHandle(arg) {
const handle = globalThis[arg.name]['handles'].get(arg.seq);
globalThis[arg.name]['handles'].delete(arg.seq);
return handle;
}
function deliverResult(arg) {
globalThis[arg.name]['callbacks'].get(arg.seq).resolve(arg.result);
globalThis[arg.name]['callbacks'].delete(arg.seq);
}
function deliverError(arg) {
const error = new Error(arg.message);
error.stack = arg.stack;
globalThis[arg.name]['callbacks'].get(arg.seq).reject(error);
globalThis[arg.name]['callbacks'].delete(arg.seq);
}
function deliverErrorValue(arg) {
globalThis[arg.name]['callbacks'].get(arg.seq).reject(arg.error);
globalThis[arg.name]['callbacks'].delete(arg.seq);
}
}
}
exports.PageBinding = PageBinding;
function addPageBinding(bindingName, needsHandle, utilityScriptSerializers) {
const binding = globalThis[bindingName];
if (binding.__installed) return;
globalThis[bindingName] = (...args) => {
const me = globalThis[bindingName];
if (needsHandle && args.slice(1).some(arg => arg !== undefined)) throw new Error(`exposeBindingHandle supports a single argument, ${args.length} received`);
let callbacks = me['callbacks'];
if (!callbacks) {
callbacks = new Map();
me['callbacks'] = callbacks;
}
const seq = (me['lastSeq'] || 0) + 1;
me['lastSeq'] = seq;
let handles = me['handles'];
if (!handles) {
handles = new Map();
me['handles'] = handles;
}
const promise = new Promise((resolve, reject) => callbacks.set(seq, {
resolve,
reject
}));
let payload;
if (needsHandle) {
handles.set(seq, args[0]);
payload = {
name: bindingName,
seq
};
} else {
const serializedArgs = [];
for (let i = 0; i < args.length; i++) {
serializedArgs[i] = utilityScriptSerializers.serializeAsCallArgument(args[i], v => {
return {
fallThrough: v
};
});
}
payload = {
name: bindingName,
seq,
serializedArgs
};
}
binding(JSON.stringify(payload));
return promise;
};
globalThis[bindingName].__installed = true;
}
class FrameThrottler {
constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {
this._acks = [];
this._defaultInterval = void 0;
this._throttlingInterval = void 0;
this._nonThrottledFrames = void 0;
this._budget = void 0;
this._throttlingEnabled = false;
this._timeoutId = void 0;
this._nonThrottledFrames = nonThrottledFrames;
this._budget = nonThrottledFrames;
this._defaultInterval = defaultInterval;
this._throttlingInterval = throttlingInterval;
this._tick();
}
dispose() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._timeoutId = undefined;
}
}
setThrottlingEnabled(enabled) {
this._throttlingEnabled = enabled;
}
recharge() {
// Send all acks, reset budget.
for (const ack of this._acks) ack();
this._acks = [];
this._budget = this._nonThrottledFrames;
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._tick();
}
}
ack(ack) {
if (!this._timeoutId) {
// Already disposed.
ack();
return;
}
this._acks.push(ack);
}
_tick() {
const ack = this._acks.shift();
if (ack) {
--this._budget;
ack();
}
if (this._throttlingEnabled && this._budget <= 0) {
// Non-throttled frame budget is exceeded. Next ack will be throttled.
this._timeoutId = setTimeout(() => this._tick(), this._throttlingInterval);
} else {
// Either not throttling, or still under budget. Next ack will be after the default timeout.
this._timeoutId = setTimeout(() => this._tick(), this._defaultInterval);
}
}
}

View File

@@ -0,0 +1,85 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PipeTransport = void 0;
var _utils = require("../utils");
var _debugLogger = require("../common/debugLogger");
/**
* 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 PipeTransport {
constructor(pipeWrite, pipeRead) {
this._pipeRead = void 0;
this._pipeWrite = void 0;
this._pendingBuffers = [];
this._waitForNextTask = (0, _utils.makeWaitForNextTask)();
this._closed = false;
this._onclose = void 0;
this.onmessage = void 0;
this._pipeRead = pipeRead;
this._pipeWrite = pipeWrite;
pipeRead.on('data', buffer => this._dispatch(buffer));
pipeRead.on('close', () => {
this._closed = true;
if (this._onclose) this._onclose.call(null);
});
pipeRead.on('error', e => _debugLogger.debugLogger.log('error', e));
pipeWrite.on('error', e => _debugLogger.debugLogger.log('error', e));
this.onmessage = undefined;
}
get onclose() {
return this._onclose;
}
set onclose(onclose) {
this._onclose = onclose;
if (onclose && !this._pipeRead.readable) onclose();
}
send(message) {
if (this._closed) throw new Error('Pipe has been closed');
this._pipeWrite.write(JSON.stringify(message));
this._pipeWrite.write('\0');
}
close() {
throw new Error('unimplemented');
}
_dispatch(buffer) {
let end = buffer.indexOf('\0');
if (end === -1) {
this._pendingBuffers.push(buffer);
return;
}
this._pendingBuffers.push(buffer.slice(0, end));
const message = Buffer.concat(this._pendingBuffers).toString();
this._waitForNextTask(() => {
if (this.onmessage) this.onmessage.call(null, JSON.parse(message));
});
let start = end + 1;
end = buffer.indexOf('\0', start);
while (end !== -1) {
const message = buffer.toString(undefined, start, end);
this._waitForNextTask(() => {
if (this.onmessage) this.onmessage.call(null, JSON.parse(message));
});
start = end + 1;
end = buffer.indexOf('\0', start);
}
this._pendingBuffers = [buffer.slice(start)];
}
}
exports.PipeTransport = PipeTransport;

View File

@@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Playwright = void 0;
exports.createPlaywright = createPlaywright;
var _android = require("./android/android");
var _backendAdb = require("./android/backendAdb");
var _chromium = require("./chromium/chromium");
var _electron = require("./electron/electron");
var _firefox = require("./firefox/firefox");
var _selectors = require("./selectors");
var _webkit = require("./webkit/webkit");
var _instrumentation = require("./instrumentation");
var _debugLogger = require("../common/debugLogger");
var _debugController = require("./debugController");
/**
* 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 _instrumentation.SdkObject {
constructor(options) {
super({
attribution: {},
instrumentation: (0, _instrumentation.createInstrumentation)()
}, undefined, 'Playwright');
this.selectors = void 0;
this.chromium = void 0;
this.android = void 0;
this.electron = void 0;
this.firefox = void 0;
this.webkit = void 0;
this.options = void 0;
this.debugController = void 0;
this._allPages = new Set();
this._allBrowsers = new Set();
this.options = options;
this.attribution.playwright = this;
this.instrumentation.addListener({
onBrowserOpen: browser => this._allBrowsers.add(browser),
onBrowserClose: browser => this._allBrowsers.delete(browser),
onPageOpen: page => this._allPages.add(page),
onPageClose: page => this._allPages.delete(page),
onCallLog: (sdkObject, metadata, logName, message) => {
_debugLogger.debugLogger.log(logName, message);
}
}, null);
this.chromium = new _chromium.Chromium(this);
this.firefox = new _firefox.Firefox(this);
this.webkit = new _webkit.WebKit(this);
this.electron = new _electron.Electron(this);
this.android = new _android.Android(this, new _backendAdb.AdbBackend());
this.selectors = new _selectors.Selectors();
this.debugController = new _debugController.DebugController(this);
}
async hideHighlight() {
await Promise.all([...this._allPages].map(p => p.hideHighlight().catch(() => {})));
}
allBrowsers() {
return [...this._allBrowsers];
}
allPages() {
return [...this._allPages];
}
}
exports.Playwright = Playwright;
function createPlaywright(options) {
return new Playwright(options);
}

View File

@@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ProgressController = void 0;
var _errors = require("../common/errors");
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
/**
* 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 ProgressController {
// Cleanups to be run only in the case of abort.
constructor(metadata, sdkObject) {
this._forceAbortPromise = new _manualPromise.ManualPromise();
this._cleanups = [];
this._logName = 'api';
this._state = 'before';
this._deadline = 0;
this._timeout = 0;
this.metadata = void 0;
this.instrumentation = void 0;
this.sdkObject = void 0;
this.metadata = metadata;
this.sdkObject = sdkObject;
this.instrumentation = sdkObject.instrumentation;
this._forceAbortPromise.catch(e => null); // Prevent unhandled promise rejection.
}
setLogName(logName) {
this._logName = logName;
}
abort(error) {
this._forceAbortPromise.reject(error);
}
async run(task, timeout) {
var _this$sdkObject$attri;
if (timeout) {
this._timeout = timeout;
this._deadline = timeout ? (0, _utils.monotonicTime)() + timeout : 0;
}
(0, _utils.assert)(this._state === 'before');
this._state = 'running';
(_this$sdkObject$attri = this.sdkObject.attribution.context) === null || _this$sdkObject$attri === void 0 ? void 0 : _this$sdkObject$attri._activeProgressControllers.add(this);
const progress = {
log: message => {
progress.logEntry({
message
});
},
logEntry: entry => {
if ('message' in entry) {
const message = entry.message;
if (this._state === 'running') this.metadata.log.push(message);
// Note: we might be sending logs after progress has finished, for example browser logs.
this.instrumentation.onCallLog(this.sdkObject, this.metadata, this._logName, message);
}
},
timeUntilDeadline: () => this._deadline ? this._deadline - (0, _utils.monotonicTime)() : 2147483647,
// 2^31-1 safe setTimeout in Node.
isRunning: () => this._state === 'running',
cleanupWhenAborted: cleanup => {
if (this._state === 'running') this._cleanups.push(cleanup);else runCleanup(cleanup);
},
throwIfAborted: () => {
if (this._state === 'aborted') throw new AbortedError();
},
beforeInputAction: async element => {
await this.instrumentation.onBeforeInputAction(this.sdkObject, this.metadata, element);
},
metadata: this.metadata
};
const timeoutError = new _errors.TimeoutError(`Timeout ${this._timeout}ms exceeded.`);
const timer = setTimeout(() => this._forceAbortPromise.reject(timeoutError), progress.timeUntilDeadline());
try {
const promise = task(progress);
const result = await Promise.race([promise, this._forceAbortPromise]);
this._state = 'finished';
return result;
} catch (e) {
this._state = 'aborted';
await Promise.all(this._cleanups.splice(0).map(runCleanup));
throw e;
} finally {
var _this$sdkObject$attri2;
(_this$sdkObject$attri2 = this.sdkObject.attribution.context) === null || _this$sdkObject$attri2 === void 0 ? void 0 : _this$sdkObject$attri2._activeProgressControllers.delete(this);
clearTimeout(timer);
}
}
}
exports.ProgressController = ProgressController;
async function runCleanup(cleanup) {
try {
await cleanup();
} catch (e) {}
}
class AbortedError extends Error {}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ProtocolError = void 0;
exports.isSessionClosedError = isSessionClosedError;
/**
* 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 ProtocolError extends Error {
constructor(sessionClosed, message) {
super(message);
this.sessionClosed = void 0;
this.sessionClosed = sessionClosed || false;
}
}
exports.ProtocolError = ProtocolError;
function isSessionClosedError(e) {
return e instanceof ProtocolError && e.sessionClosed;
}

View File

@@ -0,0 +1,659 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Recorder = void 0;
var fs = _interopRequireWildcard(require("fs"));
var _codeGenerator = require("./recorder/codeGenerator");
var _utils = require("./recorder/utils");
var _page = require("./page");
var _frames = require("./frames");
var _browserContext = require("./browserContext");
var _java = require("./recorder/java");
var _javascript = require("./recorder/javascript");
var _jsonl = require("./recorder/jsonl");
var _csharp = require("./recorder/csharp");
var _python = require("./recorder/python");
var recorderSource = _interopRequireWildcard(require("../generated/recorderSource"));
var consoleApiSource = _interopRequireWildcard(require("../generated/consoleApiSource"));
var _recorderApp = require("./recorder/recorderApp");
var _utils2 = require("../utils");
var _recorderUtils = require("./recorder/recorderUtils");
var _debugger = require("./debugger");
var _events = require("events");
var _timeoutRunner = require("../utils/timeoutRunner");
var _locatorParser = require("../utils/isomorphic/locatorParser");
var _eventsHelper = require("./../utils/eventsHelper");
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.
*/
const recorderSymbol = Symbol('recorderSymbol');
class Recorder {
static setAppFactory(recorderAppFactory) {
Recorder.recorderAppFactory = recorderAppFactory;
}
static showInspector(context) {
Recorder.show(context, {}).catch(() => {});
}
static show(context, params = {}) {
let recorderPromise = context[recorderSymbol];
if (!recorderPromise) {
const recorder = new Recorder(context, params);
recorderPromise = recorder.install().then(() => recorder);
context[recorderSymbol] = recorderPromise;
}
return recorderPromise;
}
constructor(context, params) {
this._context = void 0;
this._mode = void 0;
this._highlightedSelector = '';
this._recorderApp = null;
this._currentCallsMetadata = new Map();
this._recorderSources = [];
this._userSources = new Map();
this._debugger = void 0;
this._contextRecorder = void 0;
this._handleSIGINT = void 0;
this._omitCallTracking = false;
this._currentLanguage = void 0;
this._mode = params.mode || 'none';
this._contextRecorder = new ContextRecorder(context, params);
this._context = context;
this._omitCallTracking = !!params.omitCallTracking;
this._debugger = context.debugger();
this._handleSIGINT = params.handleSIGINT;
context.instrumentation.addListener(this, context);
this._currentLanguage = this._contextRecorder.languageName();
}
static async defaultRecorderAppFactory(recorder) {
if (process.env.PW_CODEGEN_NO_INSPECTOR) return new _recorderApp.EmptyRecorderApp();
return await _recorderApp.RecorderApp.open(recorder, recorder._context, recorder._handleSIGINT);
}
async install() {
const recorderApp = await (Recorder.recorderAppFactory || Recorder.defaultRecorderAppFactory)(this);
this._recorderApp = recorderApp;
recorderApp.once('close', () => {
this._debugger.resume(false);
this._recorderApp = null;
});
recorderApp.on('event', data => {
if (data.event === 'setMode') {
this.setMode(data.params.mode);
return;
}
if (data.event === 'selectorUpdated') {
this.setHighlightedSelector(data.params.language, data.params.selector);
return;
}
if (data.event === 'step') {
this._debugger.resume(true);
return;
}
if (data.event === 'fileChanged') {
this._currentLanguage = this._contextRecorder.languageName(data.params.file);
this._refreshOverlay();
return;
}
if (data.event === 'resume') {
this._debugger.resume(false);
return;
}
if (data.event === 'pause') {
this._debugger.pauseOnNextStatement();
return;
}
if (data.event === 'clear') {
this._contextRecorder.clearScript();
return;
}
});
await Promise.all([recorderApp.setMode(this._mode), recorderApp.setPaused(this._debugger.isPaused()), this._pushAllSources()]);
this._context.once(_browserContext.BrowserContext.Events.Close, () => {
this._contextRecorder.dispose();
this._context.instrumentation.removeListener(this);
recorderApp.close().catch(() => {});
});
this._contextRecorder.on(ContextRecorder.Events.Change, data => {
var _this$_recorderApp;
this._recorderSources = data.sources;
this._pushAllSources();
(_this$_recorderApp = this._recorderApp) === null || _this$_recorderApp === void 0 ? void 0 : _this$_recorderApp.setFileIfNeeded(data.primaryFileName);
});
await this._context.exposeBinding('__pw_recorderState', false, source => {
let actionSelector = '';
let actionPoint;
const hasActiveScreenshotCommand = [...this._currentCallsMetadata.keys()].some(isScreenshotCommand);
if (!hasActiveScreenshotCommand) {
actionSelector = this._highlightedSelector;
for (const [metadata, sdkObject] of this._currentCallsMetadata) {
if (source.page === sdkObject.attribution.page) {
actionPoint = metadata.point || actionPoint;
actionSelector = actionSelector || metadata.params.selector;
}
}
}
const uiState = {
mode: this._mode,
actionPoint,
actionSelector,
language: this._currentLanguage,
testIdAttributeName: this._contextRecorder.testIdAttributeName()
};
return uiState;
});
await this._context.exposeBinding('__pw_recorderSetSelector', false, async (_, selector) => {
var _this$_recorderApp2;
await ((_this$_recorderApp2 = this._recorderApp) === null || _this$_recorderApp2 === void 0 ? void 0 : _this$_recorderApp2.setSelector(selector, true));
});
await this._context.exposeBinding('__pw_resume', false, () => {
this._debugger.resume(false);
});
await this._context.extendInjectedScript(consoleApiSource.source);
await this._contextRecorder.install();
if (this._debugger.isPaused()) this._pausedStateChanged();
this._debugger.on(_debugger.Debugger.Events.PausedStateChanged, () => this._pausedStateChanged());
this._context.recorderAppForTest = recorderApp;
}
_pausedStateChanged() {
var _this$_recorderApp3;
// If we are called upon page.pause, we don't have metadatas, populate them.
for (const {
metadata,
sdkObject
} of this._debugger.pausedDetails()) {
if (!this._currentCallsMetadata.has(metadata)) this.onBeforeCall(sdkObject, metadata);
}
(_this$_recorderApp3 = this._recorderApp) === null || _this$_recorderApp3 === void 0 ? void 0 : _this$_recorderApp3.setPaused(this._debugger.isPaused());
this._updateUserSources();
this.updateCallLog([...this._currentCallsMetadata.keys()]);
}
setMode(mode) {
var _this$_recorderApp4;
if (this._mode === mode) return;
this._highlightedSelector = '';
this._mode = mode;
(_this$_recorderApp4 = this._recorderApp) === null || _this$_recorderApp4 === void 0 ? void 0 : _this$_recorderApp4.setMode(this._mode);
this._contextRecorder.setEnabled(this._mode === 'recording');
this._debugger.setMuted(this._mode === 'recording');
if (this._mode !== 'none' && this._context.pages().length === 1) this._context.pages()[0].bringToFront().catch(() => {});
this._refreshOverlay();
}
resume() {
this._debugger.resume(false);
}
setHighlightedSelector(language, selector) {
this._highlightedSelector = (0, _locatorParser.locatorOrSelectorAsSelector)(language, selector, this._context.selectors().testIdAttributeName());
this._refreshOverlay();
}
hideHighlightedSelecor() {
this._highlightedSelector = '';
this._refreshOverlay();
}
setOutput(codegenId, outputFile) {
this._contextRecorder.setOutput(codegenId, outputFile);
}
_refreshOverlay() {
for (const page of this._context.pages()) page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()').catch(() => {});
}
async onBeforeCall(sdkObject, metadata) {
if (this._omitCallTracking || this._mode === 'recording') return;
this._currentCallsMetadata.set(metadata, sdkObject);
this._updateUserSources();
this.updateCallLog([metadata]);
if (isScreenshotCommand(metadata)) {
this.hideHighlightedSelecor();
} else if (metadata.params && metadata.params.selector) {
var _this$_recorderApp5;
this._highlightedSelector = metadata.params.selector;
(_this$_recorderApp5 = this._recorderApp) === null || _this$_recorderApp5 === void 0 ? void 0 : _this$_recorderApp5.setSelector(this._highlightedSelector).catch(() => {});
}
}
async onAfterCall(sdkObject, metadata) {
if (this._omitCallTracking || this._mode === 'recording') return;
if (!metadata.error) this._currentCallsMetadata.delete(metadata);
this._updateUserSources();
this.updateCallLog([metadata]);
}
_updateUserSources() {
var _this$_recorderApp6;
// Remove old decorations.
for (const source of this._userSources.values()) {
source.highlight = [];
source.revealLine = undefined;
}
// Apply new decorations.
let fileToSelect = undefined;
for (const metadata of this._currentCallsMetadata.keys()) {
if (!metadata.location) continue;
const {
file,
line
} = metadata.location;
let source = this._userSources.get(file);
if (!source) {
source = {
isRecorded: false,
label: file,
id: file,
text: this._readSource(file),
highlight: [],
language: languageForFile(file)
};
this._userSources.set(file, source);
}
if (line) {
const paused = this._debugger.isPaused(metadata);
source.highlight.push({
line,
type: metadata.error ? 'error' : paused ? 'paused' : 'running'
});
source.revealLine = line;
fileToSelect = source.id;
}
}
this._pushAllSources();
if (fileToSelect) (_this$_recorderApp6 = this._recorderApp) === null || _this$_recorderApp6 === void 0 ? void 0 : _this$_recorderApp6.setFileIfNeeded(fileToSelect);
}
_pushAllSources() {
var _this$_recorderApp7;
(_this$_recorderApp7 = this._recorderApp) === null || _this$_recorderApp7 === void 0 ? void 0 : _this$_recorderApp7.setSources([...this._recorderSources, ...this._userSources.values()]);
}
async onBeforeInputAction(sdkObject, metadata) {}
async onCallLog(sdkObject, metadata, logName, message) {
this.updateCallLog([metadata]);
}
updateCallLog(metadatas) {
var _this$_recorderApp8;
if (this._mode === 'recording') return;
const logs = [];
for (const metadata of metadatas) {
if (!metadata.method || metadata.internal) continue;
let status = 'done';
if (this._currentCallsMetadata.has(metadata)) status = 'in-progress';
if (this._debugger.isPaused(metadata)) status = 'paused';
logs.push((0, _recorderUtils.metadataToCallLog)(metadata, status));
}
(_this$_recorderApp8 = this._recorderApp) === null || _this$_recorderApp8 === void 0 ? void 0 : _this$_recorderApp8.updateCallLogs(logs);
}
_readSource(fileName) {
try {
return fs.readFileSync(fileName, 'utf-8');
} catch (e) {
return '// No source available';
}
}
}
exports.Recorder = Recorder;
Recorder.recorderAppFactory = void 0;
class ContextRecorder extends _events.EventEmitter {
constructor(context, params) {
super();
this._generator = void 0;
this._pageAliases = new Map();
this._lastPopupOrdinal = 0;
this._lastDialogOrdinal = -1;
this._lastDownloadOrdinal = -1;
this._timers = new Set();
this._context = void 0;
this._params = void 0;
this._recorderSources = void 0;
this._throttledOutputFile = null;
this._orderedLanguages = [];
this._listeners = [];
this._context = context;
this._params = params;
this._recorderSources = [];
const language = params.language || context.attribution.playwright.options.sdkLanguage;
this.setOutput(language, params.outputFile);
const generator = new _codeGenerator.CodeGenerator(context._browser.options.name, params.mode === 'recording', params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage);
generator.on('change', () => {
this._recorderSources = [];
for (const languageGenerator of this._orderedLanguages) {
var _this$_throttledOutpu;
const {
header,
footer,
actions,
text
} = generator.generateStructure(languageGenerator);
const source = {
isRecorded: true,
label: languageGenerator.name,
group: languageGenerator.groupName,
id: languageGenerator.id,
text,
header,
footer,
actions,
language: languageGenerator.highlighter,
highlight: []
};
source.revealLine = text.split('\n').length - 1;
this._recorderSources.push(source);
if (languageGenerator === this._orderedLanguages[0]) (_this$_throttledOutpu = this._throttledOutputFile) === null || _this$_throttledOutpu === void 0 ? void 0 : _this$_throttledOutpu.setContent(source.text);
}
this.emit(ContextRecorder.Events.Change, {
sources: this._recorderSources,
primaryFileName: this._orderedLanguages[0].id
});
});
context.on(_browserContext.BrowserContext.Events.BeforeClose, () => {
var _this$_throttledOutpu2;
(_this$_throttledOutpu2 = this._throttledOutputFile) === null || _this$_throttledOutpu2 === void 0 ? void 0 : _this$_throttledOutpu2.flush();
});
this._listeners.push(_eventsHelper.eventsHelper.addEventListener(process, 'exit', () => {
var _this$_throttledOutpu3;
(_this$_throttledOutpu3 = this._throttledOutputFile) === null || _this$_throttledOutpu3 === void 0 ? void 0 : _this$_throttledOutpu3.flush();
}));
this._generator = generator;
}
setOutput(codegenId, outputFile) {
var _this$_generator;
const languages = new Set([new _java.JavaLanguageGenerator(), new _javascript.JavaScriptLanguageGenerator( /* isPlaywrightTest */false), new _javascript.JavaScriptLanguageGenerator( /* isPlaywrightTest */true), new _python.PythonLanguageGenerator( /* isAsync */false, /* isPytest */true), new _python.PythonLanguageGenerator( /* isAsync */false, /* isPytest */false), new _python.PythonLanguageGenerator( /* isAsync */true, /* isPytest */false), new _csharp.CSharpLanguageGenerator('mstest'), new _csharp.CSharpLanguageGenerator('nunit'), new _csharp.CSharpLanguageGenerator('library'), new _jsonl.JsonlLanguageGenerator()]);
const primaryLanguage = [...languages].find(l => l.id === codegenId);
if (!primaryLanguage) throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`);
languages.delete(primaryLanguage);
this._orderedLanguages = [primaryLanguage, ...languages];
this._throttledOutputFile = outputFile ? new ThrottledFile(outputFile) : null;
(_this$_generator = this._generator) === null || _this$_generator === void 0 ? void 0 : _this$_generator.restart();
}
languageName(id) {
for (const lang of this._orderedLanguages) {
if (!id || lang.id === id) return lang.highlighter;
}
return 'javascript';
}
async install() {
this._context.on(_browserContext.BrowserContext.Events.Page, page => this._onPage(page));
for (const page of this._context.pages()) this._onPage(page);
this._context.on(_browserContext.BrowserContext.Events.Dialog, dialog => this._onDialog(dialog.page()));
// Input actions that potentially lead to navigation are intercepted on the page and are
// performed by the Playwright.
await this._context.exposeBinding('__pw_recorderPerformAction', false, (source, action) => this._performAction(source.frame, action));
// Other non-essential actions are simply being recorded.
await this._context.exposeBinding('__pw_recorderRecordAction', false, (source, action) => this._recordAction(source.frame, action));
await this._context.extendInjectedScript(recorderSource.source);
}
setEnabled(enabled) {
this._generator.setEnabled(enabled);
}
dispose() {
for (const timer of this._timers) clearTimeout(timer);
this._timers.clear();
_eventsHelper.eventsHelper.removeEventListeners(this._listeners);
}
async _onPage(page) {
// First page is called page, others are called popup1, popup2, etc.
const frame = page.mainFrame();
page.on('close', () => {
this._generator.addAction({
frame: this._describeMainFrame(page),
committed: true,
action: {
name: 'closePage',
signals: []
}
});
this._pageAliases.delete(page);
});
frame.on(_frames.Frame.Events.InternalNavigation, event => {
if (event.isPublic) this._onFrameNavigated(frame, page);
});
page.on(_page.Page.Events.Download, () => this._onDownload(page));
const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
const pageAlias = 'page' + suffix;
this._pageAliases.set(page, pageAlias);
if (page.opener()) {
this._onPopup(page.opener(), page);
} else {
this._generator.addAction({
frame: this._describeMainFrame(page),
committed: true,
action: {
name: 'openPage',
url: page.mainFrame().url(),
signals: []
}
});
}
}
clearScript() {
this._generator.restart();
if (this._params.mode === 'recording') {
for (const page of this._context.pages()) this._onFrameNavigated(page.mainFrame(), page);
}
}
_describeMainFrame(page) {
return {
pageAlias: this._pageAliases.get(page),
isMainFrame: true,
url: page.mainFrame().url()
};
}
async _describeFrame(frame) {
const page = frame._page;
const pageAlias = this._pageAliases.get(page);
const chain = [];
for (let ancestor = frame; ancestor; ancestor = ancestor.parentFrame()) chain.push(ancestor);
chain.reverse();
if (chain.length === 1) return this._describeMainFrame(page);
const hasUniqueName = page.frames().filter(f => f.name() === frame.name()).length === 1;
const fallback = {
pageAlias,
isMainFrame: false,
url: frame.url(),
name: frame.name() && hasUniqueName ? frame.name() : undefined
};
if (chain.length > 3) return fallback;
const selectorPromises = [];
for (let i = 0; i < chain.length - 1; i++) selectorPromises.push(this._findFrameSelector(chain[i + 1], chain[i]));
const result = await (0, _timeoutRunner.raceAgainstTimeout)(() => Promise.all(selectorPromises), 2000);
if (!result.timedOut && result.result.every(selector => !!selector)) {
return {
...fallback,
selectorsChain: result.result
};
}
return fallback;
}
testIdAttributeName() {
return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || 'data-testid';
}
async _findFrameSelector(frame, parent) {
try {
const frameElement = await frame.frameElement();
if (!frameElement) return;
const utility = await parent._utilityContext();
const injected = await utility.injectedScript();
const selector = await injected.evaluate((injected, element) => {
return injected.generateSelector(element, '', true);
}, frameElement);
return selector;
} catch (e) {}
}
async _performAction(frame, action) {
// Commit last action so that no further signals are added to it.
this._generator.commitLastAction();
const frameDescription = await this._describeFrame(frame);
const actionInContext = {
frame: frameDescription,
action
};
const perform = async (action, params, cb) => {
const callMetadata = {
id: `call@${(0, _utils2.createGuid)()}`,
apiName: 'frame.' + action,
objectId: frame.guid,
pageId: frame._page.guid,
frameId: frame.guid,
startTime: (0, _utils2.monotonicTime)(),
endTime: 0,
wallTime: Date.now(),
type: 'Frame',
method: action,
params,
log: []
};
this._generator.willPerformAction(actionInContext);
try {
await frame.instrumentation.onBeforeCall(frame, callMetadata);
await cb(callMetadata);
} catch (e) {
callMetadata.endTime = (0, _utils2.monotonicTime)();
await frame.instrumentation.onAfterCall(frame, callMetadata);
this._generator.performedActionFailed(actionInContext);
return;
}
callMetadata.endTime = (0, _utils2.monotonicTime)();
await frame.instrumentation.onAfterCall(frame, callMetadata);
const timer = setTimeout(() => {
// Commit the action after 5 seconds so that no further signals are added to it.
actionInContext.committed = true;
this._timers.delete(timer);
}, 5000);
this._generator.didPerformAction(actionInContext);
this._timers.add(timer);
};
const kActionTimeout = 5000;
if (action.name === 'click') {
const {
options
} = (0, _utils.toClickOptions)(action);
await perform('click', {
selector: action.selector
}, callMetadata => frame.click(callMetadata, action.selector, {
...options,
timeout: kActionTimeout,
strict: true
}));
}
if (action.name === 'press') {
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
await perform('press', {
selector: action.selector,
key: shortcut
}, callMetadata => frame.press(callMetadata, action.selector, shortcut, {
timeout: kActionTimeout,
strict: true
}));
}
if (action.name === 'check') await perform('check', {
selector: action.selector
}, callMetadata => frame.check(callMetadata, action.selector, {
timeout: kActionTimeout,
strict: true
}));
if (action.name === 'uncheck') await perform('uncheck', {
selector: action.selector
}, callMetadata => frame.uncheck(callMetadata, action.selector, {
timeout: kActionTimeout,
strict: true
}));
if (action.name === 'select') {
const values = action.options.map(value => ({
value
}));
await perform('selectOption', {
selector: action.selector,
values
}, callMetadata => frame.selectOption(callMetadata, action.selector, [], values, {
timeout: kActionTimeout,
strict: true
}));
}
}
async _recordAction(frame, action) {
// Commit last action so that no further signals are added to it.
this._generator.commitLastAction();
const frameDescription = await this._describeFrame(frame);
const actionInContext = {
frame: frameDescription,
action
};
this._generator.addAction(actionInContext);
}
_onFrameNavigated(frame, page) {
const pageAlias = this._pageAliases.get(page);
this._generator.signal(pageAlias, frame, {
name: 'navigation',
url: frame.url()
});
}
_onPopup(page, popup) {
const pageAlias = this._pageAliases.get(page);
const popupAlias = this._pageAliases.get(popup);
this._generator.signal(pageAlias, page.mainFrame(), {
name: 'popup',
popupAlias
});
}
_onDownload(page) {
const pageAlias = this._pageAliases.get(page);
++this._lastDownloadOrdinal;
this._generator.signal(pageAlias, page.mainFrame(), {
name: 'download',
downloadAlias: this._lastDownloadOrdinal ? String(this._lastDownloadOrdinal) : ''
});
}
_onDialog(page) {
const pageAlias = this._pageAliases.get(page);
++this._lastDialogOrdinal;
this._generator.signal(pageAlias, page.mainFrame(), {
name: 'dialog',
dialogAlias: this._lastDialogOrdinal ? String(this._lastDialogOrdinal) : ''
});
}
}
ContextRecorder.Events = {
Change: 'change'
};
function languageForFile(file) {
if (file.endsWith('.py')) return 'python';
if (file.endsWith('.java')) return 'java';
if (file.endsWith('.cs')) return 'csharp';
return 'javascript';
}
class ThrottledFile {
constructor(file) {
this._file = void 0;
this._timer = void 0;
this._text = void 0;
this._file = file;
}
setContent(text) {
this._text = text;
if (!this._timer) this._timer = setTimeout(() => this.flush(), 250);
}
flush() {
if (this._timer) {
clearTimeout(this._timer);
this._timer = undefined;
}
if (this._text) fs.writeFileSync(this._file, this._text);
this._text = undefined;
}
}
function isScreenshotCommand(metadata) {
return metadata.method.toLowerCase().includes('screenshot');
}

View File

@@ -0,0 +1,154 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CodeGenerator = void 0;
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 CodeGenerator extends _events.EventEmitter {
constructor(browserName, enabled, launchOptions, contextOptions, deviceName, saveStorage) {
super();
// Make a copy of options to modify them later.
this._currentAction = null;
this._lastAction = null;
this._actions = [];
this._enabled = void 0;
this._options = void 0;
launchOptions = {
headless: false,
...launchOptions
};
contextOptions = {
...contextOptions
};
this._enabled = enabled;
this._options = {
browserName,
launchOptions,
contextOptions,
deviceName,
saveStorage
};
this.restart();
}
restart() {
this._currentAction = null;
this._lastAction = null;
this._actions = [];
this.emit('change');
}
setEnabled(enabled) {
this._enabled = enabled;
}
addAction(action) {
if (!this._enabled) return;
this.willPerformAction(action);
this.didPerformAction(action);
}
willPerformAction(action) {
if (!this._enabled) return;
this._currentAction = action;
}
performedActionFailed(action) {
if (!this._enabled) return;
if (this._currentAction === action) this._currentAction = null;
}
didPerformAction(actionInContext) {
if (!this._enabled) return;
const action = actionInContext.action;
let eraseLastAction = false;
if (this._lastAction && this._lastAction.frame.pageAlias === actionInContext.frame.pageAlias) {
const lastAction = this._lastAction.action;
// We augment last action based on the type.
if (this._lastAction && action.name === 'fill' && lastAction.name === 'fill') {
if (action.selector === lastAction.selector) eraseLastAction = true;
}
if (lastAction && action.name === 'click' && lastAction.name === 'click') {
if (action.selector === lastAction.selector && action.clickCount > lastAction.clickCount) eraseLastAction = true;
}
if (lastAction && action.name === 'navigate' && lastAction.name === 'navigate') {
if (action.url === lastAction.url) {
// Already at a target URL.
this._currentAction = null;
return;
}
}
// Check and uncheck erase click.
if (lastAction && (action.name === 'check' || action.name === 'uncheck') && lastAction.name === 'click') {
if (action.selector === lastAction.selector) eraseLastAction = true;
}
}
this._lastAction = actionInContext;
this._currentAction = null;
if (eraseLastAction) this._actions.pop();
this._actions.push(actionInContext);
this.emit('change');
}
commitLastAction() {
if (!this._enabled) return;
const action = this._lastAction;
if (action) action.committed = true;
}
signal(pageAlias, frame, signal) {
if (!this._enabled) return;
// Signal either arrives while action is being performed or shortly after.
if (this._currentAction) {
this._currentAction.action.signals.push(signal);
return;
}
if (this._lastAction && !this._lastAction.committed) {
const signals = this._lastAction.action.signals;
if (signal.name === 'navigation' && signals.length && signals[signals.length - 1].name === 'download') return;
if (signal.name === 'download' && signals.length && signals[signals.length - 1].name === 'navigation') signals.length = signals.length - 1;
this._lastAction.action.signals.push(signal);
this.emit('change');
return;
}
if (signal.name === 'navigation') {
this.addAction({
frame: {
pageAlias,
isMainFrame: frame._page.mainFrame() === frame,
url: frame.url()
},
committed: true,
action: {
name: 'navigate',
url: frame.url(),
signals: []
}
});
}
}
generateStructure(languageGenerator) {
const header = languageGenerator.generateHeader(this._options);
const footer = languageGenerator.generateFooter(this._options.saveStorage);
const actions = this._actions.map(a => languageGenerator.generateAction(a)).filter(Boolean);
const text = [header, ...actions, footer].join('\n');
return {
header,
footer,
actions,
text
};
}
}
exports.CodeGenerator = CodeGenerator;

View File

@@ -0,0 +1,307 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.CSharpLanguageGenerator = void 0;
var _language = require("./language");
var _utils = require("./utils");
var _stringUtils = require("../../utils/isomorphic/stringUtils");
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 deviceDescriptors = require('../deviceDescriptorsSource.json');
class CSharpLanguageGenerator {
constructor(mode) {
this.id = void 0;
this.groupName = '.NET C#';
this.name = void 0;
this.highlighter = 'csharp';
this._mode = void 0;
if (mode === 'library') {
this.name = 'Library';
this.id = 'csharp';
} else if (mode === 'mstest') {
this.name = 'MSTest';
this.id = 'csharp-mstest';
} else if (mode === 'nunit') {
this.name = 'NUnit';
this.id = 'csharp-nunit';
} else {
throw new Error(`Unknown C# language mode: ${mode}`);
}
this._mode = mode;
}
generateAction(actionInContext) {
const action = this._generateActionInner(actionInContext);
if (action) return action + '\n';
return '';
}
_generateActionInner(actionInContext) {
const action = actionInContext.action;
if (this._mode !== 'library' && (action.name === 'openPage' || action.name === 'closePage')) return '';
let pageAlias = actionInContext.frame.pageAlias;
if (this._mode !== 'library') pageAlias = pageAlias.replace('page', 'Page');
const formatter = new CSharpFormatter(8);
if (action.name === 'openPage') {
formatter.add(`var ${pageAlias} = await context.NewPageAsync();`);
if (action.url && action.url !== 'about:blank' && action.url !== 'chrome://newtab/') formatter.add(`await ${pageAlias}.GotoAsync(${quote(action.url)});`);
return formatter.format();
}
let subject;
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => `.FrameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.Frame(${quote(actionInContext.frame.name)})`;
} else {
subject = `${pageAlias}.FrameByUrl(${quote(actionInContext.frame.url)})`;
}
const signals = (0, _language.toSignalMap)(action);
if (signals.dialog) {
formatter.add(` void ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler(object sender, IDialog dialog)
{
Console.WriteLine($"Dialog message: {dialog.Message}");
dialog.DismissAsync();
${pageAlias}.Dialog -= ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler;
}
${pageAlias}.Dialog += ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler;`);
}
const lines = [];
const actionCall = this._generateActionCall(action, actionInContext.frame.isMainFrame);
lines.push(`await ${subject}.${actionCall};`);
if (signals.download) {
lines.unshift(`var download${signals.download.downloadAlias} = await ${pageAlias}.RunAndWaitForDownloadAsync(async () =>\n{`);
lines.push(`});`);
}
if (signals.popup) {
lines.unshift(`var ${signals.popup.popupAlias} = await ${pageAlias}.RunAndWaitForPopupAsync(async () =>\n{`);
lines.push(`});`);
}
for (const line of lines) formatter.add(line);
return formatter.format();
}
_generateActionCall(action, isPage) {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
case 'closePage':
return 'CloseAsync()';
case 'click':
{
let method = 'Click';
if (action.clickCount === 2) method = 'DblClick';
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const options = {};
if (action.button !== 'left') options.button = action.button;
if (modifiers.length) options.modifiers = modifiers;
if (action.clickCount > 2) options.clickCount = action.clickCount;
if (action.position) options.position = action.position;
if (!Object.entries(options).length) return this._asLocator(action.selector) + `.${method}Async()`;
const optionsString = formatObject(options, ' ', 'Locator' + method + 'Options');
return this._asLocator(action.selector) + `.${method}Async(${optionsString})`;
}
case 'check':
return this._asLocator(action.selector) + `.CheckAsync()`;
case 'uncheck':
return this._asLocator(action.selector) + `.UncheckAsync()`;
case 'fill':
return this._asLocator(action.selector) + `.FillAsync(${quote(action.text)})`;
case 'setInputFiles':
return this._asLocator(action.selector) + `.SetInputFilesAsync(${formatObject(action.files)})`;
case 'press':
{
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return this._asLocator(action.selector) + `.PressAsync(${quote(shortcut)})`;
}
case 'navigate':
return `GotoAsync(${quote(action.url)})`;
case 'select':
return this._asLocator(action.selector) + `.SelectOptionAsync(${formatObject(action.options)})`;
}
}
_asLocator(selector) {
return (0, _locatorGenerators.asLocator)('csharp', selector);
}
generateHeader(options) {
if (this._mode === 'library') return this.generateStandaloneHeader(options);
return this.generateTestRunnerHeader(options);
}
generateStandaloneHeader(options) {
const formatter = new CSharpFormatter(0);
formatter.add(`
using Microsoft.Playwright;
using System;
using System.Threading.Tasks;
class Program
{
public static async Task Main()
{
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
formatter.newLine();
return formatter.format();
}
generateTestRunnerHeader(options) {
const formatter = new CSharpFormatter(0);
formatter.add(`
using Microsoft.Playwright.${this._mode === 'nunit' ? 'NUnit' : 'MSTest'};
using Microsoft.Playwright;
${this._mode === 'nunit' ? `[Parallelizable(ParallelScope.Self)]
[TestFixture]` : '[TestClass]'}
public class Tests : PageTest
{`);
const formattedContextOptions = formatContextOptions(options.contextOptions, options.deviceName);
if (formattedContextOptions) {
formatter.add(`public override BrowserNewContextOptions ContextOptions()
{
return ${formattedContextOptions};
}`);
formatter.newLine();
}
formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
public async Task MyTest()
{`);
return formatter.format();
}
generateFooter(saveStorage) {
const storageStateLine = saveStorage ? `\n await context.StorageStateAsync(new BrowserContextStorageStateOptions\n {\n Path = ${quote(saveStorage)}\n });\n` : '';
return `${storageStateLine} }
}\n`;
}
}
exports.CSharpLanguageGenerator = CSharpLanguageGenerator;
function formatObject(value, indent = ' ', name = '') {
if (typeof value === 'string') {
if (['permissions', 'colorScheme', 'modifiers', 'button', 'recordHarContent', 'recordHarMode', 'serviceWorkers'].includes(name)) return `${getClassName(name)}.${toPascal(value)}`;
return quote(value);
}
if (Array.isArray(value)) return `new[] { ${value.map(o => formatObject(o, indent, name)).join(', ')} }`;
if (typeof value === 'object') {
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length) return name ? `new ${getClassName(name)}` : '';
const tokens = [];
for (const key of keys) {
const property = getPropertyName(key);
tokens.push(`${property} = ${formatObject(value[key], indent, key)},`);
}
if (name) return `new ${getClassName(name)}\n{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`;
return `{\n${indent}${tokens.join(`\n${indent}`)}\n${indent}}`;
}
if (name === 'latitude' || name === 'longitude') return String(value) + 'm';
return String(value);
}
function getClassName(value) {
switch (value) {
case 'viewport':
return 'ViewportSize';
case 'proxy':
return 'ProxySettings';
case 'permissions':
return 'ContextPermission';
case 'modifiers':
return 'KeyboardModifier';
case 'button':
return 'MouseButton';
case 'recordHarMode':
return 'HarMode';
case 'recordHarContent':
return 'HarContentPolicy';
case 'serviceWorkers':
return 'ServiceWorkerPolicy';
default:
return toPascal(value);
}
}
function getPropertyName(key) {
switch (key) {
case 'storageState':
return 'StorageStatePath';
case 'viewport':
return 'ViewportSize';
default:
return toPascal(key);
}
}
function toPascal(value) {
return value[0].toUpperCase() + value.slice(1);
}
function convertContextOptions(options) {
const result = {
...options
};
if (options.recordHar) {
result['recordHarPath'] = options.recordHar.path;
result['recordHarContent'] = options.recordHar.content;
result['recordHarMode'] = options.recordHar.mode;
result['recordHarOmitContent'] = options.recordHar.omitContent;
result['recordHarUrlFilter'] = options.recordHar.urlFilter;
delete result.recordHar;
}
return result;
}
function formatContextOptions(options, deviceName) {
const device = deviceName && deviceDescriptors[deviceName];
if (!device) {
if (!Object.entries(options).length) return '';
return formatObject(convertContextOptions(options), ' ', 'BrowserNewContextOptions');
}
options = (0, _language.sanitizeDeviceOptions)(device, options);
if (!Object.entries(options).length) return `playwright.Devices[${quote(deviceName)}]`;
return formatObject(convertContextOptions(options), ' ', `BrowserNewContextOptions(playwright.Devices[${quote(deviceName)}])`);
}
class CSharpFormatter {
constructor(offset = 0) {
this._baseIndent = void 0;
this._baseOffset = void 0;
this._lines = [];
this._baseIndent = ' '.repeat(4);
this._baseOffset = ' '.repeat(offset);
}
prepend(text) {
this._lines = text.trim().split('\n').map(line => line.trim()).concat(this._lines);
}
add(text) {
this._lines.push(...text.trim().split('\n').map(line => line.trim()));
}
newLine() {
this._lines.push('');
}
format() {
let spaces = '';
let previousLine = '';
return this._lines.map(line => {
if (line === '') return line;
if (line.startsWith('}') || line.startsWith(']') || line.includes('});') || line === ');') spaces = spaces.substring(this._baseIndent.length);
const extraSpaces = /^(for|while|if).*\(.*\)$/.test(previousLine) ? this._baseIndent : '';
previousLine = line;
line = spaces + extraSpaces + line;
if (line.endsWith('{') || line.endsWith('[') || line.endsWith('(')) spaces += this._baseIndent;
if (line.endsWith('));')) spaces = spaces.substring(this._baseIndent.length);
return this._baseOffset + line;
}).join('\n');
}
}
function quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}

View File

@@ -0,0 +1,210 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JavaLanguageGenerator = void 0;
var _language = require("./language");
var _utils = require("./utils");
var _javascript = require("./javascript");
var _stringUtils = require("../../utils/isomorphic/stringUtils");
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 deviceDescriptors = require('../deviceDescriptorsSource.json');
class JavaLanguageGenerator {
constructor() {
this.id = 'java';
this.groupName = 'Java';
this.name = 'Library';
this.highlighter = 'java';
}
generateAction(actionInContext) {
const action = actionInContext.action;
const pageAlias = actionInContext.frame.pageAlias;
const formatter = new _javascript.JavaScriptFormatter(6);
if (action.name === 'openPage') {
formatter.add(`Page ${pageAlias} = context.newPage();`);
if (action.url && action.url !== 'about:blank' && action.url !== 'chrome://newtab/') formatter.add(`${pageAlias}.navigate(${quote(action.url)});`);
return formatter.format();
}
let subject;
let inFrameLocator = false;
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
inFrameLocator = true;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${quote(actionInContext.frame.name)})`;
} else {
subject = `${pageAlias}.frameByUrl(${quote(actionInContext.frame.url)})`;
}
const signals = (0, _language.toSignalMap)(action);
if (signals.dialog) {
formatter.add(` ${pageAlias}.onceDialog(dialog -> {
System.out.println(String.format("Dialog message: %s", dialog.message()));
dialog.dismiss();
});`);
}
const actionCall = this._generateActionCall(action, inFrameLocator);
let code = `${subject}.${actionCall};`;
if (signals.popup) {
code = `Page ${signals.popup.popupAlias} = ${pageAlias}.waitForPopup(() -> {
${code}
});`;
}
if (signals.download) {
code = `Download download${signals.download.downloadAlias} = ${pageAlias}.waitForDownload(() -> {
${code}
});`;
}
formatter.add(code);
return formatter.format();
}
_generateActionCall(action, inFrameLocator) {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
case 'closePage':
return 'close()';
case 'click':
{
let method = 'click';
if (action.clickCount === 2) method = 'dblclick';
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const options = {};
if (action.button !== 'left') options.button = action.button;
if (modifiers.length) options.modifiers = modifiers;
if (action.clickCount > 2) options.clickCount = action.clickCount;
if (action.position) options.position = action.position;
const optionsText = formatClickOptions(options);
return this._asLocator(action.selector, inFrameLocator) + `.${method}(${optionsText})`;
}
case 'check':
return this._asLocator(action.selector, inFrameLocator) + `.check()`;
case 'uncheck':
return this._asLocator(action.selector, inFrameLocator) + `.uncheck()`;
case 'fill':
return this._asLocator(action.selector, inFrameLocator) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return this._asLocator(action.selector, inFrameLocator) + `.setInputFiles(${formatPath(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press':
{
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return this._asLocator(action.selector, inFrameLocator) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `navigate(${quote(action.url)})`;
case 'select':
return this._asLocator(action.selector, inFrameLocator) + `.selectOption(${formatSelectOption(action.options.length > 1 ? action.options : action.options[0])})`;
}
}
_asLocator(selector, inFrameLocator) {
return (0, _locatorGenerators.asLocator)('java', selector, inFrameLocator);
}
generateHeader(options) {
const formatter = new _javascript.JavaScriptFormatter();
formatter.add(`
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import java.util.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
return formatter.format();
}
generateFooter(saveStorage) {
const storageStateLine = saveStorage ? `\n context.storageState(new BrowserContext.StorageStateOptions().setPath(${quote(saveStorage)}));\n` : '';
return `${storageStateLine} }
}
}`;
}
}
exports.JavaLanguageGenerator = JavaLanguageGenerator;
function formatPath(files) {
if (Array.isArray(files)) {
if (files.length === 0) return 'new Path[0]';
return `new Path[] {${files.map(s => 'Paths.get(' + quote(s) + ')').join(', ')}}`;
}
return `Paths.get(${quote(files)})`;
}
function formatSelectOption(options) {
if (Array.isArray(options)) {
if (options.length === 0) return 'new String[0]';
return `new String[] {${options.map(s => quote(s)).join(', ')}}`;
}
return quote(options);
}
function formatLaunchOptions(options) {
const lines = [];
if (!Object.keys(options).filter(key => options[key] !== undefined).length) return '';
lines.push('new BrowserType.LaunchOptions()');
if (options.channel) lines.push(` .setChannel(${quote(options.channel)})`);
if (typeof options.headless === 'boolean') lines.push(` .setHeadless(false)`);
return lines.join('\n');
}
function formatContextOptions(contextOptions, deviceName) {
var _options$recordHar, _options$recordHar2, _options$recordHar3, _options$recordHar4, _options$recordHar5, _options$recordHar6, _options$recordHar7;
const lines = [];
if (!Object.keys(contextOptions).length && !deviceName) return '';
const device = deviceName ? deviceDescriptors[deviceName] : {};
const options = {
...device,
...contextOptions
};
lines.push('new Browser.NewContextOptions()');
if (options.acceptDownloads) lines.push(` .setAcceptDownloads(true)`);
if (options.bypassCSP) lines.push(` .setBypassCSP(true)`);
if (options.colorScheme) lines.push(` .setColorScheme(ColorScheme.${options.colorScheme.toUpperCase()})`);
if (options.deviceScaleFactor) lines.push(` .setDeviceScaleFactor(${options.deviceScaleFactor})`);
if (options.geolocation) lines.push(` .setGeolocation(${options.geolocation.latitude}, ${options.geolocation.longitude})`);
if (options.hasTouch) lines.push(` .setHasTouch(${options.hasTouch})`);
if (options.isMobile) lines.push(` .setIsMobile(${options.isMobile})`);
if (options.locale) lines.push(` .setLocale(${quote(options.locale)})`);
if (options.proxy) lines.push(` .setProxy(new Proxy(${quote(options.proxy.server)}))`);
if ((_options$recordHar = options.recordHar) !== null && _options$recordHar !== void 0 && _options$recordHar.content) lines.push(` .setRecordHarContent(HarContentPolicy.${(_options$recordHar2 = options.recordHar) === null || _options$recordHar2 === void 0 ? void 0 : _options$recordHar2.content.toUpperCase()})`);
if ((_options$recordHar3 = options.recordHar) !== null && _options$recordHar3 !== void 0 && _options$recordHar3.mode) lines.push(` .setRecordHarMode(HarMode.${(_options$recordHar4 = options.recordHar) === null || _options$recordHar4 === void 0 ? void 0 : _options$recordHar4.mode.toUpperCase()})`);
if ((_options$recordHar5 = options.recordHar) !== null && _options$recordHar5 !== void 0 && _options$recordHar5.omitContent) lines.push(` .setRecordHarOmitContent(true)`);
if ((_options$recordHar6 = options.recordHar) !== null && _options$recordHar6 !== void 0 && _options$recordHar6.path) lines.push(` .setRecordHarPath(Paths.get(${quote(options.recordHar.path)}))`);
if ((_options$recordHar7 = options.recordHar) !== null && _options$recordHar7 !== void 0 && _options$recordHar7.urlFilter) lines.push(` .setRecordHarUrlFilter(${quote(options.recordHar.urlFilter)})`);
if (options.serviceWorkers) lines.push(` .setServiceWorkers(ServiceWorkerPolicy.${options.serviceWorkers.toUpperCase()})`);
if (options.storageState) lines.push(` .setStorageStatePath(Paths.get(${quote(options.storageState)}))`);
if (options.timezoneId) lines.push(` .setTimezoneId(${quote(options.timezoneId)})`);
if (options.userAgent) lines.push(` .setUserAgent(${quote(options.userAgent)})`);
if (options.viewport) lines.push(` .setViewportSize(${options.viewport.width}, ${options.viewport.height})`);
return lines.join('\n');
}
function formatClickOptions(options) {
const lines = [];
if (options.button) lines.push(` .setButton(MouseButton.${options.button.toUpperCase()})`);
if (options.modifiers) lines.push(` .setModifiers(Arrays.asList(${options.modifiers.map(m => `KeyboardModifier.${m.toUpperCase()}`).join(', ')}))`);
if (options.clickCount) lines.push(` .setClickCount(${options.clickCount})`);
if (options.position) lines.push(` .setPosition(${options.position.x}, ${options.position.y})`);
if (!lines.length) return '';
lines.unshift(`new Locator.ClickOptions()`);
return lines.join('\n');
}
function quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}

View File

@@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JavaScriptLanguageGenerator = exports.JavaScriptFormatter = void 0;
var _language = require("./language");
var _utils = require("./utils");
var _stringUtils = require("../../utils/isomorphic/stringUtils");
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 deviceDescriptors = require('../deviceDescriptorsSource.json');
class JavaScriptLanguageGenerator {
constructor(isTest) {
this.id = void 0;
this.groupName = 'Node.js';
this.name = void 0;
this.highlighter = 'javascript';
this._isTest = void 0;
this.id = isTest ? 'playwright-test' : 'javascript';
this.name = isTest ? 'Test Runner' : 'Library';
this._isTest = isTest;
}
generateAction(actionInContext) {
const action = actionInContext.action;
if (this._isTest && (action.name === 'openPage' || action.name === 'closePage')) return '';
const pageAlias = actionInContext.frame.pageAlias;
const formatter = new JavaScriptFormatter(2);
if (action.name === 'openPage') {
formatter.add(`const ${pageAlias} = await context.newPage();`);
if (action.url && action.url !== 'about:blank' && action.url !== 'chrome://newtab/') formatter.add(`await ${pageAlias}.goto(${quote(action.url)});`);
return formatter.format();
}
let subject;
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => `.frameLocator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${formatObject({
name: actionInContext.frame.name
})})`;
} else {
subject = `${pageAlias}.frame(${formatObject({
url: actionInContext.frame.url
})})`;
}
const signals = (0, _language.toSignalMap)(action);
if (signals.dialog) {
formatter.add(` ${pageAlias}.once('dialog', dialog => {
console.log(\`Dialog message: $\{dialog.message()}\`);
dialog.dismiss().catch(() => {});
});`);
}
if (signals.popup) formatter.add(`const ${signals.popup.popupAlias}Promise = ${pageAlias}.waitForEvent('popup');`);
if (signals.download) formatter.add(`const download${signals.download.downloadAlias}Promise = ${pageAlias}.waitForEvent('download');`);
const actionCall = this._generateActionCall(action);
formatter.add(`await ${subject}.${actionCall};`);
if (signals.popup) formatter.add(`const ${signals.popup.popupAlias} = await ${signals.popup.popupAlias}Promise;`);
if (signals.download) formatter.add(`const download${signals.download.downloadAlias} = await download${signals.download.downloadAlias}Promise;`);
return formatter.format();
}
_generateActionCall(action) {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
case 'closePage':
return 'close()';
case 'click':
{
let method = 'click';
if (action.clickCount === 2) method = 'dblclick';
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const options = {};
if (action.button !== 'left') options.button = action.button;
if (modifiers.length) options.modifiers = modifiers;
if (action.clickCount > 2) options.clickCount = action.clickCount;
if (action.position) options.position = action.position;
const optionsString = formatOptions(options, false);
return this._asLocator(action.selector) + `.${method}(${optionsString})`;
}
case 'check':
return this._asLocator(action.selector) + `.check()`;
case 'uncheck':
return this._asLocator(action.selector) + `.uncheck()`;
case 'fill':
return this._asLocator(action.selector) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return this._asLocator(action.selector) + `.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press':
{
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return this._asLocator(action.selector) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `goto(${quote(action.url)})`;
case 'select':
return this._asLocator(action.selector) + `.selectOption(${formatObject(action.options.length > 1 ? action.options : action.options[0])})`;
}
}
_asLocator(selector) {
return (0, _locatorGenerators.asLocator)('javascript', selector);
}
generateHeader(options) {
if (this._isTest) return this.generateTestHeader(options);
return this.generateStandaloneHeader(options);
}
generateFooter(saveStorage) {
if (this._isTest) return this.generateTestFooter(saveStorage);
return this.generateStandaloneFooter(saveStorage);
}
generateTestHeader(options) {
const formatter = new JavaScriptFormatter();
const useText = formatContextOptions(options.contextOptions, options.deviceName);
formatter.add(`
import { test, expect${options.deviceName ? ', devices' : ''} } from '@playwright/test';
${useText ? '\ntest.use(' + useText + ');\n' : ''}
test('test', async ({ page }) => {`);
return formatter.format();
}
generateTestFooter(saveStorage) {
return `});`;
}
generateStandaloneHeader(options) {
const formatter = new JavaScriptFormatter();
formatter.add(`
const { ${options.browserName}${options.deviceName ? ', devices' : ''} } = require('playwright');
(async () => {
const browser = await ${options.browserName}.launch(${formatObjectOrVoid(options.launchOptions)});
const context = await browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
return formatter.format();
}
generateStandaloneFooter(saveStorage) {
const storageStateLine = saveStorage ? `\n await context.storageState({ path: ${quote(saveStorage)} });` : '';
return `\n // ---------------------${storageStateLine}
await context.close();
await browser.close();
})();`;
}
}
exports.JavaScriptLanguageGenerator = JavaScriptLanguageGenerator;
function formatOptions(value, hasArguments) {
const keys = Object.keys(value);
if (!keys.length) return '';
return (hasArguments ? ', ' : '') + formatObject(value);
}
function formatObject(value, indent = ' ') {
if (typeof value === 'string') return quote(value);
if (Array.isArray(value)) return `[${value.map(o => formatObject(o)).join(', ')}]`;
if (typeof value === 'object') {
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length) return '{}';
const tokens = [];
for (const key of keys) tokens.push(`${key}: ${formatObject(value[key])}`);
return `{\n${indent}${tokens.join(`,\n${indent}`)}\n}`;
}
return String(value);
}
function formatObjectOrVoid(value, indent = ' ') {
const result = formatObject(value, indent);
return result === '{}' ? '' : result;
}
function formatContextOptions(options, deviceName) {
const device = deviceName && deviceDescriptors[deviceName];
if (!device) return formatObjectOrVoid(options);
// Filter out all the properties from the device descriptor.
let serializedObject = formatObjectOrVoid((0, _language.sanitizeDeviceOptions)(device, options));
// When there are no additional context options, we still want to spread the device inside.
if (!serializedObject) serializedObject = '{\n}';
const lines = serializedObject.split('\n');
lines.splice(1, 0, `...devices[${quote(deviceName)}],`);
return lines.join('\n');
}
class JavaScriptFormatter {
constructor(offset = 0) {
this._baseIndent = void 0;
this._baseOffset = void 0;
this._lines = [];
this._baseIndent = ' '.repeat(2);
this._baseOffset = ' '.repeat(offset);
}
prepend(text) {
this._lines = text.trim().split('\n').map(line => line.trim()).concat(this._lines);
}
add(text) {
this._lines.push(...text.trim().split('\n').map(line => line.trim()));
}
newLine() {
this._lines.push('');
}
format() {
let spaces = '';
let previousLine = '';
return this._lines.map(line => {
if (line === '') return line;
if (line.startsWith('}') || line.startsWith(']')) spaces = spaces.substring(this._baseIndent.length);
const extraSpaces = /^(for|while|if|try).*\(.*\)$/.test(previousLine) ? this._baseIndent : '';
previousLine = line;
const callCarryOver = line.startsWith('.set');
line = spaces + extraSpaces + (callCarryOver ? this._baseIndent : '') + line;
if (line.endsWith('{') || line.endsWith('[')) spaces += this._baseIndent;
return this._baseOffset + line;
}).join('\n');
}
}
exports.JavaScriptFormatter = JavaScriptFormatter;
function quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\'');
}

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.JsonlLanguageGenerator = void 0;
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.
*/
class JsonlLanguageGenerator {
constructor() {
this.id = 'jsonl';
this.groupName = '';
this.name = 'JSONL';
this.highlighter = 'javascript';
}
generateAction(actionInContext) {
const locator = actionInContext.action.selector ? JSON.parse((0, _locatorGenerators.asLocator)('jsonl', actionInContext.action.selector)) : undefined;
const entry = {
...actionInContext.action,
pageAlias: actionInContext.frame.pageAlias,
locator
};
return JSON.stringify(entry);
}
generateHeader(options) {
return JSON.stringify(options);
}
generateFooter(saveStorage) {
return '';
}
}
exports.JsonlLanguageGenerator = JsonlLanguageGenerator;

View File

@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.sanitizeDeviceOptions = sanitizeDeviceOptions;
exports.toSignalMap = toSignalMap;
/**
* 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 sanitizeDeviceOptions(device, options) {
// Filter out all the properties from the device descriptor.
const cleanedOptions = {};
for (const property in options) {
if (JSON.stringify(device[property]) !== JSON.stringify(options[property])) cleanedOptions[property] = options[property];
}
return cleanedOptions;
}
function toSignalMap(action) {
let popup;
let download;
let dialog;
for (const signal of action.signals) {
if (signal.name === 'popup') popup = signal;else if (signal.name === 'download') download = signal;else if (signal.name === 'dialog') dialog = signal;
}
return {
popup,
download,
dialog
};
}

View File

@@ -0,0 +1,273 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PythonLanguageGenerator = void 0;
var _language = require("./language");
var _utils = require("./utils");
var _stringUtils = require("../../utils/isomorphic/stringUtils");
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 deviceDescriptors = require('../deviceDescriptorsSource.json');
class PythonLanguageGenerator {
constructor(isAsync, isPyTest) {
this.id = void 0;
this.groupName = 'Python';
this.name = void 0;
this.highlighter = 'python';
this._awaitPrefix = void 0;
this._asyncPrefix = void 0;
this._isAsync = void 0;
this._isPyTest = void 0;
this.id = isPyTest ? 'python-pytest' : isAsync ? 'python-async' : 'python';
this.name = isPyTest ? 'Pytest' : isAsync ? 'Library Async' : 'Library';
this._isAsync = isAsync;
this._isPyTest = isPyTest;
this._awaitPrefix = isAsync ? 'await ' : '';
this._asyncPrefix = isAsync ? 'async ' : '';
}
generateAction(actionInContext) {
const action = actionInContext.action;
if (this._isPyTest && (action.name === 'openPage' || action.name === 'closePage')) return '';
const pageAlias = actionInContext.frame.pageAlias;
const formatter = new PythonFormatter(4);
if (action.name === 'openPage') {
formatter.add(`${pageAlias} = ${this._awaitPrefix}context.new_page()`);
if (action.url && action.url !== 'about:blank' && action.url !== 'chrome://newtab/') formatter.add(`${this._awaitPrefix}${pageAlias}.goto(${quote(action.url)})`);
return formatter.format();
}
let subject;
if (actionInContext.frame.isMainFrame) {
subject = pageAlias;
} else if (actionInContext.frame.selectorsChain && action.name !== 'navigate') {
const locators = actionInContext.frame.selectorsChain.map(selector => `.frame_locator(${quote(selector)})`);
subject = `${pageAlias}${locators.join('')}`;
} else if (actionInContext.frame.name) {
subject = `${pageAlias}.frame(${formatOptions({
name: actionInContext.frame.name
}, false)})`;
} else {
subject = `${pageAlias}.frame(${formatOptions({
url: actionInContext.frame.url
}, false)})`;
}
const signals = (0, _language.toSignalMap)(action);
if (signals.dialog) formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`);
const actionCall = this._generateActionCall(action);
let code = `${this._awaitPrefix}${subject}.${actionCall}`;
if (signals.popup) {
code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info {
${code}
}
${signals.popup.popupAlias} = ${this._awaitPrefix}${signals.popup.popupAlias}_info.value`;
}
if (signals.download) {
code = `${this._asyncPrefix}with ${pageAlias}.expect_download() as download${signals.download.downloadAlias}_info {
${code}
}
download${signals.download.downloadAlias} = ${this._awaitPrefix}download${signals.download.downloadAlias}_info.value`;
}
formatter.add(code);
return formatter.format();
}
_generateActionCall(action) {
switch (action.name) {
case 'openPage':
throw Error('Not reached');
case 'closePage':
return 'close()';
case 'click':
{
let method = 'click';
if (action.clickCount === 2) method = 'dblclick';
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const options = {};
if (action.button !== 'left') options.button = action.button;
if (modifiers.length) options.modifiers = modifiers;
if (action.clickCount > 2) options.clickCount = action.clickCount;
if (action.position) options.position = action.position;
const optionsString = formatOptions(options, false);
return this._asLocator(action.selector) + `.${method}(${optionsString})`;
}
case 'check':
return this._asLocator(action.selector) + `.check()`;
case 'uncheck':
return this._asLocator(action.selector) + `.uncheck()`;
case 'fill':
return this._asLocator(action.selector) + `.fill(${quote(action.text)})`;
case 'setInputFiles':
return this._asLocator(action.selector) + `.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`;
case 'press':
{
const modifiers = (0, _utils.toModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join('+');
return this._asLocator(action.selector) + `.press(${quote(shortcut)})`;
}
case 'navigate':
return `goto(${quote(action.url)})`;
case 'select':
return this._asLocator(action.selector) + `.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`;
}
}
_asLocator(selector) {
return (0, _locatorGenerators.asLocator)('python', selector);
}
generateHeader(options) {
const formatter = new PythonFormatter();
if (this._isPyTest) {
const contextOptions = formatContextOptions(options.contextOptions, options.deviceName, true /* asDict */);
const fixture = contextOptions ? `
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args, playwright) {
return {${contextOptions}}
}
` : '';
formatter.add(`${options.deviceName ? 'import pytest\n' : ''}
from playwright.sync_api import Page, expect
${fixture}
def test_example(page: Page) -> None {`);
} else if (this._isAsync) {
formatter.add(`
import asyncio
from playwright.async_api import Playwright, async_playwright, expect
async def run(playwright: Playwright) -> None {
browser = await playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = await browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
} else {
formatter.add(`
from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None {
browser = playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
}
return formatter.format();
}
generateFooter(saveStorage) {
if (this._isPyTest) {
return '';
} else if (this._isAsync) {
const storageStateLine = saveStorage ? `\n await context.storage_state(path=${quote(saveStorage)})` : '';
return `\n # ---------------------${storageStateLine}
await context.close()
await browser.close()
async def main() -> None:
async with async_playwright() as playwright:
await run(playwright)
asyncio.run(main())
`;
} else {
const storageStateLine = saveStorage ? `\n context.storage_state(path=${quote(saveStorage)})` : '';
return `\n # ---------------------${storageStateLine}
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)
`;
}
}
}
exports.PythonLanguageGenerator = PythonLanguageGenerator;
function formatValue(value) {
if (value === false) return 'False';
if (value === true) return 'True';
if (value === undefined) return 'None';
if (Array.isArray(value)) return `[${value.map(formatValue).join(', ')}]`;
if (typeof value === 'string') return quote(value);
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}
function formatOptions(value, hasArguments, asDict) {
const keys = Object.keys(value).filter(key => value[key] !== undefined).sort();
if (!keys.length) return '';
return (hasArguments ? ', ' : '') + keys.map(key => {
if (asDict) return `"${(0, _stringUtils.toSnakeCase)(key)}": ${formatValue(value[key])}`;
return `${(0, _stringUtils.toSnakeCase)(key)}=${formatValue(value[key])}`;
}).join(', ');
}
function convertContextOptions(options) {
const result = {
...options
};
if (options.recordHar) {
result['record_har_path'] = options.recordHar.path;
result['record_har_content'] = options.recordHar.content;
result['record_har_mode'] = options.recordHar.mode;
result['record_har_omit_content'] = options.recordHar.omitContent;
result['record_har_url_filter'] = options.recordHar.urlFilter;
delete result.recordHar;
}
return result;
}
function formatContextOptions(options, deviceName, asDict) {
const device = deviceName && deviceDescriptors[deviceName];
if (!device) return formatOptions(convertContextOptions(options), false, asDict);
return `**playwright.devices[${quote(deviceName)}]` + formatOptions(convertContextOptions((0, _language.sanitizeDeviceOptions)(device, options)), true, asDict);
}
class PythonFormatter {
constructor(offset = 0) {
this._baseIndent = void 0;
this._baseOffset = void 0;
this._lines = [];
this._baseIndent = ' '.repeat(4);
this._baseOffset = ' '.repeat(offset);
}
prepend(text) {
this._lines = text.trim().split('\n').map(line => line.trim()).concat(this._lines);
}
add(text) {
this._lines.push(...text.trim().split('\n').map(line => line.trim()));
}
newLine() {
this._lines.push('');
}
format() {
let spaces = '';
const lines = [];
this._lines.forEach(line => {
if (line === '') return lines.push(line);
if (line === '}') {
spaces = spaces.substring(this._baseIndent.length);
return;
}
line = spaces + line;
if (line.endsWith('{')) {
spaces += this._baseIndent;
line = line.substring(0, line.length - 1).trimEnd() + ':';
}
return lines.push(this._baseOffset + line);
});
return lines.join('\n');
}
}
function quote(text) {
return (0, _stringUtils.escapeWithQuotes)(text, '\"');
}

View File

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

View File

@@ -0,0 +1,167 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RecorderApp = exports.EmptyRecorderApp = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _progress = require("../progress");
var _events = require("events");
var _instrumentation = require("../instrumentation");
var _utils = require("../../utils");
var _utilsBundle = require("../../utilsBundle");
var _crApp = require("../chromium/crApp");
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.
*/
class EmptyRecorderApp extends _events.EventEmitter {
async close() {}
async setPaused(paused) {}
async setMode(mode) {}
async setFileIfNeeded(file) {}
async setSelector(selector, focus) {}
async updateCallLogs(callLogs) {}
async setSources(sources) {}
}
exports.EmptyRecorderApp = EmptyRecorderApp;
class RecorderApp extends _events.EventEmitter {
constructor(recorder, page, wsEndpoint) {
super();
this._page = void 0;
this.wsEndpoint = void 0;
this._recorder = void 0;
this.setMaxListeners(0);
this._recorder = recorder;
this._page = page;
this.wsEndpoint = wsEndpoint;
}
async close() {
await this._page.context().close((0, _instrumentation.serverSideCallMetadata)());
}
async _init() {
await (0, _crApp.installAppIcon)(this._page);
await (0, _crApp.syncLocalStorageWithSettings)(this._page, 'recorder');
await this._page._setServerRequestInterceptor(route => {
if (!route.request().url().startsWith('https://playwright/')) return false;
const uri = route.request().url().substring('https://playwright/'.length);
const file = require.resolve('../../webpack/recorder/' + uri);
_fs.default.promises.readFile(file).then(buffer => {
route.fulfill({
requestUrl: route.request().url(),
status: 200,
headers: [{
name: 'Content-Type',
value: _utilsBundle.mime.getType(_path.default.extname(file)) || 'application/octet-stream'
}],
body: buffer.toString('base64'),
isBase64: true
});
});
return true;
});
await this._page.exposeBinding('dispatch', false, (_, data) => this.emit('event', data));
this._page.once('close', () => {
this.emit('close');
this._page.context().close((0, _instrumentation.serverSideCallMetadata)()).catch(() => {});
});
const mainFrame = this._page.mainFrame();
await mainFrame.goto((0, _instrumentation.serverSideCallMetadata)(), 'https://playwright/index.html');
}
static async open(recorder, inspectedContext, handleSIGINT) {
const sdkLanguage = inspectedContext.attribution.playwright.options.sdkLanguage;
const headed = !!inspectedContext._browser.options.headful;
const recorderPlaywright = require('../playwright').createPlaywright({
sdkLanguage: 'javascript',
isInternalPlaywright: true
});
const args = ['--app=data:text/html,', '--window-size=600,600', '--window-position=1020,10', '--test-type='];
if (process.env.PWTEST_RECORDER_PORT) args.push(`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`);
const context = await recorderPlaywright.chromium.launchPersistentContext((0, _instrumentation.serverSideCallMetadata)(), '', {
channel: (0, _registry.findChromiumChannel)(sdkLanguage),
args,
noDefaultViewport: true,
ignoreDefaultArgs: ['--enable-automation'],
colorScheme: 'no-override',
headless: !!process.env.PWTEST_CLI_HEADLESS || (0, _utils.isUnderTest)() && !headed,
useWebSocket: !!process.env.PWTEST_RECORDER_PORT,
handleSIGINT
});
const controller = new _progress.ProgressController((0, _instrumentation.serverSideCallMetadata)(), context._browser);
await controller.run(async progress => {
await context._browser._defaultContext._loadDefaultContextAsIs(progress);
});
const [page] = context.pages();
const result = new RecorderApp(recorder, page, context._browser.options.wsEndpoint);
await result._init();
return result;
}
async setMode(mode) {
await this._page.mainFrame().evaluateExpression((mode => {
window.playwrightSetMode(mode);
}).toString(), {
isFunction: true
}, mode).catch(() => {});
}
async setFileIfNeeded(file) {
await this._page.mainFrame().evaluateExpression((file => {
window.playwrightSetFileIfNeeded(file);
}).toString(), {
isFunction: true
}, file).catch(() => {});
}
async setPaused(paused) {
await this._page.mainFrame().evaluateExpression((paused => {
window.playwrightSetPaused(paused);
}).toString(), {
isFunction: true
}, paused).catch(() => {});
}
async setSources(sources) {
await this._page.mainFrame().evaluateExpression((sources => {
window.playwrightSetSources(sources);
}).toString(), {
isFunction: true
}, sources).catch(() => {});
// Testing harness for runCLI mode.
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) process._didSetSourcesForTest(sources[0].text);
}
async setSelector(selector, focus) {
if (focus) {
this._recorder.setMode('none');
this._page.bringToFront();
}
await this._page.mainFrame().evaluateExpression((arg => {
window.playwrightSetSelector(arg.selector, arg.focus);
}).toString(), {
isFunction: true
}, {
selector,
focus
}).catch(() => {});
}
async updateCallLogs(callLogs) {
await this._page.mainFrame().evaluateExpression((callLogs => {
window.playwrightUpdateLogs(callLogs);
}).toString(), {
isFunction: true
}, callLogs).catch(() => {});
}
}
exports.RecorderApp = RecorderApp;

View File

@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.metadataToCallLog = metadataToCallLog;
/**
* 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 metadataToCallLog(metadata, status) {
var _metadata$params, _metadata$params2, _metadata$error, _metadata$error$error;
let title = metadata.apiName || metadata.method;
if (metadata.method === 'waitForEventInfo') title += `(${metadata.params.info.event})`;
title = title.replace('object.expect', 'expect');
if (metadata.error) status = 'error';
const params = {
url: (_metadata$params = metadata.params) === null || _metadata$params === void 0 ? void 0 : _metadata$params.url,
selector: (_metadata$params2 = metadata.params) === null || _metadata$params2 === void 0 ? void 0 : _metadata$params2.selector
};
let duration = metadata.endTime ? metadata.endTime - metadata.startTime : undefined;
if (typeof duration === 'number' && metadata.pauseStartTime && metadata.pauseEndTime) {
duration -= metadata.pauseEndTime - metadata.pauseStartTime;
duration = Math.max(duration, 0);
}
const callLog = {
id: metadata.id,
messages: metadata.log,
title,
status,
error: (_metadata$error = metadata.error) === null || _metadata$error === void 0 ? void 0 : (_metadata$error$error = _metadata$error.error) === null || _metadata$error$error === void 0 ? void 0 : _metadata$error$error.message,
params,
duration
};
return callLog;
}

View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toClickOptions = toClickOptions;
exports.toModifiers = toModifiers;
/**
* 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 toClickOptions(action) {
let method = 'click';
if (action.clickCount === 2) method = 'dblclick';
const modifiers = toModifiers(action.modifiers);
const options = {};
if (action.button !== 'left') options.button = action.button;
if (modifiers.length) options.modifiers = modifiers;
if (action.clickCount > 2) options.clickCount = action.clickCount;
if (action.position) options.position = action.position;
return {
method,
options
};
}
function toModifiers(modifiers) {
const result = [];
if (modifiers & 1) result.push('Alt');
if (modifiers & 2) result.push('Control');
if (modifiers & 4) result.push('Meta');
if (modifiers & 8) result.push('Shift');
return result;
}

View File

@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.downloadBrowserWithProgressBar = downloadBrowserWithProgressBar;
exports.logPolitely = logPolitely;
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _child_process = _interopRequireDefault(require("child_process"));
var _userAgent = require("../../utils/userAgent");
var _fileUtils = require("../../utils/fileUtils");
var _debugLogger = require("../../common/debugLogger");
var _zipBundle = require("../../zipBundle");
var _manualPromise = require("../../utils/manualPromise");
var _utilsBundle = require("../../utilsBundle");
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.
*/
async function downloadBrowserWithProgressBar(title, browserDirectory, executablePath, downloadURLs, downloadFileName, downloadConnectionTimeout) {
if (await (0, _fileUtils.existsAsync)(browserDirectory)) {
// Already downloaded.
_debugLogger.debugLogger.log('install', `${title} is already downloaded.`);
return false;
}
const zipPath = _path.default.join(_os.default.tmpdir(), downloadFileName);
try {
const retryCount = 3;
for (let attempt = 1; attempt <= retryCount; ++attempt) {
_debugLogger.debugLogger.log('install', `downloading ${title} - attempt #${attempt}`);
const url = downloadURLs[(attempt - 1) % downloadURLs.length];
logPolitely(`Downloading ${title}` + _utilsBundle.colors.dim(` from ${url}`));
const {
error
} = await downloadFileOutOfProcess(url, zipPath, (0, _userAgent.getUserAgent)(), downloadConnectionTimeout);
if (!error) {
_debugLogger.debugLogger.log('install', `SUCCESS downloading ${title}`);
break;
}
const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || '';
_debugLogger.debugLogger.log('install', `attempt #${attempt} - ERROR: ${errorMessage}`);
if (attempt >= retryCount) throw error;
}
_debugLogger.debugLogger.log('install', `extracting archive`);
_debugLogger.debugLogger.log('install', `-- zip: ${zipPath}`);
_debugLogger.debugLogger.log('install', `-- location: ${browserDirectory}`);
await (0, _zipBundle.extract)(zipPath, {
dir: browserDirectory
});
if (executablePath) {
_debugLogger.debugLogger.log('install', `fixing permissions at ${executablePath}`);
await _fs.default.promises.chmod(executablePath, 0o755);
}
} catch (e) {
_debugLogger.debugLogger.log('install', `FAILED installation ${title} with error: ${e}`);
process.exitCode = 1;
throw e;
} finally {
if (await (0, _fileUtils.existsAsync)(zipPath)) await _fs.default.promises.unlink(zipPath);
}
logPolitely(`${title} downloaded to ${browserDirectory}`);
return true;
}
/**
* Node.js has a bug where the process can exit with 0 code even though there was an uncaught exception.
* Thats why we execute it in a separate process and check manually if the destination file exists.
* https://github.com/microsoft/playwright/issues/17394
*/
function downloadFileOutOfProcess(url, destinationPath, userAgent, downloadConnectionTimeout) {
const cp = _child_process.default.fork(_path.default.join(__dirname, 'oopDownloadMain.js'), [url, destinationPath, userAgent, String(downloadConnectionTimeout)]);
const promise = new _manualPromise.ManualPromise();
cp.on('message', message => {
if ((message === null || message === void 0 ? void 0 : message.method) === 'log') _debugLogger.debugLogger.log('install', message.params.message);
});
cp.on('exit', code => {
if (code !== 0) {
promise.resolve({
error: new Error(`Download failure, code=${code}`)
});
return;
}
if (!_fs.default.existsSync(destinationPath)) promise.resolve({
error: new Error(`Download failure, ${destinationPath} does not exist`)
});else promise.resolve({
error: null
});
});
cp.on('error', error => {
promise.resolve({
error
});
});
return promise;
}
function logPolitely(toBeLogged) {
const logLevel = process.env.npm_config_loglevel;
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel || '') > -1;
if (!logLevelDisplay) console.log(toBeLogged); // eslint-disable-line no-console
}

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