diff --git a/src/rpc/client/connection.ts b/src/rpc/client/connection.ts index 13bd8a9d08..37258e179e 100644 --- a/src/rpc/client/connection.ts +++ b/src/rpc/client/connection.ts @@ -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 { const id = ++this._lastId; const validated = method === 'debugScopeState' ? params : validateParams(type, method, params); diff --git a/src/rpc/inprocess.ts b/src/rpc/inprocess.ts new file mode 100644 index 0000000000..bc8425ed22 --- /dev/null +++ b/src/rpc/inprocess.ts @@ -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; +} diff --git a/src/server/processLauncher.ts b/src/server/processLauncher.ts index 60b398fc8d..23aecf579c 100644 --- a/src/server/processLauncher.ts +++ b/src/server/processLauncher.ts @@ -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 { - 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) diff --git a/test/fixtures/closeme.js b/test/fixtures/closeme.js index faab47e16b..3a0e6f90a9 100644 --- a/test/fixtures/closeme.js +++ b/test/fixtures/closeme.js @@ -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})`); diff --git a/test/jest/fixtures.js b/test/jest/fixtures.js index ada40e88ef..86155cf1cc 100644 --- a/test/jest/fixtures.js +++ b/test/jest/fixtures.js @@ -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) => {