diff --git a/AGENTS.md b/AGENTS.md
index e350391..11fdb34 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -20,7 +20,7 @@ Use `pnpm` for all package management tasks.
### General Architecture
- **Effect-First:** All asynchronous operations must be wrapped in `Effect`.
-- **Services:** Each service module exports a same-named interface and `Context.GenericTag` value (for example, `Browser`). Public functionality is grouped under the `Playwright` namespace, where `Playwright.Browser` and similar names work in both type and value positions.
+- **Services:** Each service module exports a same-named interface and `Context.GenericTag` value (for example, `Browser`). Core wrapper functionality is grouped under the `Playwright` namespace, where `Playwright.Browser` and similar names work in both type and value positions. Scoped browser provisioning is grouped under `PlaywrightSpawner`.
- **Constructors:** Wrap native Playwright objects with named functions such as `makeBrowser` and `makePage`. Do not add `*Service` aliases or static `Tag.make` constructors.
- **Resource Management:** Rely on Effect's `Scope` for automatic resource cleanup (browsers, contexts).
@@ -29,7 +29,7 @@ Use `pnpm` for all package management tasks.
- **Effect:** Import widely used modules from `effect` (e.g., `Effect`, `Context`, `Stream`).
- **Playwright:** Import types from `playwright-core`.
- **Internal:** Use relative imports (e.g., `./common`, `./errors`).
-- **Public API:** Re-export canonical service names and named constructors directly from `src/playwright-api.ts`; do not add import-and-alias mappings. Consumers import the namespace through `Playwright` from `effect-playwright` and the experimental browser spawner through `PlaywrightSpawner` from `effect-playwright/experimental`. Do not reintroduce top-level wrapper exports, `*Service` aliases, or the old `Environment` name.
+- **Public API:** Re-export canonical service names and named constructors directly from `src/playwright-api.ts`; do not add import-and-alias mappings. Consumers import the namespaces `Playwright` and `PlaywrightSpawner` from `effect-playwright`. Do not reintroduce top-level wrapper exports, `*Service` aliases, or the old `Environment` name.
### Error Handling
@@ -52,8 +52,7 @@ Use `pnpm` for all package management tasks.
```typescript
import { assert, layer } from "@effect/vitest";
import { Effect } from "effect";
- import { Playwright, chromium } from "effect-playwright";
- import { PlaywrightSpawner } from "effect-playwright/experimental";
+ import { Playwright, PlaywrightSpawner, chromium } from "effect-playwright";
// Use the PlaywrightSpawner layer
layer(PlaywrightSpawner.layer(chromium))("Suite Name", (it) => {
@@ -73,5 +72,4 @@ Use `pnpm` for all package management tasks.
## 4. Experimental Features
-- Features in `src/experimental/` may have different stability guarantees but should follow the same coding standards. The browser-spawning service lives in `src/experimental/playwright-spawner.ts` and is exported as `PlaywrightSpawner`.
-- Experimental services follow the same-named interface plus `Context.GenericTag` convention.
+- Features in `src/experimental/` may have different stability guarantees but should follow the same coding standards.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44ada3b..d09b2b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.
### Breaking Changes
- **Namespaced Playwright API**: All wrapper tags, canonical service types, model classes, option types, and errors now live under the root `Playwright` namespace. Tags and service types share names, so `Playwright.Browser`, `Playwright.Page`, and the other service names work in both value and type positions. Implementation-only `*Service` names are no longer exported, and wrapper constructors use names such as `Playwright.makeBrowser` and `Playwright.makePage` instead of static `make` methods. Browser engine exports remain top-level.
-- **PlaywrightSpawner**: Renamed the experimental `Environment` namespace and service to `PlaywrightSpawner`. Use `PlaywrightSpawner.PlaywrightSpawner`, `PlaywrightSpawner.layer`, and `PlaywrightSpawner.withBrowser`.
+- **PlaywrightSpawner**: Renamed the `Environment` namespace and service to `PlaywrightSpawner` and promoted it to the stable root entrypoint. Import `PlaywrightSpawner` from `effect-playwright`, then use `PlaywrightSpawner.PlaywrightSpawner`, `PlaywrightSpawner.layer`, and `PlaywrightSpawner.withBrowser`.
## 0.6.0
diff --git a/README.md b/README.md
index 109a164..208ae4a 100644
--- a/README.md
+++ b/README.md
@@ -82,15 +82,14 @@ const program = Effect.gen(function* () {
});
```
-## Playwright Spawner (Experimental)
+## Playwright Spawner
`PlaywrightSpawner` configures how browsers are launched and spawns browsers scoped to the current lifetime.
### Usage
```ts
-import { Playwright, chromium } from "effect-playwright";
-import { PlaywrightSpawner } from "effect-playwright/experimental";
+import { Playwright, PlaywrightSpawner, chromium } from "effect-playwright";
import { Effect } from "effect";
const liveLayer = PlaywrightSpawner.layer(chromium, {
diff --git a/src/browser-context.test.ts b/src/browser-context.test.ts
index 66573fe..5d46c7d 100644
--- a/src/browser-context.test.ts
+++ b/src/browser-context.test.ts
@@ -1,8 +1,8 @@
import { assert, layer } from "@effect/vitest";
import { Effect, Option } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
type TestWindow = Window & {
magicValue?: number;
diff --git a/src/common.test.ts b/src/common.test.ts
index c6a2d47..e5712ce 100644
--- a/src/common.test.ts
+++ b/src/common.test.ts
@@ -1,8 +1,8 @@
import { assert, layer } from "@effect/vitest";
import { Chunk, Effect, Fiber, Option, Stream } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
layer(PlaywrightSpawner.layer(chromium))("PlaywrightCommon", (it) => {
it.scoped("Request and Response", () =>
diff --git a/src/event-stream.test.ts b/src/event-stream.test.ts
index fd072e2..d6c2a32 100644
--- a/src/event-stream.test.ts
+++ b/src/event-stream.test.ts
@@ -1,8 +1,8 @@
import { layer } from "@effect/vitest";
import { Effect, Stream } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
layer(PlaywrightSpawner.layer(chromium))("eventStream", (it) => {
it.scoped("should complete when the page closes", () =>
diff --git a/src/experimental/index.ts b/src/experimental/index.ts
index d9487ea..b04c1a9 100644
--- a/src/experimental/index.ts
+++ b/src/experimental/index.ts
@@ -14,9 +14,3 @@
* @since 0.2.0
*/
export * as BrowserUtils from "./browser-utils";
-/**
- * Scoped browser provisioning for Effect programs.
- *
- * @since 0.7.0
- */
-export * as PlaywrightSpawner from "./playwright-spawner";
diff --git a/src/frame.test.ts b/src/frame.test.ts
index f597b4c..cbf84e6 100644
--- a/src/frame.test.ts
+++ b/src/frame.test.ts
@@ -1,8 +1,8 @@
import { assert, layer } from "@effect/vitest";
import { Effect, Option } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
import type { Frame } from "./frame";
layer(PlaywrightSpawner.layer(chromium))("Frame", (it) => {
diff --git a/src/index.ts b/src/index.ts
index ce550f0..19c83b7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,10 +1,10 @@
/**
* Effect services and value wrappers for browser automation with Playwright.
*
- * The root entrypoint exposes Playwright's browser engines directly and groups
- * the Effect-based services, models, constructors, and errors under the
- * {@link Playwright} namespace. Scoped acquisition APIs close browsers and
- * contexts automatically, while fallible operations report {@link Playwright.PlaywrightError}.
+ * The root entrypoint exposes Effect-based services, models, constructors, and
+ * errors under the {@link Playwright} namespace. {@link PlaywrightSpawner}
+ * provides scoped browser acquisition. Fallible operations report
+ * {@link Playwright.PlaywrightError}.
*
* @since 0.1.0
* @packageDocumentation
@@ -22,3 +22,9 @@ export { chromium, firefox, webkit } from "playwright-core";
* @since 0.7.0
*/
export * as Playwright from "./playwright-api";
+/**
+ * Scoped browser provisioning for Effect programs.
+ *
+ * @since 0.7.0
+ */
+export * as PlaywrightSpawner from "./playwright-spawner";
diff --git a/src/locator.test.ts b/src/locator.test.ts
index 13a4786..563eaff 100644
--- a/src/locator.test.ts
+++ b/src/locator.test.ts
@@ -1,9 +1,9 @@
///
import { assert, layer } from "@effect/vitest";
import { Effect, Option } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
layer(PlaywrightSpawner.layer(chromium))("Locator", (it) => {
it.scoped("should work", () =>
diff --git a/src/locator.ts b/src/locator.ts
index 731b03d..844f5ae 100644
--- a/src/locator.ts
+++ b/src/locator.ts
@@ -339,8 +339,7 @@ export interface Locator {
* ```ts
* import { chromium } from "@playwright/test";
* import { Effect } from "effect";
- * import { Playwright } from "effect-playwright";
- * import { PlaywrightSpawner } from "effect-playwright/experimental";
+ * import { Playwright, PlaywrightSpawner } from "effect-playwright";
*
* const program = Effect.gen(function* () {
* const browser = yield* Playwright.Browser;
diff --git a/src/page.test.ts b/src/page.test.ts
index bcb8f0e..bf28cba 100644
--- a/src/page.test.ts
+++ b/src/page.test.ts
@@ -1,8 +1,8 @@
import { assert, layer } from "@effect/vitest";
import { Effect, Fiber, Option, Ref, Stream } from "effect";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
import { Browser } from "./browser";
-import { PlaywrightSpawner } from "./experimental";
type TestWindow = Window & {
timerFired?: boolean;
diff --git a/src/experimental/playwright-spawner.test.ts b/src/playwright-spawner.test.ts
similarity index 96%
rename from src/experimental/playwright-spawner.test.ts
rename to src/playwright-spawner.test.ts
index 85f0f30..55bbf01 100644
--- a/src/experimental/playwright-spawner.test.ts
+++ b/src/playwright-spawner.test.ts
@@ -1,7 +1,6 @@
import { assert, layer } from "@effect/vitest";
import { Effect } from "effect";
-import { Playwright } from "effect-playwright";
-import { PlaywrightSpawner } from "effect-playwright/experimental";
+import { Playwright, PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
const accessFirst = Effect.gen(function* () {
diff --git a/src/experimental/playwright-spawner.ts b/src/playwright-spawner.ts
similarity index 86%
rename from src/experimental/playwright-spawner.ts
rename to src/playwright-spawner.ts
index 4f2e0c2..855f153 100644
--- a/src/experimental/playwright-spawner.ts
+++ b/src/playwright-spawner.ts
@@ -1,14 +1,14 @@
/**
- * Experimental service for provisioning a scoped Playwright browser.
+ * Service for provisioning a scoped Playwright browser.
*
* @since 0.7.0
*/
import { Context, Effect, Layer } from "effect";
import type { Scope } from "effect/Scope";
-import { Playwright } from "effect-playwright";
import type { BrowserType, LaunchOptions } from "playwright-core";
-import type { PlaywrightError } from "../errors";
+import type { PlaywrightError } from "./errors";
+import * as Playwright from "./playwright-api";
/**
* Deferred acquisition of a browser scoped to the caller's lifetime.
@@ -32,7 +32,7 @@ export interface PlaywrightSpawner {
* @since 0.7.0
*/
export const PlaywrightSpawner = Context.GenericTag(
- "effect-playwright/experimental/playwright-spawner/PlaywrightSpawner",
+ "effect-playwright/playwright-spawner/PlaywrightSpawner",
);
/**
@@ -48,8 +48,7 @@ export const PlaywrightSpawner = Context.GenericTag(
*
* ```ts
* import { Effect } from "effect";
- * import { chromium } from "effect-playwright";
- * import { PlaywrightSpawner } from "effect-playwright/experimental";
+ * import { PlaywrightSpawner, chromium } from "effect-playwright";
*
* const program = Effect.gen(function* () {
* const spawner = yield* PlaywrightSpawner.PlaywrightSpawner;
@@ -104,8 +103,7 @@ const withBrowserUnscoped = Effect.provideServiceEffect(
*
* ```ts
* import { Effect } from "effect";
- * import { Playwright, chromium } from "effect-playwright";
- * import { PlaywrightSpawner } from "effect-playwright/experimental";
+ * import { Playwright, PlaywrightSpawner, chromium } from "effect-playwright";
*
* const program = Effect.gen(function* () {
* const browser = yield* Playwright.Browser;
diff --git a/src/scratchpad/setup.ts b/src/scratchpad/setup.ts
index 84e8204..5ec634e 100644
--- a/src/scratchpad/setup.ts
+++ b/src/scratchpad/setup.ts
@@ -1,4 +1,4 @@
-import { PlaywrightSpawner } from "effect-playwright/experimental";
+import { PlaywrightSpawner } from "effect-playwright";
import { chromium } from "playwright-core";
export const liveLayer = PlaywrightSpawner.layer(chromium, {