mirror of
https://github.com/microsoft/playwright.git
synced 2024-08-17 08:00:23 +03:00
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:
parent
f1d97b0466
commit
825555cddf
@ -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
1
.gitignore
vendored
@ -21,3 +21,4 @@ lib/
|
||||
playwright-*.tgz
|
||||
/web.js
|
||||
/web.js.map
|
||||
/types/*
|
||||
|
@ -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
|
||||
|
@ -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
14
index.d.ts
vendored
@ -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>;
|
||||
|
@ -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)));
|
||||
}
|
||||
})();
|
||||
|
||||
})();
|
@ -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
20
packages/playwright-chromium/index.d.ts
vendored
Normal 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
20
packages/playwright-firefox/index.d.ts
vendored
Normal 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
20
packages/playwright-webkit/index.d.ts
vendored
Normal 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>;
|
23
packages/playwright/index.d.ts
vendored
23
packages/playwright/index.d.ts
vendored
@ -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>;
|
||||
|
@ -30,4 +30,3 @@ try {
|
||||
throw new Error('ERROR: Playwright did not download browsers');
|
||||
}
|
||||
|
||||
|
||||
|
@ -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>;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
2
test/types.d.ts
vendored
@ -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;
|
||||
|
@ -7,7 +7,7 @@
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"declaration": true
|
||||
"declaration": false
|
||||
},
|
||||
"compileOnSave": true,
|
||||
"include": ["src/**/*.ts"],
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
392
utils/generate_types/index.js
Normal file
392
utils/generate_types/index.js
Normal 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
139
utils/generate_types/overrides.d.ts
vendored
Normal 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[];
|
92
utils/generate_types/parseOverrides.js
Normal file
92
utils/generate_types/parseOverrides.js
Normal 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};
|
557
utils/generate_types/test/test.ts
Normal file
557
utils/generate_types/test/test.ts
Normal 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);
|
||||
})();
|
11
utils/generate_types/test/tsconfig.json
Normal file
11
utils/generate_types/test/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es2015",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
"test.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user