From 3e7b8e3d74606da18e0651c3fa331e1b38a85623 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 12 Feb 2021 09:05:32 -0800 Subject: [PATCH] test: add basic end-to-end driver test (#5426) - Introduce internal "out of process" start()/stop() mode. - This mode is used both in regular tests and installation tests. - Emulate basic driver installation, browser download and running. --- packages/installation-tests/driver-client.js | 41 ++++++++++++ .../installation-tests/installation-tests.sh | 17 +++++ src/outofprocess.ts | 65 +++++++++++++++++++ test/fixtures.ts | 23 +------ utils/check_deps.js | 1 + 5 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 packages/installation-tests/driver-client.js create mode 100644 src/outofprocess.ts diff --git a/packages/installation-tests/driver-client.js b/packages/installation-tests/driver-client.js new file mode 100644 index 0000000000..05129f9996 --- /dev/null +++ b/packages/installation-tests/driver-client.js @@ -0,0 +1,41 @@ +/** + * 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 { start } = require('playwright/lib/outofprocess.js'); + +(async () => { + const playwright = await start(); + console.log(`driver PID=${playwright.driverProcess.pid}`); + for (const browserType of ['chromium', 'firefox', 'webkit']) { + try { + const browser = await playwright[browserType].launch(); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.evaluate(() => navigator.userAgent); + await browser.close(); + console.log(`${browserType} SUCCESS`); + } catch (e) { + console.error(`Should be able to launch ${browserType} from ${requireName}`); + console.error(e); + process.exit(1); + } + } + await playwright.stop(); + console.log(`driver SUCCESS`); +})().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/packages/installation-tests/installation-tests.sh b/packages/installation-tests/installation-tests.sh index 0372674e27..0fab37ceba 100755 --- a/packages/installation-tests/installation-tests.sh +++ b/packages/installation-tests/installation-tests.sh @@ -38,6 +38,7 @@ function copy_test_scripts { cp "${SCRIPTS_PATH}/esm-playwright-webkit.mjs" . cp "${SCRIPTS_PATH}/sanity-electron.js" . cp "${SCRIPTS_PATH}/electron-app.js" . + cp "${SCRIPTS_PATH}/driver-client.js" . } function run_tests { @@ -57,6 +58,7 @@ function run_tests { test_playwright_cli_screenshot_should_work test_playwright_cli_install_should_work test_playwright_cli_codegen_should_work + test_playwright_driver_should_work } function test_screencast { @@ -430,6 +432,21 @@ function test_playwright_cli_codegen_should_work { echo "${FUNCNAME[0]} success" } +function test_playwright_driver_should_work { + initialize_test "${FUNCNAME[0]}" + + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install ${PLAYWRIGHT_TGZ} + + echo "Running playwright install" + PLAYWRIGHT_BROWSERS_PATH="0" npx playwright install + + copy_test_scripts + echo "Running driver-client.js" + PLAYWRIGHT_BROWSERS_PATH="0" node driver-client.js + + echo "${FUNCNAME[0]} success" +} + function initialize_test { cd ${TEST_ROOT} local TEST_NAME="./$1" diff --git a/src/outofprocess.ts b/src/outofprocess.ts new file mode 100644 index 0000000000..ff5edb0c91 --- /dev/null +++ b/src/outofprocess.ts @@ -0,0 +1,65 @@ +/** + * 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. + */ + +import { Connection } from './client/connection'; +import { Transport } from './protocol/transport'; +import { Playwright } from './client/playwright'; +import * as childProcess from 'child_process'; +import * as path from 'path'; + +export async function start() { + const client = new PlaywrightClient(); + const playwright = await client._playwright; + (playwright as any).stop = () => client.stop(); + (playwright as any).driverProcess = client._driverProcess; + return playwright; +} + +class PlaywrightClient { + _playwright: Promise; + _driverProcess: childProcess.ChildProcess; + private _closePromise: Promise; + private _onExit: (exitCode: number | null, signal: string | null) => {}; + + constructor() { + this._onExit = (exitCode: number | null, signal: string | null) => { + throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`); + }; + + this._driverProcess = childProcess.fork(path.join(__dirname, 'cli', 'cli.js'), ['run-driver'], { + stdio: 'pipe', + detached: true, + }); + this._driverProcess.unref(); + this._driverProcess.on('exit', this._onExit); + + const connection = new Connection(); + const transport = new Transport(this._driverProcess.stdin, this._driverProcess.stdout); + connection.onmessage = message => transport.send(JSON.stringify(message)); + transport.onmessage = message => connection.dispatch(JSON.parse(message)); + this._closePromise = new Promise(f => transport.onclose = f); + + this._playwright = connection.waitForObjectWithKnownName('Playwright'); + } + + async stop() { + this._driverProcess.removeListener('exit', this._onExit); + this._driverProcess.stdin.destroy(); + this._driverProcess.stdout.destroy(); + this._driverProcess.stderr.destroy(); + await this._closePromise; + } +} diff --git a/test/fixtures.ts b/test/fixtures.ts index ef2b67c00b..0091dfc459 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -21,12 +21,11 @@ import path from 'path'; import util from 'util'; import os from 'os'; import type { Browser, BrowserContext, BrowserType, Page } from '../index'; -import { Connection } from '../lib/client/connection'; -import { Transport } from '../lib/protocol/transport'; import { installCoverageHooks } from './coverage'; import { folio as httpFolio } from './http.fixtures'; import { folio as playwrightFolio } from './playwright.fixtures'; import { PlaywrightClient } from '../lib/remote/playwrightClient'; +import { start } from '../lib/outofprocess'; export { expect, config } from 'folio'; const removeFolderAsync = util.promisify(require('rimraf')); @@ -105,25 +104,9 @@ fixtures.playwright.override(async ({ browserName, testWorkerIndex, platform, mo const { coverage, uninstall } = installCoverageHooks(browserName); require('../lib/utils/utils').setUnderTest(); if (mode === 'driver') { - const connection = new Connection(); - const spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'cli', 'cli.js'), ['run-driver'], { - stdio: 'pipe', - detached: true, - }); - spawnedProcess.unref(); - const onExit = (exitCode, signal) => { - throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`); - }; - spawnedProcess.on('exit', onExit); - const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout); - connection.onmessage = message => transport.send(JSON.stringify(message)); - transport.onmessage = message => connection.dispatch(JSON.parse(message)); - const playwrightObject = await connection.waitForObjectWithKnownName('Playwright'); + const playwrightObject = await start(); await run(playwrightObject); - spawnedProcess.removeListener('exit', onExit); - spawnedProcess.stdin.destroy(); - spawnedProcess.stdout.destroy(); - spawnedProcess.stderr.destroy(); + await playwrightObject.stop(); await teardownCoverage(); } else if (mode === 'service') { const port = 9407 + testWorkerIndex * 2; diff --git a/utils/check_deps.js b/utils/check_deps.js index aecbe962d8..2af2567fa6 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -113,6 +113,7 @@ DEPS['src/install/'] = ['src/utils/']; // Client depends on chromium protocol for types. DEPS['src/client/'] = ['src/common/', 'src/utils/', 'src/protocol/', 'src/server/chromium/protocol.ts']; +DEPS['src/outofprocess.ts'] = ['src/client/', 'src/protocol/']; DEPS['src/dispatchers/'] = ['src/common/', 'src/utils/', 'src/protocol/', 'src/server/**'];