1
1
import { existsSync , mkdtempSync , realpathSync , rmSync } from "fs" ;
2
+ import crypto from "node:crypto" ;
2
3
import { tmpdir } from "os" ;
3
4
import { join } from "path" ;
5
+ import spawn from "cross-spawn" ;
4
6
import { FrameworkMap } from "frameworks/index" ;
5
7
import { readJSON } from "helpers/files" ;
8
+ import { fetch } from "undici" ;
6
9
import { describe , expect , test , afterEach , beforeEach } from "vitest" ;
7
10
import { keys , runC3 } from "./helpers" ;
8
11
import type { RunnerConfig } from "./helpers" ;
9
12
13
+ export const TEST_PREFIX = "c3-e2e-" ;
14
+
10
15
/*
11
16
Areas for future improvement:
12
- - Make these actually e2e by verifying that deployment works
13
17
- Add support for frameworks with global installs (like docusaurus, gatsby, etc)
14
18
*/
15
19
16
- describe ( "E2E: Web frameworks" , ( ) => {
20
+ type FrameworkTestConfig = RunnerConfig & {
21
+ expectResponseToContain : string ;
22
+ } ;
23
+
24
+ describe ( `E2E: Web frameworks` , ( ) => {
17
25
const tmpDirPath = realpathSync ( mkdtempSync ( join ( tmpdir ( ) , "c3-tests" ) ) ) ;
18
- const projectPath = join ( tmpDirPath , "pages-tests" ) ;
26
+ const baseProjectName = `c3-e2e- ${ crypto . randomBytes ( 3 ) . toString ( "hex" ) } ` ;
19
27
20
- beforeEach ( ( ) => {
28
+ const getProjectName = ( framework : string ) =>
29
+ `${ baseProjectName } -${ framework } ` ;
30
+ const getProjectPath = ( framework : string ) =>
31
+ join ( tmpDirPath , getProjectName ( framework ) ) ;
32
+
33
+ beforeEach ( ( ctx ) => {
34
+ const framework = ctx . meta . name ;
35
+ const projectPath = getProjectPath ( framework ) ;
21
36
rmSync ( projectPath , { recursive : true , force : true } ) ;
22
37
} ) ;
23
38
24
- afterEach ( ( ) => {
39
+ afterEach ( ( ctx ) => {
40
+ const framework = ctx . meta . name ;
41
+ const projectPath = getProjectPath ( framework ) ;
42
+ const projectName = getProjectName ( framework ) ;
43
+
25
44
if ( existsSync ( projectPath ) ) {
26
45
rmSync ( projectPath , { recursive : true } ) ;
27
46
}
47
+
48
+ try {
49
+ const { output } = spawn . sync ( "npx" , [
50
+ "wrangler" ,
51
+ "pages" ,
52
+ "project" ,
53
+ "delete" ,
54
+ "-y" ,
55
+ projectName ,
56
+ ] ) ;
57
+
58
+ if ( ! output . toString ( ) . includes ( `Successfully deleted ${ projectName } ` ) ) {
59
+ console . error ( output . toString ( ) ) ;
60
+ }
61
+ } catch ( error ) {
62
+ console . error ( `Failed to cleanup project: ${ projectName } ` ) ;
63
+ console . error ( error ) ;
64
+ }
28
65
} ) ;
29
66
30
67
const runCli = async (
31
68
framework : string ,
32
- { argv = [ ] , promptHandlers = [ ] , overrides = { } } : RunnerConfig
69
+ { argv = [ ] , promptHandlers = [ ] , overrides } : RunnerConfig
33
70
) => {
71
+ const projectPath = getProjectPath ( framework ) ;
72
+
34
73
const args = [
35
74
projectPath ,
36
75
"--type" ,
37
76
"webFramework" ,
38
77
"--framework" ,
39
78
framework ,
40
- "--no-deploy" ,
79
+ "--deploy" ,
80
+ "--no-open" ,
41
81
] ;
42
82
43
- if ( argv . length > 0 ) {
44
- args . push ( ...argv ) ;
45
- } else {
46
- args . push ( "--no-git" ) ;
47
- }
48
-
49
- // For debugging purposes, uncomment the following to see the exact
50
- // command the test uses. You can then run this via the command line.
51
- // console.log("COMMAND: ", `node ${["./dist/cli.js", ...args].join(" ")}`);
83
+ args . push ( ...argv ) ;
52
84
53
- await runC3 ( { argv : args , promptHandlers } ) ;
85
+ const { output } = await runC3 ( { argv : args , promptHandlers } ) ;
54
86
55
87
// Relevant project files should have been created
56
88
expect ( projectPath ) . toExist ( ) ;
57
-
58
89
const pkgJsonPath = join ( projectPath , "package.json" ) ;
59
90
expect ( pkgJsonPath ) . toExist ( ) ;
60
91
92
+ // Wrangler should be installed
61
93
const wranglerPath = join ( projectPath , "node_modules/wrangler" ) ;
62
94
expect ( wranglerPath ) . toExist ( ) ;
63
95
@@ -68,7 +100,7 @@ describe("E2E: Web frameworks", () => {
68
100
...frameworkConfig . packageScripts ,
69
101
} as Record < string , string > ;
70
102
71
- if ( overrides . packageScripts ) {
103
+ if ( overrides && overrides . packageScripts ) {
72
104
// override packageScripts with testing provided scripts
73
105
Object . entries ( overrides . packageScripts ) . forEach ( ( [ target , cmd ] ) => {
74
106
frameworkTargetPackageScripts [ target ] = cmd ;
@@ -79,46 +111,77 @@ describe("E2E: Web frameworks", () => {
79
111
Object . entries ( frameworkTargetPackageScripts ) . forEach ( ( [ target , cmd ] ) => {
80
112
expect ( pkgJson . scripts [ target ] ) . toEqual ( cmd ) ;
81
113
} ) ;
114
+
115
+ return { output } ;
82
116
} ;
83
117
84
- test . each ( [ "astro" , "hono" , "react" , "remix" , "vue" ] ) ( "%s" , async ( name ) => {
85
- await runCli ( name , { } ) ;
86
- } ) ;
118
+ const runCliWithDeploy = async ( framework : string ) => {
119
+ const projectName = `${ baseProjectName } -${ framework } ` ;
87
120
88
- test ( "Nuxt" , async ( ) => {
89
- await runCli ( "nuxt" , {
90
- overrides : {
91
- packageScripts : {
92
- build : "NITRO_PRESET=cloudflare-pages nuxt build" ,
93
- } ,
94
- } ,
121
+ const { argv , overrides , promptHandlers , expectResponseToContain } =
122
+ frameworkTests [ framework ] ;
123
+
124
+ await runCli ( framework , {
125
+ overrides ,
126
+ promptHandlers ,
127
+ argv : [ ... ( argv ?? [ ] ) , "--deploy" , "--no-git" ] ,
95
128
} ) ;
96
- } ) ;
97
129
98
- test ( "next" , async ( ) => {
99
- await runCli ( "next" , {
130
+ // Verify deployment
131
+ const projectUrl = `https://${ projectName } .pages.dev/` ;
132
+
133
+ const res = await fetch ( projectUrl ) ;
134
+ expect ( res . status ) . toBe ( 200 ) ;
135
+
136
+ const body = await res . text ( ) ;
137
+ expect (
138
+ body ,
139
+ `(${ framework } ) Deployed page (${ projectUrl } ) didn't contain expected string: "${ expectResponseToContain } "`
140
+ ) . toContain ( expectResponseToContain ) ;
141
+ } ;
142
+
143
+ // These are ordered based on speed and reliability for ease of debugging
144
+ const frameworkTests : Record < string , FrameworkTestConfig > = {
145
+ astro : {
146
+ expectResponseToContain : "Hello, Astronaut!" ,
147
+ } ,
148
+ hono : {
149
+ expectResponseToContain : "/api/hello" ,
150
+ } ,
151
+ qwik : {
152
+ expectResponseToContain : "Welcome to Qwik" ,
100
153
promptHandlers : [
101
154
{
102
- matcher : / D o y o u w a n t t o u s e t h e n e x t - o n - p a g e s e s l i n t - p l u g i n \? / ,
103
- input : [ "y" ] ,
155
+ matcher : / Y e s l o o k s g o o d , f i n i s h u p d a t e / ,
156
+ input : [ keys . enter ] ,
104
157
} ,
105
158
] ,
106
- } ) ;
107
- } ) ;
108
-
109
- test ( "qwik" , async ( ) => {
110
- await runCli ( "qwik" , {
159
+ } ,
160
+ remix : {
161
+ expectResponseToContain : "Welcome to Remix" ,
162
+ } ,
163
+ next : {
164
+ expectResponseToContain : "Create Next App" ,
111
165
promptHandlers : [
112
166
{
113
- matcher : / Y e s l o o k s g o o d , f i n i s h u p d a t e / ,
114
- input : [ keys . enter ] ,
167
+ matcher : / D o y o u w a n t t o u s e t h e n e x t - o n - p a g e s e s l i n t - p l u g i n \? / ,
168
+ input : [ "y" ] ,
115
169
} ,
116
170
] ,
117
- } ) ;
118
- } ) ;
119
-
120
- test ( "solid" , async ( ) => {
121
- await runCli ( "solid" , {
171
+ } ,
172
+ nuxt : {
173
+ expectResponseToContain : "Welcome to Nuxt!" ,
174
+ overrides : {
175
+ packageScripts : {
176
+ build : "NITRO_PRESET=cloudflare-pages nuxt build" ,
177
+ } ,
178
+ } ,
179
+ } ,
180
+ react : {
181
+ expectResponseToContain : "React App" ,
182
+ } ,
183
+ solid : {
184
+ expectResponseToContain : "Hello world" ,
122
185
promptHandlers : [
123
186
{
124
187
matcher : / W h i c h t e m p l a t e d o y o u w a n t t o u s e / ,
@@ -133,11 +196,9 @@ describe("E2E: Web frameworks", () => {
133
196
input : [ keys . enter ] ,
134
197
} ,
135
198
] ,
136
- } ) ;
137
- } ) ;
138
-
139
- test ( "svelte" , async ( ) => {
140
- await runCli ( "svelte" , {
199
+ } ,
200
+ svelte : {
201
+ expectResponseToContain : "SvelteKit app" ,
141
202
promptHandlers : [
142
203
{
143
204
matcher : / W h i c h S v e l t e a p p t e m p l a t e / ,
@@ -152,19 +213,21 @@ describe("E2E: Web frameworks", () => {
152
213
input : [ keys . enter ] ,
153
214
} ,
154
215
] ,
155
- } ) ;
156
- } ) ;
216
+ } ,
217
+ vue : {
218
+ expectResponseToContain : "Vite App" ,
219
+ } ,
220
+ } ;
221
+
222
+ test . concurrent . each ( Object . keys ( frameworkTests ) ) (
223
+ "%s" ,
224
+ async ( name ) => {
225
+ await runCliWithDeploy ( name ) ;
226
+ } ,
227
+ { retry : 3 }
228
+ ) ;
157
229
158
- // This test blows up in CI due to Github providing an unusual git user email address.
159
- // E.g.
160
- // ```
161
- // fatal: empty ident name (for <[email protected] .
162
- // internal.cloudapp.net>) not allowed
163
- // ```
164
230
test . skip ( "Hono (wrangler defaults)" , async ( ) => {
165
231
await runCli ( "hono" , { argv : [ "--wrangler-defaults" ] } ) ;
166
-
167
- // verify that wrangler-defaults defaults to `true` for using git
168
- expect ( join ( projectPath , ".git" ) ) . toExist ( ) ;
169
232
} ) ;
170
233
} ) ;
0 commit comments