feat(rpc): support no-serialization mode, run hook tests (#2925)

Added rpc_json_linux bots to excercise serialization - these
do not run hook tests.
This commit is contained in:
Dmitry Gozman 2020-07-13 08:31:20 -07:00 committed by GitHub
parent 6674458496
commit 6d94c92053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 42 additions and 41 deletions

View File

@ -174,6 +174,7 @@ jobs:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
transport: [json, object]
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
@ -194,21 +195,19 @@ jobs:
BROWSER: ${{ matrix.browser }}
DEBUG: "*,-pw:wrapped*"
PWCHANNEL: "1"
# Ensure output folder exists just in case it was not created by the test run.
- run: node -e "require('fs').mkdirSync(require('path').join('test', 'output-${{ matrix.browser }}'), {recursive:true})"
if: failure()
PWCHANNELTRANSPORT: ${{ matrix.transport }}
- uses: actions/upload-artifact@v1
if: failure()
with:
name: rpc-${{ matrix.browser }}-linux-output
name: rpc-${{ matrix.transport }}-${{ matrix.browser }}-linux-output
path: test/output-${{ matrix.browser }}
- uses: actions/upload-artifact@v1
if: failure()
with:
name: rpc-${{ matrix.browser }}-linux-testrun.log
name: rpc-${{ matrix.transport }}-${{ matrix.browser }}-linux-testrun.log
path: testrun.log
- uses: actions/upload-artifact@v1
if: failure()
with:
name: rpc-${{ matrix.browser }}-linux-coredumps
name: rpc-${{ matrix.transport }}-${{ matrix.browser }}-linux-coredumps
path: coredumps

View File

@ -23,8 +23,8 @@ import { Transport } from './transport';
const spawnedProcess = childProcess.fork(path.join(__dirname, 'server'), [], { stdio: 'pipe' });
const transport = new Transport(spawnedProcess.stdin, spawnedProcess.stdout);
const connection = new Connection();
connection.onmessage = message => transport.send(message);
transport.onmessage = message => connection.dispatch(message);
connection.onmessage = message => transport.send(JSON.stringify(message));
transport.onmessage = message => connection.dispatch(JSON.parse(message));
const playwright = await connection.waitForObjectWithKnownName('playwright');
const browser = await playwright.chromium.launch({ headless: false });

View File

@ -43,7 +43,7 @@ class Root extends ChannelOwner<Channel, {}> {
export class Connection {
readonly _objects = new Map<string, ChannelOwner>();
private _waitingForObject = new Map<string, any>();
onmessage = (message: string): void => {};
onmessage = (message: object): void => {};
private _lastId = 0;
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void }>();
private _rootObject: ChannelOwner;
@ -62,7 +62,7 @@ export class Connection {
const id = ++this._lastId;
const converted = { id, ...message, params: this._replaceChannelsWithGuids(message.params) };
debug('pw:channel:command')(converted);
this.onmessage(JSON.stringify(converted));
this.onmessage(converted);
return new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject }));
}
@ -70,11 +70,10 @@ export class Connection {
return this._rootObject._debugScopeState();
}
dispatch(message: string) {
const parsedMessage = JSON.parse(message);
const { id, guid, method, params, result, error } = parsedMessage;
dispatch(message: object) {
const { id, guid, method, params, result, error } = message as any;
if (id) {
debug('pw:channel:response')(parsedMessage);
debug('pw:channel:response')(message);
const callback = this._callbacks.get(id)!;
this._callbacks.delete(id);
if (error)
@ -84,7 +83,7 @@ export class Connection {
return;
}
debug('pw:channel:event')(parsedMessage);
debug('pw:channel:event')(message);
if (method === '__create__') {
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return;

View File

@ -21,8 +21,8 @@ import { PlaywrightDispatcher } from './server/playwrightDispatcher';
const dispatcherConnection = new DispatcherConnection();
const transport = new Transport(process.stdout, process.stdin);
transport.onmessage = message => dispatcherConnection.dispatch(message);
dispatcherConnection.onmessage = message => transport.send(message);
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message));
const playwright = new Playwright(__dirname, require('../../browsers.json')['browsers']);
new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), playwright);

View File

@ -113,10 +113,10 @@ class Root extends Dispatcher<{}, {}> {
export class DispatcherConnection {
readonly _dispatchers = new Map<string, Dispatcher<any, any>>();
private _rootDispatcher: Root;
onmessage = (message: string) => {};
onmessage = (message: object) => {};
async sendMessageToClient(guid: string, method: string, params: any): Promise<any> {
this.onmessage(JSON.stringify({ guid, method, params: this._replaceDispatchersWithGuids(params) }));
this.onmessage({ guid, method, params: this._replaceDispatchersWithGuids(params) });
}
constructor() {
@ -127,23 +127,22 @@ export class DispatcherConnection {
return this._rootDispatcher;
}
async dispatch(message: string) {
const parsedMessage = JSON.parse(message);
const { id, guid, method, params } = parsedMessage;
async dispatch(message: object) {
const { id, guid, method, params } = message as any;
const dispatcher = this._dispatchers.get(guid);
if (!dispatcher) {
this.onmessage(JSON.stringify({ id, error: serializeError(new Error('Target browser or context has been closed')) }));
this.onmessage({ id, error: serializeError(new Error('Target browser or context has been closed')) });
return;
}
if (method === 'debugScopeState') {
this.onmessage(JSON.stringify({ id, result: this._rootDispatcher._debugScopeState() }));
this.onmessage({ id, result: this._rootDispatcher._debugScopeState() });
return;
}
try {
const result = await (dispatcher as any)[method](this._replaceGuidsWithDispatchers(params));
this.onmessage(JSON.stringify({ id, result: this._replaceDispatchersWithGuids(result) }));
this.onmessage({ id, result: this._replaceDispatchersWithGuids(result) });
} catch (e) {
this.onmessage(JSON.stringify({ id, error: serializeError(e) }));
this.onmessage({ id, error: serializeError(e) });
}
}

View File

@ -23,7 +23,7 @@ export class Transport {
private _closed = false;
private _bytesLeft = 0;
onmessage?: (message: any) => void;
onmessage?: (message: string) => void;
onclose?: () => void;
constructor(pipeWrite: NodeJS.WritableStream, pipeRead: NodeJS.ReadableStream) {

View File

@ -335,9 +335,9 @@ describe('launchPersistentContext()', function() {
expect(error.message).toContain('can not specify page');
await removeUserDataDir(userDataDir);
});
it.skip(USES_HOOKS)('should have passed URL when launching with ignoreDefaultArgs: true', async ({browserType, defaultBrowserOptions, server}) => {
it('should have passed URL when launching with ignoreDefaultArgs: true', async ({playwrightPath, browserType, defaultBrowserOptions, server}) => {
const userDataDir = await makeUserDataDir();
const args = browserType._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank');
const args = require(playwrightPath)[browserType.name()]._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank');
const options = {
...defaultBrowserOptions,
args: [...args, server.EMPTY_PAGE],
@ -364,7 +364,7 @@ describe('launchPersistentContext()', function() {
const e = new Error('Dummy');
const options = { ...defaultBrowserOptions, __testHookBeforeCreateBrowser: () => { throw e; } };
const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e);
expect(error).toBe(e);
expect(error.message).toContain('Dummy');
await removeUserDataDir(userDataDir);
});
it('should fire close event for a persistent context', async(state) => {
@ -373,5 +373,5 @@ describe('launchPersistentContext()', function() {
context.on('close', () => closed = true);
await close(state);
expect(closed).toBe(true);
});
});
});

View File

@ -167,9 +167,13 @@ class PlaywrightEnvironment {
const dispatcherConnection = new DispatcherConnection();
const connection = new Connection();
dispatcherConnection.onmessage = async message => {
if (process.env.PWCHANNELJSON)
message = JSON.parse(JSON.stringify(message));
setImmediate(() => connection.dispatch(message));
};
connection.onmessage = async message => {
if (process.env.PWCHANNELJSON)
message = JSON.parse(JSON.stringify(message));
const result = await dispatcherConnection.dispatch(message);
await new Promise(f => setImmediate(f));
return result;

View File

@ -17,7 +17,7 @@
const utils = require('./utils');
const path = require('path');
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = utils.testOptions(browserType);
describe('Page.evaluate', function() {
it('should work', async({page, server}) => {
@ -574,14 +574,14 @@ describe('Frame.evaluate', function() {
else
expect(page._delegate._contextIdToContext.size).toBe(count);
}
it.skip(USES_HOOKS)('should dispose context on navigation', async({page, server}) => {
it.skip(CHANNEL)('should dispose context on navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);
await page.goto(server.EMPTY_PAGE);
expectContexts(page, 2);
});
it.skip(USES_HOOKS)('should dispose context on cross-origin navigation', async({page, server}) => {
it.skip(CHANNEL)('should dispose context on cross-origin navigation', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');
expect(page.frames().length).toBe(2);
expectContexts(page, 4);

View File

@ -67,7 +67,7 @@ describe('Playwright', function() {
const e = new Error('Dummy');
const options = { ...defaultBrowserOptions, __testHookBeforeCreateBrowser: () => { throw e; }, timeout: 9000 };
const error = await browserType.launch(options).catch(e => e);
expect(error).toBe(e);
expect(error.message).toContain('Dummy');
});
it.skip(USES_HOOKS)('should report launch log', async({browserType, defaultBrowserOptions}) => {
const e = new Error('Dummy');

View File

@ -17,7 +17,7 @@
const path = require('path');
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = utils.testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = utils.testOptions(browserType);
describe('Page.$eval', function() {
it('should work with css selector', async({page, server}) => {
@ -515,7 +515,7 @@ describe('text selector', () => {
expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('<div>helloworld</div><span>helloworld</span>');
});
it.skip(USES_HOOKS)('create', async ({page}) => {
it.skip(CHANNEL)('create', async ({page}) => {
await page.setContent(`<div>yo</div><div>"ya</div><div>ye ye</div>`);
expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo');
expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('"\\"ya"');
@ -744,7 +744,7 @@ describe('attribute selector', () => {
});
describe('selectors.register', () => {
it.skip(USES_HOOKS)('should work', async ({page}) => {
it.skip(CHANNEL)('should work', async ({page}) => {
const createTagSelector = () => ({
create(root, target) {
return target.nodeName;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = require('./utils').testOptions(browserType);
const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = require('./utils').testOptions(browserType);
class WritableBuffer {
constructor() {
@ -51,7 +51,7 @@ class WritableBuffer {
}
}
describe.skip(USES_HOOKS)('Recorder', function() {
describe.skip(CHANNEL)('Recorder', function() {
beforeEach(async state => {
state.context = await state.browser.newContext();
state.output = new WritableBuffer();

View File

@ -202,7 +202,7 @@ const utils = module.exports = {
GOLDEN_DIR,
OUTPUT_DIR,
ASSETS_DIR,
USES_HOOKS: !!process.env.PWCHANNEL,
USES_HOOKS: process.env.PWCHANNELTRANSPORT === 'json',
CHANNEL: !!process.env.PWCHANNEL,
HEADLESS: !!valueFromEnv('HEADLESS', true),
};