feat(rpc): run fixtures.jest.js with channel (#3227)

Also, introduce setupInProcess wrapper to be used for in-process rpc.
This commit is contained in:
Dmitry Gozman 2020-07-30 10:22:28 -07:00 committed by GitHub
parent 4961c2ddbb
commit cefb1b9727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 50 deletions

View File

@ -66,6 +66,10 @@ export class Connection {
return new Promise(f => this._waitingForObject.set(guid, f));
}
getObjectWithKnownName(guid: string): any {
return this._objects.get(guid)!;
}
async sendMessageToServer(type: string, guid: string, method: string, params: any): Promise<any> {
const id = ++this._lastId;
const validated = method === 'debugScopeState' ? params : validateParams(type, method, params);

46
src/rpc/inprocess.ts Normal file
View File

@ -0,0 +1,46 @@
/**
* 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 { DispatcherConnection } from './server/dispatcher';
import type { Playwright as PlaywrightImpl } from '../server/playwright';
import type { Playwright as PlaywrightAPI } from './client/playwright';
import { PlaywrightDispatcher } from './server/playwrightDispatcher';
import { setUseApiName } from '../progress';
import { Connection } from './client/connection';
import { isUnderTest } from '../helper';
export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI {
setUseApiName(false);
const clientConnection = new Connection();
const dispatcherConnection = new DispatcherConnection();
// Dispatch synchronously at first.
dispatcherConnection.onmessage = message => clientConnection.dispatch(message);
clientConnection.onmessage = message => dispatcherConnection.dispatch(message);
// Initialize Playwright channel.
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright);
const playwrightAPI = clientConnection.getObjectWithKnownName('Playwright');
// Switch to async dispatch after we got Playwright object.
dispatcherConnection.onmessage = message => setImmediate(() => clientConnection.dispatch(message));
clientConnection.onmessage = message => setImmediate(() => dispatcherConnection.dispatch(message));
if (isUnderTest())
playwrightAPI._toImpl = (x: any) => dispatcherConnection._dispatchers.get(x._guid)!._object;
return playwrightAPI;
}

View File

@ -19,7 +19,7 @@ import * as childProcess from 'child_process';
import * as readline from 'readline';
import * as removeFolder from 'rimraf';
import * as stream from 'stream';
import { helper } from '../helper';
import { helper, isUnderTest } from '../helper';
import { Progress } from '../progress';
export type Env = {[key: string]: string | number | boolean | undefined};
@ -113,7 +113,13 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
const listeners = [ helper.addEventListener(process, 'exit', killProcess) ];
if (options.handleSIGINT) {
listeners.push(helper.addEventListener(process, 'SIGINT', () => {
gracefullyClose().then(() => process.exit(130));
gracefullyClose().then(() => {
// Give tests a chance to dispatch any async calls.
if (isUnderTest())
setTimeout(() => process.exit(130), 0);
else
process.exit(130);
});
}));
}
if (options.handleSIGTERM)

View File

@ -9,10 +9,12 @@
const path = require('path');
const { setUnderTest } = require(path.join(playwrightPath, 'lib', 'helper'));
const { setupInProcess } = require(path.join(playwrightPath, 'lib', 'rpc', 'inprocess'));
setUnderTest();
const playwrightFile = path.join(playwrightPath, 'index');
const playwrightImpl = require(path.join(playwrightPath, 'index'));
const playwright = process.env.PWCHANNEL ? setupInProcess(playwrightImpl) : playwrightImpl;
const browserServer = await require(playwrightFile)[browserTypeName].launchServer(launchOptions);
const browserServer = await playwright[browserTypeName].launchServer(launchOptions);
browserServer.on('close', (exitCode, signal) => {
console.log(`(exitCode=>${exitCode})`);
console.log(`(signal=>${signal})`);

View File

@ -16,14 +16,12 @@
const path = require('path');
const childProcess = require('child_process');
const playwrightImpl = require('../../index');
const playwright = require('../../index');
const { TestServer } = require('../../utils/testserver/');
const { DispatcherConnection } = require('../../lib/rpc/server/dispatcher');
const { Connection } = require('../../lib/rpc/client/connection');
const { Transport } = require('../../lib/rpc/transport');
const { PlaywrightDispatcher } = require('../../lib/rpc/server/playwrightDispatcher');
const { setUseApiName } = require('../../lib/progress');
const { setupInProcess } = require('../../lib/rpc/inprocess');
const { setUnderTest } = require('../../lib/helper');
setUnderTest();
@ -80,57 +78,38 @@ module.exports = function registerFixtures(global) {
});
global.registerWorkerFixture('playwright', async({}, test) => {
Error.stackTraceLimit = 15;
if (process.env.PWCHANNEL) {
setUseApiName(false);
if (process.env.PWCHANNEL === 'wire') {
const connection = new Connection();
let toImpl;
let spawnedProcess;
let onExit;
if (process.env.PWCHANNEL === 'wire') {
spawnedProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'rpc', 'server'), [], {
stdio: 'pipe',
detached: true,
});
spawnedProcess.unref();
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));
} else {
const dispatcherConnection = new DispatcherConnection();
dispatcherConnection.onmessage = async message => {
setImmediate(() => connection.dispatch(message));
};
connection.onmessage = async message => {
const result = await dispatcherConnection.dispatch(message);
await new Promise(f => setImmediate(f));
return result;
};
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright);
toImpl = x => dispatcherConnection._dispatchers.get(x._guid)._object;
}
const spawnedProcess = childProcess.fork(path.join(__dirname, '..', '..', 'lib', 'rpc', 'server'), [], {
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');
playwrightObject.toImpl = toImpl;
await test(playwrightObject);
if (spawnedProcess) {
spawnedProcess.removeListener('exit', onExit);
spawnedProcess.stdin.destroy();
spawnedProcess.stdout.destroy();
spawnedProcess.stderr.destroy();
}
spawnedProcess.removeListener('exit', onExit);
spawnedProcess.stdin.destroy();
spawnedProcess.stdout.destroy();
spawnedProcess.stderr.destroy();
} else if (process.env.PWCHANNEL) {
const playwright = setupInProcess(playwrightImpl);
await test(playwright);
} else {
playwright.toImpl = x => x;
const playwright = playwrightImpl;
playwright._toImpl = x => x;
await test(playwright);
}
});
global.registerFixture('toImpl', async ({playwright}, test) => {
await test(playwright.toImpl);
await test(playwright._toImpl);
});
global.registerWorkerFixture('browserType', async ({playwright}, test) => {