chore(firefox): lint against the juggler protocol (#94)

This commit is contained in:
Joel Einbinder 2019-12-04 10:33:29 -08:00 committed by GitHub
parent c370327b4d
commit 492f539310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 127 additions and 10 deletions

View File

@ -8,6 +8,7 @@ node6-test/*
node6-testrunner/*
lib/
*.js
src/chromium/protocol.d.ts
src/generated/*
src/chromium/protocol.d.ts
src/firefox/protocol.d.ts
src/webkit/protocol.d.ts

3
.gitignore vendored
View File

@ -14,8 +14,9 @@
package-lock.json
yarn.lock
/node6
/src/chromium/protocol.d.ts
/src/generated/*
/src/chromium/protocol.d.ts
/src/firefox/protocol.d.ts
/src/webkit/protocol.d.ts
/utils/browser/playwright-web.js
/index.d.ts

View File

@ -26,7 +26,7 @@ try {
}
(async function() {
const {generateWebKitProtocol, generateChromeProtocol} = require('./utils/protocol-types-generator/') ;
const {generateWebKitProtocol, generateFirefoxProtocol, generateChromeProtocol} = require('./utils/protocol-types-generator/') ;
try {
const chromeRevision = await downloadBrowser('chromium', require('./chromium').createBrowserFetcher());
await generateChromeProtocol(chromeRevision);
@ -35,7 +35,8 @@ try {
}
try {
await downloadBrowser('firefox', require('./firefox').createBrowserFetcher());
const firefoxRevision = await downloadBrowser('firefox', require('./firefox').createBrowserFetcher());
await generateFirefoxProtocol(firefoxRevision);
} catch (e) {
console.warn(e.message);
}

View File

@ -58,6 +58,7 @@
"jpeg-js": "^0.3.4",
"minimist": "^1.2.0",
"ncp": "^2.0.0",
"node-stream-zip": "^1.8.2",
"pixelmatch": "^4.0.2",
"pngjs": "^3.3.3",
"text-diff": "^1.0.1",

View File

@ -19,6 +19,7 @@ import {assert} from '../helper';
import {EventEmitter} from 'events';
import * as debug from 'debug';
import { ConnectionTransport } from '../ConnectionTransport';
import { Protocol } from './protocol';
const debugProtocol = debug('playwright:protocol');
export const ConnectionEvents = {
@ -144,6 +145,12 @@ export class JugglerSession extends EventEmitter {
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
private _targetType: string;
private _sessionId: string;
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: Connection, targetType: string, sessionId: string) {
super();
this._callbacks = new Map();
@ -152,7 +159,10 @@ export class JugglerSession extends EventEmitter {
this._sessionId = sessionId;
}
send(method: string, params: any = {}): Promise<any> {
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (!this._connection)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`));
const id = this._connection._rawSend({sessionId: this._sessionId, method, params});

View File

@ -18,6 +18,7 @@
import {helper, debugError} from '../helper';
import * as js from '../javascript';
import { JugglerSession } from './Connection';
import { Protocol } from './protocol';
export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
_session: JugglerSession;
@ -104,7 +105,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
checkException(payload.exceptionDetails);
return context._createHandle(payload.result);
function rewriteError(error) {
function rewriteError(error) : never {
if (error.message.includes('Failed to find execution context with id'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
@ -167,7 +168,7 @@ function checkException(exceptionDetails?: any) {
}
}
export function deserializeValue({unserializableValue, value}) {
export function deserializeValue({unserializableValue, value}: Protocol.RemoteObject) {
if (unserializableValue === 'Infinity')
return Infinity;
if (unserializableValue === '-Infinity')

View File

@ -189,7 +189,7 @@ class InterceptableRequest {
(this.request as any)[interceptableRequestSymbol] = this;
}
async continue(overrides: any = {}) {
async continue(overrides: {url?: string, method?: string, postData?: string, headers?: {[key: string]: string}} = {}) {
assert(!overrides.url, 'Playwright-Firefox does not support overriding URL');
assert(!overrides.method, 'Playwright-Firefox does not support overriding method');
assert(!overrides.postData, 'Playwright-Firefox does not support overriding postData');

View File

@ -118,7 +118,7 @@ export class Page extends EventEmitter {
}
async emulateMedia(options: {
type?: string,
type?: ""|"screen"|"print",
colorScheme?: 'dark' | 'light' | 'no-preference' }) {
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);

View File

@ -97,6 +97,11 @@ module.exports.addTests = function({testRunner, expect, CHROME, FFOX, WEBKIT}) {
else if (FFOX)
expect(error.message).toContain('Object is not serializable');
});
it('should work with tricky values', async({page, server}) => {
const aHandle = await page.evaluateHandle(() => ({a: 1}));
const json = await aHandle.jsonValue();
expect(json).toEqual({a: 1});
});
});
describe('JSHandle.getProperties', function() {

View File

@ -1,6 +1,8 @@
// @ts-check
const path = require('path');
const fs = require('fs');
const StreamZip = require('node-stream-zip');
const vm = require('vm');
async function generateChromeProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.d.ts');
@ -114,4 +116,99 @@ function typeOfProperty(property, domain) {
return property.type;
}
module.exports = {generateChromeProtocol, generateWebKitProtocol};
async function generateFirefoxProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.d.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const zip = new StreamZip({file: path.join(revision.executablePath, '..', 'omni.ja'), storeEntries: true});
// @ts-ignore
await new Promise(x => zip.on('ready', x));
const data = zip.entryDataSync(zip.entry('chrome/juggler/content/protocol/Protocol.js'))
const ctx = vm.createContext();
const protocolJSCode = data.toString('utf8');
function inject() {
this.ChromeUtils = {
import: () => ({t})
}
const t = {};
t.String = {"$type": "string"};
t.Number = {"$type": "number"};
t.Boolean = {"$type": "boolean"};
t.Undefined = {"$type": "undefined"};
t.Any = {"$type": "any"};
t.Enum = function(values) {
return {"$type": "enum", "$values": values};
}
t.Nullable = function(scheme) {
return {...scheme, "$nullable": true};
}
t.Optional = function(scheme) {
return {...scheme, "$optional": true};
}
t.Array = function(scheme) {
return {"$type": "array", "$items": scheme};
}
t.Recursive = function(types, schemeName) {
return {"$type": "ref", "$ref": schemeName };
}
}
const json = vm.runInContext(`(${inject})();${protocolJSCode}; this.protocol.types = types; this.protocol;`, ctx);
fs.writeFileSync(outputPath, firefoxJSONToTS(json));
console.log(`Wrote protocol.d.ts for Firefox to ${path.relative(process.cwd(), outputPath)}`);
}
function firefoxJSONToTS(json) {
const domains = Object.entries(json.domains);
return `// This is generated from /utils/protocol-types-generator/index.js
export module Protocol {${Object.entries(json.types).map(([typeName, type]) => `
export type ${typeName} = ${firefoxTypeToString(type, ' ')};`).join('')}
${domains.map(([domainName, domain]) => `
export module ${domainName} {${(Object.entries(domain.events)).map(([eventName, event]) => `
export type ${eventName}Payload = ${firefoxTypeToString(event)}`).join('')}${(Object.entries(domain.methods)).map(([commandName, command]) => `
export type ${commandName}Parameters = ${firefoxTypeToString(command.params)};
export type ${commandName}ReturnValue = ${firefoxTypeToString(command.returns)};`).join('')}
}`).join('')}
export interface Events {${domains.map(([domainName, domain]) => Object.keys(domain.events).map(eventName => `
"${domainName}.${eventName}": ${domainName}.${eventName}Payload;`).join('')).join('')}
}
export interface CommandParameters {${domains.map(([domainName, domain]) => Object.keys(domain.methods).map(commandName => `
"${domainName}.${commandName}": ${domainName}.${commandName}Parameters;`).join('')).join('')}
}
export interface CommandReturnValues {${domains.map(([domainName, domain]) => Object.keys(domain.methods).map(commandName => `
"${domainName}.${commandName}": ${domainName}.${commandName}ReturnValue;`).join('')).join('')}
}
}`
}
function firefoxTypeToString(type, indent=' ') {
if (!type)
return 'void';
if (!type['$type']) {
const properties = Object.entries(type).filter(([name]) => !name.startsWith('$'));
const lines = [];
lines.push('{');
for (const [propertyName, property] of properties) {
const nameSuffix = property['$optional'] ? '?' : '';
const valueSuffix = property['$nullable'] ? '|null' : ''
lines.push(`${indent} ${propertyName}${nameSuffix}: ${firefoxTypeToString(property, indent + ' ')}${valueSuffix};`);
}
lines.push(`${indent}}`);
return lines.join('\n');
}
if (type['$type'] === 'ref')
return type['$ref'];
if (type['$type'] === 'array')
return firefoxTypeToString(type['$items'], indent) + '[]';
if (type['$type'] === 'enum')
return type['$values'].map(v => JSON.stringify(v)).join('|');
return type['$type'];
}
module.exports = {generateChromeProtocol, generateFirefoxProtocol, generateWebKitProtocol};