types: better types (#1166)

This generates typescript definitions based on the api.md, instead of autogenerating them from the typescript source code.

Now types
 - only include the public api
 - work with older versions of typescript
 - include descriptions
 - are more consistent
 - are more complete

#6
This commit is contained in:
Joel Einbinder 2020-03-20 01:30:35 -07:00 committed by GitHub
parent f1d97b0466
commit 825555cddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1342 additions and 38 deletions

View File

@ -3,12 +3,13 @@ third_party/*
utils/browser/playwright-web.js
utils/doclint/check_public_api/test/
utils/testrunner/examples/
node6/*
node6-test/*
node6-testrunner/*
lib/
*.js
src/generated/*
src/chromium/protocol.ts
src/firefox/protocol.ts
src/webkit/protocol.ts
/types/*
/index.d.ts
utils/generate_types/overrides.d.ts
utils/generate_types/test/test.ts

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ lib/
playwright-*.tgz
/web.js
/web.js.map
/types/*

View File

@ -7,7 +7,7 @@
# Injected files are included via lib/generated, see src/injected/README.md
lib/injected/
#types
!lib/**/*.d.ts
!types/*
!index.d.ts
# used for npm install scripts

View File

@ -3246,7 +3246,7 @@ ResourceType will be one of the following: `document`, `stylesheet`, `image`, `m
<!-- GEN:stop -->
#### response.body()
- returns: <Promise<[Buffer]>> Promise which resolves to a buffer with response body.
- returns: <[Promise]<[Buffer]>> Promise which resolves to a buffer with response body.
#### response.finished()
- returns: <[Promise]<?[Error]>> Waits for this response to finish, returns failure error if request failed.

14
index.d.ts vendored
View File

@ -14,11 +14,9 @@
* limitations under the License.
*/
export * from './lib/api';
export const devices: typeof import('./lib/deviceDescriptors').DeviceDescriptors;
export const errors: { TimeoutError: typeof import('./lib/errors').TimeoutError };
export const chromium: import('./lib/server/chromium').Chromium;
export const firefox: import('./lib/server/firefox').Firefox;
export const webkit: import('./lib/server/webkit').WebKit;
export const selectors: import('./lib/api').Selectors;
export type PlaywrightWeb = typeof import('./lib/web');
import * as types from './types/types';
export * from './types/types';
export const webkit: types.BrowserType<types.WebKitBrowser>;
export const chromium: types.BrowserType<types.ChromiumBrowser>;
export const firefox: types.BrowserType<types.FirefoxBrowser>;

View File

@ -17,9 +17,11 @@
// This file is only run when someone installs via the github repo
const {execSync} = require('child_process');
try {
console.log('Building playwright...');
require('child_process').execSync('npm run build', {
execSync('npm run build', {
stdio: 'ignore'
});
} catch (e) {
@ -85,8 +87,13 @@ const DOWNLOAD_PATHS = {
directories.add(path.join(__dirname, '.local-webkit'));
await Promise.all([...directories].map(directory => rmAsync(directory)));
try {
console.log('Generating types...');
execSync('npm run generate-types');
} catch (e) {
}
async function readdirAsync(dirpath) {
return fs.promises.readdir(dirpath).then(dirs => dirs.map(dir => path.join(dirpath, dir)));
}
})();
})();

View File

@ -31,11 +31,12 @@
"test-infra": "node utils/doclint/check_public_api/test/test.js && node utils/doclint/preprocessor/test.js && node utils/testrunner/test/test.js",
"lint": "npm run eslint && npm run tsc && npm run doc && npm run test-infra",
"debug-test": "node --inspect-brk test/test.js",
"clean": "rimraf lib",
"clean": "rimraf lib && rimraf types",
"prepare": "node install-from-github.js",
"build": "node utils/runWebpack.js --mode='development' && tsc -p .",
"watch": "node utils/runWebpack.js --mode='development' --watch --silent | tsc -w -p .",
"version": "node utils/sync_package_versions.js && npm run doc"
"version": "node utils/sync_package_versions.js && npm run doc",
"generate-types": "node utils/generate_types/"
},
"author": {
"name": "Microsoft Corporation"

20
packages/playwright-chromium/index.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/**
* 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 * as types from 'playwright-core/types/types';
export * from 'playwright-core/types/types';
export const chromium: types.BrowserType<types.ChromiumBrowser>;

20
packages/playwright-firefox/index.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/**
* 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 * as types from 'playwright-core/types/types';
export * from 'playwright-core/types/types';
export const firefox: types.BrowserType<types.FirefoxBrowser>;

20
packages/playwright-webkit/index.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/**
* 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 * as types from 'playwright-core/types/types';
export * from 'playwright-core/types/types';
export const webkit: types.BrowserType<types.WebKitBrowser>;

View File

@ -1 +1,22 @@
export * from "playwright-core";
/**
* 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 * as types from 'playwright-core/types/types';
export * from 'playwright-core/types/types';
export const webkit: types.BrowserType<types.WebKitBrowser>;
export const chromium: types.BrowserType<types.ChromiumBrowser>;
export const firefox: types.BrowserType<types.FirefoxBrowser>;

View File

@ -30,4 +30,3 @@ try {
throw new Error('ERROR: Playwright did not download browsers');
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Browser, ConnectOptions } from '../browser';
import { ConnectOptions } from '../browser';
import { BrowserContext } from '../browserContext';
import { BrowserServer } from './browserServer';
@ -39,7 +39,7 @@ export type LaunchOptions = BrowserArgOptions & {
env?: {[key: string]: string} | undefined
};
export interface BrowserType {
export interface BrowserType<Browser> {
executablePath(): string;
name(): string;
launch(options?: LaunchOptions & { slowMo?: number }): Promise<Browser>;

View File

@ -32,7 +32,7 @@ import { Events } from '../events';
import { ConnectionTransport } from '../transport';
import { BrowserContext } from '../browserContext';
export class Chromium implements BrowserType {
export class Chromium implements BrowserType<CRBrowser> {
private _executablePath: (string|undefined);
executablePath(): string {

View File

@ -32,7 +32,7 @@ import { launchProcess, waitForLine } from './processLauncher';
const mkdtempAsync = platform.promisify(fs.mkdtemp);
export class Firefox implements BrowserType {
export class Firefox implements BrowserType<FFBrowser> {
private _executablePath: (string|undefined);
executablePath(): string {

View File

@ -32,7 +32,7 @@ import { BrowserServer } from './browserServer';
import { Events } from '../events';
import { BrowserContext } from '../browserContext';
export class WebKit implements BrowserType {
export class WebKit implements BrowserType<WKBrowser> {
private _executablePath: (string|undefined);
executablePath(): string {

2
test/types.d.ts vendored
View File

@ -48,7 +48,7 @@ interface TestSetup<STATE> {
MAC: boolean;
LINUX: boolean;
WIN: boolean;
playwright: import('../src/server/browserType').BrowserType;
playwright: import('../src/server/browserType').BrowserType<import('../src/browser').Browser>;
selectors: import('../src/selectors').Selectors;
expect<T>(value: T): Expect<T>;
defaultBrowserOptions: import('../src/server/browserType').LaunchOptions;

View File

@ -7,7 +7,7 @@
"rootDir": "./src",
"outDir": "./lib",
"strict": true,
"declaration": true
"declaration": false
},
"compileOnSave": true,
"include": ["src/**/*.ts"],

View File

@ -33,12 +33,14 @@ Documentation.Class = class {
* @param {!Array<!Documentation.Member>} membersArray
* @param {?string=} extendsName
* @param {string=} comment
* @param {string[]=} templates
*/
constructor(name, membersArray, extendsName = null, comment = '') {
constructor(name, membersArray, extendsName = null, comment = '', templates = []) {
this.name = name;
this.membersArray = membersArray;
this.comment = comment;
this.extends = extendsName;
this.templates = templates;
this.index();
}
@ -124,8 +126,12 @@ Documentation.Member = class {
* @param {string} name
* @param {?Documentation.Type} type
* @param {!Array<!Documentation.Member>} argsArray
* @param {string=} comment
* @param {string=} returnComment
* @param {boolean=} required
* @param {string[]=} templates
*/
constructor(kind, name, type, argsArray, comment = '', returnComment = '', required = true) {
constructor(kind, name, type, argsArray, comment = '', returnComment = '', required = true, templates = []) {
if (name === 'code') debugger;
this.kind = kind;
this.name = name;
@ -134,6 +140,7 @@ Documentation.Member = class {
this.returnComment = returnComment;
this.argsArray = argsArray;
this.required = required;
this.templates = templates;
/** @type {!Map<string, !Documentation.Member>} */
this.args = new Map();
for (const arg of argsArray)
@ -144,10 +151,13 @@ Documentation.Member = class {
* @param {string} name
* @param {!Array<!Documentation.Member>} argsArray
* @param {?Documentation.Type} returnType
* @param {string=} returnComment
* @param {string=} comment
* @param {string[]=} templates
* @return {!Documentation.Member}
*/
static createMethod(name, argsArray, returnType, returnComment, comment) {
return new Documentation.Member('method', name, returnType, argsArray, comment, returnComment);
static createMethod(name, argsArray, returnType, returnComment, comment, templates) {
return new Documentation.Member('method', name, returnType, argsArray, comment, returnComment, undefined, templates);
}
/**

View File

@ -81,7 +81,7 @@ function checkSources(sources, externalDependencies) {
visit(classesByName.get(parent));
};
visit(cls);
return new Documentation.Class(expandPrefix(cls.name), Array.from(membersMap.values()));
return new Documentation.Class(expandPrefix(cls.name), Array.from(membersMap.values()), undefined, cls.comment, cls.templates);
});
}
@ -264,6 +264,7 @@ function checkSources(sources, externalDependencies) {
function serializeClass(className, symbol, node) {
/** @type {!Array<!Documentation.Member>} */
const members = classEvents.get(className) || [];
const templates = [];
for (const [name, member] of symbol.members || []) {
if (className === 'Error')
continue;
@ -281,13 +282,15 @@ function checkSources(sources, externalDependencies) {
}
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
const signature = signatureForType(memberType);
if (signature)
if (member.flags & ts.SymbolFlags.TypeParameter)
templates.push(name);
else if (signature)
members.push(serializeSignature(name, signature));
else
members.push(serializeProperty(name, memberType));
}
return new Documentation.Class(className, members);
return new Documentation.Class(className, members, undefined, undefined, templates);
}
/**
@ -312,8 +315,9 @@ function checkSources(sources, externalDependencies) {
function serializeSignature(name, signature) {
const minArgumentCount = signature.minArgumentCount || 0;
const parameters = signature.parameters.map((s, index) => serializeSymbol(s, [], index < minArgumentCount));
const templates = signature.typeParameters ? signature.typeParameters.map(t => t.symbol.name) : [];
const returnType = serializeType(signature.getReturnType());
return Documentation.Member.createMethod(name, parameters, returnType.name !== 'void' ? returnType : null);
return Documentation.Member.createMethod(name, parameters, returnType.name !== 'void' ? returnType : null, undefined, undefined, templates);
}
/**

View File

@ -107,6 +107,18 @@ class MDOutline {
*/
function parseClass(content) {
const members = [];
const commentWalker = document.createTreeWalker(content, NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
if (node instanceof HTMLElement && node.tagName === 'H4')
return NodeFilter.FILTER_ACCEPT;
if (!(node instanceof Comment))
return NodeFilter.FILTER_REJECT;
if (node.data.trim().startsWith('GEN:toc'))
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_REJECT;
}
});
const commentEnd = commentWalker.nextNode();
const headers = content.querySelectorAll('h4');
const name = content.firstChild.textContent;
let extendsName = null;
@ -116,7 +128,7 @@ class MDOutline {
commentStart = extendsElement.nextSibling;
extendsName = extendsElement.querySelector('a').textContent;
}
const comment = parseComment(extractSiblingsIntoFragment(commentStart, headers[0]));
const comment = parseComment(extractSiblingsIntoFragment(commentStart, commentEnd));
for (let i = 0; i < headers.length; i++) {
const fragment = extractSiblingsIntoFragment(headers[i], headers[i + 1]);
members.push(parseMember(fragment));
@ -143,7 +155,6 @@ class MDOutline {
}
/**
* @param {string} name
* @param {DocumentFragment} content
*/
function parseMember(content) {
@ -178,7 +189,7 @@ class MDOutline {
errors.push(`${name} has mistyped 'return' type declaration: expected exactly '${expectedText}', found '${actualText}'.`);
}
}
const comment = parseComment(extractSiblingsIntoFragment(ul ? ul.nextSibling : content));
const comment = parseComment(extractSiblingsIntoFragment(ul ? ul.nextSibling : content.querySelector('h4').nextSibling));
return {
name,
args,

View File

@ -0,0 +1,392 @@
/**
* 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.
*/
//@ts-check
const path = require('path');
const Source = require('../doclint/Source');
const {chromium} = require('../../index');
const Documentation = require('../doclint/check_public_api/Documentation');
const PROJECT_DIR = path.join(__dirname, '..', '..');
const fs = require('fs');
const {parseOverrides} = require('./parseOverrides');
const objectDefinitions = [];
const handledMethods = new Set();
/** @type {Documentation} */
let documentation;
(async function() {
const typesDir = path.join(PROJECT_DIR, 'types');
if (!fs.existsSync(typesDir))
fs.mkdirSync(typesDir)
fs.writeFileSync(path.join(typesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'src', 'chromium', 'protocol.ts')), 'utf8');
const browser = await chromium.launch();
const page = await browser.newPage();
const api = await Source.readFile(path.join(PROJECT_DIR, 'docs', 'api.md'));
const {documentation: mdDocumentation} = await require('../doclint/check_public_api/MDBuilder')(page, [api]);
await browser.close();
const sources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
const {documentation: jsDocumentation} = await require('../doclint/check_public_api/JSBuilder').checkSources(sources, Object.keys(require('../../src/web.webpack.config').externals));
documentation = mergeDocumentation(mdDocumentation, jsDocumentation);
const handledClasses = new Set();
function docClassForName(name) {
const docClass = documentation.classes.get(name);
if (!docClass)
throw new Error(`Unknown override class "${name}"`);
return docClass;
}
const overrides = await parseOverrides(className => {
handledClasses.add(className);
return writeComment(docClassForName(className).comment) + '\n';
}, (className, methodName) => {
const docClass = docClassForName(className);
const method = docClass.methods.get(methodName);
handledMethods.add(`${className}.${methodName}`);
if (!method) {
if (new Set(['on', 'addListener', 'off', 'removeListener', 'once']).has(methodName))
return '';
throw new Error(`Unknown override method "${className}.${methodName}"`);
}
return memberJSDOC(method, ' ').trimLeft();
}, (className) => {
return classBody(docClassForName(className));
});
const classes = documentation.classesArray.filter(cls => !handledClasses.has(cls.name));
const output = `// This file is generated by ${__filename.substring(path.join(__dirname, '..', '..').length)}
${overrides}
${classes.map(classDesc => classToString(classDesc)).join('\n')}
${objectDefinitionsToString()}
`;
fs.writeFileSync(path.join(typesDir, 'types.d.ts'), output, 'utf8');
})().catch(e => {
console.error(e);
process.exit(1);
})
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 {Documentation.Class} classDesc
*/
function classToString(classDesc) {
const parts = [];
if (classDesc.comment) {
parts.push(writeComment(classDesc.comment))
}
if (classDesc.templates.length)
console.error(`expected an override for "${classDesc.name}" becasue it is templated`);
parts.push(`export interface ${classDesc.name} ${classDesc.extends ? `extends ${classDesc.extends} ` : ''}{`);
parts.push(classBody(classDesc));
parts.push('}\n');
return parts.join('\n');
}
/**
* @param {string} type
*/
function argNameForType(type) {
if (type === 'void')
return null;
return type[0].toLowerCase() + type.slice(1);
}
/**
* @param {Documentation.Class} classDesc
*/
function hasUniqueEvents(classDesc) {
if (!classDesc.events.size)
return false;
const parent = parentClass(classDesc);
if (!parent)
return true;
return Array.from(classDesc.events.keys()).some(eventName => !parent.events.has(eventName));
}
/**
* @param {Documentation.Class} classDesc
*/
function createEventDescriptions(classDesc) {
if (!hasUniqueEvents(classDesc))
return [];
const descriptions = [];
for (const [eventName, value] of classDesc.events) {
const type = typeToString(value && value.type, classDesc.name, eventName, 'payload');
const argName = argNameForType(type);
const params = argName ? `${argName} : ${type}` : '';
descriptions.push({
params,
eventName,
comment: value.comment
});
}
return descriptions;
}
/**
* @param {Documentation.Class} classDesc
*/
function classBody(classDesc) {
const parts = [];
const eventDescriptions = createEventDescriptions(classDesc);
for (const method of ['on', 'once', 'addListener']) {
for (const {eventName, params, comment} of eventDescriptions) {
if (comment)
parts.push(writeComment(comment, ' '));
parts.push(` ${method}(event: '${eventName}', listener: (${params}) => void): this;\n`);
}
}
const members = classDesc.membersArray.filter(member => member.kind !== 'event');
parts.push(members.map(member => {
if (member.kind === 'event')
return '';
const jsdoc = memberJSDOC(member, ' ');
const args = argsFromMember(member, classDesc.name);
const type = typeToString(member.type, classDesc.name, member.name);
// do this late, because we still want object definitions for overridden types
if (!hasOwnMethod(classDesc, member.name))
return '';
if (member.templates.length)
console.error(`expected an override for "${classDesc.name}.${member.name}" becasue it is templated`);
return `${jsdoc}${member.name}${args}: ${type};`
}).filter(x => x).join('\n\n'));
return parts.join('\n');
}
/**
* @param {Documentation.Class} classDesc
* @param {string} methodName
*/
function hasOwnMethod(classDesc, methodName) {
if (handledMethods.has(`${classDesc.name}.${methodName}`))
return false;
while (classDesc = parentClass(classDesc)) {
if (classDesc.members.has(methodName))
return false;
}
return true;
}
/**
* @param {Documentation.Class} classDesc
*/
function parentClass(classDesc) {
if (!classDesc.extends)
return null;
return documentation.classes.get(classDesc.extends);
}
function writeComment(comment, indent = '') {
const parts = [];
parts.push(indent + '/**');
parts.push(...comment.split('\n').map(line => indent + ' * ' + line.replace(/\*\//g, '*\\/')));
parts.push(indent + ' */');
return parts.join('\n');
}
function writeComment2(comment, indent = '') {
const parts = [];
parts.push('/**');
parts.push(...comment.split('\n').map(line => indent + ' * ' + line.replace(/\*\//g, '*\\/')));
parts.push(indent + ' */\n' + indent);
return parts.join('\n');
}
/**
* @param {Documentation.Type} type
*/
function typeToString(type, ...namespace) {
if (!type)
return 'void';
let typeString = stringifyType(parseType(type.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
};
}
/**
* @return {string}
*/
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(', ')}) => ${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 {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, arg.name)}`).join(', ') + ')';
}
/**
* @param {Documentation.Member} member
* @param {string} indent
*/
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 indent;
return writeComment(lines.join('\n'), indent) + '\n' + indent;
}
/**
* @param {Documentation} mdDoc
* @param {Documentation} jsDoc
* @return {Documentation}
*/
function mergeDocumentation(mdDoc, jsDoc) {
const classes = [];
for (const mdClass of mdDoc.classesArray) {
const jsClass = jsDoc.classes.get(mdClass.name);
if (!jsClass)
classes.push(mdClass);
else
classes.push(mergeClasses(mdClass, jsClass));
}
return mdDoc;
}
/**
* @param {Documentation.Class} mdClass
* @param {Documentation.Class} jsClass
* @return {Documentation.Class}
*/
function mergeClasses(mdClass, jsClass) {
mdClass.templates = jsClass.templates;
for (const member of mdClass.membersArray)
member.templates = jsClass.members.get(member.name).templates;
return mdClass;
}

139
utils/generate_types/overrides.d.ts vendored Normal file
View File

@ -0,0 +1,139 @@
/**
* 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 { Protocol } from './protocol';
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
/**
* Can be converted to JSON
*/
type Serializable = {}
type ConnectionTransport = {}
type Boxed<Args extends any[]> = { [Index in keyof Args]: Args[Index] | JSHandle<Args[Index]> };
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
type PageFunctionOn<On, Args extends any[], R = any> = string | ((on: On, ...args: Args) => R | Promise<R>);
type Handle<T> = T extends Node ? ElementHandle<T> : JSHandle<T>;
type ElementHandleForTag<K extends keyof HTMLElementTagNameMap> = ElementHandle<HTMLElementTagNameMap[K]>;
type WaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & {
waitFor: 'visible'|'attached';
}
type HTMLOrSVGElement = SVGElement | HTMLElement;
type HTMLOrSVGElementHandle = ElementHandle<HTMLOrSVGElement>;
export interface Page {
evaluate<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<R>;
evaluateHandle<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<Handle<R>>;
$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K> | null>;
$(selector: string): Promise<HTMLOrSVGElementHandle | null>;
$$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K>[]>;
$$(selector: string): Promise<HTMLOrSVGElementHandle[]>;
$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K], Args, R>, ...args: Boxed<Args>): Promise<R>;
$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement, Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K][], Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement[], Args, R>, ...args: Boxed<Args>): Promise<R>;
waitForSelector<K extends keyof HTMLElementTagNameMap>(selector: K, options?: WaitForSelectorOptionsNotHidden): Promise<ElementHandleForTag<K>>;
waitForSelector(selector: string, options?: WaitForSelectorOptionsNotHidden): Promise<HTMLOrSVGElementHandle>;
waitForSelector<K extends keyof HTMLElementTagNameMap>(selector: K, options: PageWaitForSelectorOptions): Promise<ElementHandleForTag<K> | null>;
waitForSelector(selector: string, options: PageWaitForSelectorOptions): Promise<null|HTMLOrSVGElementHandle>;
}
export interface Frame {
evaluate<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<R>;
evaluateHandle<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<Handle<R>>;
$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K> | null>;
$(selector: string): Promise<HTMLOrSVGElementHandle | null>;
$$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K>[]>;
$$(selector: string): Promise<HTMLOrSVGElementHandle[]>;
$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K], Args, R>, ...args: Boxed<Args>): Promise<R>;
$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement, Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K][], Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement[], Args, R>, ...args: Boxed<Args>): Promise<R>;
waitForSelector<K extends keyof HTMLElementTagNameMap>(selector: K, options?: WaitForSelectorOptionsNotHidden): Promise<ElementHandleForTag<K>>;
waitForSelector(selector: string, options?: WaitForSelectorOptionsNotHidden): Promise<HTMLOrSVGElementHandle>;
waitForSelector<K extends keyof HTMLElementTagNameMap>(selector: K, options: PageWaitForSelectorOptions): Promise<ElementHandleForTag<K> | null>;
waitForSelector(selector: string, options: PageWaitForSelectorOptions): Promise<null|HTMLOrSVGElementHandle>;
}
export interface Worker {
evaluate<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<R>;
evaluateHandle<Args extends any[], R>(pageFunction: PageFunction<Args, R>, ...args: Boxed<Args>): Promise<Handle<R>>;
}
export interface JSHandle<T = any> {
jsonValue(): Promise<T>;
evaluate<Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>): Promise<R>;
evaluateHandle<Args extends any[], R>(pageFunction: PageFunctionOn<T, Args, R>, ...args: Boxed<Args>): Promise<Handle<R>>;
asElement(): T extends Node ? ElementHandle<T> : null;
}
export interface ElementHandle<T=Node> extends JSHandle<T> {
$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K> | null>;
$(selector: string): Promise<HTMLOrSVGElementHandle | null>;
$$<K extends keyof HTMLElementTagNameMap>(selector: K): Promise<ElementHandleForTag<K>[]>;
$$(selector: string): Promise<HTMLOrSVGElementHandle[]>;
$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K], Args, R>, ...args: Boxed<Args>): Promise<R>;
$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement, Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<K extends keyof HTMLElementTagNameMap, Args extends any[], R>(selector: K, pageFunction: PageFunctionOn<HTMLElementTagNameMap[K][], Args, R>, ...args: Boxed<Args>): Promise<R>;
$$eval<Args extends any[], R>(selector: string, pageFunction: PageFunctionOn<HTMLOrSVGElement[], Args, R>, ...args: Boxed<Args>): Promise<R>;
}
export interface BrowserType<Browser> {
}
export interface ChromiumBrowser extends Browser {
contexts(): Array<ChromiumBrowserContext>;
newContext(options?: BrowserNewContextOptions): Promise<ChromiumBrowserContext>;
}
export interface CDPSession {
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;
send<T extends keyof Protocol.CommandParameters>(
method: T,
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]>;
}
type DeviceDescriptor = {viewport: BrowserNewContextOptionsViewport, userAgent: string};
export namespace errors {
class TimeoutError extends Error {}
}
export const selectors: Selectors;
export const devices: {[name: string]: DeviceDescriptor} & DeviceDescriptor[];

View File

@ -0,0 +1,92 @@
/**
* 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.
*/
const path = require('path');
const ts = require('typescript');
/**
* @param {(className: string) => string} commentForClass
* @param {(className: string, methodName: string) => string} commentForMethod
* @param {(className: string) => string} extraForClass
*/
async function parseOverrides(commentForClass, commentForMethod, extraForClass) {
const filePath = path.join(__dirname, 'overrides.d.ts');
const program = ts.createProgram({
rootNames: [filePath],
options: {
target: ts.ScriptTarget.ESNext
}
});
const checker = program.getTypeChecker();
const replacers = [];
const file = program.getSourceFile(filePath);
visit(file);
let src = file.text;
for (const replacer of replacers.sort((a, b) => b.pos - a.pos)) {
src = src.substring(0, replacer.pos) + replacer.text + src.substring(replacer.pos);
}
return src;
/**
* @param {!ts.Node} node
*/
function visit(node) {
if (ts.isClassDeclaration(node) || ts.isClassExpression(node) || ts.isInterfaceDeclaration(node)) {
const symbol = node.name ? checker.getSymbolAtLocation(node.name) : node.symbol;
let className = symbol.getName();
if (className === '__class') {
let parent = node;
while (parent.parent)
parent = parent.parent;
className = path.basename(parent.fileName, '.js');
}
if (className)
serializeClass(className, symbol, node);
}
ts.forEachChild(node, visit);
}
/**
* @param {string} className
* @param {!ts.Symbol} symbol
* @param {ts.Node} node
*/
function serializeClass(className, symbol, node) {
replacers.push({
pos: node.getStart(file, false),
text: commentForClass(className),
});
for (const [name, member] of symbol.members || []) {
if (member.flags & ts.SymbolFlags.TypeParameter)
continue;
const pos = member.valueDeclaration.getStart(file, false)
replacers.push({
pos,
text: commentForMethod(className, name),
});
}
replacers.push({
pos: node.getEnd(file) - 1,
text: extraForClass(className),
});
}
}
module.exports = {parseOverrides};

View File

@ -0,0 +1,557 @@
/**
* 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 * as playwright from '../../../index';
type AssertType<T, S> = S extends T ? AssertNotAny<S> : false;
type AssertNotAny<S> = {notRealProperty: number} extends S ? false : true;
// Examples taken from README
(async () => {
const browser = await playwright.chromium.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.chromium.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.chromium.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.chromium.launch().then(async browser => {
const page = await browser.newPage();
page.on('console', message => {
console.log(message.text());
});
page.evaluate(() => console.log(5, 'hello', { foo: 'bar' }));
{
const result = await page.evaluate(() => {
return Promise.resolve(8 * 7);
});
const assertion: AssertType<number, typeof result> = true;
console.log(await page.evaluate('1 + 2'));
page.$eval('.foo', e => e.style);
}
const bodyHandle = await page.$('body');
if (!bodyHandle)
return;
{
const html = await page.evaluate(
(body: HTMLElement) => body.innerHTML,
bodyHandle
);
const assertion: AssertType<string, typeof html> = true;
}
});
import * as crypto from 'crypto';
import * as fs from 'fs';
playwright.chromium.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 = 'PUPPETEER';
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({media: 'screen'});
await page.pdf({ path: 'page.pdf' });
await page.route('**/*', (route, interceptedRequest) => {
if (
interceptedRequest.url().endsWith('.png') ||
interceptedRequest.url().endsWith('.jpg')
)
route.abort();
else route.continue();
});
await page.route(str => {
const assertion: AssertType<string, typeof str> = true;
return true;
}, interceptedRequest => {
interceptedRequest.continue();
return 'something random for no reason';
});
await page.keyboard.type('Hello'); // Types instantly
await page.keyboard.type('World', { delay: 100 }); // Types slower, like a user
const watchDog = page.waitForFunction('window.innerWidth < 100');
page.setViewportSize({ width: 50, height: 50 });
await watchDog;
let currentURL: string;
page
.waitForSelector('img', { waitFor: 'visible' })
.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.insertText('嗨');
await browser.startTracing(page, { path: 'trace.json'});
await page.goto('https://www.google.com');
await browser.stopTracing();
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.chromium.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.chromium.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
});
console.log(button.toString());
input.type('Hello World', { delay: 10 });
const buttonText = await (await button.getProperty('textContent')).jsonValue();
await page.context().clearCookies();
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();
// 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.firefox.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.$eval('#someElement', (element, text: string) => {
return element.innerHTML = text;
}, 'hey');
const elementText = await page.$$eval('.someClassName', elements => {
console.log(elements.length);
console.log(elements.map(x => x)[0].textContent);
return elements[3].innerHTML;
});
browser.close();
})();
// typed handles
(async () => {
const browser = await playwright.webkit.launch();
const page = await browser.newPage();
const windowHandle = await page.evaluateHandle(() => window);
{
const value = await page.evaluate(() => 1);
const assertion: AssertType<number, typeof value> = true;
}
{
const value = await page.mainFrame().evaluate(() => 'hello');
const assertion: AssertType<string, typeof value> = true;
}
{
const value = await page.workers()[0].evaluate(() => [1,2,3]);
const assertion: AssertType<number[], typeof value> = true;
}
{
const value = await windowHandle.evaluate((x: Window, b) => b, 'world');
const assertion: AssertType<string, typeof value> = true;
}
{
const handle = await page.evaluateHandle(() => 'hello');
const value = await handle.jsonValue();
const assertion: AssertType<string, typeof value> = true;
}
{
const handle = await page.mainFrame().evaluateHandle(() => ['a', 'b', 'c']);
const value = await handle.jsonValue();
const assertion: AssertType<string[], typeof value> = true;
}
{
const handle = await page.workers()[0].evaluateHandle(() => 123);
const value = await handle.jsonValue();
const assertion: AssertType<number, typeof value> = true;
}
{
const handle = await windowHandle.evaluateHandle((x: Window, b) => b, 123);
const value = await handle.jsonValue();
const assertion: AssertType<number, typeof value> = true;
}
{
const handle = await page.evaluateHandle(() => document.createElement('body'));
const assertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
await handle.evaluate(body => {
const assertion: AssertType<HTMLBodyElement, typeof body> = true;
});
}
await browser.close();
})();
// protocol
(async () => {
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const session = await context.newCDPSession(await context.newPage());
session.on('Runtime.executionContextCreated', payload => {
const id = payload.context.id;
const assertion: AssertType<number, typeof id> = true;
});
const obj = await session.send('Runtime.evaluate', {
expression: '1 + 1'
});
const type = obj.result.type;
const assertion: AssertType<string, typeof type> = true;
await session.detach();
await browser.close();
})();
(async () => {
const browser = await playwright.firefox.launch();
const page = await browser.newPage();
const context = page.context();
const oneTwoThree = ('pageTarget' in context) ? context['pageTarget'] : 123;
const assertion: AssertType<123, typeof oneTwoThree> = true;
await browser.close();
})();
// $eval
(async () => {
const browser = await playwright.webkit.launch();
const page = await browser.newPage();
await page.$eval('span', (element, x) => {
const spanAssertion: AssertType<HTMLSpanElement, typeof element> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
await page.$eval('my-custom-element', (element, x) => {
const elementAssertion: AssertType<HTMLElement|SVGElement, typeof element> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
await page.$$eval('my-custom-element', (elements, x) => {
const elementAssertion: AssertType<(HTMLElement|SVGElement)[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
await page.$$eval('input', (elements, x) => {
const elementAssertion: AssertType<HTMLInputElement[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
const frame = page.mainFrame();
await frame.$eval('span', (element, x, y) => {
const spanAssertion: AssertType<HTMLSpanElement, typeof element> = true;
const numberAssertion: AssertType<number, typeof x> = true;
const stringAssertion: AssertType<string, typeof y> = true;
}, 5, 'asdf');
await frame.$eval('my-custom-element', element => {
const elementAssertion: AssertType<HTMLElement|SVGElement, typeof element> = true;
});
await frame.$$eval('my-custom-element', (elements, x, y) => {
const elementAssertion: AssertType<(HTMLElement|SVGElement)[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
const stringAssertion: AssertType<string, typeof y> = true;
}, 5, await page.evaluateHandle(() => 'asdf'));
await frame.$$eval('input', (elements, x) => {
const elementAssertion: AssertType<HTMLInputElement[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
const something = Math.random() > .5 ? 'visible' : 'attached';
const handle = await page.waitForSelector('a', {waitFor: something});
await handle.$eval('span', (element, x, y) => {
const spanAssertion: AssertType<HTMLSpanElement, typeof element> = true;
const numberAssertion: AssertType<number, typeof x> = true;
const stringAssertion: AssertType<string, typeof y> = true;
}, 5, 'asdf');
await handle.$eval('my-custom-element', element => {
const elementAssertion: AssertType<HTMLElement|SVGElement, typeof element> = true;
});
await handle.$$eval('my-custom-element', (elements, x, y) => {
const elementAssertion: AssertType<(HTMLElement|SVGElement)[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
const stringAssertion: AssertType<string, typeof y> = true;
}, 5, await page.evaluateHandle(() => 'asdf'));
await handle.$$eval('input', (elements, x) => {
const elementAssertion: AssertType<HTMLInputElement[], typeof elements> = true;
const numberAssertion: AssertType<number, typeof x> = true;
}, 5);
await browser.close();
})();
// query selectors
(async () => {
const browser = await playwright.chromium.launch();
const page = await browser.newPage();
const frame = page.mainFrame();
const element = await page.waitForSelector('some-fake-element');
const elementLikes = [page, frame, element];
for (const elementLike of elementLikes) {
{
const handle = await elementLike.$('body');
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
}
{
const handle = await elementLike.$('something-strange');
const top = await handle!.evaluate(element => element.style.top);
const assertion: AssertType<string, typeof top> = true;
}
{
const handles = await elementLike.$$('body');
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>[], typeof handles> = true;
}
{
const handles = await elementLike.$$('something-strange');
const top = await handles[0].evaluate(element => element.style.top);
const assertion: AssertType<string, typeof top> = true;
}
}
const frameLikes = [page, frame];
for (const frameLike of frameLikes) {
{
const handle = await frameLike.waitForSelector('body');
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false;
}
{
const waitFor = Math.random() > .5 ? 'attached' : 'visible';
const handle = await frameLike.waitForSelector('body', {waitFor});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false;
}
{
const handle = await frameLike.waitForSelector('body', {waitFor: 'hidden'});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true;
}
{
const waitFor = Math.random() > .5 ? 'hidden' : 'visible';
const handle = await frameLike.waitForSelector('body', {waitFor});
const bodyAssertion: AssertType<playwright.ElementHandle<HTMLBodyElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true;
}
{
const handle = await frameLike.waitForSelector('something-strange');
const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false;
}
{
const waitFor = Math.random() > .5 ? 'attached' : 'visible';
const handle = await frameLike.waitForSelector('something-strange', {waitFor});
const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = false;
}
{
const waitFor = Math.random() > .5 ? 'hidden' : 'visible';
const handle = await frameLike.waitForSelector('something-strange', {waitFor});
const elementAssertion: AssertType<playwright.ElementHandle<HTMLElement|SVGElement>, typeof handle> = true;
const canBeNull: AssertType<null, typeof handle> = true;
}
}
await browser.close();
})();
// top level
(async () => {
playwright.chromium.connect;
playwright.errors.TimeoutError;
{
const iPhone = playwright.devices['iPhone'];
const assertion: AssertType<string, typeof iPhone.userAgent> = true;
const numberAssertion: AssertType<number, typeof iPhone.viewport.width> = true;
}
{
const agents = playwright.devices.map(x => x.userAgent);
const assertion: AssertType<string[], typeof agents> = true;
}
// Must be a function that evaluates to a selector engine instance.
const createTagNameEngine = () => ({
// Creates a selector that matches given target when queried at the root.
// Can return undefined if unable to create one.
create(root: Element, target: Element) {
return root.querySelector(target.tagName) === target ? target.tagName : undefined;
},
// Returns the first element matching given selector in the root's subtree.
query(root: Element, selector: string) {
return root.querySelector(selector);
},
// Returns all elements matching given selector in the root's subtree.
queryAll(root: Element, selector: string) {
return Array.from(root.querySelectorAll(selector));
}
});
// Register the engine. Selectors will be prefixed with "tag=".
await playwright.selectors.register('tag', createTagNameEngine);
})();

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"strict": true,
"target": "es2015",
"noEmit": true,
"moduleResolution": "node"
},
"include": [
"test.ts"
]
}