Skip to content
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to this project will be documented in this file.

## Unreleased

### Breaking Changes

- **`Page.reload` is a method**: `Page.reload` changed from an `Effect` property to a method that takes Playwright's reload options, like `Page.goBack()` and `Page.goForward()`. Replace `yield* page.reload` with `yield* page.reload()`.

### Features

- **Navigation responses**: `Page.goto()`, `Frame.goto()` and `Page.reload()` now return the main resource response as `Option<Playwright.Response>`, like `Page.goBack()` and `Page.goForward()`, instead of `void`. Playwright does not fail on an error status such as 404, so the response is the way to tell whether the page loaded. Code that ignores the result is unaffected, and code that needs `Effect<void, PlaywrightError>` can pipe `Effect.asVoid`.

## 0.8.0

### Breaking Changes
Expand Down
22 changes: 22 additions & 0 deletions src/frame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ layer(PlaywrightSpawner.layer(chromium))("Frame", (it) => {
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("goto should return the main resource response", () =>
Effect.gen(function* () {
const browser = yield* Browser;
const page = yield* browser.newPage();

yield* page.use((playwrightPage) =>
playwrightPage.route("http://frame.test/", (route) =>
route.fulfill({
status: 404,
body: "<!doctype html><title>Not found</title>",
}),
),
);
const response = yield* page.mainFrame().goto("http://frame.test/");

assert.deepStrictEqual(
Option.map(response, (response) => response.status()),
Option.some(404),
);
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("waitForLoadState should resolve on frame", () =>
Effect.gen(function* () {
const browser = yield* Browser;
Expand Down
18 changes: 14 additions & 4 deletions src/frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* @since 0.1.2
*/

import { Array, Context, type Effect, Option } from "effect";
import { Array, Context, Effect, Option } from "effect";
import type { Frame as CoreFrame, ElementHandle } from "playwright-core";
import { makeResponse, type Response } from "./common";
import type { PlaywrightError } from "./errors";
import { type FrameLocator, makeFrameLocator } from "./frame-locator";
import { type Locator, makeLocator } from "./locator";
Expand All @@ -19,15 +20,20 @@ import { useHelper } from "./utils";
*/
export interface Frame {
/**
* Navigates the frame to the given URL.
* Navigates the frame to the given URL and returns the main resource
* response, or `None` when there is none, such as for `about:blank` or a
* change of the URL hash.
*
* It succeeds whatever HTTP status the server answers with, including 404
* and 500, so read the status from the response.
*
* @see {@link CoreFrame.goto}
* @since 0.1.3
*/
readonly goto: (
url: string,
options?: Parameters<CoreFrame["goto"]>[1],
) => Effect.Effect<void, PlaywrightError>;
) => Effect.Effect<Option.Option<Response>, PlaywrightError>;
/**
* Waits for the frame to navigate to the given URL.
*
Expand Down Expand Up @@ -288,7 +294,11 @@ export const makeFrame = (frame: CoreFrame): Frame => {
const use = useHelper(frame);

return Frame.of({
goto: (url, options) => use((f) => f.goto(url, options)),
goto: (url, options) =>
use((f) => f.goto(url, options)).pipe(
Effect.map(Option.fromNullishOr),
Effect.map(Option.map(makeResponse)),
),
waitForURL: (url, options) => use((f) => f.waitForURL(url, options)),
waitForLoadState: (state, options) =>
use((f) => f.waitForLoadState(state, options)),
Expand Down
81 changes: 81 additions & 0 deletions src/page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,87 @@ layer(PlaywrightSpawner.layer(chromium))("Page", (it) => {
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("goto should return the main resource response", () =>
Effect.gen(function* () {
const browser = yield* Browser;
const page = yield* browser.newPage();

yield* page.use((playwrightPage) =>
playwrightPage.route("http://status.test/", (route) =>
route.fulfill({
status: 404,
body: "<!doctype html><title>Not found</title>",
}),
),
);
const response = yield* page.goto("http://status.test/");

assert.deepStrictEqual(
Option.map(response, (response) => [response.status(), response.ok()]),
Option.some([404, false]),
);
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("goto should return None when there is no response", () =>
Effect.gen(function* () {
const browser = yield* Browser;
const page = yield* browser.newPage();

const response = yield* page.goto("about:blank");

assert.isTrue(Option.isNone(response));
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("reload should return the response of the reloaded page", () =>
Effect.gen(function* () {
const browser = yield* Browser;
const page = yield* browser.newPage();
let requests = 0;

yield* page.use((playwrightPage) =>
playwrightPage.route("http://reload.test/", (route) => {
requests++;
return route.fulfill({
status: requests === 1 ? 200 : 503,
body: "<!doctype html><title>Reload</title>",
});
}),
);
yield* page.goto("http://reload.test/");
const response = yield* page.reload();

assert.deepStrictEqual(
Option.map(response, (response) => response.status()),
Option.some(503),
);
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("reload should time out after the timeout option", () =>
Effect.gen(function* () {
const browser = yield* Browser;
const page = yield* browser.newPage();
let requests = 0;

yield* page.use((playwrightPage) =>
playwrightPage.route("http://reload.test/", async (route) => {
requests++;
if (requests === 1) {
await route.fulfill({
body: "<!doctype html><title>Reload</title>",
});
}
}),
);
yield* page.goto("http://reload.test/");
const error = yield* page.reload({ timeout: 100 }).pipe(Effect.flip);

assert.strictEqual(error.reason, "Timeout");
}).pipe(PlaywrightSpawner.withBrowser),
);

it.effect("waitForTimeout should wait", () =>
Effect.gen(function* () {
const browser = yield* Browser;
Expand Down
44 changes: 38 additions & 6 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,32 @@ export interface Page {
*/
readonly sessionStorage: WebStorage;
/**
* Navigates the page to the given URL.
* Navigates the page to the given URL and returns the main resource
* response, or `None` when there is none, such as for `about:blank` or a
* change of the URL hash.
*
* It succeeds whatever HTTP status the server answers with, including 404
* and 500, so read the status from the response.
*
* **Example** (Reading the status of the page)
*
* ```ts
* import { Effect, Option } from "effect";
* import { Playwright } from "effect-playwright";
*
* const status = Effect.gen(function* () {
* const page = yield* Playwright.Page;
* const response = yield* page.goto("https://example.com");
* return Option.map(response, (response) => response.status());
* });
* ```
* @see {@link CorePage.goto}
* @since 0.1.0
*/
readonly goto: (
url: string,
options?: Parameters<CorePage["goto"]>[1],
) => Effect.Effect<void, PlaywrightError>;
) => Effect.Effect<Option.Option<Response>, PlaywrightError>;
/**
* This method internally calls [document.write()](https://developer.mozilla.org/en-US/docs/Web/API/Document/write),
* inheriting all its specific characteristics and behaviors.
Expand Down Expand Up @@ -655,12 +673,18 @@ export interface Page {
) => Effect.Effect<void, PlaywrightError>;

/**
* Reloads the page.
* Reloads the page and returns the main resource response, or `None` when
* there is none, such as for `about:blank`.
*
* It succeeds whatever HTTP status the server answers with, including 404
* and 500, so read the status from the response.
*
* @see {@link CorePage.reload}
* @since 0.1.0
*/
readonly reload: Effect.Effect<void, PlaywrightError>;
readonly reload: (
options?: Parameters<CorePage["reload"]>[0],
) => Effect.Effect<Option.Option<Response>, PlaywrightError>;
/**
* Navigate to the previous page in history.
*
Expand Down Expand Up @@ -915,7 +939,11 @@ export const makePage = (page: CorePage): Page => {
touchscreen: makeTouchscreen(page.touchscreen),
screencast: makeScreencast(page.screencast),
sessionStorage: makeWebStorage(page.sessionStorage),
goto: (url, options) => use((page) => page.goto(url, options)),
goto: (url, options) =>
use((page) => page.goto(url, options)).pipe(
Effect.map(Option.fromNullishOr),
Effect.map(Option.map(makeResponse)),
),
setContent: (html, options) =>
use((page) => page.setContent(html, options)),
waitForTimeout: (timeout) => use((page) => page.waitForTimeout(timeout)),
Expand Down Expand Up @@ -1018,7 +1046,11 @@ export const makePage = (page: CorePage): Page => {
),
frames: use((page) => Promise.resolve(page.frames().map(makeFrame))),
mainFrame: () => makeFrame(page.mainFrame()),
reload: use((page) => page.reload()),
reload: (options) =>
use((page) => page.reload(options)).pipe(
Effect.map(Option.fromNullishOr),
Effect.map(Option.map(makeResponse)),
),
goBack: (options) =>
use((page) => page.goBack(options)).pipe(
Effect.map(Option.fromNullishOr),
Expand Down
Loading