1717import { test , expect } from '@playwright/test' ;
1818import fs from 'fs' ;
1919import path from 'path' ;
20- import childProcess from 'child_process' ;
20+ import childProcess , { execSync } from 'child_process' ;
2121
2222const samplesDir = path . join ( __dirname , '..' , 'samples' ) ;
2323
24- const sampleFolders = fs . readdirSync ( samplesDir ) . filter ( ( file ) => {
25- return fs . statSync ( path . join ( samplesDir , file ) ) . isDirectory ( ) ;
26- } ) ;
24+ // Function to return all sample folders.
25+ const getAllSampleFolders = ( ) => {
26+ return fs . readdirSync ( samplesDir ) . filter ( ( file ) => {
27+ // Ensure we are only looking at directories within samplesDir, excluding find-changes.sh itself if it's a file
28+ const filePath = path . join ( samplesDir , file ) ;
29+ return fs . statSync ( filePath ) . isDirectory ( ) ;
30+ } ) ;
31+ } ;
32+
33+ // Function to return only changed sample folders.
34+ const getChangedSampleFolders = ( ) : string [ ] => {
35+ try {
36+ const scriptPath = path . join ( __dirname , '..' , 'samples' , 'find-changes.sh' ) ;
37+
38+ if ( ! fs . existsSync ( scriptPath ) ) {
39+ console . warn ( `Warning: find-changes.sh not found at ${ scriptPath } . Running tests for all samples.` ) ;
40+ return getAllSampleFolders ( ) ;
41+ }
42+
43+ // Execute the script from the project root.
44+ const projectRoot = path . join ( __dirname , '..' ) ;
45+ const output = execSync ( `sh ${ scriptPath } ` , { cwd : projectRoot , encoding : 'utf-8' } ) ;
46+
47+ // Get all folder names outputted by the script
48+ const rawChangedFolders = output . trim ( ) . split ( '\n' ) ;
49+ // Filter out empty strings that might result from multiple newlines or a trailing newline
50+ const changedFolders = rawChangedFolders . filter ( folder => folder . trim ( ) . length > 0 ) ;
51+
52+ if ( changedFolders . length === 0 ) {
53+ // This means find-changes.sh ran successfully and reported no changes.
54+ console . log ( "find-changes.sh reported no changed folders. Skipping tests." ) ;
55+ return [ ] ;
56+ }
57+
58+ // Validate that changed folders actually exist in samplesDir
59+ const validChangedFolders = changedFolders . filter ( folderName => {
60+ const folderPath = path . join ( samplesDir , folderName ) ;
61+ return fs . existsSync ( folderPath ) && fs . statSync ( folderPath ) . isDirectory ( ) ;
62+ } ) ;
63+
64+ if ( validChangedFolders . length === 0 && changedFolders . length > 0 && changedFolders [ 0 ] !== "" ) {
65+ console . warn ( "find-changes.sh outputted folder names, but none are valid sample directories. Running for all samples." ) ;
66+ return [ ] ;
67+ }
68+
69+ console . log ( "Running tests only for changed samples: " , validChangedFolders ) ;
70+ return validChangedFolders ;
71+
72+ } catch ( error ) {
73+ console . error ( "Error running find-changes.sh, falling back to all samples:" , error ) ;
74+ return getAllSampleFolders ( ) ;
75+ }
76+ } ;
77+
78+ const foldersToTest = getChangedSampleFolders ( ) ;
79+
80+ if ( foldersToTest . length === 0 ) {
81+ console . log ( "No sample folders identified to test. This might indicate no changes or an issue with find-changes.sh." ) ;
82+ } else {
83+ console . log ( `Will run tests for the following folders: ${ foldersToTest . join ( ', ' ) } ` ) ;
84+ }
2785
2886// Iterate through samples and run the same test for each one.
29- sampleFolders . forEach ( ( sampleFolder ) => {
87+ foldersToTest . forEach ( ( sampleFolder ) => {
3088 test ( `test ${ sampleFolder } ` , async ( { page } ) => {
3189
3290 // START Build the sample
33- const buildProcess = childProcess . spawn ( 'npm' , [ 'run' , 'test ' ] , {
91+ const buildProcess = childProcess . spawn ( 'npm' , [ 'run' , 'build ' ] , {
3492 cwd : path . join ( samplesDir , sampleFolder ) ,
3593 stdio : 'inherit' ,
3694 } ) ;
@@ -40,24 +98,28 @@ sampleFolders.forEach((sampleFolder) => {
4098 if ( code === 0 ) {
4199 resolve ( true ) ;
42100 } else {
43- reject ( `Build process exited with code ${ code } ` ) ;
101+ reject ( new Error ( `Build process for ${ sampleFolder } exited with code ${ code } ` ) ) ;
44102 }
45103 } ) ;
104+ buildProcess . on ( 'error' , ( err ) => {
105+ reject ( new Error ( `Failed to start build process for ${ sampleFolder } : ${ err . message } ` ) ) ;
106+ } ) ;
46107 } ) ;
47108 // END Build the sample
48109
49110 // START run the preview
50111 // Get an available port
51112 const port = 8080 ;
52-
53113 const url = `http://localhost:${ port } /` ;
54114
55115 const viteProcess = childProcess . spawn ( 'vite' , [ 'preview' , `--port=${ port } ` ] , {
56116 cwd : path . join ( samplesDir , sampleFolder ) ,
57- stdio : 'inherit' ,
117+ stdio : 'inherit' ,
118+ detached : true , // Allows parent to exit independently, though we kill it in finally
58119 } ) ;
59120
60- await new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ; // Set a timeout to let the web server start.
121+ //await new Promise((resolve) => setTimeout(resolve, 500)); // Set a timeout to let the web server start.
122+ await page . waitForTimeout ( 500 ) ;
61123 // END run the preview
62124
63125 /**
@@ -66,32 +128,24 @@ sampleFolders.forEach((sampleFolder) => {
66128 * Run `npx playwright test --ui` to launch Playwright in UI mode to iteratively debug this file.
67129 */
68130 try {
69- // Check for console errors. Define a promise, then call after page load.
70131 const consoleErrors : string [ ] = [ ] ;
71- const errorPromise = new Promise < void > ( ( resolve ) => {
72- page . on ( 'console' , ( msg => {
73- if ( msg . type ( ) === 'error' ) {
74- consoleErrors . push ( msg . text ( ) ) ;
75- resolve ( ) ;
76- }
77- } ) ) ;
78-
79- page . on ( 'pageerror' , ( exception ) => {
80- consoleErrors . push ( exception . message ) ;
81- resolve ( ) ;
82- } ) ;
83-
84- // Set a timeout to resolve the promise even if no error occurs.
85- setTimeout ( ( ) => {
86- resolve ( ) ;
87- } , 500 ) ;
132+ // Capture console errors and page errors
133+ page . on ( 'console' , ( msg ) => {
134+ if ( msg . type ( ) === 'error' ) {
135+ consoleErrors . push ( msg . text ( ) ) ;
136+ }
137+ } ) ;
138+ page . on ( 'pageerror' , ( exception ) => {
139+ consoleErrors . push ( exception . message ) ;
88140 } ) ;
89141
90142 // Navigate to the page.
143+ //await page.goto(url, { waitUntil: 'networkidle', timeout: 500 });
91144 await page . goto ( url ) ;
92145
93- // There must be no critical console errors.
94- await errorPromise ;
146+ // Allow some time for async operations and errors to be caught
147+ await page . waitForTimeout ( 500 ) ;
148+
95149 // Filter out error messages we can safely avoid.
96150 const filteredErrorMessages = [
97151 'Falling back to Raster' ,
@@ -100,23 +154,26 @@ sampleFolders.forEach((sampleFolder) => {
100154 const criticalErrors = consoleErrors . filter ( error =>
101155 ! filteredErrorMessages . some ( message => error . includes ( message ) )
102156 ) ;
157+
158+ if ( criticalErrors . length > 0 ) {
159+ console . error ( `Critical console errors found in ${ sampleFolder } :` , criticalErrors ) ;
160+ }
103161 expect ( criticalErrors ) . toHaveLength ( 0 ) ;
104162
105163 // Wait for the page DOM to load; this does NOT include the Google Maps APIs.
106- await page . waitForLoadState ( 'domcontentloaded' ) ;
164+ await page . waitForLoadState ( 'domcontentloaded' , { timeout : 500 } ) ;
107165
108166 // Wait for Google Maps to load.
109- await page . waitForFunction ( ( ) => window . google && window . google . maps ) ;
167+ await page . waitForFunction ( ( ) => window . google && window . google . maps , { timeout : 500 } ) ;
110168
111169 // Insert a delay in ms to let the map load.
112- await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
170+ await page . waitForTimeout ( 500 ) ;
113171
114172 // Assertions. These must be met or the test will fail.
115173 // The sample must load the Google Maps API.
116174 const hasGoogleMaps = await page . evaluate ( ( ) => {
117175 return typeof window . google !== 'undefined' && typeof window . google . maps !== 'undefined' ;
118176 } ) ;
119-
120177 await expect ( hasGoogleMaps ) . toBeTruthy ( ) ;
121178
122179 /**const mapElement = await page.locator('#map');
@@ -127,7 +184,17 @@ sampleFolders.forEach((sampleFolder) => {
127184 throw new Error('Assertion failed: Map is not visible.');
128185 }*/
129186 } finally {
130- viteProcess . kill ( ) ;
187+ //viteProcess.kill(); // We used to just kill the process. Curious to see about how the other stuff works.
188+ if ( viteProcess . pid ) {
189+ try {
190+ // Use process.kill for cross-platform compatibility, sending SIGINT
191+ process . kill ( viteProcess . pid , 'SIGINT' ) ;
192+ } catch ( e ) {
193+ console . warn ( `Failed to kill Vite process for ${ sampleFolder } (PID: ${ viteProcess . pid } ):` , e . message ) ;
194+ }
195+ }
196+ // Add a small delay to allow the process to terminate
197+ await page . waitForTimeout ( 500 ) ;
131198 }
132199 } ) ;
133200} ) ;
0 commit comments