diff --git a/docs/gen.md b/docs/gen.md new file mode 100644 index 0000000000..c8609eba04 --- /dev/null +++ b/docs/gen.md @@ -0,0 +1,166 @@ +##### Table of Contents + +- [class: ElementHandle](#class-elementhandle) + * [ElementHandle.boundingBox](#elementhandleboundingbox) +- [class: Keyboard](#class-keyboard) + * [Keyboard.down](#keyboarddown) + * [Keyboard.press](#keyboardpress) + * [Keyboard.sendCharacters](#keyboardsendcharacters) + * [Keyboard.type](#keyboardtype) + * [Keyboard.up](#keyboardup) + +### class: ElementHandle + +This is an element handle. + + +#### ElementHandle.boundingBox +Does something. + +- returns: <[Promise]> The element's bounding box rect. + - `height` <[number]> + - `width` <[number]> + - `x` <[number]> + - `y` <[number]> + +### class: Keyboard + +`Keyboard` provides an api for managing a virtual keyboard. +The high level api is [Keyboard.type](#keyboardtype), which takes raw characters and generates +proper keydown, keypress/input, and keyup events on your page. + +For finer control, you can use [Keyboard.down](#keyboarddown), [Keyboard.up](#keyboardup), and +[Keyboard.sendCharacters](#keyboardsendcharacters) to manually fire events as if they were generated +from a real keyboard. + +An example of holding down `Shift` in order to select and delete some text: +```js +await page.keyboard.type('Hello World!'); +await page.keyboard.press('ArrowLeft'); + +await page.keyboard.down('Shift'); +for (let i = 0; i < ' World'.length; i++) + await page.keyboard.press('ArrowLeft'); +await page.keyboard.up('Shift'); + +await page.keyboard.press('Backspace'); +// Result text will end up saying 'Hello!' +``` + +An example of pressing `A` +```js +await page.keyboard.down('Shift'); +await page.keyboard.press('KeyA'); +await page.keyboard.up('Shift'); +``` + +> **NOTE** +On MacOS, keyboard shortcuts like `⌘ A` -> Select All do not work. +See [#1313](https://github.com/puppeteer/puppeteer/issues/1313). + +#### Keyboard.down +Dispatches a `keydown` event. + +- `key` <[string]> Name of key to press, such as `ArrowLeft`. +See [USKeyboardLayout](USKeyboardLayout) for a list of all key names. +- `options` + - `text` If specified, generates an input event with this text. +- returns: <[Promise]> + +If `key` is a single character and no modifier keys besides `Shift` are being held down, +a `keypress`/`input` event will also generated. The `text` option can be specified +to force an input event to be generated. + +If `key` is a modifier key, `Shift`, `Meta`, `Control`, or `Alt`, +subsequent key presses will be sent with that modifier active. +To release the modifier key, use [Keyboard.up](#keyboardup). + +After the key is pressed once, subsequent calls to [Keyboard.down](#keyboarddown) will have +[repeat](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat) set to true. +To release the key, use [Keyboard.up](#keyboardup). + +> **NOTE** +Modifier keys DO influence `keyboard.down`. Holding down `Shift` will type the text in upper case. + +#### Keyboard.press +Shortcut for [Keyboard.down](#keyboarddown) and [Keyboard.up](#keyboardup). + +- `key` <[string]> Name of key to press, such as `ArrowLeft`. +See [USKeyboardLayout](USKeyboardLayout) for a list of all key names. +- `options` + - `delay` Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0. + - `text` If specified, generates an input event with this text. +- returns: <[Promise]> + +If `key` is a single character and no modifier keys besides `Shift` are being held down, +a `keypress`/`input` event will also generated. The `text` option can be specified +to force an input event to be generated. + +> **NOTE** +Modifier keys DO effect `keyboard.press`. Holding down `Shift` will type the text in upper case. + +#### Keyboard.sendCharacters +Dispatches a `keypress` and `input` event. This does not send a `keydown` or `keyup` event. + +- `text` <[string]> Characters to send into the page. +- returns: <[Promise]> + + +```js +page.keyboard.sendCharacters('嗨'); +``` + +> **NOTE** +Modifier keys DO NOT effect `keyboard.sendCharacters`. Holding down `Shift` will not +type the text in upper case. + +#### Keyboard.type +Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. +To press a special key, like `Control` or `ArrowDown`, use [Keyboard.press](#keyboardpress). + +- `text` <[string]> A text to type into a focused element. +- `options` + - `delay` Time to wait between key presses in milliseconds. Defaults to 0. +- returns: <[Promise]> + + +```js +await page.keyboard.type('Hello'); // Types instantly +await page.keyboard.type('World', {delay: 100}); // Types slower, like a user +``` + +> **NOTE** +Modifier keys DO NOT effect `keyboard.type`. Holding down `Shift` will not +type the text in upper case. + +#### Keyboard.up +Dispatches a `keyup` event. See [Keyboard.down](#keyboarddown) for more info. + +- `key` <[string]> Name of key to release, such as `ArrowLeft`. +See [USKeyboardLayout](USKeyboardLayout) for a list of all key names. +- returns: <[Promise]> + + +[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array" +[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean" +[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer" +[ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess" +[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element" +[ElementHandle]: #class-elementhandle "ElementHandle" +[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error" +[File]: #class-file "https://developer.mozilla.org/en-US/docs/Web/API/File" +[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function" +[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols "Iterator" +[Keyboard]: #class-keyboard "Keyboard" +[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map" +[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number" +[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object" +[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin" +[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise" +[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector" +[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable" +[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable" +[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String" +[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail" +[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time" +[xpath]: https://developer.mozilla.org/en-US/docs/Web/XPath "xpath" diff --git a/package.json b/package.json index ae0b6836a7..4576b3772a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "prepare": "node install.js", "lint": "([ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts ./src || eslint --ext js,ts ./src) && npm run tsc && npm run doc", "doc": "node utils/doclint/cli.js", + "typedoc": "typedoc --json docs/docs.json --includeDeclarations --excludeExternals --externalPattern \"**/node_modules/**\" --mode 'file' src/api.d.ts", + "newdoc": "npm run typedoc && node utils/tsdoc.js && rm docs/docs.json", "coverage": "cross-env COVERAGE=true npm run unit", "tsc": "tsc -p .", "build": "node utils/runWebpack.js --mode='development' && tsc -p .", @@ -46,6 +48,7 @@ "ws": "^6.1.0" }, "devDependencies": { + "@microsoft/tsdoc": "^0.12.16", "@types/debug": "0.0.31", "@types/extract-zip": "^1.6.2", "@types/jpeg-js": "^0.3.7", @@ -67,6 +70,7 @@ "progress": "^2.0.1", "text-diff": "^1.0.1", "ts-loader": "^6.1.2", + "typedoc": "^0.15.4", "typescript": "3.6.3", "webpack": "^4.41.0", "webpack-cli": "^3.3.9" diff --git a/src/api.d.ts b/src/api.d.ts new file mode 100644 index 0000000000..ce207f1b05 --- /dev/null +++ b/src/api.d.ts @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +declare module 'playwright' { + + /** + * `Keyboard` provides an api for managing a virtual keyboard. + * The high level api is {@link Keyboard.type}, which takes raw characters and generates + * proper keydown, keypress/input, and keyup events on your page. + * + * For finer control, you can use {@link Keyboard.down}, {@link Keyboard.up}, and + * {@link Keyboard.sendCharacters} to manually fire events as if they were generated + * from a real keyboard. + * + * @example + * An example of holding down `Shift` in order to select and delete some text: + * ```js + * await page.keyboard.type('Hello World!'); + * await page.keyboard.press('ArrowLeft'); + * + * await page.keyboard.down('Shift'); + * for (let i = 0; i < ' World'.length; i++) + * await page.keyboard.press('ArrowLeft'); + * await page.keyboard.up('Shift'); + * + * await page.keyboard.press('Backspace'); + * // Result text will end up saying 'Hello!' + * ``` + * + * @example + * An example of pressing `A` + * ```js + * await page.keyboard.down('Shift'); + * await page.keyboard.press('KeyA'); + * await page.keyboard.up('Shift'); + * ``` + * + * @remarks + * On MacOS, keyboard shortcuts like `⌘ A` -> Select All do not work. + * See {@link https://github.com/puppeteer/puppeteer/issues/1313 | #1313}. + */ + export interface Keyboard { + /** + * Dispatches a `keydown` event. + * + * If `key` is a single character and no modifier keys besides `Shift` are being held down, + * a `keypress`/`input` event will also generated. The `text` option can be specified + * to force an input event to be generated. + * + * If `key` is a modifier key, `Shift`, `Meta`, `Control`, or `Alt`, + * subsequent key presses will be sent with that modifier active. + * To release the modifier key, use {@link Keyboard.up}. + * + * After the key is pressed once, subsequent calls to {@link Keyboard.down} will have + * {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat | repeat} set to true. + * To release the key, use {@link Keyboard.up}. + * + * @remarks + * Modifier keys DO influence `keyboard.down`. Holding down `Shift` will type the text in upper case. + * + * @param key - Name of key to press, such as `ArrowLeft`. + * See {@link USKeyboardLayout} for a list of all key names. + * + * @param options.text - If specified, generates an input event with this text. + */ + down(key: string, options?: { text?: string }): Promise; + + /** + * Shortcut for {@link Keyboard.down} and {@link Keyboard.up}. + * + * If `key` is a single character and no modifier keys besides `Shift` are being held down, + * a `keypress`/`input` event will also generated. The `text` option can be specified + * to force an input event to be generated. + * + * @remarks + * Modifier keys DO effect `keyboard.press`. Holding down `Shift` will type the text in upper case. + * + * @param key - Name of key to press, such as `ArrowLeft`. + * See {@link USKeyboardLayout} for a list of all key names. + * + * @param options.text - If specified, generates an input event with this text. + * + * @param options.delay - Time to wait between `keydown` and `keyup` in milliseconds. Defaults to 0. + */ + press(key: string, options?: { text?: string, delay?: number }): Promise; + + /** + * Dispatches a `keypress` and `input` event. This does not send a `keydown` or `keyup` event. + * + * @example + * ```js + * page.keyboard.sendCharacters('嗨'); + * ``` + * + * @remarks + * Modifier keys DO NOT effect `keyboard.sendCharacters`. Holding down `Shift` will not + * type the text in upper case. + * + * @param text - Characters to send into the page. + */ + sendCharacters(text: string): Promise; + + /** + * Sends a `keydown`, `keypress`/`input`, and `keyup` event for each character in the text. + * To press a special key, like `Control` or `ArrowDown`, use {@link Keyboard.press}. + * + * @example + * ```js + * await page.keyboard.type('Hello'); // Types instantly + * await page.keyboard.type('World', {delay: 100}); // Types slower, like a user + * ``` + * + * @remarks + * Modifier keys DO NOT effect `keyboard.type`. Holding down `Shift` will not + * type the text in upper case. + * + * @param text - A text to type into a focused element. + * + * @param options.delay - Time to wait between key presses in milliseconds. Defaults to 0. + */ + type(text: string, options?: { delay?: number }): Promise; + + /** + * Dispatches a `keyup` event. See {@link Keyboard.down} for more info. + * + * @param key - Name of key to release, such as `ArrowLeft`. + * See {@link USKeyboardLayout} for a list of all key names. + */ + up(key: string): Promise; + } + + /** + * @inline + */ + export type Rect = { x: number, y: number, width: number, height: number }; + + /** + * This is an element handle. + */ + export interface ElementHandle { + /** + * Does something. + * + * @returns The element's bounding box rect. + */ + boundingBox(): Promise; + } +} diff --git a/utils/tsdoc.js b/utils/tsdoc.js new file mode 100644 index 0000000000..ace6da4e98 --- /dev/null +++ b/utils/tsdoc.js @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +const fs = require('fs'); +const path = require('path'); + +function run() { + const json = require('../docs/docs.json'); + + const md = []; + const links = new Map(); + const inlined = new Map(); + + function append(line, indent) { + line = (line || '').trim(); + line = line.replace(/{@link ([^}|]*)(?:\|([^}]*))?}/g, (match, url, text) => { + text = text || url; + if (links.has(url)) + url = links.get(url); + return `[${text.trim()}](${url.trim()})`; + }); + md.push(' '.repeat(indent || 0) + line); + } + + function appendComment(comment) { + if (!comment) + return; + if (comment.text) + append(comment.text); + for (const tag of comment.tags || []) { + append(); + if (tag.tag === 'remarks') + append(`> **NOTE** ${tag.text}`); + else + append(tag.text); + } + } + + function isNullOrUndefined(t) { + return t.type === 'intrinsic' && (t.name === 'null' || t.name === 'undefined'); + } + + function appendType(t, comment, optional, prefix, indent) { + let type = ''; + let children; + let union = ''; + let template; + if (t.type === 'reference' && t.name === 'Promise' && t.typeArguments && t.typeArguments[0].name !== 'void') { + template = t.name; + t = t.typeArguments[0]; + } + while (t.type === 'union') { + let hasInlined = false; + for (const child of t.types) { + if (isNullOrUndefined(child)) { + optional = true; + } else if ((child.type === 'reference' && inlined.has(child.name)) || child.type === 'reflection') { + if (hasInlined) + throw new Error('Cannot handle union of two inlined types'); + hasInlined = true; + t = child; + } else { + union += child.name + '|'; + } + } + if (!hasInlined) { + type = union.substring(0, union.length - 1); + union = ''; + break; + } + } + while (t.type === 'reference' && inlined.has(t.name)) + t = inlined.get(t.name); + if (t.type === 'intrinsic') { + type = t.name; + } else if (t.type === 'reference') { + type = t.name; + } else if (t.type === 'reflection') { + type = 'Object'; + children = t.declaration && t.declaration.children; + } + type = union + type; + type = `${optional ? '?' : ''}[${type}]`; + if (template) + type = `[${template}]<${type}>`; + const text = comment ? ' ' + comment.trim() : ''; + append(`${prefix}<${type}>${text}`, indent); + for (const child of children || []) + appendType(child.type, child.comment && child.comment.text, child.flags && child.flags.isOptional, `- \`${child.name}\` `, (indent || 0) + 2); + } + + function appendParameter(p, indent) { + appendType(p.type, p.comment && p.comment.text, p.flags && p.flags.isOptional, `- \`${p.name}\` `, indent); + } + + function appendReturn(r, comment) { + appendType(r, comment, false, `- returns: `); + } + + function methods(interface) { + return (interface.children || []).filter(child => child.kindString === 'Method'); + } + + const types = json.children[0].children.filter(e => e.kindString === 'Type alias'); + for (const type of types) { + if (type.comment && (type.comment.tags || []).find(tag => tag.tag === 'inline')) { + inlined.set(type.name, type.type); + } + } + + const interfaces = json.children[0].children.filter(e => e.kindString === 'Interface'); + + append(`##### Table of Contents`); + append(); + for (const c of interfaces) { + const classLink = '#class-' + c.name.toLowerCase(); + links.set(c.name, classLink); + append(`- [class: ${c.name}](${classLink})`); + for (const method of methods(c)) { + const methodName = c.name + '.' + method.signatures[0].name; + const methodLink = '#' + c.name.toLowerCase() + method.signatures[0].name.toLowerCase(); + links.set(methodName, methodLink); + append(`* [${methodName}](${methodLink})`, 2); + } + } + append(); + + for (const c of interfaces) { + append(`### class: ${c.name}`); + append(); + if (c.comment) { + append(c.comment.shortText); + append(); + } + appendComment(c.comment); + + for (const method of methods(c)) { + const signature = method.signatures[0]; + append(); + append(`#### ${c.name}.${signature.name}`); + if (signature.comment) { + append(signature.comment.shortText); + append(); + } + for (const parameter of signature.parameters || []) + appendParameter(parameter); + if (signature.type) + appendReturn(signature.type, signature.comment && signature.comment.returns); + append(); + appendComment(signature.comment); + } + } + + const bottom = []; + for (const c of interfaces) + bottom.push({id: c.name, text: `[${c.name}]: ${links.get(c.name)} "${c.name}"`}); + bottom.push({id: 'Array', text: '[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"'}); + bottom.push({id: 'Buffer', text: '[Buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer "Buffer"'}); + bottom.push({id: 'ChildProcess', text: '[ChildProcess]: https://nodejs.org/api/child_process.html "ChildProcess"'}); + bottom.push({id: 'Element', text: '[Element]: https://developer.mozilla.org/en-US/docs/Web/API/element "Element"'}); + bottom.push({id: 'Error', text: '[Error]: https://nodejs.org/api/errors.html#errors_class_error "Error"'}); + bottom.push({id: 'File', text: '[File]: #class-file "https://developer.mozilla.org/en-US/docs/Web/API/File"'}); + bottom.push({id: 'Map', text: '[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map "Map"'}); + bottom.push({id: 'Object', text: '[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"'}); + bottom.push({id: 'Promise', text: '[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"'}); + bottom.push({id: 'Serializable', text: '[Serializable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Description "Serializable"'}); + bottom.push({id: 'UIEvent.detail', text: '[UIEvent.detail]: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail "UIEvent.detail"'}); + bottom.push({id: 'UnixTime', text: '[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"'}); + bottom.push({id: 'boolean', text: '[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"'}); + bottom.push({id: 'function', text: '[function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function "Function"'}); + bottom.push({id: 'iterator', text: '[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols "Iterator"'}); + bottom.push({id: 'number', text: '[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type "Number"'}); + bottom.push({id: 'origin', text: '[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin "Origin"'}); + bottom.push({id: 'selector', text: '[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"'}); + bottom.push({id: 'stream.Readable', text: '[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"'}); + bottom.push({id: 'string', text: '[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"'}); + bottom.push({id: 'xpath', text: '[xpath]: https://developer.mozilla.org/en-US/docs/Web/XPath "xpath"'}); + bottom.sort((a, b) => a.id.localeCompare(b.id)); + + append(); + for (const {text} of bottom) + append(text); + + append(); + fs.writeFileSync(path.join(__dirname, '..', 'docs', 'gen.md'), md.join('\n')); +} + +run();