diff --git a/.gitignore b/.gitignore index a1ac524e..d720887a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,5 @@ package-lock.json yarn.lock /node6 /utils/browser/puppeteer-web.js -/index.d.ts /lib test/coverage.json diff --git a/.travis.yml b/.travis.yml index bb54d7af..e476b333 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,7 +67,6 @@ jobs: - npm run test-install - npm run lint - npm run test-doclint - - npm run test-types # Runs unit tests on Linux + Firefox - node_js: "10.19.0" diff --git a/package.json b/package.json index 2cfec48c..2049bc03 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "doc": "node utils/doclint/cli.js", "tsc": "tsc --version && tsc -p . && cp src/protocol.d.ts lib/", "apply-next-version": "node utils/apply_next_version.js", - "test-types": "node utils/doclint/generate_types && tsc --version && tsc -p utils/doclint/generate_types/test/", "update-protocol-d-ts": "node utils/protocol-types-generator update", "compare-protocol-d-ts": "node utils/protocol-types-generator compare", "test-install": "scripts/test-install.sh" diff --git a/utils/doclint/generate_types/index.js b/utils/doclint/generate_types/index.js deleted file mode 100644 index acb6f130..00000000 --- a/utils/doclint/generate_types/index.js +++ /dev/null @@ -1,307 +0,0 @@ -const path = require('path'); -const Source = require('../Source'); -const puppeteer = require('../../..'); -const PROJECT_DIR = path.join(__dirname, '..', '..', '..'); -const fs = require('fs'); -const objectDefinitions = []; -(async function () { - const browser = await puppeteer.launch(); - const page = (await browser.pages())[0]; - const api = await Source.readFile(path.join(PROJECT_DIR, 'docs', 'api.md')); - const { documentation } = await require('../check_public_api/MDBuilder')( - page, - [api] - ); - await browser.close(); - const classes = documentation.classesArray.slice(1); - const root = documentation.classesArray[0]; - const output = `// This file is generated by ${__filename.substring( - path.join(__dirname, '..', '..').length - )} -import { ChildProcess } from 'child_process'; -import { EventEmitter } from 'events'; -/** - * Can be converted to JSON - */ -interface Serializable {} -interface ConnectionTransport {} - -${root.methodsArray - .map( - (method) => ` -${memberJSDOC(method, '')}export function ${method.name}${argsFromMember( - method - )} : ${typeToString(method.type, method.name)}; -` - ) - .join('')} -${root.propertiesArray - .map( - (property) => ` -${memberJSDOC(property, '')}export const ${property.name}${argsFromMember( - property - )} : ${typeToString(property.type, property.name)}; -` - ) - .join('')} -${classes.map((classDesc) => classToString(classDesc)).join('\n')} -${objectDefinitionsToString()} -`; - fs.writeFileSync(path.join(PROJECT_DIR, 'index.d.ts'), output, 'utf8'); -})(); - -function objectDefinitionsToString() { - let definition; - const parts = []; - while ((definition = objectDefinitions.pop())) { - const { name, properties } = definition; - parts.push(`interface ${name} {`); - parts.push( - properties - .map( - (member) => - ` ${memberJSDOC(member, ' ')}${nameForProperty( - member - )}${argsFromMember(member, name)}: ${typeToString( - member.type, - name, - member.name - )};` - ) - .join('\n\n') - ); - parts.push('}\n'); - } - return parts.join('\n'); -} - -function nameForProperty(member) { - return member.required || member.name.startsWith('...') - ? member.name - : member.name + '?'; -} - -/** - * @param {import('./check_public_api/Documentation').Class} classDesc - */ -function classToString(classDesc) { - const parts = []; - if (classDesc.comment) { - parts.push(`/** - * ${classDesc.comment.split('\n').join('\n * ')} - */`); - } - parts.push( - `export interface ${classDesc.name} ${ - classDesc.extends ? `extends ${classDesc.extends} ` : '' - }{` - ); - for (const method of ['on', 'once', 'addListener']) { - for (const [eventName, value] of classDesc.events) { - if (value.comment) { - parts.push(' /**'); - parts.push(...value.comment.split('\n').map((line) => ' * ' + line)); - parts.push(' */'); - } - parts.push( - ` ${method}(event: '${eventName}', listener: (arg0 : ${typeToString( - value && value.type, - classDesc.name, - eventName, - 'payload' - )}) => void): this;\n` - ); - } - } - const members = classDesc.membersArray.filter( - (member) => member.kind !== 'event' - ); - parts.push( - members - .map( - (member) => - ` ${memberJSDOC(member, ' ')}${member.name}${argsFromMember( - member, - classDesc.name - )}: ${typeToString(member.type, classDesc.name, member.name)};` - ) - .join('\n\n') - ); - parts.push('}\n'); - return parts.join('\n'); -} - -/** - * @param {import('./check_public_api/Documentation').Type} type - */ -function typeToString(type, ...namespace) { - /* A hack to special case TypeScript types that this type generator - * does not understand / parse correctly. - * Because the goal is to generate these types from the TypeScript - * source, this parser won't be around for much longer so investing in - * improving the parser's ability is not worth it. - */ - const specialCaseTypeMaps = new Map([ - [ - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - '"load"|"domcontentloaded"|"networkidle0"|"networkidle2"|Array', - ], - ]); - - if (!type) return 'void'; - - if (specialCaseTypeMaps.has(type.name)) - return specialCaseTypeMaps.get(type.name); - - let typeString = stringifyType(parseType(type.name)); - for (let i = 0; i < type.properties.length; i++) - typeString = typeString.replace('arg' + i, type.properties[i].name); - if (type.properties.length && typeString.indexOf('Object') !== -1) { - const name = namespace - .map((n) => n[0].toUpperCase() + n.substring(1)) - .join(''); - typeString = typeString.replace('Object', name); - objectDefinitions.push({ name, properties: type.properties }); - } - return typeString; -} - -/** - * @param {string} type - */ -function parseType(type) { - type = type.trim(); - if (type.startsWith('?')) { - const parsed = parseType(type.substring(1)); - parsed.nullable = true; - return parsed; - } - if (type.startsWith('...')) - return parseType('Array<' + type.substring(3) + '>'); - let name = type; - let next = null; - let template = null; - let args = null; - let retType = null; - let firstTypeLength = type.length; - for (let i = 0; i < type.length; i++) { - if (type[i] === '<') { - name = type.substring(0, i); - const matching = matchingBracket(type.substring(i), '<', '>'); - template = parseType(type.substring(i + 1, i + matching - 1)); - firstTypeLength = i + matching; - break; - } - if (type[i] === '(') { - name = type.substring(0, i); - const matching = matchingBracket(type.substring(i), '(', ')'); - args = parseType(type.substring(i + 1, i + matching - 1)); - i = i + matching; - if (type[i] === ':') { - retType = parseType(type.substring(i + 1)); - next = retType.next; - retType.next = null; - break; - } - } - if (type[i] === '|' || type[i] === ',') { - name = type.substring(0, i); - firstTypeLength = i; - break; - } - } - let pipe = null; - if (type[firstTypeLength] === '|') - pipe = parseType(type.substring(firstTypeLength + 1)); - else if (type[firstTypeLength] === ',') - next = parseType(type.substring(firstTypeLength + 1)); - if (name === 'Promise' && !template) template = parseType('void'); - return { - name, - args, - retType, - template, - pipe, - next, - }; -} - -function stringifyType(parsedType) { - if (!parsedType) return 'void'; - let out = parsedType.name; - if (parsedType.args) { - let args = parsedType.args; - const stringArgs = []; - while (args) { - const arg = args; - args = args.next; - arg.next = null; - stringArgs.push(stringifyType(arg)); - } - out = `(${stringArgs - .map((type, index) => `arg${index} : ${type}`) - .join(', ')}, ...args: any[]) => ${stringifyType(parsedType.retType)}`; - } else if (parsedType.name === 'function') { - out = 'Function'; - } - if (parsedType.nullable) out = 'null|' + out; - if (parsedType.template) - out += '<' + stringifyType(parsedType.template) + '>'; - if (parsedType.pipe) out += '|' + stringifyType(parsedType.pipe); - if (parsedType.next) out += ', ' + stringifyType(parsedType.next); - return out.trim(); -} - -function matchingBracket(str, open, close) { - let count = 1; - let i = 1; - for (; i < str.length && count; i++) { - if (str[i] === open) count++; - else if (str[i] === close) count--; - } - return i; -} - -/** - * @param {import('./check_public_api/Documentation').Member} member - */ -function argsFromMember(member, ...namespace) { - if (member.kind === 'property') return ''; - return ( - '(' + - member.argsArray - .map( - (arg) => - `${nameForProperty(arg)}: ${typeToString( - arg.type, - ...namespace, - member.name, - 'options' - )}` - ) - .join(', ') + - ')' - ); -} -/** - * @param {import('./check_public_api/Documentation').Member} member - */ -function memberJSDOC(member, indent) { - const lines = []; - if (member.comment) lines.push(...member.comment.split('\n')); - lines.push( - ...member.argsArray.map( - (arg) => - `@param ${arg.name.replace(/\./g, '')} ${arg.comment.replace( - '\n', - ' ' - )}` - ) - ); - if (member.returnComment) lines.push(`@returns ${member.returnComment}`); - if (!lines.length) return ''; - return `/** -${indent} * ${lines.join('\n' + indent + ' * ')} -${indent} */ -${indent}`; -} diff --git a/utils/doclint/generate_types/test/test.ts b/utils/doclint/generate_types/test/test.ts deleted file mode 100644 index eea0f340..00000000 --- a/utils/doclint/generate_types/test/test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import * as puppeteer from "../../../../index"; - -// Examples taken from README -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto("https://example.com"); - await page.screenshot({ path: "example.png" }); - - browser.close(); -})(); - -(async () => { - const browser = await puppeteer.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 puppeteer.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 -puppeteer.launch().then(async browser => { - const page = await browser.newPage(); - page.on("console", (...args: any[]) => { - for (let i = 0; i < args.length; ++i) console.log(`${i}: ${args[i]}`); - }); - page.evaluate(() => console.log(5, "hello", { foo: "bar" })); - - const result = await page.evaluate(() => { - return Promise.resolve(8 * 7); - }); - console.log(await page.evaluate("1 + 2")); - - const bodyHandle = await page.$("body"); - - // Typings for this are really difficult since they depend on internal state - // of the page class. - const html = await page.evaluate( - (body: HTMLElement) => body.innerHTML, - bodyHandle - ); -}); - -import * as crypto from "crypto"; -import * as fs from "fs"; - -puppeteer.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("screen"); - await page.pdf({ path: "page.pdf" }); - - await page.setRequestInterception(true); - page.on("request", interceptedRequest => { - if ( - interceptedRequest.url().endsWith(".png") || - interceptedRequest.url().endsWith(".jpg") - ) - interceptedRequest.abort(); - else interceptedRequest.continue(); - }); - - page.keyboard.type("Hello"); // Types instantly - page.keyboard.type("World", { delay: 100 }); // Types slower, like a user - - const watchDog = page.waitForFunction("window.innerWidth < 100"); - page.setViewport({ width: 50, height: 50 }); - await watchDog; - - let currentURL: string; - page - .waitForSelector("img", { visible: true }) - .then(() => console.log("First URL with image: " + currentURL)); - for (currentURL of [ - "https://example.com", - "https://google.com", - "https://bbc.com" - ]) { - await page.goto(currentURL); - } - - page.keyboard.type("Hello World!"); - page.keyboard.press("ArrowLeft"); - - page.keyboard.down("Shift"); - // tslint:disable-next-line prefer-for-of - for (let i = 0; i < " World".length; i++) { - page.keyboard.press("ArrowLeft"); - } - page.keyboard.up("Shift"); - page.keyboard.press("Backspace"); - page.keyboard.sendCharacter("嗨"); - - await page.tracing.start({ path: "trace.json" }); - await page.goto("https://www.google.com"); - await page.tracing.stop(); - - page.on("dialog", async dialog => { - console.log(dialog.message()); - await dialog.dismiss(); - browser.close(); - }); - - const inputElement = (await page.$("input[type=submit]"))!; - await inputElement.click(); -}); - -// Example with launch options -(async () => { - const browser = await puppeteer.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 puppeteer.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: puppeteer.ConsoleMessage, ...args: any[]) => { - console.log(event.text, event.type); - for (let i = 0; i < args.length; ++i) console.log(`${i}: ${args[i]}`); - }); - - await button.focus(); - await button.press("Enter"); - await button.screenshot({ - type: "jpeg", - omitBackground: true, - clip: { - x: 0, - y: 0, - width: 200, - height: 100 - } - }); - console.log(button.toString()); - input.type("Hello World", { delay: 10 }); - - const buttonText = await (await button.getProperty('textContent')).jsonValue(); - - await page.deleteCookie(...await page.cookies()); - - const metrics = await page.metrics(); - console.log(metrics.Documents, metrics.Frames, metrics.JSEventListeners); - - const navResponse = await page.waitForNavigation({ - timeout: 1000 - }); - console.log(navResponse.ok, navResponse.status, navResponse.url, navResponse.headers); - - // evaluate example - const bodyHandle = (await page.$('body'))!; - const html = await page.evaluate((body : HTMLBodyElement) => body.innerHTML, bodyHandle); - await bodyHandle.dispose(); - - // getProperties example - const handle = await page.evaluateHandle(() => ({ window, document })); - const properties = await handle.getProperties(); - const windowHandle = properties.get('window'); - const documentHandle = properties.get('document'); - await handle.dispose(); - - // queryObjects example - // Create a Map object - await page.evaluate(() => (window as any).map = new Map()); - // Get a handle to the Map object prototype - const mapPrototype = await page.evaluateHandle(() => Map.prototype); - // Query all map instances into an array - const mapInstances = await page.queryObjects(mapPrototype); - // Count amount of map objects in heap - const count = await page.evaluate((maps: Map[]) => maps.length, mapInstances); - await mapInstances.dispose(); - await mapPrototype.dispose(); - - // evaluateHandle example - const aHandle = await page.evaluateHandle(() => document.body); - const resultHandle = await page.evaluateHandle((body: Element) => body.innerHTML, aHandle); - console.log(await resultHandle.jsonValue()); - await resultHandle.dispose(); - - browser.close(); -})(); - -// test $eval and $$eval -(async () => { - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - await page.goto("https://example.com"); - await page.$eval('#someElement', (element, text: string) => { - return element.innerHTML = text; - }, 'hey'); - - let elementText = await page.$$eval('.someClassName', (elements) => { - console.log(elements.length); - console.log(elements.map(x => x)[0].textContent); - return elements[3].innerHTML; - }); - - browser.close(); -})(); diff --git a/utils/doclint/generate_types/test/tsconfig.json b/utils/doclint/generate_types/test/tsconfig.json deleted file mode 100644 index a301a9e7..00000000 --- a/utils/doclint/generate_types/test/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": true, - "target": "es2015", - "noEmit": true - }, - "include": [ - "test.ts", - "../../../../index.d.ts" - ] -} \ No newline at end of file