Skip to content

Commit

Permalink
Merge pull request #15 from lino-levan/feat-file-chooser
Browse files Browse the repository at this point in the history
feat: file chooser api + `page.waitForEvent`
  • Loading branch information
lino-levan authored Sep 12, 2023
2 parents 463fba9 + 5904bb9 commit bf315f1
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 124 deletions.
2 changes: 1 addition & 1 deletion bindings/_tools/generate/getProtocol.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { existsSync } from "https://deno.land/std@0.198.0/fs/exists.ts";
import { existsSync } from "https://deno.land/std@0.201.0/fs/exists.ts";
import { getBinary } from "../../../src/cache.ts";

export interface JSDocable {
Expand Down
138 changes: 51 additions & 87 deletions deno.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/pages/guides/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ await button!.click();

// Type in the search input
const input = await page.$("#search-input");
await input!.type("pyro", { delay: 100 });
await input!.type("pyro", { delay: 1000 });

// Wait for the search results to come back
await page.waitForNetworkIdle({ idleConnections: 0, idleTime: 1000 });
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ensureDirSync } from "https://deno.land/std@0.198.0/fs/ensure_dir.ts";
import { ensureDirSync } from "https://deno.land/std@0.201.0/fs/ensure_dir.ts";
import { launch } from "../../mod.ts";
import { type PageProps } from "https://deno.land/x/[email protected]/page.ts";
import { ensureFileSync } from "https://deno.land/std@0.198.0/fs/ensure_file.ts";
import { ensureFileSync } from "https://deno.land/std@0.201.0/fs/ensure_file.ts";

export const config = {
title: "Showcase",
Expand Down
2 changes: 1 addition & 1 deletion examples/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ await button!.click();

// Type in the search input
const input = await page.$("#search-input");
await input!.type("pyro", { delay: 100 });
await input!.type("pyro", { delay: 1000 });

// Wait for the search results to come back
await page.waitForNetworkIdle({ idleConnections: 0, idleTime: 1000 });
Expand Down
2 changes: 1 addition & 1 deletion src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { retry } from "https://deno.land/std@0.198.0/async/retry.ts";
import { retry } from "https://deno.land/std@0.201.0/async/retry.ts";

import { Celestial, PROTOCOL_VERSION } from "../bindings/celestial.ts";
import { getBinary } from "./cache.ts";
Expand Down
4 changes: 2 additions & 2 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ensureDirSync } from "https://deno.land/std@0.198.0/fs/ensure_dir.ts";
import { resolve } from "https://deno.land/std@0.198.0/path/mod.ts";
import { ensureDirSync } from "https://deno.land/std@0.201.0/fs/ensure_dir.ts";
import { resolve } from "https://deno.land/std@0.201.0/path/mod.ts";

export const SUPPORTED_VERSIONS = {
chrome: "118.0.5943.0",
Expand Down
27 changes: 9 additions & 18 deletions src/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,27 @@ export type DialogType = Page_DialogType;
*/
export class Dialog {
#celestial: Celestial;
#defaultValue: string;
#message: string;
#type: DialogType;

constructor(celestial: Celestial, config: Page_javascriptDialogOpening) {
this.#celestial = celestial;
this.#defaultValue = config.defaultPrompt ?? "";
this.#message = config.message;
this.#type = config.type;
}

/**
* The default value of the prompt, or an empty string if the dialog is not a prompt.
*/
get defaultValue() {
return this.#defaultValue;
}
readonly defaultValue: string;

/**
* The message displayed in the dialog.
*/
get message() {
return this.#message;
}
readonly message: string;

/**
* The type of the dialog.
*/
get type() {
return this.#type;
readonly type: DialogType;

constructor(celestial: Celestial, config: Page_javascriptDialogOpening) {
this.#celestial = celestial;
this.defaultValue = config.defaultPrompt ?? "";
this.message = config.message;
this.type = config.type;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/elementHandle.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { deadline } from "https://deno.land/std@0.198.0/async/deadline.ts";
import { deadline } from "https://deno.land/std@0.201.0/async/deadline.ts";

import { Celestial } from "../bindings/celestial.ts";
import { KeyboardTypeOptions } from "./keyboard.ts";
Expand Down
35 changes: 35 additions & 0 deletions src/fileChooser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { resolve } from "https://deno.land/[email protected]/path/resolve.ts";

import type {
Celestial,
Page_fileChooserOpened,
} from "../bindings/celestial.ts";

/**
* Dialog provides an api for managing a page's dialog events.
*/
export class FileChooser {
#celestial: Celestial;
#backendNodeId: number;

/**
* Whether this file chooser accepts multiple files.
*/
readonly multiple: boolean;

constructor(celestial: Celestial, config: Required<Page_fileChooserOpened>) {
this.multiple = config.mode === "selectMultiple";
this.#celestial = celestial;
this.#backendNodeId = config.backendNodeId;
}

/**
* Sets the value of the file input this chooser is associated with. If some of the filePaths are relative paths, then they are resolved relative to the current working directory. For empty array, clears the selected files.
*/
async setFiles(files: string[]) {
await this.#celestial.DOM.setFileInputFiles({
files: files.map((file) => resolve(file)),
backendNodeId: this.#backendNodeId,
});
}
}
39 changes: 37 additions & 2 deletions src/page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { deadline } from "https://deno.land/std@0.198.0/async/deadline.ts";
import { deadline } from "https://deno.land/std@0.201.0/async/deadline.ts";

import { Celestial, Network_Cookie } from "../bindings/celestial.ts";
import { Browser } from "./browser.ts";
Expand All @@ -8,6 +8,7 @@ import { Mouse } from "./mouse.ts";
import { Keyboard } from "./keyboard.ts";
import { Touchscreen } from "./touchscreen.ts";
import { Dialog } from "./dialog.ts";
import { FileChooser } from "./fileChooser.ts";

export type DeleteCookieOptions = Omit<
Parameters<Celestial["Network"]["deleteCookies"]>[0],
Expand All @@ -30,7 +31,7 @@ export type ScreenshotOptions = Parameters<
export type Cookie = Network_Cookie;

export type WaitForOptions = {
waitUntil?: "load" | "networkidle0" | "networkidle2";
waitUntil?: "none" | "load" | "networkidle0" | "networkidle2";
};

export type WaitForNetworkIdleOptions = {
Expand All @@ -50,6 +51,7 @@ export interface EvaluateOptions<T> {

export interface PageEventMap {
"dialog": DialogEvent;
"filechooser": FileChooserEvent;
}

export class DialogEvent extends CustomEvent<Dialog> {
Expand All @@ -58,6 +60,12 @@ export class DialogEvent extends CustomEvent<Dialog> {
}
}

export class FileChooserEvent extends CustomEvent<FileChooser> {
constructor(detail: FileChooser) {
super("filechooser", { detail });
}
}

/**
* Page provides methods to interact with a single tab in the browser
*/
Expand Down Expand Up @@ -96,6 +104,16 @@ export class Page extends EventTarget {
);
});

this.#celestial.addEventListener("Page.fileChooserOpened", (e) => {
const { frameId, mode, backendNodeId } = e.detail;
if (!backendNodeId) return;
this.dispatchEvent(
new FileChooserEvent(
new FileChooser(this.#celestial, { frameId, mode, backendNodeId }),
),
);
});

this.mouse = new Mouse(this.#celestial);
this.keyboard = new Keyboard(this.#celestial);
this.touchscreen = new Touchscreen(this.#celestial);
Expand Down Expand Up @@ -378,6 +396,19 @@ export class Page extends EventTarget {
return this.#url;
}

waitForEvent<T extends keyof PageEventMap>(
event: T,
): Promise<PageEventMap[T]["detail"]> {
return new Promise((resolve) => {
this.addEventListener(
event,
(e) =>
resolve(e.detail as unknown as Promise<PageEventMap[T]["detail"]>),
{ once: true },
);
});
}

/**
* Runs a function in the context of the page until it returns a truthy value.
*/
Expand Down Expand Up @@ -406,6 +437,10 @@ export class Page extends EventTarget {
async waitForNavigation(options?: WaitForOptions) {
options = options ?? { waitUntil: "networkidle2" };

if (options.waitUntil === "none") {
return;
}

if (options.waitUntil !== "load") {
await this.waitForNavigation({ waitUntil: "load" });
}
Expand Down
4 changes: 2 additions & 2 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { deadline } from "https://deno.land/std@0.198.0/async/deadline.ts";
import { retry } from "https://deno.land/std@0.198.0/async/retry.ts";
import { deadline } from "https://deno.land/std@0.201.0/async/deadline.ts";
import { retry } from "https://deno.land/std@0.201.0/async/retry.ts";

export const BASE_URL = "http://localhost:9222";

Expand Down
56 changes: 55 additions & 1 deletion tests/dialog_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from "https://deno.land/std@0.198.0/assert/assert_equals.ts";
import { assertEquals } from "https://deno.land/std@0.201.0/assert/assert_equals.ts";

import { launch } from "../mod.ts";

Expand Down Expand Up @@ -26,6 +26,32 @@ Deno.test("Accepting basic alert", async () => {
await browser.close();
});

Deno.test("Accepting basic alert with playwright-like syntax", async () => {
// Launch browser
const browser = await launch();

// Open the webpage
const page = await browser.newPage();

// listen for dialog events
const dialogPromise = page.waitForEvent("dialog");

// navigate to a page with an alert
// note: waitUntil: "none" is required because we're using a data url
await page.goto("data:text/html,<script>alert('hi');</script>", {
waitUntil: "none",
});

// handle dialog
const dialog = await dialogPromise;
assertEquals(dialog.message, "hi");
assertEquals(dialog.type, "alert");
await dialog.accept();

// Close browser
await browser.close();
});

Deno.test("Accepting prompt", async () => {
// Launch browser
const browser = await launch();
Expand Down Expand Up @@ -75,3 +101,31 @@ Deno.test("Declining comfirm", async () => {
// Close browser
await browser.close();
});

Deno.test("Input choose file", async () => {
// Launch browser
const browser = await launch();

// Open the webpage
const page = await browser.newPage();

// navigate to a page with an alert
await page.goto('data:text/html,<input type="file"></input>', {
waitUntil: "none",
});

// click input and handle file chooser
const element = await page.$("input");

const [fileChooser] = await Promise.all([
page.waitForEvent("filechooser"),
element?.click(),
]);

assertEquals(fileChooser.multiple, false);

await fileChooser.setFiles(["./tests/assets/file.txt"]);

// Close browser
await browser.close();
});
4 changes: 2 additions & 2 deletions tests/evaluate_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertEquals } from "https://deno.land/std@0.198.0/assert/assert_equals.ts";
import { assertSnapshot } from "https://deno.land/std@0.198.0/testing/snapshot.ts";
import { assertEquals } from "https://deno.land/std@0.201.0/assert/assert_equals.ts";
import { assertSnapshot } from "https://deno.land/std@0.201.0/testing/snapshot.ts";

import { launch } from "../mod.ts";

Expand Down
2 changes: 1 addition & 1 deletion tests/extract_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertSnapshot } from "https://deno.land/std@0.198.0/testing/snapshot.ts";
import { assertSnapshot } from "https://deno.land/std@0.201.0/testing/snapshot.ts";

import { launch } from "../mod.ts";

Expand Down
2 changes: 1 addition & 1 deletion tests/stealth_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert } from "https://deno.land/std@0.198.0/assert/assert.ts";
import { assert } from "https://deno.land/std@0.201.0/assert/assert.ts";

import { launch } from "../mod.ts";

Expand Down
2 changes: 1 addition & 1 deletion tests/wait_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertSnapshot } from "https://deno.land/std@0.198.0/testing/snapshot.ts";
import { assertSnapshot } from "https://deno.land/std@0.201.0/testing/snapshot.ts";

import { launch } from "../mod.ts";

Expand Down

0 comments on commit bf315f1

Please sign in to comment.