Skip to content
Draft
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
11 changes: 4 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,15 @@
"playwright-core": "^1.60.0"
},
"peerDependencies": {
"@effect/platform": "^0.93.3",
"effect": "^3.19.6"
"effect": "^4.0.0-beta.70"
},
"devDependencies": {
"@biomejs/biome": "2.4.14",
"@effect/cli": "^0.75.1",
"@effect/language-service": "0.85.1",
"@effect/platform": "^0.96.1",
"@effect/platform-node": "^0.106.0",
"@effect/vitest": "^0.29.0",
"@effect/platform-node": "4.0.0-beta.70",
"@effect/vitest": "4.0.0-beta.70",
"@types/node": "^25.6.1",
"effect": "^3.21.2",
"effect": "4.0.0-beta.70",
"playwright": "^1.60.0",
"ts-morph": "^28.0.0",
"tsdown": "0.22.0",
Expand Down
566 changes: 175 additions & 391 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/browser-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type TestWindow = Window & {
layer(PlaywrightEnvironment.layer(chromium))(
"PlaywrightBrowserContext",
(it) => {
it.scoped("should wrap context methods", () =>
it.effect("should wrap context methods", () =>
Effect.gen(function* () {
const browser = yield* PlaywrightBrowser;
const context = yield* browser.newContext();
Expand Down Expand Up @@ -51,7 +51,7 @@ layer(PlaywrightEnvironment.layer(chromium))(
}).pipe(PlaywrightEnvironment.withBrowser),
);

it.scoped("addInitScript should execute script in all new pages", () =>
it.effect("addInitScript should execute script in all new pages", () =>
Effect.gen(function* () {
const browser = yield* PlaywrightBrowser;
const context = yield* browser.newContext();
Expand Down
39 changes: 20 additions & 19 deletions src/browser-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Effect, identity, Option, Stream } from "effect";
import { Context, Effect, identity, Option, Queue, Stream } from "effect";
import type {
BrowserContext,
ConsoleMessage,
Expand Down Expand Up @@ -94,7 +94,7 @@ export interface PlaywrightBrowserContextService {
* @see {@link BrowserContext.pages}
* @since 0.1.0
*/
readonly pages: () => Array<typeof PlaywrightPage.Service>;
readonly pages: () => Array<PlaywrightPage["Service"]>;
/**
* Opens a new page in the browser context.
*
Expand All @@ -106,10 +106,7 @@ export interface PlaywrightBrowserContextService {
* @see {@link BrowserContext.newPage}
* @since 0.1.0
*/
readonly newPage: Effect.Effect<
typeof PlaywrightPage.Service,
PlaywrightError
>;
readonly newPage: Effect.Effect<PlaywrightPage["Service"], PlaywrightError>;
/**
* Closes the browser context.
*
Expand Down Expand Up @@ -268,9 +265,10 @@ export interface PlaywrightBrowserContextService {
/**
* @category tag
*/
export class PlaywrightBrowserContext extends Context.Tag(
"effect-playwright/PlaywrightBrowserContext",
)<PlaywrightBrowserContext, PlaywrightBrowserContextService>() {
export class PlaywrightBrowserContext extends Context.Service<
PlaywrightBrowserContext,
PlaywrightBrowserContextService
>()("effect-playwright/PlaywrightBrowserContext") {
/**
* Creates a `PlaywrightBrowserContext` from a Playwright `BrowserContext` instance.
*
Expand All @@ -289,7 +287,7 @@ export class PlaywrightBrowserContext extends Context.Tag(
close: use((c) => c.close()),
addInitScript: (script, arg) => use((c) => c.addInitScript(script, arg)),
browser: () =>
Option.fromNullable(context.browser()).pipe(
Option.fromNullishOr(context.browser()).pipe(
Option.map(PlaywrightBrowser.make),
),
clearCookies: (options) => use((c) => c.clearCookies(options)),
Expand All @@ -307,20 +305,23 @@ export class PlaywrightBrowserContext extends Context.Tag(
context.setDefaultNavigationTimeout(timeout),
setDefaultTimeout: (timeout) => context.setDefaultTimeout(timeout),
setStorageState: (options) => use((c) => c.setStorageState(options)),
eventStream: <K extends keyof BrowserContextEvents>(event: K) =>
Stream.asyncPush<BrowserContextEvents[K]>((emit) =>
Effect.acquireRelease(
eventStream: <K extends keyof typeof eventMappings>(event: K) =>
Stream.callback<BrowserContextEvents[K]>((queue) => {
const handler = (value: BrowserContextEvents[K]) =>
Queue.offerUnsafe(queue, value);
const closeHandler = () => Queue.endUnsafe(queue);
return Effect.acquireRelease(
Effect.sync(() => {
context.on(event, emit.single);
context.once("close", emit.end);
context.on(event, handler);
context.once("close", closeHandler);
}),
() =>
Effect.sync(() => {
context.off(event, emit.single);
context.off("close", emit.end);
context.off(event, handler);
context.off("close", closeHandler);
}),
),
).pipe(
);
}).pipe(
Stream.map((e) => {
const mapping = eventMappings[event];
// biome-ignore lint/suspicious/noExplicitAny: Don't know how to fix this …
Expand Down
30 changes: 15 additions & 15 deletions src/browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { assert, layer } from "@effect/vitest";
import { Chunk, Effect, Fiber, Stream } from "effect";
import { Effect, Fiber, Stream } from "effect";
import { chromium } from "playwright-core";
import type { PlaywrightBrowser } from "./browser";
import { Playwright } from "./index";

layer(Playwright.layer)("PlaywrightBrowser", (it) => {
it.scoped("newPage should create a page", () =>
it.effect("newPage should create a page", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -15,7 +15,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("use should allow accessing raw browser", () =>
it.effect("use should allow accessing raw browser", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -27,7 +27,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("browserType should return the browser type", () =>
it.effect("browserType should return the browser type", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -37,7 +37,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("version should return the browser version", () =>
it.effect("version should return the browser version", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -48,7 +48,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("close should close the browser", () =>
it.effect("close should close the browser", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -61,7 +61,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
assert.isFalse(isConnected);
}),
);
it.scoped("contexts should return the list of contexts", () =>
it.effect("contexts should return the list of contexts", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -75,7 +75,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("newContext should create a new context", () =>
it.effect("newContext should create a new context", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -88,7 +88,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("newContext should allow creating pages", () =>
it.effect("newContext should allow creating pages", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -102,7 +102,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
}),
);

it.scoped("contexts should reflect newPage creation", () =>
it.effect("contexts should reflect newPage creation", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);
Expand All @@ -119,7 +119,7 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
it.effect("newContext and browser finalizers should work", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
let capturedBrowser: typeof PlaywrightBrowser.Service | undefined;
let capturedBrowser: PlaywrightBrowser["Service"] | undefined;

yield* Effect.scoped(
Effect.gen(function* () {
Expand All @@ -144,20 +144,20 @@ layer(Playwright.layer)("PlaywrightBrowser", (it) => {
assert.isFalse(isConnected);
}),
);
it.scoped("eventStream should emit disconnected event", () =>
it.effect("eventStream should emit disconnected event", () =>
Effect.gen(function* () {
const playwright = yield* Playwright;
const browser = yield* playwright.launchScoped(chromium);

const eventsFiber = yield* browser
.eventStream("disconnected")
.pipe(Stream.runCollect, Effect.fork);
.pipe(Stream.runCollect, Effect.forkChild());

yield* browser.close;
const events = yield* Fiber.join(eventsFiber);
assert.strictEqual(Chunk.size(events), 1);
assert.strictEqual(events.length, 1);

const firstEvent = yield* Chunk.head(events);
const firstEvent = events[0];
assert.strictEqual(firstEvent.version(), browser.version());
}),
);
Expand Down
38 changes: 21 additions & 17 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Effect, Stream } from "effect";
import { Context, Effect, Queue, Stream } from "effect";
import type { Scope } from "effect/Scope";
import type {
Browser,
Expand Down Expand Up @@ -47,7 +47,7 @@ export interface PlaywrightBrowserService {
*/
readonly newPage: (
options?: NewPageOptions,
) => Effect.Effect<typeof PlaywrightPage.Service, PlaywrightError>;
) => Effect.Effect<PlaywrightPage["Service"], PlaywrightError>;
/**
* A generic utility to execute any promise-based method on the underlying Playwright `Browser`.
* Can be used to access any Browser functionality not directly exposed by this service.
Expand All @@ -74,12 +74,12 @@ export interface PlaywrightBrowserService {
* Returns the list of all open browser contexts.
* @see {@link Browser.contexts}
*/
readonly contexts: () => Array<typeof PlaywrightBrowserContext.Service>;
readonly contexts: () => Array<PlaywrightBrowserContext["Service"]>;

readonly newContext: (
options?: NewContextOptions,
) => Effect.Effect<
typeof PlaywrightBrowserContext.Service,
PlaywrightBrowserContext["Service"],
PlaywrightError,
Scope
>;
Expand Down Expand Up @@ -140,9 +140,10 @@ export interface PlaywrightBrowserService {
/**
* @category tag
*/
export class PlaywrightBrowser extends Context.Tag(
"effect-playwright/PlaywrightBrowser",
)<PlaywrightBrowser, PlaywrightBrowserService>() {
export class PlaywrightBrowser extends Context.Service<
PlaywrightBrowser,
PlaywrightBrowserService
>()("effect-playwright/PlaywrightBrowser") {
/**
* @category constructor
*/
Expand All @@ -159,27 +160,30 @@ export class PlaywrightBrowser extends Context.Tag(
use((browser) =>
browser.newContext(options).then(PlaywrightBrowserContext.make),
),
(context) => context.close.pipe(Effect.ignoreLogged),
(context) => context.close.pipe(Effect.ignore),
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignoreLogged no longer a thing? investigate

),
browserType: () => browser.browserType(),
version: () => browser.version(),
isConnected: () => browser.isConnected(),
bind: (title, options) => use((browser) => browser.bind(title, options)),
unbind: use((browser) => browser.unbind()),
eventStream: <K extends keyof BrowserEvents>(event: K) =>
Stream.asyncPush<BrowserEvents[K]>((emit) =>
Effect.acquireRelease(
eventStream: <K extends keyof typeof eventMappings>(event: K) =>
Stream.callback<BrowserEvents[K]>((queue) => {
const handler = (value: BrowserEvents[K]) =>
Queue.offerUnsafe(queue, value);
const closeHandler = () => Queue.endUnsafe(queue);
return Effect.acquireRelease(
Effect.sync(() => {
browser.on(event, emit.single);
browser.once("disconnected", emit.end);
browser.on(event, handler);
browser.once("disconnected", closeHandler);
}),
() =>
Effect.sync(() => {
browser.off(event, emit.single);
browser.off("disconnected", emit.end);
browser.off(event, handler);
browser.off("disconnected", closeHandler);
}),
),
).pipe(
);
}).pipe(
Stream.map((e) => {
const mapping = eventMappings[event];
// biome-ignore lint/suspicious/noExplicitAny: Don't know how to fix this …
Expand Down
9 changes: 5 additions & 4 deletions src/clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,18 @@ export interface PlaywrightClockService {
* @since 0.1.0
* @category tag
*/
export class PlaywrightClock extends Context.Tag(
"effect-playwright/PlaywrightClock",
)<PlaywrightClock, PlaywrightClockService>() {
export class PlaywrightClock extends Context.Service<
PlaywrightClock,
PlaywrightClockService
>()("effect-playwright/PlaywrightClock") {
/**
* Creates a `PlaywrightClock` from a Playwright `Clock` instance.
*
* @param clock - The Playwright `Clock` instance to wrap.
* @since 0.1.0
* @category constructor
*/
static make(clock: Clock): typeof PlaywrightClock.Service {
static make(clock: Clock): PlaywrightClock["Service"] {
const use = useHelper(clock);

return PlaywrightClock.of({
Expand Down
Loading
Loading