Skip to content

Commit 7f4fa1c

Browse files
authored
Merge pull request #8 from Jobflow-io/page-frames-and-waitforloadstate
2 parents 63ce4b0 + 44355af commit 7f4fa1c

File tree

4 files changed

+112
-17
lines changed

4 files changed

+112
-17
lines changed

src/frame.test.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Effect } from "effect";
33
import { chromium } from "playwright-core";
44
import { PlaywrightBrowser } from "./browser";
55
import { PlaywrightEnvironment } from "./experimental";
6-
import { PlaywrightFrame } from "./frame";
6+
import type { PlaywrightFrameService } from "./frame";
77

88
layer(PlaywrightEnvironment.layer(chromium))("PlaywrightFrame", (it) => {
99
it.scoped("should wrap frame methods", () =>
@@ -20,41 +20,76 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightFrame", (it) => {
2020
document.body.appendChild(iframe);
2121
});
2222

23+
// wait for iframe to load or something. has to be networkidle for some reason
24+
yield* page.waitForLoadState("networkidle");
25+
2326
// Get the frame
24-
const frameService = yield* page.use(async (p) => {
25-
let frame = p.frames().find((f) => f.name() === "test-frame");
26-
if (!frame) {
27-
// Wait a bit if not found immediately (though srcdoc is sync-ish, iframe loading is async)
28-
await p.waitForLoadState("networkidle");
29-
frame = p.frames().find((f) => f.name() === "test-frame");
30-
}
31-
if (!frame) throw new Error("Frame not found");
32-
return PlaywrightFrame.make(frame);
33-
});
27+
const frames = yield* page.frames;
28+
29+
const isTestFrame = (f: PlaywrightFrameService) =>
30+
f.name.pipe(Effect.map((n) => n === "test-frame"));
31+
32+
const frame = yield* Effect.findFirst(frames, isTestFrame).pipe(
33+
Effect.flatten,
34+
Effect.retry({
35+
times: 3,
36+
}),
37+
);
38+
39+
assert.isOk(frame, "Frame not found");
3440

3541
// Test title
36-
const title = yield* frameService.title;
42+
const title = yield* frame.title;
3743
assert.strictEqual(title, "Frame Title");
3844

3945
// Test content
40-
const content = yield* frameService.content;
46+
const content = yield* frame.content;
4147
assert.isTrue(content.includes("Hello from Frame"));
4248

4349
// Test evaluate
44-
const result = yield* frameService.evaluate(() => 1 + 1);
50+
const result = yield* frame.evaluate(() => 1 + 1);
4551
assert.strictEqual(result, 2);
4652

4753
// Test locator
48-
const text = yield* frameService.locator("#target").textContent();
54+
const text = yield* frame.locator("#target").textContent();
4955
assert.strictEqual(text, "Hello from Frame");
5056

5157
// Test getByText
52-
const byText = yield* frameService.getByText("Hello from Frame").count;
58+
const byText = yield* frame.getByText("Hello from Frame").count;
5359
assert.strictEqual(byText, 1);
5460

5561
// Test name
56-
const name = yield* frameService.name;
62+
const name = yield* frame.name;
5763
assert.strictEqual(name, "test-frame");
5864
}).pipe(PlaywrightEnvironment.withBrowser),
5965
);
66+
67+
it.scoped("waitForLoadState should resolve on frame", () =>
68+
Effect.gen(function* () {
69+
const browser = yield* PlaywrightBrowser;
70+
const page = yield* browser.newPage();
71+
72+
// Load a page that already has an iframe
73+
yield* page.goto(
74+
"data:text/html,<html><body><iframe name='test-frame' src='about:blank'></iframe></body></html>",
75+
);
76+
77+
// Wait for the main page to settle
78+
yield* page.waitForLoadState("load");
79+
80+
// Get the frame - it should be there now
81+
// Get the frame - it should be there now
82+
const frames = yield* page.frames;
83+
const frameService = frames.find((f) =>
84+
Effect.runSync(f.name.pipe(Effect.map((n) => n === "test-frame"))),
85+
);
86+
87+
assert.isOk(frameService, "Frame not found");
88+
89+
// Wait for 'load' state on the frame
90+
yield* frameService.waitForLoadState("load");
91+
92+
assert.ok(true);
93+
}).pipe(PlaywrightEnvironment.withBrowser),
94+
);
6095
});

src/frame.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ export interface PlaywrightFrameService {
3030
url: Parameters<Frame["waitForURL"]>[0],
3131
options?: Parameters<Frame["waitForURL"]>[1],
3232
) => Effect.Effect<void, PlaywrightError>;
33+
/**
34+
* Waits for the frame to reach the given load state.
35+
*
36+
* @see {@link Frame.waitForLoadState}
37+
* @since 0.2.0
38+
*/
39+
readonly waitForLoadState: (
40+
state?: Parameters<Frame["waitForLoadState"]>[0],
41+
options?: Parameters<Frame["waitForLoadState"]>[1],
42+
) => Effect.Effect<void, PlaywrightError>;
3343
/**
3444
* Evaluates a function in the context of the frame.
3545
*
@@ -164,6 +174,8 @@ export class PlaywrightFrame extends Context.Tag(
164174
return PlaywrightFrame.of({
165175
goto: (url, options) => use((f) => f.goto(url, options)),
166176
waitForURL: (url, options) => use((f) => f.waitForURL(url, options)),
177+
waitForLoadState: (state, options) =>
178+
use((f) => f.waitForLoadState(state, options)),
167179
evaluate: <R, Arg>(f: PageFunction<Arg, R>, arg?: Arg) =>
168180
use((frame) => frame.evaluate<R, Arg>(f, arg as Arg)),
169181
title: use((f) => f.title()),

src/page.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,21 @@ layer(PlaywrightEnvironment.layer(chromium))("PlaywrightPage", (it) => {
215215
);
216216
}).pipe(PlaywrightEnvironment.withBrowser),
217217
);
218+
219+
it.scoped("waitForLoadState should resolve", () =>
220+
Effect.gen(function* () {
221+
const browser = yield* PlaywrightBrowser;
222+
const page = yield* browser.newPage();
223+
224+
// Using about:blank and history API to simulate some activity, but networkidle is tricky on blank page.
225+
// load and domcontentloaded are safer.
226+
yield* page.goto("about:blank");
227+
228+
// Wait for 'load' state which should already be true or happen quickly
229+
yield* page.waitForLoadState("load");
230+
231+
// No assertion needed other than it doesn't timeout/error
232+
assert.ok(true);
233+
}).pipe(PlaywrightEnvironment.withBrowser),
234+
);
218235
});

src/page.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ export interface PlaywrightPageService {
109109
url: Parameters<Page["waitForURL"]>[0],
110110
options?: Parameters<Page["waitForURL"]>[1],
111111
) => Effect.Effect<void, PlaywrightError>;
112+
/**
113+
* Waits for the page to reach the given load state.
114+
*
115+
* NOTE: Most of the time, this method is not needed because Playwright auto-waits before every action.
116+
*
117+
* @example
118+
* ```ts
119+
* yield* page.waitForLoadState("domcontentloaded");
120+
* ```
121+
*
122+
* @see {@link Page.waitForLoadState}
123+
* @since 0.2.0
124+
*/
125+
readonly waitForLoadState: (
126+
state?: Parameters<Page["waitForLoadState"]>[0],
127+
options?: Parameters<Page["waitForLoadState"]>[1],
128+
) => Effect.Effect<void, PlaywrightError>;
112129
/**
113130
* Evaluates a function in the context of the page.
114131
*
@@ -232,6 +249,17 @@ export interface PlaywrightPageService {
232249
*/
233250
readonly url: Effect.Effect<string, PlaywrightError>;
234251

252+
/**
253+
* Returns all frames attached to the page.
254+
*
255+
* @see {@link Page.frames}
256+
* @since 0.2.0
257+
*/
258+
readonly frames: Effect.Effect<
259+
ReadonlyArray<typeof PlaywrightFrame.Service>,
260+
PlaywrightError
261+
>;
262+
235263
/**
236264
* Creates a stream of the given event from the page.
237265
*
@@ -284,6 +312,8 @@ export class PlaywrightPage extends Context.Tag(
284312
return PlaywrightPage.of({
285313
goto: (url, options) => use((p) => p.goto(url, options)),
286314
waitForURL: (url, options) => use((p) => p.waitForURL(url, options)),
315+
waitForLoadState: (state, options) =>
316+
use((p) => p.waitForLoadState(state, options)),
287317
title: use((p) => p.title()),
288318
evaluate: <R, Arg>(f: PageFunction<Arg, R>, arg?: Arg) =>
289319
use((p) => p.evaluate<R, Arg>(f, arg as Arg)),
@@ -297,6 +327,7 @@ export class PlaywrightPage extends Context.Tag(
297327
PlaywrightLocator.make(page.getByLabel(label, options)),
298328
getByTestId: (testId) => PlaywrightLocator.make(page.getByTestId(testId)),
299329
url: Effect.sync(() => page.url()),
330+
frames: use((p) => Promise.resolve(p.frames().map(PlaywrightFrame.make))),
300331
reload: use((p) => p.reload()),
301332
close: use((p) => p.close()),
302333
click: (selector, options) => use((p) => p.click(selector, options)),

0 commit comments

Comments
 (0)