@@ -17,7 +17,7 @@ const {
1717 getCiVisEvpProxyConfig
1818} = require ( '../helpers' )
1919const { FakeCiVisIntake } = require ( '../ci-visibility-intake' )
20- const webAppServer = require ( '../ci-visibility/web-app-server' )
20+ const { createWebAppServer } = require ( '../ci-visibility/web-app-server' )
2121const coverageFixture = require ( '../ci-visibility/fixtures/coverage.json' )
2222const {
2323 TEST_STATUS ,
@@ -125,7 +125,7 @@ moduleTypes.forEach(({
125125
126126 this . retries ( 2 )
127127 this . timeout ( 80000 )
128- let cwd , receiver , childProcess , webAppPort , secondWebAppServer
128+ let cwd , receiver , childProcess , webAppPort , webAppServer , secondWebAppServer
129129
130130 if ( type === 'commonJS' ) {
131131 testCommand = testCommand ( version )
@@ -140,29 +140,57 @@ moduleTypes.forEach(({
140140 const { NODE_OPTIONS , ...restOfEnv } = process . env
141141 // Install cypress' browser before running the tests
142142 await execPromise ( 'npx cypress install' , { cwd, env : restOfEnv , stdio : 'inherit' } )
143-
144- await /** @type {Promise<void> } */ ( new Promise ( resolve => webAppServer . listen ( 0 , 'localhost' , ( ) => {
145- webAppPort = webAppServer . address ( ) . port
146- resolve ( )
147- } ) ) )
148143 } )
149144
150145 after ( async ( ) => {
151- await new Promise ( resolve => webAppServer . close ( resolve ) )
146+ // Cleanup second web app server if it exists
152147 if ( secondWebAppServer ) {
153148 await new Promise ( resolve => secondWebAppServer . close ( resolve ) )
154149 }
155150 } )
156151
157152 beforeEach ( async function ( ) {
158153 receiver = await new FakeCiVisIntake ( ) . start ( )
154+
155+ // Create a fresh web server for each test to avoid state issues
156+ webAppServer = createWebAppServer ( )
157+ await new Promise ( ( resolve , reject ) => {
158+ webAppServer . once ( 'error' , reject )
159+ webAppServer . listen ( 0 , 'localhost' , ( ) => {
160+ webAppPort = webAppServer . address ( ) . port
161+ webAppServer . removeListener ( 'error' , reject )
162+ resolve ( )
163+ } )
164+ } )
159165 } )
160166
161167 // Cypress child processes can sometimes hang or take longer to
162168 // terminate. This can cause `FakeCiVisIntake#stop` to be delayed
163169 // because there are pending connections.
164170 afterEach ( async ( ) => {
165- childProcess . kill ( )
171+ if ( childProcess && childProcess . pid ) {
172+ try {
173+ childProcess . kill ( 'SIGKILL' )
174+ } catch ( error ) {
175+ // Process might already be dead - this is fine, ignore error
176+ }
177+
178+ // Don't wait for exit - Cypress processes can hang indefinitely in uninterruptible I/O
179+ // The OS will clean up zombies, and fresh server per test prevents port conflicts
180+ }
181+
182+ // Close web server before stopping receiver
183+ if ( webAppServer ) {
184+ await new Promise ( ( resolve ) => {
185+ webAppServer . close ( ( err ) => {
186+ if ( err ) {
187+ // eslint-disable-next-line no-console
188+ console . error ( 'Web server close error:' , err )
189+ }
190+ resolve ( )
191+ } )
192+ } )
193+ }
166194
167195 // Add timeout to prevent hanging
168196 const stopPromise = receiver . stop ( )
@@ -176,6 +204,9 @@ moduleTypes.forEach(({
176204 // eslint-disable-next-line no-console
177205 console . warn ( 'Receiver stop timed out:' , error . message )
178206 }
207+
208+ // Small delay to allow OS to release ports
209+ await new Promise ( resolve => setTimeout ( resolve , 100 ) )
179210 } )
180211
181212 it ( 'instruments tests with the APM protocol (old agents)' , async ( ) => {
0 commit comments