Skip to content

Commit 2151757

Browse files
authored
feat(rpc): run rpc tests in-process and out-of-process (#2929)
1 parent 0346c3a commit 2151757

16 files changed

+120
-82
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ jobs:
174174
fail-fast: false
175175
matrix:
176176
browser: [chromium, firefox, webkit]
177-
transport: [json, object]
177+
transport: [wire, object]
178178
runs-on: ubuntu-18.04
179179
steps:
180180
- uses: actions/checkout@v2
@@ -194,8 +194,7 @@ jobs:
194194
env:
195195
BROWSER: ${{ matrix.browser }}
196196
DEBUG: "*,-pw:wrapped*"
197-
PWCHANNEL: "1"
198-
PWCHANNELTRANSPORT: ${{ matrix.transport }}
197+
PWCHANNEL: ${{ matrix.transport }}
199198
- uses: actions/upload-artifact@v1
200199
if: failure()
201200
with:

src/rpc/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Transport } from './transport';
2222
(async () => {
2323
const spawnedProcess = childProcess.fork(path.join(__dirname, 'server'), [], { stdio: 'pipe' });
2424
const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout);
25+
transport.onclose = () => process.exit(0);
2526
const connection = new Connection();
2627
connection.onmessage = message => transport.send(JSON.stringify(message));
2728
transport.onmessage = message => connection.dispatch(JSON.parse(message));

src/rpc/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { PlaywrightDispatcher } from './server/playwrightDispatcher';
2121

2222
const dispatcherConnection = new DispatcherConnection();
2323
const transport = new Transport(process.stdout, process.stdin);
24+
transport.onclose = () => process.exit(0);
2425
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
2526
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message));
2627

src/rpc/transport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class Transport {
2929
constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) {
3030
this._pipeWrite = pipeWrite;
3131
pipeRead.on('data', buffer => this._dispatch(buffer));
32-
pipeRead.on('close', () => process.exit(0));
32+
pipeRead.on('close', () => this.onclose && this.onclose());
3333
this.onmessage = undefined;
3434
this.onclose = undefined;
3535
}

test/chromium/session.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('../utils').testOptions(browserType);
17+
const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = require('../utils').testOptions(browserType);
1818

1919
describe('ChromiumBrowserContext.createSession', function() {
2020
it('should work', async function({page, browser, server}) {
@@ -66,7 +66,7 @@ describe('ChromiumBrowserContext.createSession', function() {
6666
}
6767
expect(error.message).toContain(CHANNEL ? 'Target browser or context has been closed' : 'Session closed.');
6868
});
69-
it('should throw nice errors', async function({page, browser}) {
69+
it.skip(USES_HOOKS)('should throw nice errors', async function({page, browser}) {
7070
const client = await page.context().newCDPSession(page);
7171
const error = await theSourceOfTheProblems().catch(error => error);
7272
expect(error.stack).toContain('theSourceOfTheProblems');

test/dispatchevent.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
const utils = require('./utils');
18-
const {FFOX, CHROMIUM, WEBKIT, WIN} = utils.testOptions(browserType);
18+
const {FFOX, CHROMIUM, WEBKIT, WIN, USES_HOOKS} = utils.testOptions(browserType);
1919

2020
describe('Page.dispatchEvent(click)', function() {
2121
it('should dispatch click event', async({page, server}) => {
@@ -97,7 +97,7 @@ describe('Page.dispatchEvent(click)', function() {
9797
await watchdog;
9898
expect(await page.evaluate(() => window.clicked)).toBe(true);
9999
});
100-
it('should be atomic', async({page}) => {
100+
it.skip(USES_HOOKS)('should be atomic', async({page}) => {
101101
const createDummySelector = () => ({
102102
create(root, target) {},
103103
query(root, selector) {

test/elementhandle.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
const utils = require('./utils');
19-
const {FFOX, CHROMIUM, WEBKIT} = require('./utils').testOptions(browserType);
19+
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = require('./utils').testOptions(browserType);
2020

2121
describe('ElementHandle.boundingBox', function() {
2222
it.fail(FFOX && !HEADLESS)('should work', async({page, server}) => {
@@ -484,7 +484,7 @@ describe('ElementHandle convenience API', function() {
484484
expect(await handle.textContent()).toBe('Text,\nmore text');
485485
expect(await page.textContent('#inner')).toBe('Text,\nmore text');
486486
});
487-
it('textContent should be atomic', async({page}) => {
487+
it.skip(USES_HOOKS)('textContent should be atomic', async({page}) => {
488488
const createDummySelector = () => ({
489489
create(root, target) {},
490490
query(root, selector) {
@@ -506,7 +506,7 @@ describe('ElementHandle convenience API', function() {
506506
expect(tc).toBe('Hello');
507507
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('modified');
508508
});
509-
it('innerText should be atomic', async({page}) => {
509+
it.skip(USES_HOOKS)('innerText should be atomic', async({page}) => {
510510
const createDummySelector = () => ({
511511
create(root, target) {},
512512
query(root, selector) {
@@ -528,7 +528,7 @@ describe('ElementHandle convenience API', function() {
528528
expect(tc).toBe('Hello');
529529
expect(await page.evaluate(() => document.querySelector('div').innerText)).toBe('modified');
530530
});
531-
it('innerHTML should be atomic', async({page}) => {
531+
it.skip(USES_HOOKS)('innerHTML should be atomic', async({page}) => {
532532
const createDummySelector = () => ({
533533
create(root, target) {},
534534
query(root, selector) {
@@ -550,7 +550,7 @@ describe('ElementHandle convenience API', function() {
550550
expect(tc).toBe('Hello<span>world</span>');
551551
expect(await page.evaluate(() => document.querySelector('div').innerHTML)).toBe('modified');
552552
});
553-
it('getAttribute should be atomic', async({page}) => {
553+
it.skip(USES_HOOKS)('getAttribute should be atomic', async({page}) => {
554554
const createDummySelector = () => ({
555555
create(root, target) {},
556556
query(root, selector) {

test/emulation.spec.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
const utils = require('./utils');
1919
const {FFOX, CHROMIUM, WEBKIT, LINUX} = utils.testOptions(browserType);
20-
const iPhone = playwright.devices['iPhone 6'];
21-
const iPhoneLandscape = playwright.devices['iPhone 6 landscape'];
2220

2321
describe('BrowserContext({viewport})', function() {
2422
it('should get the proper default viewport size', async({page, server}) => {
@@ -84,7 +82,8 @@ describe('BrowserContext({viewport})', function() {
8482

8583
describe.skip(FFOX)('viewport.isMobile', () => {
8684
// Firefox does not support isMobile.
87-
it('should support mobile emulation', async({browser, server}) => {
85+
it('should support mobile emulation', async({playwright, browser, server}) => {
86+
const iPhone = playwright.devices['iPhone 6'];
8887
const context = await browser.newContext({ ...iPhone });
8988
const page = await context.newPage();
9089
await page.goto(server.PREFIX + '/mobile.html');
@@ -93,7 +92,8 @@ describe.skip(FFOX)('viewport.isMobile', () => {
9392
expect(await page.evaluate(() => window.innerWidth)).toBe(400);
9493
await context.close();
9594
});
96-
it('should support touch emulation', async({browser, server}) => {
95+
it('should support touch emulation', async({playwright, browser, server}) => {
96+
const iPhone = playwright.devices['iPhone 6'];
9797
const context = await browser.newContext({ ...iPhone });
9898
const page = await context.newPage();
9999
await page.goto(server.PREFIX + '/mobile.html');
@@ -114,7 +114,8 @@ describe.skip(FFOX)('viewport.isMobile', () => {
114114
return promise;
115115
}
116116
});
117-
it('should be detectable by Modernizr', async({browser, server}) => {
117+
it('should be detectable by Modernizr', async({playwright, browser, server}) => {
118+
const iPhone = playwright.devices['iPhone 6'];
118119
const context = await browser.newContext({ ...iPhone });
119120
const page = await context.newPage();
120121
await page.goto(server.PREFIX + '/detect-touch.html');
@@ -129,7 +130,9 @@ describe.skip(FFOX)('viewport.isMobile', () => {
129130
expect(await page.evaluate(() => Modernizr.touchevents)).toBe(true);
130131
await context.close();
131132
});
132-
it('should support landscape emulation', async({browser, server}) => {
133+
it('should support landscape emulation', async({playwright, browser, server}) => {
134+
const iPhone = playwright.devices['iPhone 6'];
135+
const iPhoneLandscape = playwright.devices['iPhone 6 landscape'];
133136
const context1 = await browser.newContext({ ...iPhone });
134137
const page1 = await context1.newPage();
135138
await page1.goto(server.PREFIX + '/mobile.html');
@@ -184,15 +187,17 @@ describe.skip(FFOX)('viewport.isMobile', () => {
184187
});
185188

186189
describe.skip(FFOX)('Page.emulate', function() {
187-
it('should work', async({browser, server}) => {
190+
it('should work', async({playwright, browser, server}) => {
191+
const iPhone = playwright.devices['iPhone 6'];
188192
const context = await browser.newContext({ ...iPhone });
189193
const page = await context.newPage();
190194
await page.goto(server.PREFIX + '/mobile.html');
191195
expect(await page.evaluate(() => window.innerWidth)).toBe(375);
192196
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
193197
await context.close();
194198
});
195-
it('should support clicking', async({browser, server}) => {
199+
it('should support clicking', async({playwright, browser, server}) => {
200+
const iPhone = playwright.devices['iPhone 6'];
196201
const context = await browser.newContext({ ...iPhone });
197202
const page = await context.newPage();
198203
await page.goto(server.PREFIX + '/input/button.html');

test/environments.js

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ const utils = require('./utils');
1919
const fs = require('fs');
2020
const path = require('path');
2121
const rm = require('rimraf').sync;
22+
const childProcess = require('child_process');
2223
const {TestServer} = require('../utils/testserver/');
2324
const { DispatcherConnection } = require('../lib/rpc/server/dispatcher');
2425
const { Connection } = require('../lib/rpc/client/connection');
26+
const { Transport } = require('../lib/rpc/transport');
2527
const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher');
2628

2729
class ServerEnvironment {
@@ -156,37 +158,72 @@ class TraceTestEnvironment {
156158
class PlaywrightEnvironment {
157159
constructor(playwright) {
158160
this._playwright = playwright;
161+
this.spawnedProcess = undefined;
162+
this.expectExit = false;
159163
}
160164

161165
name() { return 'Playwright'; };
162166

163167
async beforeAll(state) {
164-
// Channel substitute
165-
this.overriddenPlaywright = this._playwright;
166168
if (process.env.PWCHANNEL) {
167-
const dispatcherConnection = new DispatcherConnection();
168169
const connection = new Connection();
169-
dispatcherConnection.onmessage = async message => {
170-
if (process.env.PWCHANNELJSON)
171-
message = JSON.parse(JSON.stringify(message));
172-
setImmediate(() => connection.dispatch(message));
173-
};
174-
connection.onmessage = async message => {
175-
if (process.env.PWCHANNELJSON)
176-
message = JSON.parse(JSON.stringify(message));
177-
const result = await dispatcherConnection.dispatch(message);
178-
await new Promise(f => setImmediate(f));
179-
return result;
180-
};
181-
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), this._playwright);
182-
this.overriddenPlaywright = await connection.waitForObjectWithKnownName('playwright');
170+
if (process.env.PWCHANNEL === 'wire') {
171+
this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], {
172+
stdio: 'pipe',
173+
detached: process.platform !== 'win32',
174+
});
175+
this.spawnedProcess.once('exit', (exitCode, signal) => {
176+
this.spawnedProcess = undefined;
177+
if (!this.expectExit)
178+
throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`);
179+
});
180+
process.on('exit', () => this._killProcess());
181+
const transport = new Transport(this.spawnedProcess.stdin, this.spawnedProcess.stdout);
182+
connection.onmessage = message => transport.send(JSON.stringify(message));
183+
transport.onmessage = message => connection.dispatch(JSON.parse(message));
184+
} else {
185+
const dispatcherConnection = new DispatcherConnection();
186+
dispatcherConnection.onmessage = async message => {
187+
setImmediate(() => connection.dispatch(message));
188+
};
189+
connection.onmessage = async message => {
190+
const result = await dispatcherConnection.dispatch(message);
191+
await new Promise(f => setImmediate(f));
192+
return result;
193+
};
194+
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), this._playwright);
195+
state.toImpl = x => dispatcherConnection._dispatchers.get(x._guid)._object;
196+
}
197+
state.playwright = await connection.waitForObjectWithKnownName('playwright');
198+
} else {
199+
state.toImpl = x => x;
200+
state.playwright = this._playwright;
183201
}
184-
state.playwright = this.overriddenPlaywright;
185202
}
186203

187204
async afterAll(state) {
205+
if (this.spawnedProcess) {
206+
const exited = new Promise(f => this.spawnedProcess.once('exit', f));
207+
this.expectExit = true;
208+
this.spawnedProcess.kill();
209+
await exited;
210+
}
188211
delete state.playwright;
189212
}
213+
214+
_killProcess() {
215+
if (this.spawnedProcess && this.spawnedProcess.pid) {
216+
this.expectExit = true;
217+
try {
218+
if (process.platform === 'win32')
219+
childProcess.execSync(`taskkill /pid ${this.spawnedProcess.pid} /T /F`);
220+
else
221+
process.kill(-this.spawnedProcess.pid, 'SIGKILL');
222+
} catch (e) {
223+
// the process might have already stopped
224+
}
225+
}
226+
}
190227
}
191228

192229
class BrowserTypeEnvironment {

test/evaluation.spec.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
const utils = require('./utils');
1919
const path = require('path');
20-
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = utils.testOptions(browserType);
20+
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = utils.testOptions(browserType);
2121

2222
describe('Page.evaluate', function() {
2323
it('should work', async({page, server}) => {
@@ -373,7 +373,7 @@ describe('Page.evaluate', function() {
373373
});
374374
expect(result).toBe(undefined);
375375
});
376-
it.slow()('should transfer 100Mb of data from page to node.js', async({page, server}) => {
376+
it.slow().skip(USES_HOOKS)('should transfer 100Mb of data from page to node.js', async({page, server}) => {
377377
const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
378378
expect(a.length).toBe(100 * 1024 * 1024);
379379
});
@@ -568,25 +568,25 @@ describe('Frame.evaluate', function() {
568568
expect(await page.frames()[1].evaluate(() => document.body.textContent.trim())).toBe(`Hi, I'm frame`);
569569
});
570570

571-
function expectContexts(page, count) {
571+
function expectContexts(pageImpl, count) {
572572
if (CHROMIUM)
573-
expect(page._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
573+
expect(pageImpl._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
574574
else
575-
expect(page._delegate._contextIdToContext.size).toBe(count);
575+
expect(pageImpl._delegate._contextIdToContext.size).toBe(count);
576576
}
577-
it.skip(CHANNEL)('should dispose context on navigation', async({page, server}) => {
577+
it.skip(USES_HOOKS)('should dispose context on navigation', async({page, server, toImpl}) => {
578578
await page.goto(server.PREFIX + '/frames/one-frame.html');
579579
expect(page.frames().length).toBe(2);
580-
expectContexts(page, 4);
580+
expectContexts(toImpl(page), 4);
581581
await page.goto(server.EMPTY_PAGE);
582-
expectContexts(page, 2);
582+
expectContexts(toImpl(page), 2);
583583
});
584-
it.skip(CHANNEL)('should dispose context on cross-origin navigation', async({page, server}) => {
584+
it.skip(USES_HOOKS)('should dispose context on cross-origin navigation', async({page, server, toImpl}) => {
585585
await page.goto(server.PREFIX + '/frames/one-frame.html');
586586
expect(page.frames().length).toBe(2);
587-
expectContexts(page, 4);
587+
expectContexts(toImpl(page), 4);
588588
await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
589-
expectContexts(page, 2);
589+
expectContexts(toImpl(page), 2);
590590
});
591591

592592
it('should execute after cross-site navigation', async({page, server}) => {

0 commit comments

Comments
 (0)