@@ -12,100 +12,176 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
1212
1313const starterTemplateDir = "../../../starters/nextjs/basic" ;
1414
15+ // Define scenarios to test
16+ interface Scenario {
17+ name : string ; // Name of the scenario
18+ setup ?: ( cwd : string ) => Promise < void > ; // Optional setup function before building the app
19+ tests ?: string [ ] ; // List of test files to run
20+ }
21+
22+ const scenarios : Scenario [ ] = [
23+ {
24+ name : "basic" ,
25+ // No setup needed for basic scenario
26+ tests : [ "app.spec.ts" ] ,
27+ } ,
28+ {
29+ name : "with-middleware" ,
30+ setup : async ( cwd : string ) => {
31+ // Create a middleware.ts file
32+ const middlewareContent = `
33+ import type { NextRequest } from 'next/server'
34+
35+ export function middleware(request: NextRequest) {
36+ // This is a simple middleware that doesn't modify the request
37+ console.log('Middleware executed', request.nextUrl.pathname);
38+ }
39+
40+ export const config = {
41+ matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
42+ };
43+ ` ;
44+
45+ await fsExtra . writeFile ( join ( cwd , "src" , "middleware.ts" ) , middlewareContent ) ;
46+ console . log ( `Created middleware.ts file` ) ;
47+ } ,
48+ tests : [ "middleware.spec.ts" ] , // Only run middleware-specific tests
49+ } ,
50+ ] ;
51+
1552const errors : any [ ] = [ ] ;
1653
1754await rmdir ( join ( __dirname , "runs" ) , { recursive : true } ) . catch ( ( ) => undefined ) ;
1855
19- console . log ( "\nBuilding and starting test project..." ) ;
20-
21- const runId = Math . random ( ) . toString ( ) . split ( "." ) [ 1 ] ;
22- const cwd = join ( __dirname , "runs" , runId ) ;
23- await mkdirp ( cwd ) ;
24-
25- console . log ( `[${ runId } ] Copying ${ starterTemplateDir } to working directory` ) ;
26- await cp ( starterTemplateDir , cwd , { recursive : true } ) ;
27-
28- console . log ( `[${ runId } ] > npm ci --silent --no-progress` ) ;
29- await promiseSpawn ( "npm" , [ "ci" , "--silent" , "--no-progress" ] , {
30- cwd,
31- stdio : "inherit" ,
32- shell : true ,
33- } ) ;
34-
35- const buildScript = relative ( cwd , join ( __dirname , "../dist/bin/build.js" ) ) ;
36- console . log ( `[${ runId } ] > node ${ buildScript } ` ) ;
37-
38- const packageJson = JSON . parse ( readFileSync ( join ( cwd , "package.json" ) , "utf-8" ) ) ;
39- const frameworkVersion = packageJson . dependencies . next . replace ( "^" , "" ) ;
40- await promiseSpawn ( "node" , [ buildScript ] , {
41- cwd,
42- stdio : "inherit" ,
43- shell : true ,
44- env : {
45- ...process . env ,
46- FRAMEWORK_VERSION : frameworkVersion ,
47- } ,
48- } ) ;
49-
50- const bundleYaml = parseYaml ( readFileSync ( join ( cwd , ".apphosting/bundle.yaml" ) ) . toString ( ) ) ;
56+ // Run each scenario
57+ for ( const scenario of scenarios ) {
58+ console . log ( `\n\nRunning scenario: ${ scenario . name } ` ) ;
5159
52- const runCommand = bundleYaml . runConfig . runCommand ;
60+ const runId = `${ scenario . name } -${ Math . random ( ) . toString ( ) . split ( "." ) [ 1 ] } ` ;
61+ const cwd = join ( __dirname , "runs" , runId ) ;
62+ await mkdirp ( cwd ) ;
5363
54- if ( typeof runCommand !== "string" ) {
55- throw new Error ( "runCommand must be a string" ) ;
56- }
64+ console . log ( `[${ runId } ] Copying ${ starterTemplateDir } to working directory` ) ;
65+ await cp ( starterTemplateDir , cwd , { recursive : true } ) ;
5766
58- const [ runScript , ...runArgs ] = runCommand . split ( " " ) ;
59- let resolveHostname : ( it : string ) => void ;
60- let rejectHostname : ( ) => void ;
61- const hostnamePromise = new Promise < string > ( ( resolve , reject ) => {
62- resolveHostname = resolve ;
63- rejectHostname = reject ;
64- } ) ;
65- const port = 8080 + Math . floor ( Math . random ( ) * 1000 ) ;
66- console . log ( `[${ runId } ] > PORT=${ port } ${ runCommand } ` ) ;
67- const run = spawn ( runScript , runArgs , {
68- cwd,
69- shell : true ,
70- env : {
71- NODE_ENV : "production" ,
72- PORT : port . toString ( ) ,
73- PATH : process . env . PATH ,
74- } ,
75- } ) ;
76- run . stderr . on ( "data" , ( data ) => console . error ( data . toString ( ) ) ) ;
77- run . stdout . on ( "data" , ( data ) => {
78- console . log ( data . toString ( ) ) ;
79- // Check for the "Ready in" message to determine when the server is fully started
80- if ( data . toString ( ) . includes ( `Ready in` ) ) {
81- // We use 0.0.0.0 instead of localhost to avoid issues when ipv6 is not available (Node 18)
82- resolveHostname ( `http://0.0.0.0:${ port } ` ) ;
83- }
84- } ) ;
85- run . on ( "close" , ( code ) => {
86- if ( code ) {
87- rejectHostname ( ) ;
67+ // Run scenario-specific setup if provided
68+ if ( scenario . setup ) {
69+ console . log ( `[${ runId } ] Running setup for ${ scenario . name } ` ) ;
70+ await scenario . setup ( cwd ) ;
8871 }
89- } ) ;
90- const host = await hostnamePromise ;
9172
92- console . log ( "\n\n" ) ;
93-
94- try {
95- console . log ( `> HOST=${ host } ts-mocha -p tsconfig.json e2e/*.spec.ts` ) ;
96- await promiseSpawn ( "ts-mocha" , [ "-p" , "tsconfig.json" , "e2e/*.spec.ts" ] , {
97- shell : true ,
73+ console . log ( `[${ runId } ] > npm ci --silent --no-progress` ) ;
74+ await promiseSpawn ( "npm" , [ "ci" , "--silent" , "--no-progress" ] , {
75+ cwd,
9876 stdio : "inherit" ,
99- env : {
100- ...process . env ,
101- HOST : host ,
102- } ,
103- } ) . finally ( ( ) => {
104- run . stdin . end ( ) ;
105- run . kill ( "SIGKILL" ) ;
77+ shell : true ,
10678 } ) ;
107- } catch ( e ) {
108- errors . push ( e ) ;
79+
80+ const buildScript = relative ( cwd , join ( __dirname , "../dist/bin/build.js" ) ) ;
81+ const buildLogPath = join ( cwd , "build.log" ) ;
82+ console . log ( `[${ runId } ] > node ${ buildScript } (output written to ${ buildLogPath } )` ) ;
83+
84+ const packageJson = JSON . parse ( readFileSync ( join ( cwd , "package.json" ) , "utf-8" ) ) ;
85+ const frameworkVersion = packageJson . dependencies . next . replace ( "^" , "" ) ;
86+
87+ try {
88+ await promiseSpawn ( "node" , [ buildScript ] , {
89+ cwd,
90+ stdioString : true ,
91+ stdio : "pipe" ,
92+ shell : true ,
93+ env : {
94+ ...process . env ,
95+ FRAMEWORK_VERSION : frameworkVersion ,
96+ } ,
97+ } ) . then ( ( result ) => {
98+ // Write stdout and stderr to the log file
99+ fsExtra . writeFileSync ( buildLogPath , result . stdout + result . stderr ) ;
100+ } ) ;
101+
102+ const bundleYaml = parseYaml ( readFileSync ( join ( cwd , ".apphosting/bundle.yaml" ) ) . toString ( ) ) ;
103+
104+ const runCommand = bundleYaml . runConfig . runCommand ;
105+
106+ if ( typeof runCommand !== "string" ) {
107+ throw new Error ( "runCommand must be a string" ) ;
108+ }
109+
110+ const [ runScript , ...runArgs ] = runCommand . split ( " " ) ;
111+ let resolveHostname : ( it : string ) => void ;
112+ let rejectHostname : ( ) => void ;
113+ const hostnamePromise = new Promise < string > ( ( resolve , reject ) => {
114+ resolveHostname = resolve ;
115+ rejectHostname = reject ;
116+ } ) ;
117+ const port = 8080 + Math . floor ( Math . random ( ) * 1000 ) ;
118+ const runLogPath = join ( cwd , "run.log" ) ;
119+ console . log ( `[${ runId } ] > PORT=${ port } ${ runCommand } (output written to ${ runLogPath } )` ) ;
120+ const runLogStream = fsExtra . createWriteStream ( runLogPath ) ;
121+
122+ const run = spawn ( runScript , runArgs , {
123+ cwd,
124+ shell : true ,
125+ env : {
126+ NODE_ENV : "production" ,
127+ PORT : port . toString ( ) ,
128+ PATH : process . env . PATH ,
129+ } ,
130+ } ) ;
131+
132+ run . stderr . on ( "data" , ( data ) => {
133+ const output = data . toString ( ) ;
134+ runLogStream . write ( output ) ;
135+ } ) ;
136+
137+ run . stdout . on ( "data" , ( data ) => {
138+ const output = data . toString ( ) ;
139+ runLogStream . write ( output ) ;
140+ // Check for the "Ready in" message to determine when the server is fully started
141+ if ( output . includes ( `Ready in` ) ) {
142+ // We use 0.0.0.0 instead of localhost to avoid issues when ipv6 is not available (Node 18)
143+ resolveHostname ( `http://0.0.0.0:${ port } ` ) ;
144+ }
145+ } ) ;
146+
147+ run . on ( "close" , ( code ) => {
148+ runLogStream . end ( ) ;
149+ if ( code ) {
150+ rejectHostname ( ) ;
151+ }
152+ } ) ;
153+ const host = await hostnamePromise ;
154+
155+ console . log ( "\n\n" ) ;
156+
157+ try {
158+ // Determine which test files to run
159+ const testPattern = scenario . tests
160+ ? scenario . tests . map ( ( test ) => `e2e/${ test } ` ) . join ( " " )
161+ : "e2e/*.spec.ts" ;
162+
163+ console . log (
164+ `> HOST=${ host } SCENARIO=${ scenario . name } ts-mocha -p tsconfig.json ${ testPattern } ` ,
165+ ) ;
166+ await promiseSpawn ( "ts-mocha" , [ "-p" , "tsconfig.json" , ...testPattern . split ( " " ) ] , {
167+ shell : true ,
168+ stdio : "inherit" ,
169+ env : {
170+ ...process . env ,
171+ HOST : host ,
172+ SCENARIO : scenario . name ,
173+ } ,
174+ } ) . finally ( ( ) => {
175+ run . stdin . end ( ) ;
176+ run . kill ( "SIGKILL" ) ;
177+ } ) ;
178+ } catch ( e ) {
179+ errors . push ( e ) ;
180+ }
181+ } catch ( e ) {
182+ console . error ( `Error in scenario ${ scenario . name } :` , e ) ;
183+ errors . push ( e ) ;
184+ }
109185}
110186
111187if ( errors . length ) {
0 commit comments