overengineeringstudio / overengineeringstudio/effect-utils

Add @overeng/effect-playwright package

Open
#26 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:effect type:feature
Dominant language
TypeScript
Stars
82
Forks
2
Avg merge
1d 8h
Merged PRs (30d)
121

Description

Summary

Create an Effect-native Playwright wrapper package (@overeng/effect-playwright) that provides idiomatic Effect APIs for browser automation and testing.

Motivation

Playwright is a powerful browser automation tool, but its Promise-based API doesn't integrate naturally with Effect's resource management, error handling, and observability patterns. An Effect-native wrapper would provide:

  • Resource safety: Automatic cleanup of browser contexts and pages via Effect.acquireRelease
  • Typed errors: Custom TaggedError types instead of generic exceptions
  • Observability: Built-in spans for tracing browser operations
  • Streaming: Console message handling as Effect Streams
  • Composability: Layer-based dependency injection for browser contexts

Design Decisions to Align On

1. Service Architecture

Option A: Single BrowserContext service (livestore approach)

export class BrowserContext extends Context.Tag('Playwright.BrowserContext')<
  BrowserContext,
  { browserContext: PW.BrowserContext }
>() {}

Option B: Separate Browser and Page services

export class Browser extends Context.Tag('Playwright.Browser')<Browser, PW.Browser>() {}
export class Page extends Context.Tag('Playwright.Page')<Page, PW.Page>() {}

Option C: Full hierarchy (BrowserBrowserContextPage)

Recommendation: Start with Option A (simple) but design for extensibility to Option C.

2. API Surface

What Playwright APIs should we wrap initially?

Core (must have):

  • Browser/context launch and lifecycle
  • Page navigation (goto, reload, goBack, goForward)
  • Element interactions (click, fill, type, press)
  • Waiting (waitForSelector, waitForNavigation, waitForLoadState)
  • Screenshots and PDFs

Extended (nice to have):

  • Console message streaming
  • Network request interception
  • File uploads/downloads
  • Dialogs handling
  • Frames and iframes
3. Error Handling Strategy

Option A: Single PlaywrightError with discriminated union

export class PlaywrightError extends Schema.TaggedError<PlaywrightError>()('PlaywrightError', {
  _tag: Schema.Literal('Timeout', 'ElementNotFound', 'NavigationFailed', ...),
  message: Schema.String,
  cause: Schema.Defect,
}) {}

Option B: Separate error classes per operation type

export class TimeoutError extends Schema.TaggedError<TimeoutError>()('Playwright.TimeoutError', {...}) {}
export class ElementNotFoundError extends Schema.TaggedError<ElementNotFoundError>()('Playwright.ElementNotFoundError', {...}) {}

Recommendation: Option B for better type-level error tracking in Effect channels.

4. Locator Strategy

How to handle Playwright's Locator API?

Option A: Pass-through (use Playwright locators directly)

const click = (locator: PW.Locator) => Effect.tryPromise(() => locator.click())

Option B: Effect-wrapped locators

export class Locator extends Context.Tag('Playwright.Locator')<Locator, PW.Locator>() {}
const getByRole = (role: string) => Effect.map(Page, (page) => page.getByRole(role))

Recommendation: Option A initially - locators are lightweight and don't need resource management.

5. Testing Integration

Should we provide vitest/playwright test runner integration?

// Option: Effect-aware test helper
export const it = {
  effect: <E, A>(name: string, effect: Effect.Effect<A, E, BrowserContext>) =>
    test(name, () => Effect.runPromise(effect.pipe(Effect.provide(BrowserContextLive))))
}

Reference Implementation

Based on @livestore/effect-playwright:

// Key patterns to adopt:
// 1. Layer.scoped for browser lifecycle
export const browserContextLayer = (params: MakeBrowserContextParams) =>
  Layer.scoped(BrowserContext, browserContext(params))

// 2. Effect.addFinalizer for cleanup
yield* Effect.addFinalizer(() => Effect.promise(() => browserContext.close()))

// 3. Stream.asyncPush for event handling
export const pageConsole = ({ page }) =>
  Stream.asyncPush<ConsoleMessage, SiteError>((emit) =>
    Effect.acquireRelease(
      Effect.sync(() => {
        page.on('console', (msg) => emit.single(parseMessage(msg)))
        return cleanup
      }),
      () => Effect.sync(() => page.off('console', ...))
    )
  )

// 4. Schema.TaggedError for typed errors
export class SiteError extends Schema.TaggedError<SiteError>()('Playwright.SiteError', {
  label: Schema.String,
  messages: Schema.Union(Schema.Array(ConsoleMessage), Schema.Defect),
}) {}

Proposed Package Structure

packages/@overeng/effect-playwright/
├── src/
│   ├── mod.ts              # Main exports
│   ├── BrowserContext.ts   # Browser context service & layer
│   ├── Page.ts             # Page operations
│   ├── Locator.ts          # Locator utilities (if wrapped)
│   ├── Console.ts          # Console message streaming
│   ├── Errors.ts           # Error types
│   └── Testing.ts          # Test runner integration
├── package.json
└── tsconfig.json

Questions for Discussion

  1. Should we support both @playwright/test and playwright packages?
  2. Do we need Chrome extension testing support (like livestore)?
  3. Should console streaming be opt-in or always-on?
  4. How should we handle headless vs headed mode configuration?
  5. Should we integrate with @effect/vitest for test helpers?

Next Steps

  1. Align on design decisions above
  2. Create initial package scaffold
  3. Implement core browser context management
  4. Add page navigation and basic interactions
  5. Add console streaming
  6. Add test helpers
  7. Write documentation and examples

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed package structure under packages/@overeng/effect-playwright/ and the referenced @livestore/effect-playwright implementation. Resolve the listed service, API, error, locator, and testing decisions before implementing the scaffold; done means an agreed package with the core lifecycle, navigation, interactions, streaming, and test-helper scope defined.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
testing, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.