diff --git a/scripts/test-ts-definition-files.ts b/scripts/test-ts-definition-files.ts index e65fbfa079c..7e4e8373721 100644 --- a/scripts/test-ts-definition-files.ts +++ b/scripts/test-ts-definition-files.ts @@ -10,6 +10,10 @@ const EXPECTED_ERRORS = new Map([ "bad.ts(6,35): error TS2551: Property 'launh' does not exist on type", "bad.ts(8,29): error TS2551: Property 'devics' does not exist on type", 'bad.ts(12,39): error TS2554: Expected 0 arguments, but got 1.', + "bad.ts(20,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn", + "bad.ts(20,34): error TS2339: Property 'innerText' does not exist on type 'number'.", + "bad.ts(24,45): error TS2344: Type '(x: number) => string' does not satisfy the constraint 'EvaluateFn'.", + "bad.ts(27,34): error TS2339: Property 'innerText' does not exist on type 'number'.", ], ], [ @@ -35,6 +39,8 @@ const EXPECTED_ERRORS = new Map([ "bad.js(7,29): error TS2551: Property 'devics' does not exist on type", 'bad.js(11,39): error TS2554: Expected 0 arguments, but got 1.', "bad.js(15,9): error TS2322: Type 'ElementHandle | null' is not assignable to type 'ElementHandle'", + "bad.js(22,5): error TS2345: Argument of type '(divElem: number) => any' is not assignable to parameter of type 'EvaluateFn'.", + "bad.js(22,26): error TS2339: Property 'innerText' does not exist on type 'number'.", ], ], [ @@ -67,6 +73,13 @@ const EXPECTED_ERRORS = new Map([ ]); const PROJECT_FOLDERS = [...EXPECTED_ERRORS.keys()]; +if (!process.env.CI) { + console.log(`IMPORTANT: this script assumes you have compiled Puppeteer +and its types file before running. Make sure you have run: +=> npm run tsc && npm run generate-d-ts +before executing this script locally.`); +} + function packPuppeteer() { console.log('Packing Puppeteer'); const result = spawnSync('npm', ['pack'], { diff --git a/src/common/JSHandle.ts b/src/common/JSHandle.ts index fd4875071b5..b731f52973a 100644 --- a/src/common/JSHandle.ts +++ b/src/common/JSHandle.ts @@ -105,7 +105,7 @@ export function createJSHandle( * * @public */ -export class JSHandle { +export class JSHandle { /** * @internal */ @@ -154,7 +154,7 @@ export class JSHandle { * ``` */ - async evaluate( + async evaluate>( pageFunction: T | string, ...args: SerializableOrJSHandle[] ): Promise>> { @@ -193,7 +193,7 @@ export class JSHandle { */ async getProperty(propertyName: string): Promise { const objectHandle = await this.evaluateHandle( - (object: HTMLElement, propertyName: string) => { + (object: Element, propertyName: string) => { const result = { __proto__: null }; result[propertyName] = object[propertyName]; return result; @@ -328,7 +328,7 @@ export class JSHandle { */ export class ElementHandle< ElementType extends Element = Element -> extends JSHandle { +> extends JSHandle { private _page: Page; private _frameManager: FrameManager; @@ -521,24 +521,25 @@ export class ElementHandle< '"' ); - return this.evaluate< - (element: HTMLSelectElement, values: string[]) => string[] - >((element, values) => { - if (element.nodeName.toLowerCase() !== 'select') - throw new Error('Element is not a element.'); - const options = Array.from(element.options); - element.value = undefined; - for (const option of options) { - option.selected = values.includes(option.value); - if (option.selected && !element.multiple) break; - } - element.dispatchEvent(new Event('input', { bubbles: true })); - element.dispatchEvent(new Event('change', { bubbles: true })); - return options - .filter((option) => option.selected) - .map((option) => option.value); - }, values); + const options = Array.from(element.options); + element.value = undefined; + for (const option of options) { + option.selected = values.includes(option.value); + if (option.selected && !element.multiple) break; + } + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); + return options + .filter((option) => option.selected) + .map((option) => option.value); + }, + values + ); } /** @@ -549,9 +550,14 @@ export class ElementHandle< * relative to the {@link https://nodejs.org/api/process.html#process_process_cwd | current working directory} */ async uploadFile(...filePaths: string[]): Promise { - const isMultiple = await this.evaluate< - (element: HTMLInputElement) => boolean - >((element) => element.multiple); + const isMultiple = await this.evaluate<(element: Element) => boolean>( + (element) => { + if (!(element instanceof HTMLInputElement)) { + throw new Error('uploadFile can only be called on an input element.'); + } + return element.multiple; + } + ); assert( filePaths.length <= 1 || isMultiple, 'Multiple file uploads only work with ' @@ -588,7 +594,7 @@ export class ElementHandle< // not actually update the files in that case, so the solution is to eval the element // value to a new FileList directly. if (files.length === 0) { - await this.evaluate<(element: HTMLInputElement) => void>((element) => { + await (this as ElementHandle).evaluate((element) => { element.files = new DataTransfer().files; // Dispatch events for this case because it should behave akin to a user action. @@ -619,7 +625,7 @@ export class ElementHandle< * Calls {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus | focus} on the element. */ async focus(): Promise { - await this.evaluate<(element: HTMLElement) => void>((element) => + await (this as ElementHandle).evaluate((element) => element.focus() ); } diff --git a/test-ts-types/js-cjs-import-esm-output/package.json b/test-ts-types/js-cjs-import-esm-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/js-cjs-import-esm-output/package.json +++ b/test-ts-types/js-cjs-import-esm-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/test-ts-types/js-esm-import-cjs-output/bad.js b/test-ts-types/js-esm-import-cjs-output/bad.js index ba7b56133d4..cda9916d830 100644 --- a/test-ts-types/js-esm-import-cjs-output/bad.js +++ b/test-ts-types/js-esm-import-cjs-output/bad.js @@ -14,5 +14,12 @@ async function run() { */ const div = await page.$('div'); console.log('got a div!', div); + const contentsOfDiv = await div.evaluate( + /** + * @param {number} divElem + * @returns number + */ + (divElem) => divElem.innerText + ); } run(); diff --git a/test-ts-types/js-esm-import-cjs-output/package.json b/test-ts-types/js-esm-import-cjs-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/js-esm-import-cjs-output/package.json +++ b/test-ts-types/js-esm-import-cjs-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/test-ts-types/js-esm-import-esm-output/package.json b/test-ts-types/js-esm-import-esm-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/js-esm-import-esm-output/package.json +++ b/test-ts-types/js-esm-import-esm-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/test-ts-types/ts-cjs-import-cjs-output/package.json b/test-ts-types/ts-cjs-import-cjs-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/ts-cjs-import-cjs-output/package.json +++ b/test-ts-types/ts-cjs-import-cjs-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/test-ts-types/ts-esm-import-cjs-output/package.json b/test-ts-types/ts-esm-import-cjs-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/ts-esm-import-cjs-output/package.json +++ b/test-ts-types/ts-esm-import-cjs-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/test-ts-types/ts-esm-import-esm-output/bad.ts b/test-ts-types/ts-esm-import-esm-output/bad.ts index 4aeb9707096..d08e6f450ad 100644 --- a/test-ts-types/ts-esm-import-esm-output/bad.ts +++ b/test-ts-types/ts-esm-import-esm-output/bad.ts @@ -10,9 +10,21 @@ async function run() { const browser2 = await puppeteer.launch(); // 'foo' is invalid argument const page = await browser2.newPage('foo'); - const div = (await page.$('div')) as puppeteer.ElementHandle< - HTMLAnchorElement - >; + const div = (await page.$( + 'div' + )) as puppeteer.ElementHandle; console.log('got a div!', div); + const contentsOfDiv = await div.evaluate( + // Bad: the type system will know here that divElem is an HTMLAnchorElement + // and won't let me tell it it's a number + (divElem: number) => divElem.innerText + ); + // Bad: the type system will know here that divElem is an HTMLAnchorElement + // and won't let me tell it it's a number via the generic + const contentsOfDiv2 = await div.evaluate<(x: number) => string>( + // Bad: now I've forced it to be a number (which is an error also) + // I can't call `innerText` on it. + (divElem: number) => divElem.innerText + ); } run(); diff --git a/test-ts-types/ts-esm-import-esm-output/good.ts b/test-ts-types/ts-esm-import-esm-output/good.ts index ed7764140d8..af19f0efb63 100644 --- a/test-ts-types/ts-esm-import-esm-output/good.ts +++ b/test-ts-types/ts-esm-import-esm-output/good.ts @@ -9,5 +9,7 @@ async function run() { const page = await browser.newPage(); const div = (await page.$('div')) as ElementHandle; console.log('got a div!', div); + + const contentsOfDiv = await div.evaluate((divElem) => divElem.innerText); } run(); diff --git a/test-ts-types/ts-esm-import-esm-output/package.json b/test-ts-types/ts-esm-import-esm-output/package.json index 063237ea8a3..d2aa4f9434a 100644 --- a/test-ts-types/ts-esm-import-esm-output/package.json +++ b/test-ts-types/ts-esm-import-esm-output/package.json @@ -7,7 +7,7 @@ "compile": "../../node_modules/.bin/tsc" }, "devDependencies": { - "typescript": "^4.1.3" + "typescript": "4.2.4" }, "dependencies": { "puppeteer": "file:../../puppeteer.tgz" diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index 989b78bcee9..68e60bc96a8 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -28,10 +28,12 @@ const EXCLUDE_PROPERTIES = new Set([ 'Page.create', 'JSHandle.toString', 'TimeoutError.name', - /* This isn't an actual property, but a TypeScript generic. + /* These are not actual properties, but a TypeScript generic. * DocLint incorrectly parses it as a property. */ 'ElementHandle.ElementType', + 'ElementHandle.HandleObjectType', + 'JSHandle.HandleObjectType', ]); /** @@ -885,6 +887,20 @@ function compareDocumentations(actual, expected) { expectedName: 'unknown', }, ], + [ + 'Method Page.queryObjects() prototypeHandle', + { + actualName: 'JSHandle', + expectedName: 'JSHandle', + }, + ], + [ + 'Method ExecutionContext.queryObjects() prototypeHandle', + { + actualName: 'JSHandle', + expectedName: 'JSHandle', + }, + ], ]); const expectedForSource = expectedNamingMismatches.get(source);