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.
This commit is contained in:
Dmitry Gozman 2021-02-12 09:05:32 -08:00 committed by GitHub
parent 8500592326
commit 3e7b8e3d74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 127 additions and 20 deletions

View File

@ -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);
});

View File

@ -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"

65
src/outofprocess.ts Normal file
View File

@ -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<Playwright>;
_driverProcess: childProcess.ChildProcess;
private _closePromise: Promise<void>;
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;
}
}

View File

@ -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;

View File

@ -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/**'];