Skip to content

Commit dc70afe

Browse files
authored
Merge pull request #9 from Jobflow-io/browser-utils
2 parents 7f4fa1c + f934243 commit dc70afe

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { assert, layer } from "@effect/vitest";
2+
import { Chunk, Effect, Fiber, Stream } from "effect";
3+
import { chromium } from "playwright-core";
4+
import { Playwright } from "../index";
5+
import * as BrowserUtils from "./browser-utils";
6+
7+
layer(Playwright.layer)("BrowserUtils", (it) => {
8+
it.scoped("allPages should return all pages from all contexts", () =>
9+
Effect.gen(function* () {
10+
const playwright = yield* Playwright;
11+
const browser = yield* playwright.launchScoped(chromium);
12+
13+
const context1 = yield* browser.newContext();
14+
yield* context1.newPage;
15+
yield* context1.newPage;
16+
17+
const context2 = yield* browser.newContext();
18+
yield* context2.newPage;
19+
20+
const pages = yield* BrowserUtils.allPages(browser);
21+
assert.strictEqual(pages.length, 3);
22+
}),
23+
);
24+
25+
it.scoped("allFrames should return all frames from all pages", () =>
26+
Effect.gen(function* () {
27+
const playwright = yield* Playwright;
28+
const browser = yield* playwright.launchScoped(chromium);
29+
30+
yield* browser.newPage();
31+
yield* browser.newPage();
32+
33+
// Retrieve frames
34+
const frames = yield* BrowserUtils.allFrames(browser);
35+
36+
// Each page has at least one frame (the main frame)
37+
assert.strictEqual(frames.length, 2);
38+
}),
39+
);
40+
41+
it.scoped(
42+
"allFrameNavigatedEventStream should capture navigations from existing and new pages across multiple contexts",
43+
() =>
44+
Effect.gen(function* () {
45+
const playwright = yield* Playwright;
46+
const browser = yield* playwright.launchScoped(chromium);
47+
48+
// Setup contexts and an initial page
49+
const context1 = yield* browser.newContext();
50+
const context2 = yield* browser.newContext();
51+
const page1 = yield* context1.newPage;
52+
53+
// Start the event stream
54+
const stream = BrowserUtils.allFrameNavigatedEventStream(browser);
55+
const eventFiber = yield* stream.pipe(Stream.runCollect, Effect.fork);
56+
57+
// 1. Navigate existing page
58+
yield* page1.goto(
59+
"data:text/html,<div>Page 1 (existing in context 1)</div>",
60+
);
61+
62+
// 2. Create new page in context 1 and navigate
63+
const page2 = yield* context1.newPage;
64+
yield* page2.goto(
65+
"data:text/html,<div>Page 2 (new in context 1)</div>",
66+
);
67+
68+
// 3. Create new page in context 2 and navigate
69+
const page3 = yield* context2.newPage;
70+
71+
yield* page3.goto(
72+
"data:text/html,<div>Page 3 (new in context 2)</div>",
73+
);
74+
75+
// 4. Create new page in context 2 and navigate
76+
const page4 = yield* context2.newPage;
77+
78+
yield* page4.goto(
79+
"data:text/html,<div>Page 4 (new in context 2)</div>",
80+
);
81+
82+
yield* browser.close;
83+
84+
const events = yield* Fiber.join(eventFiber);
85+
assert.strictEqual(Chunk.size(events), 4);
86+
}),
87+
);
88+
89+
it.scoped("page eventStream should capture framenavigated", () =>
90+
Effect.gen(function* () {
91+
const playwright = yield* Playwright;
92+
const browser = yield* playwright.launchScoped(chromium);
93+
const page = yield* browser.newPage();
94+
95+
const fiber = yield* BrowserUtils.allFrameNavigatedEventStream(
96+
browser,
97+
).pipe(Stream.take(1), Stream.runCollect, Effect.fork);
98+
99+
yield* page.goto("https://example.com");
100+
101+
const events = yield* Fiber.join(fiber);
102+
assert.strictEqual(Chunk.size(events), 1);
103+
}),
104+
);
105+
});

src/experimental/browser-utils.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Array, Effect, pipe, Stream } from "effect";
2+
import type { PlaywrightBrowserService } from "../browser";
3+
4+
/**
5+
* Returns all pages in the browser from all contexts.
6+
* @category util
7+
*/
8+
export const allPages = (browser: PlaywrightBrowserService) =>
9+
browser.contexts.pipe(
10+
Effect.flatMap((contexts) =>
11+
Effect.all(contexts.map((context) => context.pages)),
12+
),
13+
Effect.map(Array.flatten),
14+
);
15+
16+
/**
17+
* Returns all frames in the browser from all pages in all contexts.
18+
* @category util
19+
*/
20+
export const allFrames = (browser: PlaywrightBrowserService) =>
21+
allPages(browser).pipe(
22+
Effect.flatMap((pages) => Effect.all(pages.map((page) => page.frames))),
23+
);
24+
25+
/**
26+
* Returns a stream of all framenavigated events for all current and future pages in the browser.
27+
* In all current contexts (but not future contexts).
28+
* @category util
29+
*/
30+
export const allFrameNavigatedEventStream = (
31+
browser: PlaywrightBrowserService,
32+
) =>
33+
Effect.gen(function* () {
34+
const contexts = yield* browser.contexts;
35+
const pages = yield* pipe(
36+
contexts.map((c) => c.pages),
37+
Effect.all,
38+
Effect.map(Array.flatten),
39+
);
40+
41+
// listen for framenavigated for all current pages
42+
const currentPages = pages.map((page) =>
43+
page.eventStream("framenavigated"),
44+
);
45+
46+
// and all future pages
47+
const newPages = pipe(
48+
contexts.map((c) => c.eventStream("page")),
49+
Stream.mergeAll({ concurrency: "unbounded" }),
50+
Stream.flatMap((page) => page.eventStream("framenavigated"), {
51+
concurrency: "unbounded",
52+
}),
53+
);
54+
55+
return Stream.mergeAll([newPages, ...currentPages], {
56+
concurrency: "unbounded",
57+
});
58+
}).pipe(Stream.unwrap);

src/experimental/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
* @packageDocumentation
66
*/
77

8+
export * as BrowserUtils from "./browser-utils";
89
export * as PlaywrightEnvironment from "./environment";

0 commit comments

Comments
 (0)