diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac1fbc0557..b1151a7f17 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -119,7 +119,7 @@ jobs: - run: npm ci - run: npm run build - run: node lib/cli/cli install-deps - - run: bash packages/installation-tests/installation-tests.sh + - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- bash packages/installation-tests/installation-tests.sh headful_linux: name: "Headful Linux" diff --git a/packages/installation-tests/inspector-custom-executable.js b/packages/installation-tests/inspector-custom-executable.js new file mode 100644 index 0000000000..ab62b3bd8d --- /dev/null +++ b/packages/installation-tests/inspector-custom-executable.js @@ -0,0 +1,26 @@ +/** + * 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 playwright = require('playwright'); + +(async () => { + const browser = await playwright.chromium.launch({ + executablePath: '/opt/google/chrome/chrome' + }); + const context = await browser.newContext(); + await context.newPage(); + await browser.close(); + console.log('SUCCESS'); +})(); diff --git a/packages/installation-tests/installation-tests.sh b/packages/installation-tests/installation-tests.sh index cf7c879688..1881112d99 100755 --- a/packages/installation-tests/installation-tests.sh +++ b/packages/installation-tests/installation-tests.sh @@ -39,6 +39,7 @@ mkdir -p "${TEST_ROOT}" NODE_VERSION="$(node --version)" function copy_test_scripts { + cp "${SCRIPTS_PATH}/inspector-custom-executable.js" . cp "${SCRIPTS_PATH}/sanity.js" . cp "${SCRIPTS_PATH}/screencast.js" . cp "${SCRIPTS_PATH}/esm.mjs" . @@ -55,6 +56,7 @@ function run_tests { test_screencast test_typescript_types test_skip_browser_download + test_skip_browser_download_inspect_with_custom_executable test_playwright_global_installation_subsequent_installs test_playwright_should_work test_playwright_should_work_with_relative_home_path @@ -194,6 +196,32 @@ function test_skip_browser_download { echo "${FUNCNAME[0]} success" } +function test_skip_browser_download_inspect_with_custom_executable { + initialize_test "${FUNCNAME[0]}" + copy_test_scripts + + OUTPUT=$(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install ${PLAYWRIGHT_TGZ}) + if [[ "${OUTPUT}" != *"Skipping browsers download because"* ]]; then + echo "missing log message that browsers download is skipped" + exit 1 + fi + + if [[ "$(uname)" != "Linux" ]]; then + echo + echo "Skipping test on non-Linux platform" + echo + return + fi + + OUTPUT=$(PWDEBUG=1 node inspector-custom-executable.js) + if [[ "${OUTPUT}" != *"SUCCESS"* ]]; then + echo "missing log message that launch succeeded: ${OUTPUT}" + exit 1 + fi + + echo "${FUNCNAME[0]} success" +} + function test_playwright_should_work { initialize_test "${FUNCNAME[0]}" diff --git a/src/server/browser.ts b/src/server/browser.ts index 14d441d8d3..0cfd567800 100644 --- a/src/server/browser.ts +++ b/src/server/browser.ts @@ -46,6 +46,7 @@ export type BrowserOptions = PlaywrightOptions & { headful?: boolean, persistent?: types.BrowserContextOptions, // Undefined means no persistent context. browserProcess: BrowserProcess, + customExecutablePath?: string; proxy?: ProxySettings, protocolLogger: types.ProtocolLogger, browserLogsCollector: RecentLogsCollector, diff --git a/src/server/browserType.ts b/src/server/browserType.ts index 539048578b..b71de74cc6 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -51,7 +51,7 @@ export abstract class BrowserType extends SdkObject { this._registry = playwrightOptions.registry; } - executablePath(options?: types.LaunchOptions): string { + executablePath(channel?: types.BrowserChannel): string { return this._registry.executablePath(this._name) || ''; } @@ -110,6 +110,7 @@ export abstract class BrowserType extends SdkObject { headful: !options.headless, downloadsPath, browserProcess, + customExecutablePath: options.executablePath, proxy: options.proxy, protocolLogger, browserLogsCollector, @@ -166,7 +167,7 @@ export abstract class BrowserType extends SdkObject { else browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir)); - const executable = executablePath || this.executablePath(options); + const executable = executablePath || this.executablePath(options.channel); if (!executable) throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); if (!(await existsAsync(executable))) { diff --git a/src/server/chromium/chromium.ts b/src/server/chromium/chromium.ts index 2531f29440..400951305b 100644 --- a/src/server/chromium/chromium.ts +++ b/src/server/chromium/chromium.ts @@ -44,10 +44,10 @@ export class Chromium extends BrowserType { this._devtools = this._createDevTools(); } - executablePath(options?: types.LaunchOptions): string { - if (options?.channel) - return findChromiumChannel(options.channel); - return super.executablePath(options); + executablePath(channel?: types.BrowserChannel): string { + if (channel) + return findChromiumChannel(channel); + return super.executablePath(channel); } async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number) { diff --git a/src/server/supplements/recorder/recorderApp.ts b/src/server/supplements/recorder/recorderApp.ts index 7688622b6f..0f7e67ae60 100644 --- a/src/server/supplements/recorder/recorderApp.ts +++ b/src/server/supplements/recorder/recorderApp.ts @@ -26,8 +26,10 @@ import { internalCallMetadata } from '../../instrumentation'; import type { CallLog, EventData, Mode, Source } from './recorderTypes'; import { BrowserContext } from '../../browserContext'; import { isUnderTest } from '../../../utils/utils'; +import * as types from '../../types'; const readFileAsync = util.promisify(fs.readFile); +const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); declare global { interface Window { @@ -101,8 +103,17 @@ export class RecorderApp extends EventEmitter { ]; if (process.env.PWTEST_RECORDER_PORT) args.push(`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`); + let channel: types.BrowserChannel | undefined; + let executablePath: string | undefined; + if (inspectedContext._browser.options.isChromium) { + channel = inspectedContext._browser.options.channel; + const defaultExecutablePath = recorderPlaywright.chromium.executablePath(channel); + if (!(await existsAsync(defaultExecutablePath))) + executablePath = inspectedContext._browser.options.customExecutablePath; + } const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', { - channel: inspectedContext._browser.options.channel, + channel, + executablePath, sdkLanguage: inspectedContext._options.sdkLanguage, args, noDefaultViewport: true, diff --git a/src/server/webkit/webkit.ts b/src/server/webkit/webkit.ts index 1c1660d1a9..09e6f7521f 100644 --- a/src/server/webkit/webkit.ts +++ b/src/server/webkit/webkit.ts @@ -31,16 +31,16 @@ export class WebKit extends BrowserType { super('webkit', playwrightOptions); } - executablePath(options?: types.LaunchOptions): string { - if (options?.channel) { + executablePath(channel?: types.BrowserChannel): string { + if (channel) { let executablePath = undefined; - if ((options.channel as any) === 'technology-preview') + if ((channel as any) === 'technology-preview') executablePath = this._registry.executablePath('webkit-technology-preview'); - assert(executablePath, `unsupported webkit channel "${options.channel}"`); - assert(fs.existsSync(executablePath), `webkit channel "${options.channel}" is not installed. Try running 'npx playwright install webkit-technology-preview'`); + assert(executablePath, `unsupported webkit channel "${channel}"`); + assert(fs.existsSync(executablePath), `webkit channel "${channel}" is not installed. Try running 'npx playwright install webkit-technology-preview'`); return executablePath; } - return super.executablePath(options); + return super.executablePath(channel); } _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise {