| name | implement-playwright-method |
| description | Implement a Playwright method in effect-playwright by analyzing source code and mapping types |
What I do
I guide you through implementing a Playwright method in the effect-playwright library. This involves analyzing the original Playwright source code to determine behavior (throwing vs. non-throwing) and mapping types to the Effect ecosystem (Effect, Option, Stream).
When to use me
Use this skill when you need to add a missing method to effect-playwright or update an existing one.
Procedure
1. Locate Playwright Source Code
First, find the implementation of the method in the Playwright codebase to understand its behavior.
- Look in
context/playwright/packages/playwright-core/src/client/.
- Common files:
page.ts, locator.ts, browser.ts, frame.ts, elementHandle.ts.
2. Analyze the Method
Determine if the method can throw and what it returns. Do not blindly follow existing patterns in effect-playwright. Always analyze the original Playwright source code to determine behavior.
Can it throw?
- Async / Promise-based: If the method returns a
Promise, it interacts with the Playwright server and can throw (e.g., timeouts, target closed).
- Mapping: Map to
Effect<Return, PlaywrightError>.
- Sync but Unsafe: If a synchronous method performs validation or logic that explicitly throws errors.
- Mapping: Map to
Effect<Return, PlaywrightError> (using Effect.sync or Effect.try).
- Sync and Safe: If the method purely returns a property or creates a new object without side effects or throwing (e.g.,
url(), locator(), getByRole()).
- Mapping: Map 1:1. Return the value directly. Do NOT wrap in
Effect.
- Preference: Prefer 1:1 mappings over abstractions like getters. If Playwright uses a method
page.url(), use page.url() in the effect wrapper, not a getter page.url.
Return Type Mapping
Promise<T> -> Effect<T, PlaywrightError>
Promise<void> -> Effect<void, PlaywrightError>
T (Safe Sync) -> T (Direct return)
T (Factory) -> Wrapper<T> (e.g., PlaywrightLocator.Service)
T | null -> Option<T> (if sync) or Effect<Option<T>, PlaywrightError> (if async)
- Playwright Object (e.g.,
Page) -> Wrapped Object (e.g., PlaywrightPage)
3. Handle Sub-APIs / Nested Properties
Some Playwright interfaces expose other classes as properties (e.g., Page.keyboard, Page.mouse, BrowserContext.tracing).
- Create a new Wrapper: Create a new file, Service, and Tag for the sub-API (e.g.,
PlaywrightKeyboardService wrapping Keyboard).
- Expose as a Sync Property: Expose it as a direct, read-only property on the parent service. Do not wrap property access in an
Effect.
Example (Interface in Parent):
export interface PlaywrightPageService {
readonly keyboard: PlaywrightKeyboardService;
}
Example (Implementation in Parent's make):
static make(page: Page): PlaywrightPageService {
return PlaywrightPage.of({
keyboard: PlaywrightKeyboard.make(page.keyboard),
});
}
4. Define the Interface
Add the method to the Service interface in the corresponding src/X.ts file (e.g., PlaywrightPageService in src/page.ts).
Example (Async Method - Throws):
readonly click: (
selector: string,
options?: Parameters<Page["click"]>[1]
) => Effect.Effect<void, PlaywrightError>;
Example (Sync Factory - Safe):
readonly locator: (
selector: string,
options?: Parameters<Page["locator"]>[1]
) => typeof PlaywrightLocator.Service;
Example (Sync Method - Safe):
readonly url: () => string;
Example (Nullable Return):
readonly textContent: Effect.Effect<Option.Option<string>, PlaywrightError>;
5. Implement the Method
Implement the method in the make function of the implementation class (e.g., PlaywrightPage.make).
-
Async Methods: Use useHelper(originalObject).
click: (selector, options) => use((p) => p.click(selector, options)),
-
Sync Safe Methods: Return directly.
url: () => page.url(),
-
Factories: Return the wrapped object directly.
locator: (selector, options) => PlaywrightLocator.make(page.locator(selector, options)),
-
Nullable Returns: Use Option.fromNullable.
textContent: use((l) => l.textContent()).pipe(
Effect.map(Option.fromNullable)
),
attribute: (name) => Option.fromNullable(element.getAttribute(name)),
-
Returning Playwright Objects: Wrap them.
waitForPopup: use((p) => p.waitForEvent("popup")).pipe(
Effect.map(PlaywrightPage.make)
),
6. Verify
- Ensure types match
PlaywrightXService.
- Run
pnpm exec biome check --write . to ensure code style and lint rules are followed.
- Run
pnpm type-check and pnpm test to verify implementation.