mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor: use browsers for launchers (#9937)
This commit is contained in:
parent
817288cd90
commit
c8f6adf9f3
@ -8,15 +8,15 @@ sidebar_label: ProductLauncher.launch
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class ProductLauncher {
|
class ProductLauncher {
|
||||||
launch(object: PuppeteerNodeLaunchOptions): Promise<Browser>;
|
launch(options?: PuppeteerNodeLaunchOptions): Promise<Browser>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ----------------------------------------------------------------------- | ----------- |
|
| --------- | ----------------------------------------------------------------------- | ------------ |
|
||||||
| object | [PuppeteerNodeLaunchOptions](./puppeteer.puppeteernodelaunchoptions.md) | |
|
| options | [PuppeteerNodeLaunchOptions](./puppeteer.puppeteernodelaunchoptions.md) | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
|
@ -28,4 +28,4 @@ The constructor for this class is marked as internal. Third-party code should no
|
|||||||
| ------------------------------------------------------------------------ | --------- | ----------- |
|
| ------------------------------------------------------------------------ | --------- | ----------- |
|
||||||
| [defaultArgs(object)](./puppeteer.productlauncher.defaultargs.md) | | |
|
| [defaultArgs(object)](./puppeteer.productlauncher.defaultargs.md) | | |
|
||||||
| [executablePath(channel)](./puppeteer.productlauncher.executablepath.md) | | |
|
| [executablePath(channel)](./puppeteer.productlauncher.executablepath.md) | | |
|
||||||
| [launch(object)](./puppeteer.productlauncher.launch.md) | | |
|
| [launch(options)](./puppeteer.productlauncher.launch.md) | | |
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -9482,6 +9482,7 @@
|
|||||||
"version": "19.8.3",
|
"version": "19.8.3",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@puppeteer/browsers": "0.3.2",
|
||||||
"chromium-bidi": "0.4.6",
|
"chromium-bidi": "0.4.6",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
@ -14464,6 +14465,7 @@
|
|||||||
"puppeteer-core": {
|
"puppeteer-core": {
|
||||||
"version": "file:packages/puppeteer-core",
|
"version": "file:packages/puppeteer-core",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@puppeteer/browsers": "0.3.2",
|
||||||
"chromium-bidi": "0.4.6",
|
"chromium-bidi": "0.4.6",
|
||||||
"cross-fetch": "3.1.5",
|
"cross-fetch": "3.1.5",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
|
@ -270,10 +270,9 @@ class Process {
|
|||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
await this.#runHooks();
|
await this.#runHooks();
|
||||||
if (this.#exited) {
|
if (!this.#exited) {
|
||||||
return this.#browserProcessExiting;
|
this.kill();
|
||||||
}
|
}
|
||||||
this.kill();
|
|
||||||
return this.#browserProcessExiting;
|
return this.#browserProcessExiting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,8 @@
|
|||||||
"clean": "if-file-deleted",
|
"clean": "if-file-deleted",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"generate:package-json",
|
"generate:package-json",
|
||||||
"generate:sources"
|
"generate:sources",
|
||||||
|
"../browsers:build"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"{compat,src,third_party}/**",
|
"{compat,src,third_party}/**",
|
||||||
@ -140,7 +141,8 @@
|
|||||||
"proxy-from-env": "1.1.0",
|
"proxy-from-env": "1.1.0",
|
||||||
"tar-fs": "2.1.1",
|
"tar-fs": "2.1.1",
|
||||||
"unbzip2-stream": "1.4.3",
|
"unbzip2-stream": "1.4.3",
|
||||||
"ws": "8.13.0"
|
"ws": "8.13.0",
|
||||||
|
"@puppeteer/browsers": "0.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">= 4.7.4"
|
"typescript": ">= 4.7.4"
|
||||||
|
@ -275,6 +275,13 @@ export class Connection extends EventEmitter {
|
|||||||
}) as Promise<ProtocolMapping.Commands[T]['returnType']>;
|
}) as Promise<ProtocolMapping.Commands[T]['returnType']>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async closeBrowser(): Promise<void> {
|
||||||
|
await this.send('Browser.close');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -1,392 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2020 Google Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import childProcess from 'child_process';
|
|
||||||
import fs from 'fs';
|
|
||||||
import {rename, unlink} from 'fs/promises';
|
|
||||||
import path from 'path';
|
|
||||||
import readline from 'readline';
|
|
||||||
|
|
||||||
import type {Connection as BiDiConnection} from '../common/bidi/bidi.js';
|
|
||||||
import {Connection} from '../common/Connection.js';
|
|
||||||
import {debug} from '../common/Debug.js';
|
|
||||||
import {TimeoutError} from '../common/Errors.js';
|
|
||||||
import {NodeWebSocketTransport as WebSocketTransport} from '../common/NodeWebSocketTransport.js';
|
|
||||||
import {Product} from '../common/Product.js';
|
|
||||||
import {
|
|
||||||
addEventListener,
|
|
||||||
debugError,
|
|
||||||
PuppeteerEventListener,
|
|
||||||
removeEventListeners,
|
|
||||||
} from '../common/util.js';
|
|
||||||
import {assert} from '../util/assert.js';
|
|
||||||
import {isErrnoException, isErrorLike} from '../util/ErrorLike.js';
|
|
||||||
import {rm, rmSync} from '../util/fs.js';
|
|
||||||
|
|
||||||
import {LaunchOptions} from './LaunchOptions.js';
|
|
||||||
import {PipeTransport} from './PipeTransport.js';
|
|
||||||
|
|
||||||
const debugLauncher = debug('puppeteer:launcher');
|
|
||||||
|
|
||||||
const PROCESS_ERROR_EXPLANATION = `Puppeteer was unable to kill the process which ran the browser binary.
|
|
||||||
This means that, on future Puppeteer launches, Puppeteer might not be able to launch the browser.
|
|
||||||
Please check your open processes and ensure that the browser processes that Puppeteer launched have been killed.
|
|
||||||
If you think this is a bug, please report it on the Puppeteer issue tracker.`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export class BrowserRunner {
|
|
||||||
#product: Product;
|
|
||||||
#executablePath: string;
|
|
||||||
#processArguments: string[];
|
|
||||||
#userDataDir: string;
|
|
||||||
#isTempUserDataDir?: boolean;
|
|
||||||
#closed = true;
|
|
||||||
#listeners: PuppeteerEventListener[] = [];
|
|
||||||
#processClosing!: Promise<void>;
|
|
||||||
|
|
||||||
proc?: childProcess.ChildProcess;
|
|
||||||
connection?: Connection;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
product: Product,
|
|
||||||
executablePath: string,
|
|
||||||
processArguments: string[],
|
|
||||||
userDataDir: string,
|
|
||||||
isTempUserDataDir?: boolean
|
|
||||||
) {
|
|
||||||
this.#product = product;
|
|
||||||
this.#executablePath = executablePath;
|
|
||||||
this.#processArguments = processArguments;
|
|
||||||
this.#userDataDir = userDataDir;
|
|
||||||
this.#isTempUserDataDir = isTempUserDataDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
start(options: LaunchOptions): void {
|
|
||||||
const {handleSIGINT, handleSIGTERM, handleSIGHUP, dumpio, env, pipe} =
|
|
||||||
options;
|
|
||||||
let stdio: Array<'ignore' | 'pipe'>;
|
|
||||||
if (pipe) {
|
|
||||||
if (dumpio) {
|
|
||||||
stdio = ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
|
|
||||||
} else {
|
|
||||||
stdio = ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dumpio) {
|
|
||||||
stdio = ['pipe', 'pipe', 'pipe'];
|
|
||||||
} else {
|
|
||||||
stdio = ['pipe', 'ignore', 'pipe'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(!this.proc, 'This process has previously been started.');
|
|
||||||
debugLauncher(
|
|
||||||
`Calling ${this.#executablePath} ${this.#processArguments.join(' ')}`
|
|
||||||
);
|
|
||||||
this.proc = childProcess.spawn(
|
|
||||||
this.#executablePath,
|
|
||||||
this.#processArguments,
|
|
||||||
{
|
|
||||||
// On non-windows platforms, `detached: true` makes child process a
|
|
||||||
// leader of a new process group, making it possible to kill child
|
|
||||||
// process tree with `.kill(-pid)` command. @see
|
|
||||||
// https://nodejs.org/api/child_process.html#child_process_options_detached
|
|
||||||
detached: process.platform !== 'win32',
|
|
||||||
env,
|
|
||||||
stdio,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (dumpio) {
|
|
||||||
this.proc.stderr?.pipe(process.stderr);
|
|
||||||
this.proc.stdout?.pipe(process.stdout);
|
|
||||||
}
|
|
||||||
this.#closed = false;
|
|
||||||
this.#processClosing = new Promise((fulfill, reject) => {
|
|
||||||
this.proc!.once('exit', async () => {
|
|
||||||
this.#closed = true;
|
|
||||||
// Cleanup as processes exit.
|
|
||||||
if (this.#isTempUserDataDir) {
|
|
||||||
try {
|
|
||||||
await rm(this.#userDataDir);
|
|
||||||
fulfill();
|
|
||||||
} catch (error) {
|
|
||||||
debugError(error);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.#product === 'firefox') {
|
|
||||||
try {
|
|
||||||
// When an existing user profile has been used remove the user
|
|
||||||
// preferences file and restore possibly backuped preferences.
|
|
||||||
await unlink(path.join(this.#userDataDir, 'user.js'));
|
|
||||||
|
|
||||||
const prefsBackupPath = path.join(
|
|
||||||
this.#userDataDir,
|
|
||||||
'prefs.js.puppeteer'
|
|
||||||
);
|
|
||||||
if (fs.existsSync(prefsBackupPath)) {
|
|
||||||
const prefsPath = path.join(this.#userDataDir, 'prefs.js');
|
|
||||||
await unlink(prefsPath);
|
|
||||||
await rename(prefsBackupPath, prefsPath);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
debugError(error);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fulfill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.#listeners = [addEventListener(process, 'exit', this.kill.bind(this))];
|
|
||||||
if (handleSIGINT) {
|
|
||||||
this.#listeners.push(
|
|
||||||
addEventListener(process, 'SIGINT', () => {
|
|
||||||
this.kill();
|
|
||||||
process.exit(130);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (handleSIGTERM) {
|
|
||||||
this.#listeners.push(
|
|
||||||
addEventListener(process, 'SIGTERM', this.close.bind(this))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (handleSIGHUP) {
|
|
||||||
this.#listeners.push(
|
|
||||||
addEventListener(process, 'SIGHUP', this.close.bind(this))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): Promise<void> {
|
|
||||||
if (this.#closed) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
if (this.#isTempUserDataDir) {
|
|
||||||
this.kill();
|
|
||||||
} else if (this.connection) {
|
|
||||||
// Attempt to close the browser gracefully
|
|
||||||
this.connection.send('Browser.close').catch(error => {
|
|
||||||
debugError(error);
|
|
||||||
this.kill();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Cleanup this listener last, as that makes sure the full callback runs. If we
|
|
||||||
// perform this earlier, then the previous function calls would not happen.
|
|
||||||
removeEventListeners(this.#listeners);
|
|
||||||
return this.#processClosing;
|
|
||||||
}
|
|
||||||
|
|
||||||
kill(): void {
|
|
||||||
// If the process failed to launch (for example if the browser executable path
|
|
||||||
// is invalid), then the process does not get a pid assigned. A call to
|
|
||||||
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
|
|
||||||
if (this.proc && this.proc.pid && pidExists(this.proc.pid)) {
|
|
||||||
const proc = this.proc;
|
|
||||||
try {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
childProcess.exec(`taskkill /pid ${this.proc.pid} /T /F`, error => {
|
|
||||||
if (error) {
|
|
||||||
// taskkill can fail to kill the process e.g. due to missing permissions.
|
|
||||||
// Let's kill the process via Node API. This delays killing of all child
|
|
||||||
// processes of `this.proc` until the main Node.js process dies.
|
|
||||||
proc.kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// on linux the process group can be killed with the group id prefixed with
|
|
||||||
// a minus sign. The process group id is the group leader's pid.
|
|
||||||
const processGroupId = -this.proc.pid;
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.kill(processGroupId, 'SIGKILL');
|
|
||||||
} catch (error) {
|
|
||||||
// Killing the process group can fail due e.g. to missing permissions.
|
|
||||||
// Let's kill the process via Node API. This delays killing of all child
|
|
||||||
// processes of `this.proc` until the main Node.js process dies.
|
|
||||||
proc.kill('SIGKILL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(
|
|
||||||
`${PROCESS_ERROR_EXPLANATION}\nError cause: ${
|
|
||||||
isErrorLike(error) ? error.stack : error
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to remove temporary profile directory to avoid littering.
|
|
||||||
try {
|
|
||||||
if (this.#isTempUserDataDir) {
|
|
||||||
rmSync(this.#userDataDir);
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
|
|
||||||
// Cleanup this listener last, as that makes sure the full callback runs. If we
|
|
||||||
// perform this earlier, then the previous function calls would not happen.
|
|
||||||
removeEventListeners(this.#listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
async setupWebDriverBiDiConnection(options: {
|
|
||||||
timeout: number;
|
|
||||||
slowMo: number;
|
|
||||||
preferredRevision: string;
|
|
||||||
protocolTimeout?: number;
|
|
||||||
}): Promise<BiDiConnection> {
|
|
||||||
assert(this.proc, 'BrowserRunner not started.');
|
|
||||||
|
|
||||||
const {timeout, slowMo, preferredRevision, protocolTimeout} = options;
|
|
||||||
let browserWSEndpoint = await waitForWSEndpoint(
|
|
||||||
this.proc,
|
|
||||||
timeout,
|
|
||||||
preferredRevision,
|
|
||||||
/^WebDriver BiDi listening on (ws:\/\/.*)$/
|
|
||||||
);
|
|
||||||
browserWSEndpoint += '/session';
|
|
||||||
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
|
||||||
const BiDi = await import(
|
|
||||||
/* webpackIgnore: true */ '../common/bidi/bidi.js'
|
|
||||||
);
|
|
||||||
return new BiDi.Connection(transport, slowMo, protocolTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setupConnection(options: {
|
|
||||||
usePipe?: boolean;
|
|
||||||
timeout: number;
|
|
||||||
slowMo: number;
|
|
||||||
preferredRevision: string;
|
|
||||||
protocolTimeout?: number;
|
|
||||||
}): Promise<Connection> {
|
|
||||||
assert(this.proc, 'BrowserRunner not started.');
|
|
||||||
|
|
||||||
const {usePipe, timeout, slowMo, preferredRevision, protocolTimeout} =
|
|
||||||
options;
|
|
||||||
if (!usePipe) {
|
|
||||||
const browserWSEndpoint = await waitForWSEndpoint(
|
|
||||||
this.proc,
|
|
||||||
timeout,
|
|
||||||
preferredRevision
|
|
||||||
);
|
|
||||||
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
|
||||||
this.connection = new Connection(
|
|
||||||
browserWSEndpoint,
|
|
||||||
transport,
|
|
||||||
slowMo,
|
|
||||||
protocolTimeout
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// stdio was assigned during start(), and the 'pipe' option there adds the
|
|
||||||
// 4th and 5th items to stdio array
|
|
||||||
const {3: pipeWrite, 4: pipeRead} = this.proc.stdio;
|
|
||||||
const transport = new PipeTransport(
|
|
||||||
pipeWrite as NodeJS.WritableStream,
|
|
||||||
pipeRead as NodeJS.ReadableStream
|
|
||||||
);
|
|
||||||
this.connection = new Connection('', transport, slowMo, protocolTimeout);
|
|
||||||
}
|
|
||||||
return this.connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForWSEndpoint(
|
|
||||||
browserProcess: childProcess.ChildProcess,
|
|
||||||
timeout: number,
|
|
||||||
preferredRevision: string,
|
|
||||||
regex = /^DevTools listening on (ws:\/\/.*)$/
|
|
||||||
): Promise<string> {
|
|
||||||
assert(browserProcess.stderr, '`browserProcess` does not have stderr.');
|
|
||||||
const rl = readline.createInterface(browserProcess.stderr);
|
|
||||||
let stderr = '';
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const listeners = [
|
|
||||||
addEventListener(rl, 'line', onLine),
|
|
||||||
addEventListener(rl, 'close', () => {
|
|
||||||
return onClose();
|
|
||||||
}),
|
|
||||||
addEventListener(browserProcess, 'exit', () => {
|
|
||||||
return onClose();
|
|
||||||
}),
|
|
||||||
addEventListener(browserProcess, 'error', error => {
|
|
||||||
return onClose(error);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0;
|
|
||||||
|
|
||||||
function onClose(error?: Error): void {
|
|
||||||
cleanup();
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
[
|
|
||||||
'Failed to launch the browser process!' +
|
|
||||||
(error ? ' ' + error.message : ''),
|
|
||||||
stderr,
|
|
||||||
'',
|
|
||||||
'TROUBLESHOOTING: https://pptr.dev/troubleshooting',
|
|
||||||
'',
|
|
||||||
].join('\n')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTimeout(): void {
|
|
||||||
cleanup();
|
|
||||||
reject(
|
|
||||||
new TimeoutError(
|
|
||||||
`Timed out after ${timeout} ms while trying to connect to the browser! Only Chrome at revision r${preferredRevision} is guaranteed to work.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onLine(line: string): void {
|
|
||||||
stderr += line + '\n';
|
|
||||||
const match = line.match(regex);
|
|
||||||
if (!match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
// The RegExp matches, so this will obviously exist.
|
|
||||||
resolve(match[1]!);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup(): void {
|
|
||||||
if (timeoutId) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
removeEventListeners(listeners);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pidExists(pid: number): boolean {
|
|
||||||
try {
|
|
||||||
return process.kill(pid, 0);
|
|
||||||
} catch (error) {
|
|
||||||
if (isErrnoException(error)) {
|
|
||||||
if (error.code && error.code === 'ESRCH') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,38 @@
|
|||||||
import {accessSync} from 'fs';
|
/**
|
||||||
|
* Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import {mkdtemp} from 'fs/promises';
|
import {mkdtemp} from 'fs/promises';
|
||||||
import os from 'os';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {Browser} from '../api/Browser.js';
|
import {
|
||||||
import {CDPBrowser} from '../common/Browser.js';
|
computeSystemExecutablePath,
|
||||||
import {assert} from '../util/assert.js';
|
Browser as SupportedBrowsers,
|
||||||
|
ChromeReleaseChannel as BrowsersChromeReleaseChannel,
|
||||||
|
} from '@puppeteer/browsers';
|
||||||
|
|
||||||
|
import {debugError} from '../common/util.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
import {rm} from '../util/fs.js';
|
||||||
|
|
||||||
import {BrowserRunner} from './BrowserRunner.js';
|
|
||||||
import {
|
import {
|
||||||
BrowserLaunchArgumentOptions,
|
BrowserLaunchArgumentOptions,
|
||||||
ChromeReleaseChannel,
|
ChromeReleaseChannel,
|
||||||
PuppeteerNodeLaunchOptions,
|
PuppeteerNodeLaunchOptions,
|
||||||
} from './LaunchOptions.js';
|
} from './LaunchOptions.js';
|
||||||
import {ProductLauncher} from './ProductLauncher.js';
|
import {ProductLauncher, ResolvedLaunchArgs} from './ProductLauncher.js';
|
||||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,28 +43,19 @@ export class ChromeLauncher extends ProductLauncher {
|
|||||||
super(puppeteer, 'chrome');
|
super(puppeteer, 'chrome');
|
||||||
}
|
}
|
||||||
|
|
||||||
override async launch(
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
override async computeLaunchArguments(
|
||||||
options: PuppeteerNodeLaunchOptions = {}
|
options: PuppeteerNodeLaunchOptions = {}
|
||||||
): Promise<Browser> {
|
): Promise<ResolvedLaunchArgs> {
|
||||||
const {
|
const {
|
||||||
ignoreDefaultArgs = false,
|
ignoreDefaultArgs = false,
|
||||||
args = [],
|
args = [],
|
||||||
dumpio = false,
|
pipe = false,
|
||||||
|
debuggingPort,
|
||||||
channel,
|
channel,
|
||||||
executablePath,
|
executablePath,
|
||||||
pipe = false,
|
|
||||||
env = process.env,
|
|
||||||
handleSIGINT = true,
|
|
||||||
handleSIGTERM = true,
|
|
||||||
handleSIGHUP = true,
|
|
||||||
ignoreHTTPSErrors = false,
|
|
||||||
defaultViewport = {width: 800, height: 600},
|
|
||||||
slowMo = 0,
|
|
||||||
timeout = 30000,
|
|
||||||
waitForInitialPage = true,
|
|
||||||
debuggingPort,
|
|
||||||
protocol,
|
|
||||||
protocolTimeout,
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const chromeArguments = [];
|
const chromeArguments = [];
|
||||||
@ -104,82 +114,29 @@ export class ChromeLauncher extends ProductLauncher {
|
|||||||
chromeExecutable = this.executablePath(channel);
|
chromeExecutable = this.executablePath(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
|
return {
|
||||||
const runner = new BrowserRunner(
|
executablePath: chromeExecutable,
|
||||||
this.product,
|
args: chromeArguments,
|
||||||
chromeExecutable,
|
isTempUserDataDir,
|
||||||
chromeArguments,
|
|
||||||
userDataDir,
|
userDataDir,
|
||||||
isTempUserDataDir
|
};
|
||||||
);
|
}
|
||||||
runner.start({
|
|
||||||
handleSIGHUP,
|
|
||||||
handleSIGTERM,
|
|
||||||
handleSIGINT,
|
|
||||||
dumpio,
|
|
||||||
env,
|
|
||||||
pipe: usePipe,
|
|
||||||
});
|
|
||||||
|
|
||||||
let browser;
|
/**
|
||||||
try {
|
* @internal
|
||||||
const connection = await runner.setupConnection({
|
*/
|
||||||
usePipe,
|
override async cleanUserDataDir(
|
||||||
timeout,
|
path: string,
|
||||||
slowMo,
|
opts: {isTemp: boolean}
|
||||||
preferredRevision: this.puppeteer.browserRevision,
|
): Promise<void> {
|
||||||
protocolTimeout,
|
if (opts.isTemp) {
|
||||||
});
|
|
||||||
|
|
||||||
if (protocol === 'webDriverBiDi') {
|
|
||||||
try {
|
|
||||||
const BiDi = await import(
|
|
||||||
/* webpackIgnore: true */ '../common/bidi/bidi.js'
|
|
||||||
);
|
|
||||||
const bidiConnection = await BiDi.connectBidiOverCDP(connection);
|
|
||||||
browser = await BiDi.Browser.create({
|
|
||||||
connection: bidiConnection,
|
|
||||||
closeCallback: runner.close.bind(runner),
|
|
||||||
process: runner.proc,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
runner.kill();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
browser = await CDPBrowser._create(
|
|
||||||
this.product,
|
|
||||||
connection,
|
|
||||||
[],
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
defaultViewport,
|
|
||||||
runner.proc,
|
|
||||||
runner.close.bind(runner),
|
|
||||||
options.targetFilter
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
runner.kill();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitForInitialPage) {
|
|
||||||
try {
|
try {
|
||||||
await browser.waitForTarget(
|
await rm(path);
|
||||||
t => {
|
|
||||||
return t.type() === 'page';
|
|
||||||
},
|
|
||||||
{timeout}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await browser.close();
|
debugError(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return browser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
override defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
||||||
@ -248,86 +205,27 @@ export class ChromeLauncher extends ProductLauncher {
|
|||||||
|
|
||||||
override executablePath(channel?: ChromeReleaseChannel): string {
|
override executablePath(channel?: ChromeReleaseChannel): string {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
return this.#executablePathForChannel(channel);
|
return computeSystemExecutablePath({
|
||||||
|
browser: SupportedBrowsers.CHROME,
|
||||||
|
channel: convertPuppeteerChannelToBrowsersChannel(channel),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.resolveExecutablePath();
|
return this.resolveExecutablePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
function convertPuppeteerChannelToBrowsersChannel(
|
||||||
* @internal
|
channel: ChromeReleaseChannel
|
||||||
*/
|
): BrowsersChromeReleaseChannel {
|
||||||
#executablePathForChannel(channel: ChromeReleaseChannel): string {
|
switch (channel) {
|
||||||
const platform = os.platform();
|
case 'chrome':
|
||||||
|
return BrowsersChromeReleaseChannel.STABLE;
|
||||||
let chromePath: string | undefined;
|
case 'chrome-dev':
|
||||||
switch (platform) {
|
return BrowsersChromeReleaseChannel.DEV;
|
||||||
case 'win32':
|
case 'chrome-beta':
|
||||||
switch (channel) {
|
return BrowsersChromeReleaseChannel.BETA;
|
||||||
case 'chrome':
|
case 'chrome-canary':
|
||||||
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe`;
|
return BrowsersChromeReleaseChannel.CANARY;
|
||||||
break;
|
|
||||||
case 'chrome-beta':
|
|
||||||
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome Beta\\Application\\chrome.exe`;
|
|
||||||
break;
|
|
||||||
case 'chrome-canary':
|
|
||||||
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome SxS\\Application\\chrome.exe`;
|
|
||||||
break;
|
|
||||||
case 'chrome-dev':
|
|
||||||
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome Dev\\Application\\chrome.exe`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'darwin':
|
|
||||||
switch (channel) {
|
|
||||||
case 'chrome':
|
|
||||||
chromePath =
|
|
||||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
||||||
break;
|
|
||||||
case 'chrome-beta':
|
|
||||||
chromePath =
|
|
||||||
'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta';
|
|
||||||
break;
|
|
||||||
case 'chrome-canary':
|
|
||||||
chromePath =
|
|
||||||
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
|
|
||||||
break;
|
|
||||||
case 'chrome-dev':
|
|
||||||
chromePath =
|
|
||||||
'/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'linux':
|
|
||||||
switch (channel) {
|
|
||||||
case 'chrome':
|
|
||||||
chromePath = '/opt/google/chrome/chrome';
|
|
||||||
break;
|
|
||||||
case 'chrome-beta':
|
|
||||||
chromePath = '/opt/google/chrome-beta/chrome';
|
|
||||||
break;
|
|
||||||
case 'chrome-dev':
|
|
||||||
chromePath = '/opt/google/chrome-unstable/chrome';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chromePath) {
|
|
||||||
throw new Error(
|
|
||||||
`Unable to detect browser executable path for '${channel}' on ${platform}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Chrome exists and is accessible.
|
|
||||||
try {
|
|
||||||
accessSync(chromePath);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(
|
|
||||||
`Could not find Google Chrome executable for channel '${channel}' at '${chromePath}'.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return chromePath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import {rename, unlink, mkdtemp} from 'fs/promises';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {Browser} from '../api/Browser.js';
|
import {Browser as SupportedBrowsers, createProfile} from '@puppeteer/browsers';
|
||||||
import {CDPBrowser} from '../common/Browser.js';
|
|
||||||
import {assert} from '../util/assert.js';
|
import {debugError} from '../common/util.js';
|
||||||
|
import {assert} from '../util/assert.js';
|
||||||
|
import {rm} from '../util/fs.js';
|
||||||
|
|
||||||
import {BrowserRunner} from './BrowserRunner.js';
|
|
||||||
import {
|
import {
|
||||||
BrowserLaunchArgumentOptions,
|
BrowserLaunchArgumentOptions,
|
||||||
PuppeteerNodeLaunchOptions,
|
PuppeteerNodeLaunchOptions,
|
||||||
} from './LaunchOptions.js';
|
} from './LaunchOptions.js';
|
||||||
import {ProductLauncher} from './ProductLauncher.js';
|
import {ProductLauncher, ResolvedLaunchArgs} from './ProductLauncher.js';
|
||||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,29 +39,19 @@ export class FirefoxLauncher extends ProductLauncher {
|
|||||||
constructor(puppeteer: PuppeteerNode) {
|
constructor(puppeteer: PuppeteerNode) {
|
||||||
super(puppeteer, 'firefox');
|
super(puppeteer, 'firefox');
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
override async launch(
|
* @internal
|
||||||
|
*/
|
||||||
|
override async computeLaunchArguments(
|
||||||
options: PuppeteerNodeLaunchOptions = {}
|
options: PuppeteerNodeLaunchOptions = {}
|
||||||
): Promise<Browser> {
|
): Promise<ResolvedLaunchArgs> {
|
||||||
const {
|
const {
|
||||||
ignoreDefaultArgs = false,
|
ignoreDefaultArgs = false,
|
||||||
args = [],
|
args = [],
|
||||||
dumpio = false,
|
|
||||||
executablePath,
|
executablePath,
|
||||||
pipe = false,
|
pipe = false,
|
||||||
env = process.env,
|
|
||||||
handleSIGINT = true,
|
|
||||||
handleSIGTERM = true,
|
|
||||||
handleSIGHUP = true,
|
|
||||||
ignoreHTTPSErrors = false,
|
|
||||||
defaultViewport = {width: 800, height: 600},
|
|
||||||
slowMo = 0,
|
|
||||||
timeout = 30000,
|
|
||||||
extraPrefsFirefox = {},
|
extraPrefsFirefox = {},
|
||||||
waitForInitialPage = true,
|
|
||||||
debuggingPort = null,
|
debuggingPort = null,
|
||||||
protocol = 'cdp',
|
|
||||||
protocolTimeout,
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const firefoxArguments = [];
|
const firefoxArguments = [];
|
||||||
@ -91,14 +99,17 @@ export class FirefoxLauncher extends ProductLauncher {
|
|||||||
// When using a custom Firefox profile it needs to be populated
|
// When using a custom Firefox profile it needs to be populated
|
||||||
// with required preferences.
|
// with required preferences.
|
||||||
isTempUserDataDir = false;
|
isTempUserDataDir = false;
|
||||||
const prefs = this.defaultPreferences(extraPrefsFirefox);
|
|
||||||
this.writePreferences(prefs, userDataDir);
|
|
||||||
} else {
|
} else {
|
||||||
userDataDir = await this._createProfile(extraPrefsFirefox);
|
userDataDir = await mkdtemp(this.getProfilePath());
|
||||||
firefoxArguments.push('--profile');
|
firefoxArguments.push('--profile');
|
||||||
firefoxArguments.push(userDataDir);
|
firefoxArguments.push(userDataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createProfile(SupportedBrowsers.FIREFOX, {
|
||||||
|
path: userDataDir,
|
||||||
|
preferences: extraPrefsFirefox,
|
||||||
|
});
|
||||||
|
|
||||||
let firefoxExecutable: string;
|
let firefoxExecutable: string;
|
||||||
if (this.puppeteer._isPuppeteerCore || executablePath) {
|
if (this.puppeteer._isPuppeteerCore || executablePath) {
|
||||||
assert(
|
assert(
|
||||||
@ -110,86 +121,44 @@ export class FirefoxLauncher extends ProductLauncher {
|
|||||||
firefoxExecutable = this.executablePath();
|
firefoxExecutable = this.executablePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
const runner = new BrowserRunner(
|
return {
|
||||||
this.product,
|
isTempUserDataDir,
|
||||||
firefoxExecutable,
|
|
||||||
firefoxArguments,
|
|
||||||
userDataDir,
|
userDataDir,
|
||||||
isTempUserDataDir
|
args: firefoxArguments,
|
||||||
);
|
executablePath: firefoxExecutable,
|
||||||
runner.start({
|
};
|
||||||
handleSIGHUP,
|
}
|
||||||
handleSIGTERM,
|
|
||||||
handleSIGINT,
|
|
||||||
dumpio,
|
|
||||||
env,
|
|
||||||
pipe,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (protocol === 'webDriverBiDi') {
|
/**
|
||||||
let browser: Browser;
|
* @internal
|
||||||
|
*/
|
||||||
|
override async cleanUserDataDir(
|
||||||
|
userDataDir: string,
|
||||||
|
opts: {isTemp: boolean}
|
||||||
|
): Promise<void> {
|
||||||
|
if (opts.isTemp) {
|
||||||
try {
|
try {
|
||||||
const connection = await runner.setupWebDriverBiDiConnection({
|
await rm(userDataDir);
|
||||||
timeout,
|
|
||||||
slowMo,
|
|
||||||
preferredRevision: this.puppeteer.browserRevision,
|
|
||||||
protocolTimeout,
|
|
||||||
});
|
|
||||||
const BiDi = await import(
|
|
||||||
/* webpackIgnore: true */ '../common/bidi/bidi.js'
|
|
||||||
);
|
|
||||||
browser = await BiDi.Browser.create({
|
|
||||||
connection,
|
|
||||||
closeCallback: runner.close.bind(runner),
|
|
||||||
process: runner.proc,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runner.kill();
|
debugError(error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
let browser;
|
|
||||||
try {
|
|
||||||
const connection = await runner.setupConnection({
|
|
||||||
usePipe: pipe,
|
|
||||||
timeout,
|
|
||||||
slowMo,
|
|
||||||
preferredRevision: this.puppeteer.browserRevision,
|
|
||||||
protocolTimeout,
|
|
||||||
});
|
|
||||||
browser = await CDPBrowser._create(
|
|
||||||
this.product,
|
|
||||||
connection,
|
|
||||||
[],
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
defaultViewport,
|
|
||||||
runner.proc,
|
|
||||||
runner.close.bind(runner),
|
|
||||||
options.targetFilter
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
runner.kill();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitForInitialPage) {
|
|
||||||
try {
|
try {
|
||||||
await browser.waitForTarget(
|
// When an existing user profile has been used remove the user
|
||||||
t => {
|
// preferences file and restore possibly backuped preferences.
|
||||||
return t.type() === 'page';
|
await unlink(path.join(userDataDir, 'user.js'));
|
||||||
},
|
|
||||||
{timeout}
|
const prefsBackupPath = path.join(userDataDir, 'prefs.js.puppeteer');
|
||||||
);
|
if (fs.existsSync(prefsBackupPath)) {
|
||||||
|
const prefsPath = path.join(userDataDir, 'prefs.js');
|
||||||
|
await unlink(prefsPath);
|
||||||
|
await rename(prefsBackupPath, prefsPath);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await browser.close();
|
debugError(error);
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return browser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override executablePath(): string {
|
override executablePath(): string {
|
||||||
@ -245,256 +214,4 @@ export class FirefoxLauncher extends ProductLauncher {
|
|||||||
firefoxArguments.push(...args);
|
firefoxArguments.push(...args);
|
||||||
return firefoxArguments;
|
return firefoxArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultPreferences(extraPrefs: {[x: string]: unknown}): {
|
|
||||||
[x: string]: unknown;
|
|
||||||
} {
|
|
||||||
const server = 'dummy.test';
|
|
||||||
|
|
||||||
const defaultPrefs = {
|
|
||||||
// Make sure Shield doesn't hit the network.
|
|
||||||
'app.normandy.api_url': '',
|
|
||||||
// Disable Firefox old build background check
|
|
||||||
'app.update.checkInstallTime': false,
|
|
||||||
// Disable automatically upgrading Firefox
|
|
||||||
'app.update.disabledForTesting': true,
|
|
||||||
|
|
||||||
// Increase the APZ content response timeout to 1 minute
|
|
||||||
'apz.content_response_timeout': 60000,
|
|
||||||
|
|
||||||
// Prevent various error message on the console
|
|
||||||
// jest-puppeteer asserts that no error message is emitted by the console
|
|
||||||
'browser.contentblocking.features.standard':
|
|
||||||
'-tp,tpPrivate,cookieBehavior0,-cm,-fp',
|
|
||||||
|
|
||||||
// Enable the dump function: which sends messages to the system
|
|
||||||
// console
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115
|
|
||||||
'browser.dom.window.dump.enabled': true,
|
|
||||||
// Disable topstories
|
|
||||||
'browser.newtabpage.activity-stream.feeds.system.topstories': false,
|
|
||||||
// Always display a blank page
|
|
||||||
'browser.newtabpage.enabled': false,
|
|
||||||
// Background thumbnails in particular cause grief: and disabling
|
|
||||||
// thumbnails in general cannot hurt
|
|
||||||
'browser.pagethumbnails.capturing_disabled': true,
|
|
||||||
|
|
||||||
// Disable safebrowsing components.
|
|
||||||
'browser.safebrowsing.blockedURIs.enabled': false,
|
|
||||||
'browser.safebrowsing.downloads.enabled': false,
|
|
||||||
'browser.safebrowsing.malware.enabled': false,
|
|
||||||
'browser.safebrowsing.passwords.enabled': false,
|
|
||||||
'browser.safebrowsing.phishing.enabled': false,
|
|
||||||
|
|
||||||
// Disable updates to search engines.
|
|
||||||
'browser.search.update': false,
|
|
||||||
// Do not restore the last open set of tabs if the browser has crashed
|
|
||||||
'browser.sessionstore.resume_from_crash': false,
|
|
||||||
// Skip check for default browser on startup
|
|
||||||
'browser.shell.checkDefaultBrowser': false,
|
|
||||||
|
|
||||||
// Disable newtabpage
|
|
||||||
'browser.startup.homepage': 'about:blank',
|
|
||||||
// Do not redirect user when a milstone upgrade of Firefox is detected
|
|
||||||
'browser.startup.homepage_override.mstone': 'ignore',
|
|
||||||
// Start with a blank page about:blank
|
|
||||||
'browser.startup.page': 0,
|
|
||||||
|
|
||||||
// Do not allow background tabs to be zombified on Android: otherwise for
|
|
||||||
// tests that open additional tabs: the test harness tab itself might get
|
|
||||||
// unloaded
|
|
||||||
'browser.tabs.disableBackgroundZombification': false,
|
|
||||||
// Do not warn when closing all other open tabs
|
|
||||||
'browser.tabs.warnOnCloseOtherTabs': false,
|
|
||||||
// Do not warn when multiple tabs will be opened
|
|
||||||
'browser.tabs.warnOnOpen': false,
|
|
||||||
|
|
||||||
// Disable the UI tour.
|
|
||||||
'browser.uitour.enabled': false,
|
|
||||||
// Turn off search suggestions in the location bar so as not to trigger
|
|
||||||
// network connections.
|
|
||||||
'browser.urlbar.suggest.searches': false,
|
|
||||||
// Disable first run splash page on Windows 10
|
|
||||||
'browser.usedOnWindows10.introURL': '',
|
|
||||||
// Do not warn on quitting Firefox
|
|
||||||
'browser.warnOnQuit': false,
|
|
||||||
|
|
||||||
// Defensively disable data reporting systems
|
|
||||||
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`,
|
|
||||||
'datareporting.healthreport.logging.consoleEnabled': false,
|
|
||||||
'datareporting.healthreport.service.enabled': false,
|
|
||||||
'datareporting.healthreport.service.firstRun': false,
|
|
||||||
'datareporting.healthreport.uploadEnabled': false,
|
|
||||||
|
|
||||||
// Do not show datareporting policy notifications which can interfere with tests
|
|
||||||
'datareporting.policy.dataSubmissionEnabled': false,
|
|
||||||
'datareporting.policy.dataSubmissionPolicyBypassNotification': true,
|
|
||||||
|
|
||||||
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
|
|
||||||
// This doesn't affect Puppeteer but spams console (Bug 1424372)
|
|
||||||
'devtools.jsonview.enabled': false,
|
|
||||||
|
|
||||||
// Disable popup-blocker
|
|
||||||
'dom.disable_open_during_load': false,
|
|
||||||
|
|
||||||
// Enable the support for File object creation in the content process
|
|
||||||
// Required for |Page.setFileInputFiles| protocol method.
|
|
||||||
'dom.file.createInChild': true,
|
|
||||||
|
|
||||||
// Disable the ProcessHangMonitor
|
|
||||||
'dom.ipc.reportProcessHangs': false,
|
|
||||||
|
|
||||||
// Disable slow script dialogues
|
|
||||||
'dom.max_chrome_script_run_time': 0,
|
|
||||||
'dom.max_script_run_time': 0,
|
|
||||||
|
|
||||||
// Only load extensions from the application and user profile
|
|
||||||
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
|
|
||||||
'extensions.autoDisableScopes': 0,
|
|
||||||
'extensions.enabledScopes': 5,
|
|
||||||
|
|
||||||
// Disable metadata caching for installed add-ons by default
|
|
||||||
'extensions.getAddons.cache.enabled': false,
|
|
||||||
|
|
||||||
// Disable installing any distribution extensions or add-ons.
|
|
||||||
'extensions.installDistroAddons': false,
|
|
||||||
|
|
||||||
// Disabled screenshots extension
|
|
||||||
'extensions.screenshots.disabled': true,
|
|
||||||
|
|
||||||
// Turn off extension updates so they do not bother tests
|
|
||||||
'extensions.update.enabled': false,
|
|
||||||
|
|
||||||
// Turn off extension updates so they do not bother tests
|
|
||||||
'extensions.update.notifyUser': false,
|
|
||||||
|
|
||||||
// Make sure opening about:addons will not hit the network
|
|
||||||
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`,
|
|
||||||
|
|
||||||
// Temporarily force disable BFCache in parent (https://bit.ly/bug-1732263)
|
|
||||||
'fission.bfcacheInParent': false,
|
|
||||||
|
|
||||||
// Force all web content to use a single content process
|
|
||||||
'fission.webContentIsolationStrategy': 0,
|
|
||||||
|
|
||||||
// Allow the application to have focus even it runs in the background
|
|
||||||
'focusmanager.testmode': true,
|
|
||||||
// Disable useragent updates
|
|
||||||
'general.useragent.updates.enabled': false,
|
|
||||||
// Always use network provider for geolocation tests so we bypass the
|
|
||||||
// macOS dialog raised by the corelocation provider
|
|
||||||
'geo.provider.testing': true,
|
|
||||||
// Do not scan Wifi
|
|
||||||
'geo.wifi.scan': false,
|
|
||||||
// No hang monitor
|
|
||||||
'hangmonitor.timeout': 0,
|
|
||||||
// Show chrome errors and warnings in the error console
|
|
||||||
'javascript.options.showInConsole': true,
|
|
||||||
|
|
||||||
// Disable download and usage of OpenH264: and Widevine plugins
|
|
||||||
'media.gmp-manager.updateEnabled': false,
|
|
||||||
// Prevent various error message on the console
|
|
||||||
// jest-puppeteer asserts that no error message is emitted by the console
|
|
||||||
'network.cookie.cookieBehavior': 0,
|
|
||||||
|
|
||||||
// Disable experimental feature that is only available in Nightly
|
|
||||||
'network.cookie.sameSite.laxByDefault': false,
|
|
||||||
|
|
||||||
// Do not prompt for temporary redirects
|
|
||||||
'network.http.prompt-temp-redirect': false,
|
|
||||||
|
|
||||||
// Disable speculative connections so they are not reported as leaking
|
|
||||||
// when they are hanging around
|
|
||||||
'network.http.speculative-parallel-limit': 0,
|
|
||||||
|
|
||||||
// Do not automatically switch between offline and online
|
|
||||||
'network.manage-offline-status': false,
|
|
||||||
|
|
||||||
// Make sure SNTP requests do not hit the network
|
|
||||||
'network.sntp.pools': server,
|
|
||||||
|
|
||||||
// Disable Flash.
|
|
||||||
'plugin.state.flash': 0,
|
|
||||||
|
|
||||||
'privacy.trackingprotection.enabled': false,
|
|
||||||
|
|
||||||
// Can be removed once Firefox 89 is no longer supported
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1710839
|
|
||||||
'remote.enabled': true,
|
|
||||||
|
|
||||||
// Don't do network connections for mitm priming
|
|
||||||
'security.certerrors.mitm.priming.enabled': false,
|
|
||||||
// Local documents have access to all other local documents,
|
|
||||||
// including directory listings
|
|
||||||
'security.fileuri.strict_origin_policy': false,
|
|
||||||
// Do not wait for the notification button security delay
|
|
||||||
'security.notification_enable_delay': 0,
|
|
||||||
|
|
||||||
// Ensure blocklist updates do not hit the network
|
|
||||||
'services.settings.server': `http://${server}/dummy/blocklist/`,
|
|
||||||
|
|
||||||
// Do not automatically fill sign-in forms with known usernames and
|
|
||||||
// passwords
|
|
||||||
'signon.autofillForms': false,
|
|
||||||
// Disable password capture, so that tests that include forms are not
|
|
||||||
// influenced by the presence of the persistent doorhanger notification
|
|
||||||
'signon.rememberSignons': false,
|
|
||||||
|
|
||||||
// Disable first-run welcome page
|
|
||||||
'startup.homepage_welcome_url': 'about:blank',
|
|
||||||
|
|
||||||
// Disable first-run welcome page
|
|
||||||
'startup.homepage_welcome_url.additional': '',
|
|
||||||
|
|
||||||
// Disable browser animations (tabs, fullscreen, sliding alerts)
|
|
||||||
'toolkit.cosmeticAnimations.enabled': false,
|
|
||||||
|
|
||||||
// Prevent starting into safe mode after application crashes
|
|
||||||
'toolkit.startup.max_resumed_crashes': -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Object.assign(defaultPrefs, extraPrefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the user.js file with custom preferences as needed to allow
|
|
||||||
* Firefox's CDP support to properly function. These preferences will be
|
|
||||||
* automatically copied over to prefs.js during startup of Firefox. To be
|
|
||||||
* able to restore the original values of preferences a backup of prefs.js
|
|
||||||
* will be created.
|
|
||||||
*
|
|
||||||
* @param prefs - List of preferences to add.
|
|
||||||
* @param profilePath - Firefox profile to write the preferences to.
|
|
||||||
*/
|
|
||||||
async writePreferences(
|
|
||||||
prefs: {[x: string]: unknown},
|
|
||||||
profilePath: string
|
|
||||||
): Promise<void> {
|
|
||||||
const lines = Object.entries(prefs).map(([key, value]) => {
|
|
||||||
return `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.promises.writeFile(
|
|
||||||
path.join(profilePath, 'user.js'),
|
|
||||||
lines.join('\n')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a backup of the preferences file if it already exitsts.
|
|
||||||
const prefsPath = path.join(profilePath, 'prefs.js');
|
|
||||||
if (fs.existsSync(prefsPath)) {
|
|
||||||
const prefsBackupPath = path.join(profilePath, 'prefs.js.puppeteer');
|
|
||||||
await fs.promises.copyFile(prefsPath, prefsBackupPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _createProfile(extraPrefs: {[x: string]: unknown}): Promise<string> {
|
|
||||||
const temporaryProfilePath = await fs.promises.mkdtemp(
|
|
||||||
this.getProfilePath()
|
|
||||||
);
|
|
||||||
|
|
||||||
const prefs = this.defaultPreferences(extraPrefs);
|
|
||||||
await this.writePreferences(prefs, temporaryProfilePath);
|
|
||||||
|
|
||||||
return temporaryProfilePath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,39 @@ import {existsSync} from 'fs';
|
|||||||
import os, {tmpdir} from 'os';
|
import os, {tmpdir} from 'os';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
|
||||||
import {Browser} from '../api/Browser.js';
|
import {
|
||||||
|
CDP_WEBSOCKET_ENDPOINT_REGEX,
|
||||||
|
launch,
|
||||||
|
TimeoutError as BrowsersTimeoutError,
|
||||||
|
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
|
||||||
|
} from '@puppeteer/browsers';
|
||||||
|
|
||||||
|
import {Browser, BrowserCloseCallback} from '../api/Browser.js';
|
||||||
|
import {CDPBrowser} from '../common/Browser.js';
|
||||||
|
import {Connection} from '../common/Connection.js';
|
||||||
|
import {TimeoutError} from '../common/Errors.js';
|
||||||
|
import {NodeWebSocketTransport as WebSocketTransport} from '../common/NodeWebSocketTransport.js';
|
||||||
import {Product} from '../common/Product.js';
|
import {Product} from '../common/Product.js';
|
||||||
|
import {debugError} from '../common/util.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BrowserLaunchArgumentOptions,
|
BrowserLaunchArgumentOptions,
|
||||||
ChromeReleaseChannel,
|
ChromeReleaseChannel,
|
||||||
PuppeteerNodeLaunchOptions,
|
PuppeteerNodeLaunchOptions,
|
||||||
} from './LaunchOptions.js';
|
} from './LaunchOptions.js';
|
||||||
|
import {PipeTransport} from './PipeTransport.js';
|
||||||
import {PuppeteerNode} from './PuppeteerNode.js';
|
import {PuppeteerNode} from './PuppeteerNode.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export type ResolvedLaunchArgs = {
|
||||||
|
isTempUserDataDir: boolean;
|
||||||
|
userDataDir: string;
|
||||||
|
executablePath: string;
|
||||||
|
args: string[];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a launcher - a class that is able to create and launch a browser instance.
|
* Describes a launcher - a class that is able to create and launch a browser instance.
|
||||||
*
|
*
|
||||||
@ -57,9 +80,113 @@ export class ProductLauncher {
|
|||||||
return this.#product;
|
return this.#product;
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(object: PuppeteerNodeLaunchOptions): Promise<Browser>;
|
async launch(options: PuppeteerNodeLaunchOptions = {}): Promise<Browser> {
|
||||||
launch(): Promise<Browser> {
|
const {
|
||||||
throw new Error('Not implemented');
|
dumpio = false,
|
||||||
|
env = process.env,
|
||||||
|
handleSIGINT = true,
|
||||||
|
handleSIGTERM = true,
|
||||||
|
handleSIGHUP = true,
|
||||||
|
ignoreHTTPSErrors = false,
|
||||||
|
defaultViewport = {width: 800, height: 600},
|
||||||
|
slowMo = 0,
|
||||||
|
timeout = 30000,
|
||||||
|
waitForInitialPage = true,
|
||||||
|
protocol,
|
||||||
|
protocolTimeout,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const launchArgs = await this.computeLaunchArguments(options);
|
||||||
|
|
||||||
|
const usePipe = launchArgs.args.includes('--remote-debugging-pipe');
|
||||||
|
|
||||||
|
const onProcessExit = async () => {
|
||||||
|
await this.cleanUserDataDir(launchArgs.userDataDir, {
|
||||||
|
isTemp: launchArgs.isTempUserDataDir,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const browserProcess = launch({
|
||||||
|
executablePath: launchArgs.executablePath,
|
||||||
|
args: launchArgs.args,
|
||||||
|
handleSIGHUP,
|
||||||
|
handleSIGTERM,
|
||||||
|
handleSIGINT,
|
||||||
|
dumpio,
|
||||||
|
env,
|
||||||
|
pipe: usePipe,
|
||||||
|
onExit: onProcessExit,
|
||||||
|
});
|
||||||
|
|
||||||
|
let browser: Browser;
|
||||||
|
let connection: Connection;
|
||||||
|
let closing = false;
|
||||||
|
|
||||||
|
const browserCloseCallback = async () => {
|
||||||
|
if (closing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closing = true;
|
||||||
|
await this.closeBrowser(browserProcess, connection);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.#product === 'firefox' && protocol === 'webDriverBiDi') {
|
||||||
|
browser = await this.createBiDiBrowser(
|
||||||
|
browserProcess,
|
||||||
|
browserCloseCallback,
|
||||||
|
{
|
||||||
|
timeout,
|
||||||
|
protocolTimeout,
|
||||||
|
slowMo,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (usePipe) {
|
||||||
|
connection = await this.createCDPPipeConnection(browserProcess, {
|
||||||
|
timeout,
|
||||||
|
protocolTimeout,
|
||||||
|
slowMo,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
connection = await this.createCDPSocketConnection(browserProcess, {
|
||||||
|
timeout,
|
||||||
|
protocolTimeout,
|
||||||
|
slowMo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (protocol === 'webDriverBiDi') {
|
||||||
|
browser = await this.createBiDiOverCDPBrowser(
|
||||||
|
browserProcess,
|
||||||
|
connection,
|
||||||
|
browserCloseCallback
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
browser = await CDPBrowser._create(
|
||||||
|
this.product,
|
||||||
|
connection,
|
||||||
|
[],
|
||||||
|
ignoreHTTPSErrors,
|
||||||
|
defaultViewport,
|
||||||
|
browserProcess.nodeProcess,
|
||||||
|
browserCloseCallback,
|
||||||
|
options.targetFilter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
browserCloseCallback();
|
||||||
|
if (error instanceof BrowsersTimeoutError) {
|
||||||
|
throw new TimeoutError(error.message);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waitForInitialPage && protocol !== 'webDriverBiDi') {
|
||||||
|
await this.waitForPageTarget(browser, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
executablePath(channel?: ChromeReleaseChannel): string;
|
executablePath(channel?: ChromeReleaseChannel): string;
|
||||||
@ -81,6 +208,153 @@ export class ProductLauncher {
|
|||||||
return this.actualBrowserRevision;
|
return this.actualBrowserRevision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async computeLaunchArguments(
|
||||||
|
options: PuppeteerNodeLaunchOptions
|
||||||
|
): Promise<ResolvedLaunchArgs>;
|
||||||
|
protected async computeLaunchArguments(): Promise<ResolvedLaunchArgs> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async cleanUserDataDir(
|
||||||
|
path: string,
|
||||||
|
opts: {isTemp: boolean}
|
||||||
|
): Promise<void>;
|
||||||
|
protected async cleanUserDataDir(): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async closeBrowser(
|
||||||
|
browserProcess: ReturnType<typeof launch>,
|
||||||
|
connection?: Connection
|
||||||
|
): Promise<void> {
|
||||||
|
if (connection) {
|
||||||
|
// Attempt to close the browser gracefully
|
||||||
|
try {
|
||||||
|
await connection.closeBrowser();
|
||||||
|
await browserProcess.hasClosed();
|
||||||
|
} catch (error) {
|
||||||
|
debugError(error);
|
||||||
|
await browserProcess.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await browserProcess.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async waitForPageTarget(
|
||||||
|
browser: Browser,
|
||||||
|
timeout: number
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await browser.waitForTarget(
|
||||||
|
t => {
|
||||||
|
return t.type() === 'page';
|
||||||
|
},
|
||||||
|
{timeout}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
await browser.close();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async createCDPSocketConnection(
|
||||||
|
browserProcess: ReturnType<typeof launch>,
|
||||||
|
opts: {timeout: number; protocolTimeout: number | undefined; slowMo: number}
|
||||||
|
): Promise<Connection> {
|
||||||
|
const browserWSEndpoint = await browserProcess.waitForLineOutput(
|
||||||
|
CDP_WEBSOCKET_ENDPOINT_REGEX,
|
||||||
|
opts.timeout
|
||||||
|
);
|
||||||
|
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
||||||
|
return new Connection(
|
||||||
|
browserWSEndpoint,
|
||||||
|
transport,
|
||||||
|
opts.slowMo,
|
||||||
|
opts.protocolTimeout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async createCDPPipeConnection(
|
||||||
|
browserProcess: ReturnType<typeof launch>,
|
||||||
|
opts: {timeout: number; protocolTimeout: number | undefined; slowMo: number}
|
||||||
|
): Promise<Connection> {
|
||||||
|
// stdio was assigned during start(), and the 'pipe' option there adds the
|
||||||
|
// 4th and 5th items to stdio array
|
||||||
|
const {3: pipeWrite, 4: pipeRead} = browserProcess.nodeProcess.stdio;
|
||||||
|
const transport = new PipeTransport(
|
||||||
|
pipeWrite as NodeJS.WritableStream,
|
||||||
|
pipeRead as NodeJS.ReadableStream
|
||||||
|
);
|
||||||
|
return new Connection('', transport, opts.slowMo, opts.protocolTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async createBiDiOverCDPBrowser(
|
||||||
|
browserProcess: ReturnType<typeof launch>,
|
||||||
|
connection: Connection,
|
||||||
|
closeCallback: BrowserCloseCallback
|
||||||
|
): Promise<Browser> {
|
||||||
|
const BiDi = await import(
|
||||||
|
/* webpackIgnore: true */ '../common/bidi/bidi.js'
|
||||||
|
);
|
||||||
|
const bidiConnection = await BiDi.connectBidiOverCDP(connection);
|
||||||
|
return await BiDi.Browser.create({
|
||||||
|
connection: bidiConnection,
|
||||||
|
closeCallback,
|
||||||
|
process: browserProcess.nodeProcess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected async createBiDiBrowser(
|
||||||
|
browserProcess: ReturnType<typeof launch>,
|
||||||
|
closeCallback: BrowserCloseCallback,
|
||||||
|
opts: {timeout: number; protocolTimeout: number | undefined; slowMo: number}
|
||||||
|
): Promise<Browser> {
|
||||||
|
const browserWSEndpoint =
|
||||||
|
(await browserProcess.waitForLineOutput(
|
||||||
|
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
|
||||||
|
opts.timeout
|
||||||
|
)) + '/session';
|
||||||
|
const transport = await WebSocketTransport.create(browserWSEndpoint);
|
||||||
|
const BiDi = await import(
|
||||||
|
/* webpackIgnore: true */ '../common/bidi/bidi.js'
|
||||||
|
);
|
||||||
|
const bidiConnection = new BiDi.Connection(
|
||||||
|
transport,
|
||||||
|
opts.slowMo,
|
||||||
|
opts.protocolTimeout
|
||||||
|
);
|
||||||
|
return await BiDi.Browser.create({
|
||||||
|
connection: bidiConnection,
|
||||||
|
closeCallback,
|
||||||
|
process: browserProcess.nodeProcess,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './BrowserFetcher.js';
|
export * from './BrowserFetcher.js';
|
||||||
export * from './BrowserRunner.js';
|
|
||||||
export * from './ChromeLauncher.js';
|
export * from './ChromeLauncher.js';
|
||||||
export * from './FirefoxLauncher.js';
|
export * from './FirefoxLauncher.js';
|
||||||
export * from './LaunchOptions.js';
|
export * from './LaunchOptions.js';
|
||||||
|
@ -104,8 +104,10 @@ export const describeInstallation = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
if (process.env['KEEP_SANDBOX']) {
|
if (!process.env['KEEP_SANDBOX']) {
|
||||||
await rm(sandbox, {recursive: true, force: true, maxRetries: 5});
|
await rm(sandbox, {recursive: true, force: true, maxRetries: 5});
|
||||||
|
} else {
|
||||||
|
console.log('sandbox saved in ' + sandbox);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import {readAsset} from './util.js';
|
|||||||
|
|
||||||
describeInstallation(
|
describeInstallation(
|
||||||
'`puppeteer-core`',
|
'`puppeteer-core`',
|
||||||
{dependencies: ['puppeteer-core']},
|
{dependencies: ['@puppeteer/browsers', 'puppeteer-core']},
|
||||||
({itEvaluates}) => {
|
({itEvaluates}) => {
|
||||||
itEvaluates('CommonJS', {commonjs: true}, async () => {
|
itEvaluates('CommonJS', {commonjs: true}, async () => {
|
||||||
return readAsset('puppeteer-core', 'requires.cjs');
|
return readAsset('puppeteer-core', 'requires.cjs');
|
||||||
|
@ -462,10 +462,13 @@ describe('Launcher specs', function () {
|
|||||||
const options = Object.assign({}, defaultBrowserOptions);
|
const options = Object.assign({}, defaultBrowserOptions);
|
||||||
options.ignoreDefaultArgs = true;
|
options.ignoreDefaultArgs = true;
|
||||||
const browser = await puppeteer.launch(options);
|
const browser = await puppeteer.launch(options);
|
||||||
const page = await browser.newPage();
|
try {
|
||||||
expect(await page.evaluate('11 * 11')).toBe(121);
|
const page = await browser.newPage();
|
||||||
await page.close();
|
expect(await page.evaluate('11 * 11')).toBe(121);
|
||||||
await browser.close();
|
await page.close();
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('should filter out ignored default arguments in Chrome', async () => {
|
it('should filter out ignored default arguments in Chrome', async () => {
|
||||||
const {defaultBrowserOptions, puppeteer} = getTestState();
|
const {defaultBrowserOptions, puppeteer} = getTestState();
|
||||||
|
Loading…
Reference in New Issue
Block a user