feat: basic d.ts file

This commit is contained in:
Joel Einbinder 2019-12-05 23:52:01 +00:00
parent 7af47b6fcb
commit e5f58c2c4b
10 changed files with 25 additions and 534 deletions

View file

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

7
.gitignore vendored
View file

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

View file

@ -6,6 +6,9 @@
!lib/**/*.js
# Injected files are included via lib/generated, see src/injected/README.md
lib/injected/
#types
!lib/**/*.d.ts
!index.d.ts
# root for "playwright" package
!index.js

7
index.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
import * as chromium from './chromium';
import * as firefox from './firefox';
import * as webkit from './webkit';
declare function pickBrowser(browser: 'chromium'): typeof chromium;
declare function pickBrowser(browser: 'firefox'): typeof firefox;
declare function pickBrowser(browser: 'webkit'): typeof webkit;
export = pickBrowser;

View file

@ -18,7 +18,7 @@
"wunit": "cross-env BROWSER=webkit node test/test.js",
"debug-unit": "node --inspect-brk test/test.js",
"test-doclint": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js",
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && npm run test-types && node utils/testrunner/test/test.js",
"test": "npm run lint --silent && npm run coverage && npm run test-doclint && node utils/testrunner/test/test.js",
"prepare": "node install.js",
"lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts ./src || eslint --ext js,ts ./src) && npm run tsc && npm run doc",
"doc": "node utils/doclint/cli.js",
@ -28,7 +28,6 @@
"watch": "node utils/runWebpack.js --mode='development' --watch --silent | tsc -w -p .",
"apply-next-version": "node utils/apply_next_version.js",
"bundle": "npx browserify -r ./index.js:playwright -o utils/browser/playwright-web.js",
"test-types": "node utils/doclint/generate_types && npx -p typescript@2.1 tsc -p utils/doclint/generate_types/test/",
"unit-bundle": "node utils/browser/test.js"
},
"author": {

View file

@ -1,14 +1,13 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"target": "ESNext",
"module": "commonjs",
"lib": ["esnext", "dom"],
"sourceMap": true,
"rootDir": "./src",
"outDir": "./lib",
"strictBindCallApply": true
"strictBindCallApply": true,
"declaration": true
},
"compileOnSave": true,
"include": ["src/**/*.ts"],

View file

@ -1,221 +0,0 @@
const path = require('path');
const Source = require('../Source');
const playwright = require('../../..');
const PROJECT_DIR = path.join(__dirname, '..', '..', '..');
const fs = require('fs');
const objectDefinitions = [];
(async function() {
const browser = await playwright.launch();
const page = (await browser.pages())[0];
const api = await Source.readFile(path.join(PROJECT_DIR, 'docs', 'api.md'));
const {documentation} = await require('../check_public_api/MDBuilder')(page, [api]);
await browser.close();
const classes = documentation.classesArray.slice(1);
const root = documentation.classesArray[0];
const output = `// This file is generated by ${__filename.substring(path.join(__dirname, '..', '..').length)}
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
/**
* Can be converted to JSON
*/
interface Serializable {}
interface ConnectionTransport {}
${root.methodsArray.map(method => `
${memberJSDOC(method, '')}export function ${method.name}${argsFromMember(method)} : ${typeToString(method.type, method.name)};
`).join('')}
${root.propertiesArray.map(property => `
${memberJSDOC(property, '')}export const ${property.name}${argsFromMember(property)} : ${typeToString(property.type, property.name)};
`).join('')}
${classes.map(classDesc => classToString(classDesc)).join('\n')}
${objectDefinitionsToString()}
`;
fs.writeFileSync(path.join(PROJECT_DIR, 'index.d.ts'), output, 'utf8');
})();
function objectDefinitionsToString() {
let definition;
const parts = [];
while ((definition = objectDefinitions.pop())) {
const {name, properties} = definition;
parts.push(`interface ${name} {`);
parts.push(properties.map(member => ` ${memberJSDOC(member, ' ')}${nameForProperty(member)}${argsFromMember(member, name)}: ${typeToString(member.type, name, member.name)};`).join('\n\n'));
parts.push('}\n');
}
return parts.join('\n');
}
function nameForProperty(member) {
return (member.required || member.name.startsWith('...')) ? member.name : member.name + '?';
}
/**
* @param {import('./check_public_api/Documentation').Class} classDesc
*/
function classToString(classDesc) {
const parts = [];
if (classDesc.comment) {
parts.push(`/**
* ${classDesc.comment.split('\n').join('\n * ')}
*/`);
}
parts.push(`export interface ${classDesc.name} ${classDesc.extends ? `extends ${classDesc.extends} ` : ''}{`);
for (const method of ['on', 'once', 'addListener']) {
for (const [eventName, value] of classDesc.events) {
if (value.comment) {
parts.push(' /**');
parts.push(...value.comment.split('\n').map(line => ' * ' + line));
parts.push(' */');
}
parts.push(` ${method}(event: '${eventName}', listener: (arg0 : ${typeToString(value && value.type, classDesc.name, eventName, 'payload')}) => void): this;\n`);
}
}
const members = classDesc.membersArray.filter(member => member.kind !== 'event');
parts.push(members.map(member => ` ${memberJSDOC(member, ' ')}${member.name}${argsFromMember(member, classDesc.name)}: ${typeToString(member.type, classDesc.name, member.name)};`).join('\n\n'));
parts.push('}\n');
return parts.join('\n');
}
/**
* @param {import('./check_public_api/Documentation').Type} type
*/
function typeToString(type, ...namespace) {
if (!type)
return 'void';
let typeString = stringifyType(parseType(type.name));
for (let i = 0; i < type.properties.length; i++)
typeString = typeString.replace('arg' + i, type.properties[i].name);
if (type.properties.length && typeString.indexOf('Object') !== -1) {
const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join('');
typeString = typeString.replace('Object', name);
objectDefinitions.push({name, properties: type.properties});
}
return typeString;
}
/**
* @param {string} type
*/
function parseType(type) {
type = type.trim();
if (type.startsWith('?')) {
const parsed = parseType(type.substring(1));
parsed.nullable = true;
return parsed;
}
if (type.startsWith('...'))
return parseType('Array<' + type.substring(3) + '>');
let name = type;
let next = null;
let template = null;
let args = null;
let retType = null;
let firstTypeLength = type.length;
for (let i = 0; i < type.length; i++) {
if (type[i] === '<') {
name = type.substring(0, i);
const matching = matchingBracket(type.substring(i), '<', '>');
template = parseType(type.substring(i + 1, i + matching - 1));
firstTypeLength = i + matching;
break;
}
if (type[i] === '(') {
name = type.substring(0, i);
const matching = matchingBracket(type.substring(i), '(', ')');
args = parseType(type.substring(i + 1, i + matching - 1));
i = i + matching;
if (type[i] === ':') {
retType = parseType(type.substring(i + 1));
next = retType.next;
retType.next = null;
break;
}
}
if (type[i] === '|' || type[i] === ',') {
name = type.substring(0, i);
firstTypeLength = i;
break;
}
}
let pipe = null;
if (type[firstTypeLength] === '|')
pipe = parseType(type.substring(firstTypeLength + 1));
else if (type[firstTypeLength] === ',')
next = parseType(type.substring(firstTypeLength + 1));
if (name === 'Promise' && !template)
template = parseType('void');
return {
name,
args,
retType,
template,
pipe,
next
};
}
function stringifyType(parsedType) {
if (!parsedType)
return 'void';
let out = parsedType.name;
if (parsedType.args) {
let args = parsedType.args;
const stringArgs = [];
while (args) {
const arg = args;
args = args.next;
arg.next = null;
stringArgs.push(stringifyType(arg));
}
out = `(${stringArgs.map((type, index) => `arg${index} : ${type}`).join(', ')}, ...args: any[]) => ${stringifyType(parsedType.retType)}`;
} else if (parsedType.name === 'function') {
out = 'Function';
}
if (parsedType.nullable)
out = 'null|' + out;
if (parsedType.template)
out += '<' + stringifyType(parsedType.template) + '>';
if (parsedType.pipe)
out += '|' + stringifyType(parsedType.pipe);
if (parsedType.next)
out += ', ' + stringifyType(parsedType.next);
return out.trim();
}
function matchingBracket(str, open, close) {
let count = 1;
let i = 1;
for (; i < str.length && count; i++) {
if (str[i] === open)
count++;
else if (str[i] === close)
count--;
}
return i;
}
/**
* @param {import('./check_public_api/Documentation').Member} member
*/
function argsFromMember(member, ...namespace) {
if (member.kind === 'property')
return '';
return '(' + member.argsArray.map(arg => `${nameForProperty(arg)}: ${typeToString(arg.type, ...namespace, member.name, 'options')}`).join(', ') + ')';
}
/**
* @param {import('./check_public_api/Documentation').Member} member
*/
function memberJSDOC(member, indent) {
const lines = [];
if (member.comment)
lines.push(...member.comment.split('\n'));
lines.push(...member.argsArray.map(arg => `@param ${arg.name.replace(/\./g, '')} ${arg.comment.replace('\n', ' ')}`));
if (member.returnComment)
lines.push(`@returns ${member.returnComment}`);
if (!lines.length)
return '';
return `/**
${indent} * ${lines.join('\n' + indent + ' * ')}
${indent} */
${indent}`;
}

View file

@ -1,283 +0,0 @@
import * as playwright from "../../../../index";
// Examples taken from README
(async () => {
const browser = await playwright.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.screenshot({ path: "example.png" });
browser.close();
})();
(async () => {
const browser = await playwright.launch();
const page = await browser.newPage();
await page.goto("https://news.ycombinator.com", { waitUntil: "networkidle0" });
await page.pdf({ path: "hn.pdf", format: "A4" });
browser.close();
})();
(async () => {
const browser = await playwright.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// Get the "viewport" of the page, as reported by the page.
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
};
});
console.log("Dimensions:", dimensions);
browser.close();
})();
// The following examples are taken from the docs itself
playwright.launch().then(async browser => {
const page = await browser.newPage();
page.on("console", (...args: any[]) => {
for (let i = 0; i < args.length; ++i) console.log(`${i}: ${args[i]}`);
});
page.evaluate(() => console.log(5, "hello", { foo: "bar" }));
const result = await page.evaluate(() => {
return Promise.resolve(8 * 7);
});
console.log(await page.evaluate("1 + 2"));
const bodyHandle = await page.$("body");
// Typings for this are really difficult since they depend on internal state
// of the page class.
const html = await page.evaluate(
(body: HTMLElement) => body.innerHTML,
bodyHandle
);
});
import * as crypto from "crypto";
import * as fs from "fs";
playwright.launch().then(async browser => {
const page = await browser.newPage();
page.on("console", console.log);
await page.exposeFunction("md5", (text: string) =>
crypto
.createHash("md5")
.update(text)
.digest("hex")
);
await page.evaluate(async () => {
// use window.md5 to compute hashes
const myString = "PLAYWRIGHT";
const myHash = await (window as any).md5(myString);
console.log(`md5 of ${myString} is ${myHash}`);
});
browser.close();
page.on("console", console.log);
await page.exposeFunction("readfile", async (filePath: string) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, "utf8", (err, text) => {
if (err) reject(err);
else resolve(text);
});
});
});
await page.evaluate(async () => {
// use window.readfile to read contents of a file
const content = await (window as any).readfile("/etc/hosts");
console.log(content);
});
await page.emulateMedia("screen");
await page.pdf({ path: "page.pdf" });
await page.interception.enable();
page.on("request", interceptedRequest => {
if (
interceptedRequest.url().endsWith(".png") ||
interceptedRequest.url().endsWith(".jpg")
)
interceptedRequest.abort();
else interceptedRequest.continue();
});
page.keyboard.type("Hello"); // Types instantly
page.keyboard.type("World", { delay: 100 }); // Types slower, like a user
const watchDog = page.waitForFunction("window.innerWidth < 100");
page.setViewport({ width: 50, height: 50 });
await watchDog;
let currentURL: string;
page
.waitForSelector("img", { visible: true })
.then(() => console.log("First URL with image: " + currentURL));
for (currentURL of [
"https://example.com",
"https://google.com",
"https://bbc.com"
]) {
await page.goto(currentURL);
}
page.keyboard.type("Hello World!");
page.keyboard.press("ArrowLeft");
page.keyboard.down("Shift");
// tslint:disable-next-line prefer-for-of
for (let i = 0; i < " World".length; i++) {
page.keyboard.press("ArrowLeft");
}
page.keyboard.up("Shift");
page.keyboard.press("Backspace");
page.keyboard.sendCharacter("嗨");
await page.tracing.start({ path: "trace.json" });
await page.goto("https://www.google.com");
await page.tracing.stop();
page.on("dialog", async dialog => {
console.log(dialog.message());
await dialog.dismiss();
browser.close();
});
const inputElement = (await page.$("input[type=submit]"))!;
await inputElement.click();
});
// Example with launch options
(async () => {
const browser = await playwright.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
],
handleSIGINT: true,
handleSIGHUP: true,
handleSIGTERM: true,
});
const page = await browser.newPage();
await page.goto("https://example.com");
await page.screenshot({ path: "example.png" });
browser.close();
})();
// Test v0.12 features
(async () => {
const browser = await playwright.launch({
devtools: true,
env: {
JEST_TEST: true
}
});
const page = await browser.newPage();
const button = (await page.$("#myButton"))!;
const div = (await page.$("#myDiv"))!;
const input = (await page.$("#myInput"))!;
if (!button)
throw new Error('Unable to select myButton');
if (!input)
throw new Error('Unable to select myInput');
await page.addStyleTag({
url: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
});
console.log(page.url());
page.type("#myInput", "Hello World!");
page.on("console", (event: playwright.ConsoleMessage, ...args: any[]) => {
console.log(event.text, event.type);
for (let i = 0; i < args.length; ++i) console.log(`${i}: ${args[i]}`);
});
await button.focus();
await button.press("Enter");
await button.screenshot({
type: "jpeg",
omitBackground: true,
clip: {
x: 0,
y: 0,
width: 200,
height: 100
}
});
console.log(button.toString());
input.type("Hello World", { delay: 10 });
const buttonText = await (await button.getProperty('textContent')).jsonValue();
await page.deleteCookie(...await page.cookies());
const metrics = await page.metrics();
console.log(metrics.Documents, metrics.Frames, metrics.JSEventListeners);
const navResponse = await page.waitForNavigation({
timeout: 1000
});
console.log(navResponse.ok, navResponse.status, navResponse.url, navResponse.headers);
// evaluate example
const bodyHandle = (await page.$('body'))!;
const html = await page.evaluate((body : HTMLBodyElement) => body.innerHTML, bodyHandle);
await bodyHandle.dispose();
// getProperties example
const handle = await page.evaluateHandle(() => ({ window, document }));
const properties = await handle.getProperties();
const windowHandle = properties.get('window');
const documentHandle = properties.get('document');
await handle.dispose();
// queryObjects example
// Create a Map object
await page.evaluate(() => (window as any).map = new Map());
// Get a handle to the Map object prototype
const mapPrototype = await page.evaluateHandle(() => Map.prototype);
// Query all map instances into an array
const mapInstances = await page.queryObjects(mapPrototype);
// Count amount of map objects in heap
const count = await page.evaluate((maps: Map<any, any>[]) => maps.length, mapInstances);
await mapInstances.dispose();
await mapPrototype.dispose();
// evaluateHandle example
const aHandle = await page.evaluateHandle(() => document.body);
const resultHandle = await page.evaluateHandle((body: Element) => body.innerHTML, aHandle);
console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
browser.close();
})();
// test $eval and $$eval
(async () => {
const browser = await playwright.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.$eval('#someElement', (element, text: string) => {
return element.innerHTML = text;
}, 'hey');
let elementText = await page.$$eval('.someClassName', (elements) => {
console.log(elements.length);
console.log(elements.map(x => x)[0].textContent);
return elements[3].innerHTML;
});
browser.close();
})();

View file

@ -1,12 +0,0 @@
{
"compilerOptions": {
"noImplicitAny": true,
"target": "es2015",
"noEmit": true,
"types": ["node"]
},
"include": [
"test.ts",
"../../../../index.d.ts"
]
}

View file

@ -6,7 +6,7 @@ const vm = require('vm');
const os = require('os');
async function generateChromeProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.d.ts');
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const playwright = await require('../../chromium');
@ -18,17 +18,17 @@ async function generateChromeProtocol(revision) {
const version = await browser.version();
await browser.close();
fs.writeFileSync(outputPath, jsonToTS(json));
console.log(`Wrote protocol.d.ts for ${version} to ${path.relative(process.cwd(), outputPath)}`);
console.log(`Wrote protocol.ts for ${version} to ${path.relative(process.cwd(), outputPath)}`);
}
async function generateWebKitProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'webkit', 'protocol.d.ts');
const outputPath = path.join(__dirname, '..', '..', 'src', 'webkit', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const json = JSON.parse(fs.readFileSync(path.join(revision.folderPath, 'protocol.json'), 'utf8'));
fs.writeFileSync(outputPath, jsonToTS({domains: json}));
console.log(`Wrote protocol.d.ts for WebKit to ${path.relative(process.cwd(), outputPath)}`);
console.log(`Wrote protocol.ts for WebKit to ${path.relative(process.cwd(), outputPath)}`);
}
function jsonToTS(json) {
@ -118,7 +118,7 @@ function typeOfProperty(property, domain) {
}
async function generateFirefoxProtocol(revision) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.d.ts');
const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const omnija = os.platform() === 'darwin' ?
@ -164,7 +164,7 @@ async function generateFirefoxProtocol(revision) {
}
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)}`);
console.log(`Wrote protocol.ts for Firefox to ${path.relative(process.cwd(), outputPath)}`);
}
function firefoxJSONToTS(json) {