Skip to content

Commit 198d45b

Browse files
authored
Merge pull request #2 from Jobflow-io/push-zlqvzpuwmtkx
2 parents fb0c8f7 + 9486859 commit 198d45b

File tree

3 files changed

+161
-27
lines changed

3 files changed

+161
-27
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,36 @@ const program = Effect.gen(function* () {
5555
}).pipe(Effect.scoped);
5656
```
5757

58+
## Connecting via CDP
59+
60+
You can connect to an existing browser instance using the Chrome DevTools Protocol (CDP).
61+
62+
```ts
63+
const program = Effect.gen(function* () {
64+
const playwright = yield* Playwright;
65+
66+
// Use connectCDPScoped to automatically close the CONNECTION when the scope ends
67+
// Note: This does NOT close the browser process itself, only the CDP connection.
68+
const browser = yield* playwright.connectCDPScoped("http://localhost:9222");
69+
70+
const page = yield* browser.newPage();
71+
// ...
72+
}).pipe(Effect.scoped);
73+
```
74+
75+
If you need to manage the connection lifecycle manually, use `connectCDP`:
76+
77+
```ts
78+
const program = Effect.gen(function* () {
79+
const playwright = yield* Playwright;
80+
const browser = yield* playwright.connectCDP("http://localhost:9222");
81+
82+
// ... use browser ...
83+
84+
yield* browser.close;
85+
});
86+
```
87+
5888
## PlaywrightEnvironment (Experimental)
5989

6090
The `PlaywrightEnvironment` simplifies setup by allowing you to configure the browser type and launch options once and reuse them across your application.

src/playwright.test.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assert, expect, layer } from "@effect/vitest";
1+
import { assert, layer } from "@effect/vitest";
22
import { Effect } from "effect";
33
import { Playwright } from "effect-playwright";
44
import { chromium } from "playwright-core";
@@ -66,7 +66,76 @@ layer(Playwright.layer)("Playwright", (it) => {
6666
result._tag === "PlaywrightError",
6767
"Expected failure with timeout 0",
6868
);
69-
expect(result.reason).toBe("Timeout");
69+
assert(result.reason === "Timeout", "Expected reason to be timeout");
70+
}),
71+
);
72+
73+
it.scoped(
74+
"should connect via CDP (confirm browser.close only closes CDP connection)",
75+
Effect.fn(function* () {
76+
const playwright = yield* Playwright;
77+
78+
// 1. Launch a browser that exposes CDP
79+
const directBrowser = yield* playwright.launchScoped(chromium, {
80+
args: [
81+
"--remote-debugging-port=9222",
82+
"--remote-debugging-address=127.0.0.1",
83+
],
84+
});
85+
86+
// 2. Connect to it via CDP
87+
const browser = yield* playwright.connectCDP("http://127.0.0.1:9222");
88+
89+
// 3. Cleanup connection now
90+
yield* browser.close;
91+
92+
assert(
93+
(yield* directBrowser.isConnected) === true,
94+
"Expected direct browser to be still connected",
95+
);
96+
97+
const page = yield* directBrowser.newPage();
98+
const content = yield* page.evaluate(() => "eval works");
99+
assert(content === "eval works", "Expected content to be eval works");
100+
}),
101+
);
102+
103+
it.scoped(
104+
"should connect via CDP and close automatically with scope",
105+
Effect.fn(function* () {
106+
const playwright = yield* Playwright;
107+
108+
// 1. Launch a browser that exposes CDP
109+
const directBrowser = yield* playwright.launchScoped(chromium, {
110+
args: [
111+
"--remote-debugging-port=9223",
112+
"--remote-debugging-address=127.0.0.1",
113+
],
114+
});
115+
116+
// 2. Connect to it via CDP using connectCDPScoped
117+
yield* Effect.gen(function* () {
118+
const browser = yield* playwright.connectCDPScoped(
119+
"http://127.0.0.1:9223",
120+
);
121+
const isConnected = yield* browser.isConnected;
122+
assert(isConnected === true, "Expected connected true");
123+
}).pipe(Effect.scoped);
124+
125+
// 3. After scope, connection should be closed
126+
// We can't easily check the CDP browser object as it's out of scope
127+
// but we can check if the direct browser is still connected
128+
assert(
129+
(yield* directBrowser.isConnected) === true,
130+
"Expected browser to still be connected",
131+
);
132+
133+
const page = yield* directBrowser.newPage();
134+
const content = yield* page.evaluate(() => "eval after cdp closed");
135+
assert(
136+
content === "eval after cdp closed",
137+
"Expected content to be correct",
138+
);
70139
}),
71140
);
72141
});

src/playwright.ts

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,19 @@ export interface PlaywrightService {
7171
/**
7272
* Connects to a browser instance via Chrome DevTools Protocol (CDP).
7373
*
74-
* Unlike {@link launchScoped}, this method does **not** close the browser when the
75-
* scope is closed. It is the caller's responsibility to manage the browser's
74+
* Unlike {@link connectCDPScoped}, this method does **not** close the connection when the
75+
* scope is closed. It is the caller's responsibility to manage the connection's
7676
* lifecycle.
7777
*
78-
* If you want to close the browser using a scope simply add a finalizer:
78+
* If you want to close the connection using a scope simply add a finalizer:
7979
*
8080
* ```ts
8181
* import { Effect } from "effect";
8282
* import { Playwright } from "effect-playwright";
8383
*
8484
* const program = Effect.gen(function* () {
85-
* const browser = yield* Playwright.connectCDP(cdpUrl);
85+
* const playwright = yield* Playwright;
86+
* const browser = yield* playwright.connectCDP(cdpUrl);
8687
* yield* Effect.addFinalizer(() => browser.close.pipe(Effect.ignore));
8788
* });
8889
*
@@ -97,6 +98,39 @@ export interface PlaywrightService {
9798
cdpUrl: string,
9899
options?: ConnectOverCDPOptions,
99100
) => Effect.Effect<typeof PlaywrightBrowser.Service, PlaywrightError>;
101+
/**
102+
* Connects to a browser instance via Chrome DevTools Protocol (CDP) managed by a Scope.
103+
*
104+
* This method automatically closes the connection when the scope is closed.
105+
*
106+
* Note that closing a CDP connection does **not** close the browser instance itself,
107+
* only the CDP connection.
108+
*
109+
* ```ts
110+
* import { Effect } from "effect";
111+
* import { Playwright } from "effect-playwright";
112+
*
113+
* const program = Effect.gen(function* () {
114+
* const playwright = yield* Playwright;
115+
* const browser = yield* playwright.connectCDPScoped(cdpUrl);
116+
* // Connection will be closed automatically when scope closes
117+
* });
118+
*
119+
* await Effect.runPromise(program);
120+
* ```
121+
*
122+
* @param cdpUrl - The CDP URL to connect to.
123+
* @param options - Optional options for connecting to the CDP URL.
124+
* @since 0.1.1
125+
*/
126+
connectCDPScoped: (
127+
cdpUrl: string,
128+
options?: ConnectOverCDPOptions,
129+
) => Effect.Effect<
130+
typeof PlaywrightBrowser.Service,
131+
PlaywrightError,
132+
Scope.Scope
133+
>;
100134
}
101135

102136
const launch: (
@@ -114,6 +148,19 @@ const launch: (
114148
return browser;
115149
});
116150

151+
const connectCDP: (
152+
cdpUrl: string,
153+
options?: ConnectOverCDPOptions,
154+
) => Effect.Effect<typeof PlaywrightBrowser.Service, PlaywrightError> =
155+
Effect.fn(function* (cdpUrl: string, options?: ConnectOverCDPOptions) {
156+
const browser = yield* Effect.tryPromise({
157+
try: () => chromium.connectOverCDP(cdpUrl, options),
158+
catch: wrapError,
159+
});
160+
161+
return PlaywrightBrowser.make(browser);
162+
});
163+
117164
export class Playwright extends Context.Tag(
118165
"effect-playwright/index/Playwright",
119166
)<Playwright, PlaywrightService>() {
@@ -122,26 +169,14 @@ export class Playwright extends Context.Tag(
122169
*/
123170
static readonly layer = Layer.succeed(Playwright, {
124171
launch,
125-
launchScoped: Effect.fn(function* (
126-
browserType: BrowserType,
127-
options?: LaunchOptions,
128-
) {
129-
const browser = yield* launch(browserType, options);
130-
131-
// cleanup
132-
yield* Effect.addFinalizer(() => browser.close.pipe(Effect.ignore));
133-
return browser;
134-
}),
135-
connectCDP: Effect.fn(function* (
136-
cdpUrl: string,
137-
options?: ConnectOverCDPOptions,
138-
) {
139-
const browser = yield* Effect.tryPromise({
140-
try: () => chromium.connectOverCDP(cdpUrl, options),
141-
catch: wrapError,
142-
});
143-
144-
return PlaywrightBrowser.make(browser);
145-
}),
172+
launchScoped: (browserType, options) =>
173+
Effect.acquireRelease(launch(browserType, options), (browser) =>
174+
browser.close.pipe(Effect.ignore),
175+
),
176+
connectCDP,
177+
connectCDPScoped: (cdpUrl, options) =>
178+
Effect.acquireRelease(connectCDP(cdpUrl, options), (browser) =>
179+
browser.close.pipe(Effect.ignore),
180+
),
146181
});
147182
}

0 commit comments

Comments
 (0)