@@ -24,7 +24,7 @@ import { test as baseTest, expect, mcpServerPath } from './fixtures';
24
24
import type { Config } from '../../packages/playwright/src/mcp/config' ;
25
25
import { ListRootsRequestSchema } from 'packages/playwright/lib/mcp/sdk/bundle' ;
26
26
27
- const test = baseTest . extend < { serverEndpoint : ( options ?: { args ?: string [ ] , noPort ?: boolean } ) => Promise < { url : URL , stderr : ( ) => string } > } > ( {
27
+ const test = baseTest . extend < { serverEndpoint : ( options ?: { args ?: string [ ] , noPort ?: boolean } ) => Promise < { url : URL , stderr : ( ) => string , kill : ( ) => void } > } > ( {
28
28
serverEndpoint : async ( { mcpHeadless } , use , testInfo ) => {
29
29
let cp : ChildProcess | undefined ;
30
30
const userDataDir = testInfo . outputPath ( 'user-data-dir' ) ;
@@ -55,7 +55,10 @@ const test = baseTest.extend<{ serverEndpoint: (options?: { args?: string[], noP
55
55
resolve ( match [ 1 ] ) ;
56
56
} ) ) ;
57
57
58
- return { url : new URL ( url ) , stderr : ( ) => stderr } ;
58
+ return { url : new URL ( url ) , stderr : ( ) => stderr , kill : ( ) => {
59
+ cp ?. kill ( 'SIGTERM' ) ;
60
+ cp = undefined ;
61
+ } } ;
59
62
} ) ;
60
63
cp ?. kill ( 'SIGTERM' ) ;
61
64
} ,
@@ -245,6 +248,73 @@ test('http transport browser lifecycle (persistent, multiclient)', async ({ serv
245
248
await client2 . close ( ) ;
246
249
} ) ;
247
250
251
+ test ( 'http transport shared context' , async ( { serverEndpoint, server } ) => {
252
+ const { url, stderr, kill } = await serverEndpoint ( { args : [ '--shared-browser-context' ] } ) ;
253
+
254
+ // Create first client and navigate
255
+ const transport1 = new StreamableHTTPClientTransport ( new URL ( '/mcp' , url ) ) ;
256
+ const client1 = new Client ( { name : 'test1' , version : '1.0.0' } ) ;
257
+ await client1 . connect ( transport1 ) ;
258
+ await client1 . callTool ( {
259
+ name : 'browser_navigate' ,
260
+ arguments : { url : server . HELLO_WORLD } ,
261
+ } ) ;
262
+
263
+ // Create second client - should reuse the same browser context
264
+ const transport2 = new StreamableHTTPClientTransport ( new URL ( '/mcp' , url ) ) ;
265
+ const client2 = new Client ( { name : 'test2' , version : '1.0.0' } ) ;
266
+ await client2 . connect ( transport2 ) ;
267
+
268
+ // Get tabs from second client - should see the tab created by first client
269
+ const tabsResult = await client2 . callTool ( {
270
+ name : 'browser_tabs' ,
271
+ arguments : { action : 'list' } ,
272
+ } ) ;
273
+
274
+ // Should have at least one tab (the one created by client1)
275
+ expect ( tabsResult . content [ 0 ] ?. text ) . toContain ( 'tabs' ) ;
276
+
277
+ await transport1 . terminateSession ( ) ;
278
+ await client1 . close ( ) ;
279
+
280
+ // Second client should still work since context is shared
281
+ await client2 . callTool ( {
282
+ name : 'browser_snapshot' ,
283
+ arguments : { } ,
284
+ } ) ;
285
+
286
+ await transport2 . terminateSession ( ) ;
287
+ await client2 . close ( ) ;
288
+
289
+ await expect ( async ( ) => {
290
+ const lines = stderr ( ) . split ( '\n' ) ;
291
+ expect ( lines . filter ( line => line . match ( / c r e a t e h t t p s e s s i o n / ) ) . length ) . toBe ( 2 ) ;
292
+ expect ( lines . filter ( line => line . match ( / d e l e t e h t t p s e s s i o n / ) ) . length ) . toBe ( 2 ) ;
293
+
294
+ // Should have only one context creation since it's shared
295
+ expect ( lines . filter ( line => line . match ( / c r e a t e s h a r e d b r o w s e r c o n t e x t / ) ) . length ) . toBe ( 1 ) ;
296
+
297
+ // Should see client connect/disconnect messages
298
+ expect ( lines . filter ( line => line . match ( / s h a r e d c o n t e x t c l i e n t c o n n e c t e d / ) ) . length ) . toBe ( 2 ) ;
299
+ expect ( lines . filter ( line => line . match ( / s h a r e d c o n t e x t c l i e n t d i s c o n n e c t e d / ) ) . length ) . toBe ( 2 ) ;
300
+ expect ( lines . filter ( line => line . match ( / c r e a t e c o n t e x t / ) ) . length ) . toBe ( 2 ) ;
301
+ expect ( lines . filter ( line => line . match ( / c l o s e c o n t e x t / ) ) . length ) . toBe ( 2 ) ;
302
+
303
+ // Context should only close when the server shuts down.
304
+ expect ( lines . filter ( line => line . match ( / c l o s e b r o w s e r c o n t e x t c o m p l e t e \( p e r s i s t e n t \) / ) ) . length ) . toBe ( 0 ) ;
305
+ } ) . toPass ( ) ;
306
+
307
+ kill ( ) ;
308
+
309
+ if ( process . platform !== 'win32' ) {
310
+ await expect ( async ( ) => {
311
+ const lines = stderr ( ) . split ( '\n' ) ;
312
+ // Context should only close when the server shuts down.
313
+ expect ( lines . filter ( line => line . match ( / c l o s e b r o w s e r c o n t e x t c o m p l e t e \( p e r s i s t e n t \) / ) ) . length ) . toBe ( 1 ) ;
314
+ } ) . toPass ( ) ;
315
+ }
316
+ } ) ;
317
+
248
318
test ( 'http transport (default)' , async ( { serverEndpoint } ) => {
249
319
const { url } = await serverEndpoint ( ) ;
250
320
const transport = new StreamableHTTPClientTransport ( url ) ;
0 commit comments