@@ -30,35 +30,44 @@ type CliResult = {
3030type NpmRegistry = {
3131 url : string ;
3232 status : number ;
33+ hang : boolean ;
3334 requests : string [ ] ;
3435} ;
3536
3637const test = baseTest . extend < { npmRegistry : NpmRegistry } > ( {
3738 npmRegistry : async ( { } , use ) => {
38- const registry : NpmRegistry = { url : '' , status : 200 , requests : [ ] } ;
39+ const registry : NpmRegistry = { url : '' , status : 200 , hang : false , requests : [ ] } ;
3940 const server = http . createServer ( ( req , res ) => {
4041 registry . requests . push ( req . url ! ) ;
42+ if ( registry . hang )
43+ return ;
4144 res . statusCode = registry . status ;
4245 res . setHeader ( 'content-type' , 'application/json' ) ;
43- res . end ( JSON . stringify ( { version : '99.0.0' } ) ) ;
46+ res . end ( JSON . stringify ( {
47+ 'name' : '@playwright/cli' ,
48+ 'dist-tags' : { latest : '99.0.0' } ,
49+ 'versions' : { '99.0.0' : { name : '@playwright/cli' , version : '99.0.0' } } ,
50+ } ) ) ;
4451 } ) ;
4552 await new Promise < void > ( resolve => server . listen ( 0 , '127.0.0.1' , resolve ) ) ;
4653 registry . url = `http://127.0.0.1:${ ( server . address ( ) as AddressInfo ) . port } ` ;
4754 await use ( registry ) ;
55+ server . closeAllConnections ( ) ;
4856 await new Promise ( resolve => server . close ( resolve ) ) ;
4957 } ,
5058} ) ;
5159
52- // Enables the update check and isolates it from the npm config of the machine running the tests.
53- function updateCheckEnv ( env : Record < string , string > = { } ) : Record < string , string > {
60+ // Enables the update check and points npm at the test registry, isolated from
61+ // the npm config of the machine running the tests.
62+ function updateCheckEnv ( npmRegistry : NpmRegistry ) : Record < string , string > {
5463 return {
5564 'CI' : '' ,
5665 'NO_UPDATE_NOTIFIER' : '' ,
57- 'npm_config_registry' : '' ,
66+ 'npm_config_registry' : npmRegistry . url ,
5867 'npm_config_@playwright:registry' : '' ,
5968 'npm_config_userconfig' : test . info ( ) . outputPath ( 'user-npmrc' ) ,
6069 'npm_config_globalconfig' : test . info ( ) . outputPath ( 'global-npmrc' ) ,
61- ... env ,
70+ 'npm_config_cache' : test . info ( ) . outputPath ( 'npm-cache' ) ,
6271 } ;
6372}
6473
@@ -110,53 +119,52 @@ test('open data URL', async ({}) => {
110119 } ) ) ;
111120} ) ;
112121
113- test ( 'warns when installed skill is out of date' , async ( { } ) => {
122+ test ( 'warns when installed skill is out of date' , async ( { npmRegistry } ) => {
114123 expect ( await runCli ( [ 'install' , '--skills' ] , { NO_UPDATE_NOTIFIER : '1' } ) ) . toEqual ( expect . objectContaining ( {
115124 exitCode : 0 ,
116125 } ) ) ;
117126
118127 const skillFile = path . join ( test . info ( ) . outputPath ( ) , '.claude' , 'skills' , 'playwright-cli' , 'SKILL.md' ) ;
119128 fs . appendFileSync ( skillFile , 'x' ) ;
120129
121- const env = { CI : '' , NO_UPDATE_NOTIFIER : '' } ;
122- expect ( await runCli ( [ '--help ' ] , env ) ) . toEqual ( expect . objectContaining ( {
130+ const env = updateCheckEnv ( npmRegistry ) ;
131+ expect ( await runCli ( [ 'list ' ] , env ) ) . toEqual ( expect . objectContaining ( {
123132 error : expect . stringContaining ( 'does not match the tool version' ) ,
124133 } ) ) ;
125134
126- expect ( await runCli ( [ '--help ' ] , env ) ) . toEqual ( expect . objectContaining ( {
135+ expect ( await runCli ( [ 'list ' ] , env ) ) . toEqual ( expect . objectContaining ( {
127136 error : expect . not . stringContaining ( 'does not match the tool version' ) ,
128137 } ) ) ;
129138} ) ;
130139
131- test ( 'does not warn when installed skill only differs in line endings' , async ( { } ) => {
140+ test ( 'does not warn when installed skill only differs in line endings' , async ( { npmRegistry } ) => {
132141 expect ( await runCli ( [ 'install' , '--skills' ] , { NO_UPDATE_NOTIFIER : '1' } ) ) . toEqual ( expect . objectContaining ( {
133142 exitCode : 0 ,
134143 } ) ) ;
135144
136145 const skillFile = path . join ( test . info ( ) . outputPath ( ) , '.claude' , 'skills' , 'playwright-cli' , 'SKILL.md' ) ;
137146 fs . writeFileSync ( skillFile , fs . readFileSync ( skillFile , 'utf8' ) . replace ( / \n / g, '\r\n' ) ) ;
138147
139- expect ( await runCli ( [ '--help ' ] , { CI : '' , NO_UPDATE_NOTIFIER : '' } ) ) . toEqual ( expect . objectContaining ( {
148+ expect ( await runCli ( [ 'list ' ] , updateCheckEnv ( npmRegistry ) ) ) . toEqual ( expect . objectContaining ( {
140149 error : expect . not . stringContaining ( 'does not match the tool version' ) ,
141150 } ) ) ;
142151} ) ;
143152
144- test ( 'caches the update check in the default registry directory' , async ( { } ) => {
153+ test ( 'caches the update check in the default registry directory' , async ( { npmRegistry } ) => {
145154 // Redirect the home/cache directories so the real user cache is untouched, and
146155 // leave PLAYWRIGHT_CLI_INSTALLATION_FOR_TEST empty so the default path is used.
147156 const home = test . info ( ) . outputPath ( 'home' ) ;
148157 fs . mkdirSync ( home , { recursive : true } ) ;
149158 const env = {
150- CI : '' ,
151- NO_UPDATE_NOTIFIER : '' ,
159+ ...updateCheckEnv ( npmRegistry ) ,
152160 PLAYWRIGHT_CLI_INSTALLATION_FOR_TEST : '' ,
153161 HOME : home ,
154162 USERPROFILE : home ,
155163 XDG_CACHE_HOME : path . join ( home , '.cache' ) ,
156164 LOCALAPPDATA : path . join ( home , 'AppData' , 'Local' ) ,
157165 } ;
158166
159- expect ( await runCli ( [ '--version ' ] , env ) ) . toEqual ( expect . objectContaining ( { exitCode : 0 } ) ) ;
167+ expect ( await runCli ( [ 'list ' ] , env ) ) . toEqual ( expect . objectContaining ( { exitCode : 0 } ) ) ;
160168
161169 const found : string [ ] = [ ] ;
162170 const walk = ( dir : string ) => {
@@ -174,46 +182,62 @@ test('caches the update check in the default registry directory', async ({}) =>
174182 expect ( JSON . parse ( fs . readFileSync ( found [ 0 ] , 'utf8' ) ) . lastCheck ) . toEqual ( expect . any ( Number ) ) ;
175183} ) ;
176184
177- test ( 'update check uses the registry from npm config env' , async ( { npmRegistry } ) => {
178- expect ( await runCli ( [ '--version' ] , updateCheckEnv ( { npm_config_registry : npmRegistry . url } ) ) ) . toEqual ( expect . objectContaining ( {
185+ test ( 'update check asks npm for the latest version on setup commands only' , async ( { npmRegistry } ) => {
186+ const env = updateCheckEnv ( npmRegistry ) ;
187+ expect ( await runCli ( [ '--version' ] , env ) ) . toEqual ( expect . objectContaining ( {
188+ error : '' ,
189+ exitCode : 0 ,
190+ } ) ) ;
191+ expect ( npmRegistry . requests ) . toEqual ( [ ] ) ;
192+
193+ expect ( await runCli ( [ 'list' ] , env ) ) . toEqual ( expect . objectContaining ( {
179194 error : expect . stringContaining ( 'Update available for @playwright/cli' ) ,
180195 exitCode : 0 ,
181196 } ) ) ;
182- expect ( npmRegistry . requests ) . toEqual ( [ '/@playwright%2fcli/latest ' ] ) ;
197+ expect ( npmRegistry . requests ) . toEqual ( [ '/@playwright%2fcli' ] ) ;
183198} ) ;
184199
185- test ( 'update check prefers the scoped registry from project .npmrc' , async ( { npmRegistry } ) => {
186- fs . writeFileSync ( test . info ( ) . outputPath ( 'package.json' ) , '{}' ) ;
187- fs . writeFileSync ( test . info ( ) . outputPath ( '.npmrc' ) , [
188- `registry=${ npmRegistry . url } /default/` ,
189- `@playwright:registry=${ npmRegistry . url } /scoped/` ,
190- ] . join ( '\n' ) ) ;
191- fs . writeFileSync ( test . info ( ) . outputPath ( 'user-npmrc' ) , `registry=${ npmRegistry . url } /user/` ) ;
192-
193- expect ( await runCli ( [ '--version' ] , updateCheckEnv ( ) ) ) . toEqual ( expect . objectContaining ( {
194- error : expect . stringContaining ( 'Update available for @playwright/cli' ) ,
200+ test ( 'update check on install does not warn about the skills being installed' , async ( { npmRegistry } ) => {
201+ expect ( await runCli ( [ 'install' , '--skills' ] , { NO_UPDATE_NOTIFIER : '1' } ) ) . toEqual ( expect . objectContaining ( {
195202 exitCode : 0 ,
196203 } ) ) ;
197- expect ( npmRegistry . requests ) . toEqual ( [ '/scoped/@playwright%2fcli/latest' ] ) ;
204+
205+ const skillFile = path . join ( test . info ( ) . outputPath ( ) , '.claude' , 'skills' , 'playwright-cli' , 'SKILL.md' ) ;
206+ fs . appendFileSync ( skillFile , 'x' ) ;
207+
208+ const result = await runCli ( [ 'install' , '--skills' ] , updateCheckEnv ( npmRegistry ) ) ;
209+ expect ( result . exitCode ) . toBe ( 0 ) ;
210+ expect ( result . error ) . toContain ( 'Update available for @playwright/cli' ) ;
211+ expect ( result . error ) . not . toContain ( 'does not match the tool version' ) ;
212+ expect ( npmRegistry . requests ) . toEqual ( [ '/@playwright%2fcli' ] ) ;
198213} ) ;
199214
200- test ( 'update check uses the registry from user .npmrc' , async ( { npmRegistry } ) => {
201- fs . writeFileSync ( test . info ( ) . outputPath ( 'user-npmrc' ) , `registry=${ npmRegistry . url } /user/` ) ;
215+ test ( 'update check uses the scoped registry from project .npmrc' , async ( { npmRegistry } ) => {
216+ fs . writeFileSync ( test . info ( ) . outputPath ( 'package.json' ) , '{}' ) ;
217+ fs . writeFileSync ( test . info ( ) . outputPath ( '.npmrc' ) , `@playwright:registry=${ npmRegistry . url } /scoped/` ) ;
202218
203- expect ( await runCli ( [ '--version ' ] , updateCheckEnv ( ) ) ) . toEqual ( expect . objectContaining ( {
219+ expect ( await runCli ( [ 'list ' ] , updateCheckEnv ( npmRegistry ) ) ) . toEqual ( expect . objectContaining ( {
204220 error : expect . stringContaining ( 'Update available for @playwright/cli' ) ,
205221 exitCode : 0 ,
206222 } ) ) ;
207- expect ( npmRegistry . requests ) . toEqual ( [ '/user /@playwright%2fcli/latest ' ] ) ;
223+ expect ( npmRegistry . requests ) . toEqual ( [ '/scoped /@playwright%2fcli' ] ) ;
208224} ) ;
209225
210- test ( 'update check failure in the configured registry is silent' , async ( { npmRegistry } ) => {
226+ test ( 'update check failure is silent' , async ( { npmRegistry } ) => {
211227 npmRegistry . status = 401 ;
212- const packageJson = JSON . parse ( fs . readFileSync ( path . join ( __dirname , '../package.json' ) , 'utf8' ) ) ;
213- expect ( await runCli ( [ '--version' ] , updateCheckEnv ( { npm_config_registry : npmRegistry . url } ) ) ) . toEqual ( {
214- output : packageJson . version ,
228+ expect ( await runCli ( [ 'list' ] , updateCheckEnv ( npmRegistry ) ) ) . toEqual ( expect . objectContaining ( {
215229 error : '' ,
216230 exitCode : 0 ,
217- } ) ;
218- expect ( npmRegistry . requests ) . toEqual ( [ '/@playwright%2fcli/latest' ] ) ;
231+ } ) ) ;
232+ expect ( npmRegistry . requests ) . toEqual ( [ '/@playwright%2fcli' ] ) ;
233+ } ) ;
234+
235+ test ( 'update check does not wait for a hanging registry' , async ( { npmRegistry } ) => {
236+ npmRegistry . hang = true ;
237+ const start = Date . now ( ) ;
238+ expect ( await runCli ( [ 'list' ] , updateCheckEnv ( npmRegistry ) ) ) . toEqual ( expect . objectContaining ( {
239+ error : '' ,
240+ exitCode : 0 ,
241+ } ) ) ;
242+ expect ( Date . now ( ) - start ) . toBeLessThan ( 10000 ) ;
219243} ) ;
0 commit comments