Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: file chooser api + page.waitForEvent #15

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading