2022-10-21 13:09:21 +00:00
|
|
|
import {accessSync} from 'fs';
|
|
|
|
import {mkdtemp} from 'fs/promises';
|
|
|
|
import os from 'os';
|
2022-06-22 22:13:39 +00:00
|
|
|
import path from 'path';
|
2022-09-14 14:40:58 +00:00
|
|
|
import {CDPBrowser} from '../common/Browser.js';
|
2022-10-05 12:17:03 +00:00
|
|
|
import {assert} from '../util/assert.js';
|
2022-06-22 22:13:39 +00:00
|
|
|
import {BrowserRunner} from './BrowserRunner.js';
|
|
|
|
import {
|
|
|
|
BrowserLaunchArgumentOptions,
|
|
|
|
ChromeReleaseChannel,
|
|
|
|
PuppeteerNodeLaunchOptions,
|
|
|
|
} from './LaunchOptions.js';
|
2022-10-21 13:09:21 +00:00
|
|
|
import {ProductLauncher} from './ProductLauncher.js';
|
|
|
|
import {PuppeteerNode} from './PuppeteerNode.js';
|
2022-06-22 22:13:39 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2022-10-21 13:09:21 +00:00
|
|
|
export class ChromeLauncher extends ProductLauncher {
|
|
|
|
constructor(puppeteer: PuppeteerNode) {
|
|
|
|
super(puppeteer, 'chrome');
|
2022-06-22 22:13:39 +00:00
|
|
|
}
|
|
|
|
|
2022-10-21 13:09:21 +00:00
|
|
|
override async launch(
|
|
|
|
options: PuppeteerNodeLaunchOptions = {}
|
|
|
|
): Promise<CDPBrowser> {
|
2022-06-22 22:13:39 +00:00
|
|
|
const {
|
|
|
|
ignoreDefaultArgs = false,
|
|
|
|
args = [],
|
|
|
|
dumpio = false,
|
|
|
|
channel,
|
|
|
|
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,
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
const chromeArguments = [];
|
|
|
|
if (!ignoreDefaultArgs) {
|
|
|
|
chromeArguments.push(...this.defaultArgs(options));
|
|
|
|
} else if (Array.isArray(ignoreDefaultArgs)) {
|
|
|
|
chromeArguments.push(
|
|
|
|
...this.defaultArgs(options).filter(arg => {
|
|
|
|
return !ignoreDefaultArgs.includes(arg);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
chromeArguments.push(...args);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
!chromeArguments.some(argument => {
|
|
|
|
return argument.startsWith('--remote-debugging-');
|
|
|
|
})
|
|
|
|
) {
|
|
|
|
if (pipe) {
|
|
|
|
assert(
|
|
|
|
!debuggingPort,
|
|
|
|
'Browser should be launched with either pipe or debugging port - not both.'
|
|
|
|
);
|
|
|
|
chromeArguments.push('--remote-debugging-pipe');
|
|
|
|
} else {
|
|
|
|
chromeArguments.push(`--remote-debugging-port=${debuggingPort || 0}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-27 05:40:30 +00:00
|
|
|
let isTempUserDataDir = false;
|
2022-06-22 22:13:39 +00:00
|
|
|
|
|
|
|
// Check for the user data dir argument, which will always be set even
|
|
|
|
// with a custom directory specified via the userDataDir option.
|
|
|
|
let userDataDirIndex = chromeArguments.findIndex(arg => {
|
|
|
|
return arg.startsWith('--user-data-dir');
|
|
|
|
});
|
|
|
|
if (userDataDirIndex < 0) {
|
2022-06-27 05:40:30 +00:00
|
|
|
isTempUserDataDir = true;
|
2022-06-22 22:13:39 +00:00
|
|
|
chromeArguments.push(
|
2022-10-21 13:09:21 +00:00
|
|
|
`--user-data-dir=${await mkdtemp(this.getProfilePath())}`
|
2022-06-22 22:13:39 +00:00
|
|
|
);
|
|
|
|
userDataDirIndex = chromeArguments.length - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const userDataDir = chromeArguments[userDataDirIndex]!.split('=', 2)[1];
|
|
|
|
assert(typeof userDataDir === 'string', '`--user-data-dir` is malformed');
|
|
|
|
|
|
|
|
let chromeExecutable = executablePath;
|
2022-10-21 13:09:21 +00:00
|
|
|
if (!chromeExecutable) {
|
2022-06-22 22:13:39 +00:00
|
|
|
assert(
|
2022-10-21 13:09:21 +00:00
|
|
|
channel || !this.puppeteer._isPuppeteerCore,
|
|
|
|
`An \`executablePath\` or \`channel\` must be specified for \`puppeteer-core\``
|
2022-06-22 22:13:39 +00:00
|
|
|
);
|
2022-10-21 13:09:21 +00:00
|
|
|
chromeExecutable = this.executablePath(channel);
|
2022-06-22 22:13:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
|
|
|
|
const runner = new BrowserRunner(
|
|
|
|
this.product,
|
|
|
|
chromeExecutable,
|
|
|
|
chromeArguments,
|
|
|
|
userDataDir,
|
|
|
|
isTempUserDataDir
|
|
|
|
);
|
|
|
|
runner.start({
|
|
|
|
handleSIGHUP,
|
|
|
|
handleSIGTERM,
|
|
|
|
handleSIGINT,
|
|
|
|
dumpio,
|
|
|
|
env,
|
|
|
|
pipe: usePipe,
|
|
|
|
});
|
|
|
|
|
|
|
|
let browser;
|
|
|
|
try {
|
|
|
|
const connection = await runner.setupConnection({
|
|
|
|
usePipe,
|
|
|
|
timeout,
|
|
|
|
slowMo,
|
2022-10-21 13:09:21 +00:00
|
|
|
preferredRevision: this.puppeteer.browserRevision,
|
2022-06-22 22:13:39 +00:00
|
|
|
});
|
2022-09-14 14:40:58 +00:00
|
|
|
browser = await CDPBrowser._create(
|
2022-07-21 18:50:46 +00:00
|
|
|
this.product,
|
2022-06-22 22:13:39 +00:00
|
|
|
connection,
|
|
|
|
[],
|
|
|
|
ignoreHTTPSErrors,
|
|
|
|
defaultViewport,
|
|
|
|
runner.proc,
|
2022-08-11 09:15:00 +00:00
|
|
|
runner.close.bind(runner),
|
|
|
|
options.targetFilter
|
2022-06-22 22:13:39 +00:00
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
runner.kill();
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (waitForInitialPage) {
|
|
|
|
try {
|
|
|
|
await browser.waitForTarget(
|
|
|
|
t => {
|
|
|
|
return t.type() === 'page';
|
|
|
|
},
|
|
|
|
{timeout}
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
await browser.close();
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return browser;
|
|
|
|
}
|
|
|
|
|
2022-10-21 13:09:21 +00:00
|
|
|
override defaultArgs(options: BrowserLaunchArgumentOptions = {}): string[] {
|
2022-11-10 10:43:37 +00:00
|
|
|
// See https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
|
2022-06-22 22:13:39 +00:00
|
|
|
const chromeArguments = [
|
|
|
|
'--allow-pre-commit-input',
|
|
|
|
'--disable-background-networking',
|
|
|
|
'--disable-background-timer-throttling',
|
|
|
|
'--disable-backgrounding-occluded-windows',
|
|
|
|
'--disable-breakpad',
|
|
|
|
'--disable-client-side-phishing-detection',
|
|
|
|
'--disable-component-extensions-with-background-pages',
|
2022-11-10 10:43:37 +00:00
|
|
|
'--disable-component-update',
|
2022-06-22 22:13:39 +00:00
|
|
|
'--disable-default-apps',
|
|
|
|
'--disable-dev-shm-usage',
|
|
|
|
'--disable-extensions',
|
2022-07-29 08:01:15 +00:00
|
|
|
// AcceptCHFrame disabled because of crbug.com/1348106.
|
2022-11-10 10:43:37 +00:00
|
|
|
'--disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints',
|
2022-06-22 22:13:39 +00:00
|
|
|
'--disable-hang-monitor',
|
|
|
|
'--disable-ipc-flooding-protection',
|
|
|
|
'--disable-popup-blocking',
|
|
|
|
'--disable-prompt-on-repost',
|
|
|
|
'--disable-renderer-backgrounding',
|
|
|
|
'--disable-sync',
|
2022-11-10 10:43:37 +00:00
|
|
|
'--enable-automation',
|
|
|
|
// TODO(sadym): remove '--enable-blink-features=IdleDetection' once
|
|
|
|
// IdleDetection is turned on by default.
|
|
|
|
'--enable-blink-features=IdleDetection',
|
|
|
|
'--enable-features=NetworkServiceInProcess2',
|
|
|
|
'--export-tagged-pdf',
|
2022-06-22 22:13:39 +00:00
|
|
|
'--force-color-profile=srgb',
|
|
|
|
'--metrics-recording-only',
|
|
|
|
'--no-first-run',
|
|
|
|
'--password-store=basic',
|
|
|
|
'--use-mock-keychain',
|
|
|
|
];
|
|
|
|
const {
|
|
|
|
devtools = false,
|
|
|
|
headless = !devtools,
|
|
|
|
args = [],
|
|
|
|
userDataDir,
|
|
|
|
} = options;
|
|
|
|
if (userDataDir) {
|
|
|
|
chromeArguments.push(`--user-data-dir=${path.resolve(userDataDir)}`);
|
|
|
|
}
|
|
|
|
if (devtools) {
|
|
|
|
chromeArguments.push('--auto-open-devtools-for-tabs');
|
|
|
|
}
|
|
|
|
if (headless) {
|
|
|
|
chromeArguments.push(
|
2022-12-07 13:54:00 +00:00
|
|
|
headless === 'new' ? '--headless=new' : '--headless',
|
2022-06-22 22:13:39 +00:00
|
|
|
'--hide-scrollbars',
|
|
|
|
'--mute-audio'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
args.every(arg => {
|
|
|
|
return arg.startsWith('-');
|
|
|
|
})
|
|
|
|
) {
|
|
|
|
chromeArguments.push('about:blank');
|
|
|
|
}
|
|
|
|
chromeArguments.push(...args);
|
|
|
|
return chromeArguments;
|
|
|
|
}
|
|
|
|
|
2022-10-21 13:09:21 +00:00
|
|
|
override executablePath(channel?: ChromeReleaseChannel): string {
|
2022-06-22 22:13:39 +00:00
|
|
|
if (channel) {
|
2022-10-21 13:09:21 +00:00
|
|
|
return this.#executablePathForChannel(channel);
|
2022-06-22 22:13:39 +00:00
|
|
|
} else {
|
2022-10-21 13:09:21 +00:00
|
|
|
return this.resolveExecutablePath();
|
2022-06-22 22:13:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-21 13:09:21 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
#executablePathForChannel(channel: ChromeReleaseChannel): string {
|
|
|
|
const platform = os.platform();
|
|
|
|
|
|
|
|
let chromePath: string | undefined;
|
|
|
|
switch (platform) {
|
|
|
|
case 'win32':
|
|
|
|
switch (channel) {
|
|
|
|
case 'chrome':
|
|
|
|
chromePath = `${process.env['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe`;
|
|
|
|
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;
|
2022-06-22 22:13:39 +00:00
|
|
|
}
|
|
|
|
}
|