From 30bdac9d6975b2edd3cdebc600aa800b8b909bb4 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Mon, 18 Sep 2017 09:53:34 +0200 Subject: [PATCH] Add typings for puppeteer --- types/puppeteer/index.d.ts | 386 +++++++++++++++++++++++++++++ types/puppeteer/puppeteer-tests.ts | 155 ++++++++++++ types/puppeteer/tsconfig.json | 16 ++ types/puppeteer/tslint.json | 1 + 4 files changed, 558 insertions(+) create mode 100644 types/puppeteer/index.d.ts create mode 100644 types/puppeteer/puppeteer-tests.ts create mode 100644 types/puppeteer/tsconfig.json create mode 100644 types/puppeteer/tslint.json diff --git a/types/puppeteer/index.d.ts b/types/puppeteer/index.d.ts new file mode 100644 index 0000000000..e7092deab6 --- /dev/null +++ b/types/puppeteer/index.d.ts @@ -0,0 +1,386 @@ +// Type definitions for puppeteer 0.10 +// Project: https://github.com/GoogleChrome/puppeteer#readme +// Definitions by: Marvin Hagemeister +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 + +/// + +export interface Keyboard { + down(key: string, options?: { text: string }): Promise; + sendCharacter(char: string): Promise; + up(key: string): Promise; +} + +export interface MousePressOptions { + button: MouseButtons; + clickCount: number; +} + +export interface Mouse { + click(x: number, y: number, options: ClickOptions): Promise; + down(options?: MousePressOptions): Promise; + move(x: number, y: number, options?: { steps: number }): Promise; + up(options?: MousePressOptions): Promise; +} + +export interface Touchscreen { + tap(x: number, y: number): Promise; +} + +export interface Tracing { + start(options: { path: string; screenshots?: boolean }): Promise; + stop(): Promise; +} + +export interface Dialog { + accept(promptText?: string): Promise; + defaultValue(): string; + dismiss(): Promise; + message(): string; + type: "alert" | "beforeunload" | "confirm" | "prompt"; +} + +export type PageEvents = + | "console" + | "dialog" + | "error" + | "frameattached" + | "framedetached" + | "framenavigated" + | "load" + | "pageerror" + | "request" + | "requestfailed" + | "requestfinished" + | "response"; + +export interface AuthOptions { + username: string; + password: string; +} + +export type MouseButtons = "left" | "right" | "middle"; + +export interface ClickOptions { + /** defaults to left */ + button: MouseButtons; + /** defaults to 1 */ + clickCount: number; + /** + * Time to wait between mousedown and mouseup in milliseconds. + * Defaults to 0. + */ + delay: number; +} + +export interface Cookie { + name: string; + value: string; + domain: string; + path: string; + expires: number; + httpOnly: boolean; + secure: boolean; + sameSite: "Strict" | "Lax"; +} + +export interface Viewport { + width: number; + height: number; + deviceScaleFactor?: number; + isMobile?: boolean; + hasTouch?: boolean; + isLandscape?: boolean; +} + +export interface EmulateOptions { + viewport: Viewport; + userAgent: string; +} + +export type EvaluateFn = (elem?: ElementHandle) => Promise; + +export interface NavigationOptions { + timeout: number; + waitUntil: "load" | "networkidle" | "networkIdleTimeout"; + networkIdleInflight: number; + networkIdleTimeout: number; +} + +export type PDFFormat = + | "Letter" + | "Legal" + | "Tabload" + | "Ledger" + | "A0" + | "A1" + | "A2" + | "A3" + | "A4" + | "A5"; + +export interface PDFOptions { + /** If no path is provided, the PDF won't be saved to the disk. */ + path: string; + scale: number; + displayHeaderFooter: boolean; + printBackground: false; + landscape: false; + /** + * Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty + * string, which means print all pages. + */ + pageRanges: string; + format: PDFFormat; + width: string; + height: string; + margin: { + top: string; + right: string; + bottom: string; + left: string; + }; +} + +export interface ScreenshotOptions { + path: string; + type?: "jpeg" | "png"; + /** The quality of the image, between 0-100. Not applicable to png images. */ + quality?: number; + fullPage?: boolean; + clip?: { + x: number; + y: number; + width: number; + height: number; + }; + omitBackground?: boolean; +} + +export interface PageFnOptions { + polling: "raf" | "mutation" | number; + timeout: number; +} + +export interface ElementHandle { + click(options?: ClickOptions): Promise; + dispose(): Promise; + hover(): Promise; + tap(): Promise; + uploadFile(...filePaths: string[]): Promise; +} + +export type Headers = Record; +export type HttpMethod = + | "GET" + | "POST" + | "PATCH" + | "PUT" + | "DELETE" + | "OPTIONS"; + +export type ResourceType = + | "Document" + | "Stylesheet" + | "Image" + | "Media" + | "Font" + | "Script" + | "TextTrack" + | "XHR" + | "Fetch" + | "EventSource" + | "WebSocket" + | "Manifest" + | "Other"; + +export interface Overrides { + url?: string; + method?: HttpMethod; + postData?: string; + headers?: Headers; +} + +export interface Request { + abort(): Promise; + continue(overrides?: Overrides): Promise; + headers: Headers; + method: HttpMethod; + postData: string | undefined; + resourceType: ResourceType; + response(): Response | null; + url: string; +} + +export interface Response { + buffer(): Promise; + headers: Headers; + json(): Promise; + ok: boolean; + request(): Request; + status: number; + text(): Promise; + url: string; +} + +export interface FrameBase { + $(selector: string): Promise; + $$(selector: string): Promise; + $eval( + selector: string, + fn: (...args: Array) => void + ): Promise; + addScriptTag(url: string): Promise; + injectFile(filePath: string): Promise; + evaluate( + fn: T | EvaluateFn, + ...args: Array + ): Promise; + title(): Promise; + url(): Promise; + waitFor( + // fn can be an abritary function + // tslint:disable-next-line ban-types + selectorOrFunctionOrTimeout: string | number | Function, + options?: object + ): Promise; + waitForFunction( + // fn can be an abritary function + // tslint:disable-next-line ban-types + fn: string | Function, + options?: PageFnOptions, + ...args: any[] + ): Promise; + waitForNavigation(options?: NavigationOptions): Promise; + waitForSelector( + selector: string, + options?: { visible: boolean; timeout: number } + ): Promise; +} + +export interface Frame extends FrameBase { + childFrames(): Frame[]; + isDetached(): boolean; + name(): string; + parentFrame(): Frame | undefined; +} + +export interface EventObj { + console: string; + dialog: Dialog; + error: Error; + frameattached: Frame; + framedetached: Frame; + framenavigated: Frame; + load: undefined; + pageerror: string; + request: Request; + requestfailed: Request; + requestfinished: Request; + response: Response; +} + +export interface Page extends FrameBase { + on(event: "console", handler: (...args: any[]) => void): void; + on( + event: K, + handler: (e: EventObj[K], ...args: any[]) => void + ): void; + authenticate(credentials: AuthOptions | null): Promise; + click(selector: string, options?: ClickOptions): Promise; + close(): Promise; + content(): Promise; + cookies(...urls: string[]): Cookie; + emulate(options: Partial): Promise; + emulateMedia(mediaType: string | null): Promise; + evaluateOnNewDocument( + fn: EvaluateFn, + ...args: object[] + ): Promise; + + // Argument `fn` can be an arbitrary function + exposeFunction(name: string, fn: any): Promise; + + focus(selector: string): Promise; + frames(): Frame[]; + goBack(options?: Partial): Promise; + goForward(options?: Partial): Promise; + goto(url: string, options?: Partial): Promise; + hover(selector: string): Promise; + keyboard: Keyboard; + mainFrame(): Frame; + mouse: Mouse; + pdf(options?: Partial): Promise; + plainText(): Promise; + press(key: string, options?: { text: string; delay: number }): Promise; + reload(options?: NavigationOptions): Promise; + screenshot(options?: ScreenshotOptions): Promise; + setContent(html: string): Promise; + setCookie(...cookies: Cookie[]): Promise; + setExtraHTTPHeaders(headers: Headers): Promise; + setJavaScriptEnabled(enabled: boolean): Promise; + setRequestInterceptionEnabled(value: boolean): Promise; + setUserAgent(userAgent: string): Promise; + setViewport(viewport: Viewport): Promise; + tap(selector: string): Promise; + touchscreen: Touchscreen; + tracing: Tracing; + type(text: string, options?: { delay: number }): Promise; + viewport(): Viewport; +} + +export interface Browser { + close(): Promise; + newPage(): Promise; + version(): Promise; + wsEndpoint(): string; +} + +export interface LaunchOptions { + /** Whether to ignore HTTPS errors during navigation. Defaults to false. */ + ignoreHTTPSErrors: boolean; + /** Whether to run Chromium in headless mode. Defaults to true. */ + headless: boolean; + /** + * Path to a Chromium executable to run instead of bundled Chromium. If + * executablePath is a relative path, then it is resolved relative to current + * working directory. + */ + executablePath: string; + /** + * Slows down Puppeteer operations by the specified amount of milliseconds. + * Useful so that you can see what is going on. + */ + slowMo: number; + /** + * Additional arguments to pass to the Chromium instance. List of Chromium + * flags can be found here. + */ + args: string[]; + /** Close chrome process on Ctrl-C. Defaults to true. */ + handleSIGINT: boolean; + /** + * Maximum time in milliseconds to wait for the Chrome instance to start. + * Defaults to 30000 (30 seconds). Pass 0 to disable timeout. + */ + timeout: number; + /** + * Whether to pipe browser process stdout and stderr into process.stdout and + * process.stderr. Defaults to false. + */ + dumpio: boolean; + /** Path to a User Data Directory. */ + userDataDir: string; +} + +export interface ConnectOptions { + browserWSEndpoint?: string; + ignoreHTTPSErrors?: boolean; +} + +/** Attaches Puppeteer to an existing Chromium instance */ +export function connect(options?: ConnectOptions): Promise; +/** Path where Puppeteer expects to find bundled Chromium */ +export function executablePath(): string; +export function launch(options?: LaunchOptions): Promise; diff --git a/types/puppeteer/puppeteer-tests.ts b/types/puppeteer/puppeteer-tests.ts new file mode 100644 index 0000000000..d80f4aa2f2 --- /dev/null +++ b/types/puppeteer/puppeteer-tests.ts @@ -0,0 +1,155 @@ +import * as puppeteer from "puppeteer"; + +// 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: "networkidle" }); + 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) => { + 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.setRequestInterceptionEnabled(true); + page.on("request", interceptedRequest => { + if ( + interceptedRequest.url.endsWith(".png") || + interceptedRequest.url.endsWith(".jpg") + ) + interceptedRequest.abort(); + else interceptedRequest.continue(); + }); + + page.type("Hello"); // Types instantly + page.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") + .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.type("Hello World!"); + page.press("ArrowLeft"); + + page.keyboard.down("Shift"); + // tslint:disable-next-line prefer-for-of + for (let i = 0; i < " World".length; i++) { + page.press("ArrowLeft"); + } + page.keyboard.up("Shift"); + page.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(); +}); diff --git a/types/puppeteer/tsconfig.json b/types/puppeteer/tsconfig.json new file mode 100644 index 0000000000..287537e136 --- /dev/null +++ b/types/puppeteer/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es6", "dom", "es2017"], + "target": "es2017", + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": ["../"], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": ["index.d.ts", "puppeteer-tests.ts"] +} diff --git a/types/puppeteer/tslint.json b/types/puppeteer/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/puppeteer/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }