14
14
* limitations under the License.
15
15
*/
16
16
17
+ import fs from 'fs' ;
18
+ import path from 'path' ;
17
19
import { fileURLToPath } from 'url' ;
18
20
import { chromium } from 'playwright' ;
21
+ import packageJSON from '../../package.json' assert { type : 'json' } ;
19
22
import { test as base , expect } from '../../tests/fixtures.js' ;
20
23
21
- import type { BrowserContext } from 'playwright' ;
22
24
import type { Client } from '@modelcontextprotocol/sdk/client/index.js' ;
25
+ import type { BrowserContext } from 'playwright' ;
23
26
import type { StartClient } from '../../tests/fixtures.js' ;
24
27
25
28
type BrowserWithExtension = {
26
29
userDataDir : string ;
27
30
launch : ( mode ?: 'disable-extension' ) => Promise < BrowserContext > ;
28
31
} ;
29
32
30
- const test = base . extend < { browserWithExtension : BrowserWithExtension } > ( {
31
- browserWithExtension : async ( { mcpBrowser } , use , testInfo ) => {
33
+ type TestFixtures = {
34
+ browserWithExtension : BrowserWithExtension ,
35
+ pathToExtension : string ,
36
+ useShortConnectionTimeout : ( timeoutMs : number ) => void
37
+ } ;
38
+
39
+ const test = base . extend < TestFixtures > ( {
40
+ pathToExtension : async ( { } , use ) => {
41
+ await use ( fileURLToPath ( new URL ( '../dist' , import . meta. url ) ) ) ;
42
+ } ,
43
+
44
+ browserWithExtension : async ( { mcpBrowser, pathToExtension } , use , testInfo ) => {
32
45
// The flags no longer work in Chrome since
33
46
// https://chromium.googlesource.com/chromium/src/+/290ed8046692651ce76088914750cb659b65fb17%5E%21/chrome/browser/extensions/extension_service.cc?pli=1#
34
47
test . skip ( 'chromium' !== mcpBrowser , '--load-extension is not supported for official builds of Chromium' ) ;
35
48
36
- const pathToExtension = fileURLToPath ( new URL ( '../dist' , import . meta. url ) ) ;
37
-
38
49
let browserContext : BrowserContext | undefined ;
39
50
const userDataDir = testInfo . outputPath ( 'extension-user-data-dir' ) ;
40
51
await use ( {
@@ -60,9 +71,16 @@ const test = base.extend<{ browserWithExtension: BrowserWithExtension }>({
60
71
return browserContext ;
61
72
}
62
73
} ) ;
63
-
64
74
await browserContext ?. close ( ) ;
65
75
} ,
76
+
77
+ useShortConnectionTimeout : async ( { } , use ) => {
78
+ await use ( ( timeoutMs : number ) => {
79
+ process . env . PWMCP_TEST_CONNECTION_TIMEOUT = timeoutMs . toString ( ) ;
80
+ } ) ;
81
+ process . env . PWMCP_TEST_CONNECTION_TIMEOUT = undefined ;
82
+ } ,
83
+
66
84
} ) ;
67
85
68
86
async function startAndCallConnectTool ( browserWithExtension : BrowserWithExtension , startClient : StartClient ) : Promise < Client > {
@@ -99,6 +117,21 @@ async function startWithExtensionFlag(browserWithExtension: BrowserWithExtension
99
117
return client ;
100
118
}
101
119
120
+ const testWithOldVersion = test . extend ( {
121
+ pathToExtension : async ( { } , use , testInfo ) => {
122
+ const extensionDir = testInfo . outputPath ( 'extension' ) ;
123
+ const oldPath = fileURLToPath ( new URL ( '../dist' , import . meta. url ) ) ;
124
+
125
+ await fs . promises . cp ( oldPath , extensionDir , { recursive : true } ) ;
126
+ const manifestPath = path . join ( extensionDir , 'manifest.json' ) ;
127
+ const manifest = JSON . parse ( await fs . promises . readFile ( manifestPath , 'utf8' ) ) ;
128
+ manifest . version = '0.0.1' ;
129
+ await fs . promises . writeFile ( manifestPath , JSON . stringify ( manifest , null , 2 ) + '\n' ) ;
130
+
131
+ await use ( extensionDir ) ;
132
+ } ,
133
+ } ) ;
134
+
102
135
for ( const [ mode , startClientMethod ] of [
103
136
[ 'connect-tool' , startAndCallConnectTool ] ,
104
137
[ 'extension-flag' , startWithExtensionFlag ] ,
@@ -160,8 +193,8 @@ for (const [mode, startClientMethod] of [
160
193
expect ( browserContext . pages ( ) ) . toHaveLength ( 4 ) ;
161
194
} ) ;
162
195
163
- test ( `extension not installed timeout (${ mode } )` , async ( { browserWithExtension, startClient, server } ) => {
164
- process . env . PWMCP_TEST_CONNECTION_TIMEOUT = ' 100' ;
196
+ test ( `extension not installed timeout (${ mode } )` , async ( { browserWithExtension, startClient, server, useShortConnectionTimeout } ) => {
197
+ useShortConnectionTimeout ( 100 ) ;
165
198
166
199
const browserContext = await browserWithExtension . launch ( ) ;
167
200
@@ -180,8 +213,32 @@ for (const [mode, startClientMethod] of [
180
213
} ) ;
181
214
182
215
await confirmationPagePromise ;
216
+ } ) ;
183
217
184
- process . env . PWMCP_TEST_CONNECTION_TIMEOUT = undefined ;
218
+ testWithOldVersion ( `extension version mismatch (${ mode } )` , async ( { browserWithExtension, startClient, server, useShortConnectionTimeout } ) => {
219
+ useShortConnectionTimeout ( 500 ) ;
220
+
221
+ // Prelaunch the browser, so that it is properly closed after the test.
222
+ const browserContext = await browserWithExtension . launch ( ) ;
223
+
224
+ const client = await startClientMethod ( browserWithExtension , startClient ) ;
225
+
226
+ const confirmationPagePromise = browserContext . waitForEvent ( 'page' , page => {
227
+ return page . url ( ) . startsWith ( 'chrome-extension://jakfalbnbhgkpmoaakfflhflbfpkailf/connect.html' ) ;
228
+ } ) ;
229
+
230
+ const navigateResponse = client . callTool ( {
231
+ name : 'browser_navigate' ,
232
+ arguments : { url : server . HELLO_WORLD } ,
233
+ } ) ;
234
+
235
+ const confirmationPage = await confirmationPagePromise ;
236
+ await expect ( confirmationPage . locator ( '.status-banner' ) ) . toHaveText ( `Incompatible Playwright MCP version: ${ packageJSON . version } (extension version: 0.0.1). Please install the latest version of the extension.` ) ;
237
+
238
+ expect ( await navigateResponse ) . toHaveResponse ( {
239
+ result : expect . stringContaining ( 'Extension connection timeout.' ) ,
240
+ isError : true ,
241
+ } ) ;
185
242
} ) ;
186
243
187
244
}
0 commit comments