diff --git a/src/experimental/browser-utils.test.ts b/src/experimental/browser-utils.test.ts new file mode 100644 index 0000000..a697af4 --- /dev/null +++ b/src/experimental/browser-utils.test.ts @@ -0,0 +1,105 @@ +import { assert, layer } from "@effect/vitest"; +import { Chunk, Effect, Fiber, Stream } from "effect"; +import { chromium } from "playwright-core"; +import { Playwright } from "../index"; +import * as BrowserUtils from "./browser-utils"; + +layer(Playwright.layer)("BrowserUtils", (it) => { + it.scoped("allPages should return all pages from all contexts", () => + Effect.gen(function* () { + const playwright = yield* Playwright; + const browser = yield* playwright.launchScoped(chromium); + + const context1 = yield* browser.newContext(); + yield* context1.newPage; + yield* context1.newPage; + + const context2 = yield* browser.newContext(); + yield* context2.newPage; + + const pages = yield* BrowserUtils.allPages(browser); + assert.strictEqual(pages.length, 3); + }), + ); + + it.scoped("allFrames should return all frames from all pages", () => + Effect.gen(function* () { + const playwright = yield* Playwright; + const browser = yield* playwright.launchScoped(chromium); + + yield* browser.newPage(); + yield* browser.newPage(); + + // Retrieve frames + const frames = yield* BrowserUtils.allFrames(browser); + + // Each page has at least one frame (the main frame) + assert.strictEqual(frames.length, 2); + }), + ); + + it.scoped( + "allFrameNavigatedEventStream should capture navigations from existing and new pages across multiple contexts", + () => + Effect.gen(function* () { + const playwright = yield* Playwright; + const browser = yield* playwright.launchScoped(chromium); + + // Setup contexts and an initial page + const context1 = yield* browser.newContext(); + const context2 = yield* browser.newContext(); + const page1 = yield* context1.newPage; + + // Start the event stream + const stream = BrowserUtils.allFrameNavigatedEventStream(browser); + const eventFiber = yield* stream.pipe(Stream.runCollect, Effect.fork); + + // 1. Navigate existing page + yield* page1.goto( + "data:text/html,
Page 1 (existing in context 1)
", + ); + + // 2. Create new page in context 1 and navigate + const page2 = yield* context1.newPage; + yield* page2.goto( + "data:text/html,
Page 2 (new in context 1)
", + ); + + // 3. Create new page in context 2 and navigate + const page3 = yield* context2.newPage; + + yield* page3.goto( + "data:text/html,
Page 3 (new in context 2)
", + ); + + // 4. Create new page in context 2 and navigate + const page4 = yield* context2.newPage; + + yield* page4.goto( + "data:text/html,
Page 4 (new in context 2)
", + ); + + yield* browser.close; + + const events = yield* Fiber.join(eventFiber); + assert.strictEqual(Chunk.size(events), 4); + }), + ); + + it.scoped("page eventStream should capture framenavigated", () => + Effect.gen(function* () { + const playwright = yield* Playwright; + const browser = yield* playwright.launchScoped(chromium); + const page = yield* browser.newPage(); + + const fiber = yield* BrowserUtils.allFrameNavigatedEventStream( + browser, + ).pipe(Stream.take(1), Stream.runCollect, Effect.fork); + + yield* page.goto("https://example.com"); + + const events = yield* Fiber.join(fiber); + assert.strictEqual(Chunk.size(events), 1); + }), + ); +}); diff --git a/src/experimental/browser-utils.ts b/src/experimental/browser-utils.ts new file mode 100644 index 0000000..e126575 --- /dev/null +++ b/src/experimental/browser-utils.ts @@ -0,0 +1,58 @@ +import { Array, Effect, pipe, Stream } from "effect"; +import type { PlaywrightBrowserService } from "../browser"; + +/** + * Returns all pages in the browser from all contexts. + * @category util + */ +export const allPages = (browser: PlaywrightBrowserService) => + browser.contexts.pipe( + Effect.flatMap((contexts) => + Effect.all(contexts.map((context) => context.pages)), + ), + Effect.map(Array.flatten), + ); + +/** + * Returns all frames in the browser from all pages in all contexts. + * @category util + */ +export const allFrames = (browser: PlaywrightBrowserService) => + allPages(browser).pipe( + Effect.flatMap((pages) => Effect.all(pages.map((page) => page.frames))), + ); + +/** + * Returns a stream of all framenavigated events for all current and future pages in the browser. + * In all current contexts (but not future contexts). + * @category util + */ +export const allFrameNavigatedEventStream = ( + browser: PlaywrightBrowserService, +) => + Effect.gen(function* () { + const contexts = yield* browser.contexts; + const pages = yield* pipe( + contexts.map((c) => c.pages), + Effect.all, + Effect.map(Array.flatten), + ); + + // listen for framenavigated for all current pages + const currentPages = pages.map((page) => + page.eventStream("framenavigated"), + ); + + // and all future pages + const newPages = pipe( + contexts.map((c) => c.eventStream("page")), + Stream.mergeAll({ concurrency: "unbounded" }), + Stream.flatMap((page) => page.eventStream("framenavigated"), { + concurrency: "unbounded", + }), + ); + + return Stream.mergeAll([newPages, ...currentPages], { + concurrency: "unbounded", + }); + }).pipe(Stream.unwrap); diff --git a/src/experimental/index.ts b/src/experimental/index.ts index eb2d70b..5272085 100644 --- a/src/experimental/index.ts +++ b/src/experimental/index.ts @@ -5,4 +5,5 @@ * @packageDocumentation */ +export * as BrowserUtils from "./browser-utils"; export * as PlaywrightEnvironment from "./environment";