-
Notifications
You must be signed in to change notification settings - Fork 452
chore(ci): fix flakey integration tests #7958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f0fb392
49f0934
3e1052d
932672a
24f03db
3d7750a
70ac7ad
516de52
7e66582
5c987b4
76d00cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,18 +29,16 @@ | |
| pathname?: string | undefined | ||
| siteUrl: string | ||
| }) => { | ||
| const response = await fetch(`${siteUrl}${pathname}`, { headers }) | ||
| const url = `${siteUrl}${pathname}` | ||
| const response = await fetch(url, { headers }) | ||
| const body = await response.text() | ||
| const requestId = response.headers.get('x-nf-request-id') ?? '' | ||
| if (content === undefined) { | ||
| expect(response.status).toBe(404) | ||
| return | ||
| } | ||
| expect(response.status, `status should be 200. request id: ${response.headers.get('x-nf-request-id') ?? ''}`).toBe( | ||
| 200, | ||
| ) | ||
| expect(body, `body should be as expected. request id: ${response.headers.get('x-nf-request-id') ?? ''}`).toEqual( | ||
| content, | ||
| ) | ||
| expect(response.status, `status should be 200. url: ${url} request id: ${requestId}`).toBe(200) | ||
| expect(body, `body should be as expected. url: ${url} request id: ${requestId}`).toEqual(content) | ||
| } | ||
|
|
||
| type Deploy = { | ||
|
|
@@ -57,6 +55,15 @@ | |
| logs: string | ||
| function_logs: string | ||
| edge_function_logs: string | ||
| source_zip_filename?: string | ||
| } | ||
|
|
||
| const parseDeploy = (output: string): Deploy => { | ||
| try { | ||
| return JSON.parse(output) | ||
| } catch { | ||
| throw new Error(`Failed to parse deploy output as JSON. Raw output:\n${output}`) | ||
| } | ||
| } | ||
|
|
||
| const validateDeploy = async ({ | ||
|
|
@@ -93,7 +100,7 @@ | |
| vi.setConfig({ maxConcurrency: 3 }) | ||
|
|
||
| describe.skipIf(disableLiveTests).concurrent('commands/deploy', { timeout: 300_000 }, () => { | ||
| beforeAll(async () => { | ||
|
Check failure on line 103 in tests/integration/commands/deploy/deploy.test.ts
|
||
| const { account, siteId } = await createLiveTestSite(SITE_NAME) | ||
| context.siteId = siteId | ||
| context.account = account | ||
|
|
@@ -101,8 +108,9 @@ | |
|
|
||
| afterAll(async () => { | ||
| const { siteId } = context | ||
| console.log(`deleting test site "${SITE_NAME}". ${siteId}`) | ||
| await callCli(['sites:delete', siteId, '--force']) | ||
| // TODO: temporarily disabled to debug deploy test failures — re-enable after investigation | ||
| console.log(`skipping deletion of test site "${SITE_NAME}". ${siteId}`) | ||
| // await callCli(['sites:delete', siteId, '--force']) | ||
| }) | ||
|
|
||
| test('should deploy project when dir flag is passed', async (t) => { | ||
|
|
@@ -118,7 +126,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build', '--dir', 'public'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| }) | ||
|
|
@@ -142,7 +150,7 @@ | |
|
|
||
| const deploy = await callCli(['deploy', '--json', '--no-build', '--site', SITE_NAME], { | ||
| cwd: builder.directory, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| }) | ||
|
|
@@ -167,7 +175,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| }) | ||
|
|
@@ -214,9 +222,7 @@ | |
| if (shouldRunBuildBeforeDeploy) { | ||
| await callCli(['build'], options) | ||
| } | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then((output: string) => | ||
| JSON.parse(output), | ||
| ) | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then(parseDeploy) | ||
|
|
||
| // give edge functions manifest a couple ticks to propagate | ||
| await pause(500) | ||
|
|
@@ -264,9 +270,7 @@ | |
| if (shouldRunBuildBeforeDeploy) { | ||
| await callCli(['build', '--cwd', pathPrefix], options) | ||
| } | ||
| const deploy = await callCli(['deploy', '--json', '--no-build', '--cwd', pathPrefix], options).then( | ||
| (output: string) => JSON.parse(output), | ||
| ) | ||
| const deploy = await callCli(['deploy', '--json', '--no-build', '--cwd', pathPrefix], options).then(parseDeploy) | ||
|
|
||
| // give edge functions manifest a couple ticks to propagate | ||
| await pause(500) | ||
|
|
@@ -312,9 +316,7 @@ | |
| if (shouldRunBuildBeforeDeploy) { | ||
| await callCli(['build'], options) | ||
| } | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then((output: string) => | ||
| JSON.parse(output), | ||
| ) | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then(parseDeploy) | ||
|
|
||
| // give edge functions manifest a couple ticks to propagate | ||
| await pause(500) | ||
|
|
@@ -359,9 +361,7 @@ | |
| } | ||
|
|
||
| // skipping running build here, because it cleans up frameworks API directories | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then((output: string) => | ||
| JSON.parse(output), | ||
| ) | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then(parseDeploy) | ||
|
|
||
| // give edge functions manifest a couple ticks to propagate | ||
| await pause(500) | ||
|
|
@@ -567,7 +567,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build', '--dir', 'public'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty('logs', `https://app.netlify.com/projects/${SITE_NAME}/deploys/${deploy.deploy_id}`) | ||
|
|
@@ -594,7 +594,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build', '--dir', 'public', '--prod'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty('logs', `https://app.netlify.com/projects/${SITE_NAME}/deploys/${deploy.deploy_id}`) | ||
|
|
@@ -720,7 +720,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' }) | ||
| await validateContent({ | ||
|
|
@@ -765,7 +765,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' }) | ||
| await validateContent({ | ||
|
|
@@ -800,7 +800,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content: 'index' }) | ||
| await validateContent({ | ||
|
|
@@ -860,14 +860,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const response = await fetch(`${deployUrl}/.netlify/functions/hello`) | ||
| t.expect(await response.text()).toEqual('Hello') | ||
|
|
@@ -970,14 +966,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| // Add retry logic for fetching deployed functions | ||
| const fetchWithRetry = async (url: string, maxRetries = 5) => { | ||
|
|
@@ -1031,14 +1023,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
| const response = await fetch(`${deployUrl}/.netlify/functions/func-1`).then((res) => res.text()) | ||
|
|
||
| t.expect(response).toEqual('Internal') | ||
|
|
@@ -1089,23 +1077,18 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const deploy = (await callCli( | ||
| ['deploy', '--json'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const deploy = await callCli(['deploy', '--json'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const fullDeploy = (await callCli( | ||
| const fullDeploy = await callCli( | ||
| ['api', 'getDeploy', '--data', JSON.stringify({ deploy_id: deploy.deploy_id })], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| ).then(parseDeploy) | ||
|
Comment on lines
+1106
to
+1112
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n "getDeploy" --type ts -C 3Repository: netlify/cli Length of output: 6885 🏁 Script executed: rg -n "parseDeploy" --type ts -C 5Repository: netlify/cli Length of output: 27567 🏁 Script executed: rg -n "getDeploy" --type ts -A 10 src/commands/apiRepository: netlify/cli Length of output: 37 🏁 Script executed: rg -n "getDeploy|api.*command" --type ts src/commands | head -50Repository: netlify/cli Length of output: 3804 🏁 Script executed: cat -n src/commands/api/api.ts | head -100Repository: netlify/cli Length of output: 2224 🏁 Script executed: cat -n tests/integration/commands/deploy/deploy.test.ts | head -100Repository: netlify/cli Length of output: 3564 🏁 Script executed: rg -n "logJson|--json" src/commands/deploy/deploy.ts -B 3 -A 3 | head -50Repository: netlify/cli Length of output: 950 🏁 Script executed: rg -n "jsonData\s*=" src/commands/deploy/deploy.ts -B 5 -A 15 | head -100Repository: netlify/cli Length of output: 37 🏁 Script executed: sed -n '800,850p' src/commands/deploy/deploy.tsRepository: netlify/cli Length of output: 1965 🏁 Script executed: rg -n "type JsonData|interface JsonData" src/commands/deploy/deploy.ts -B 2 -A 15Repository: netlify/cli Length of output: 419 🏁 Script executed: sed -n '1094,1115p' tests/integration/commands/deploy/deploy.test.tsRepository: netlify/cli Length of output: 1079 🏁 Script executed: rg -n "summary" tests/integration/commands/deploy/deploy.test.ts | head -20Repository: netlify/cli Length of output: 194 🏁 Script executed: rg -n "const deploy = await callCli\(\['deploy'" tests/integration/commands/deploy/deploy.test.ts -A 3 | head -40Repository: netlify/cli Length of output: 1735 🏁 Script executed: rg -n "@netlify/api" src/commands/api/api.tsRepository: netlify/cli Length of output: 112 🏁 Script executed: sed -n '44,59p' tests/integration/commands/deploy/deploy.test.tsRepository: netlify/cli Length of output: 337 The The test's Consider defining a separate type for the API response or transforming the API response to match the expected structure. 🤖 Prompt for AI Agents |
||
|
|
||
| const redirectsMessage = fullDeploy.summary.messages.find(({ title }) => title === '3 redirect rules processed') | ||
| t.expect(redirectsMessage).toBeDefined() | ||
|
|
@@ -1174,14 +1157,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json', '--no-build'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
| const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) | ||
| expect(response).toEqual('Pre-bundled') | ||
| }) | ||
|
|
@@ -1233,14 +1212,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json', '--no-build', '--skip-functions-cache'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as Deploy | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json', '--no-build', '--skip-functions-cache'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) | ||
| t.expect(response).toEqual('Bundled at deployment') | ||
|
|
@@ -1294,14 +1269,10 @@ | |
| }) | ||
| .build() | ||
|
|
||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json', '--no-build'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as { deploy_url: string } | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) | ||
| t.expect(response).toEqual('Bundled at deployment') | ||
|
|
@@ -1353,14 +1324,10 @@ | |
| .build() | ||
|
|
||
| await execa.command('npm install', { cwd: builder.directory }) | ||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json', '--no-build'], | ||
| { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as { deploy_url: string } | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json', '--no-build'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const response = await fetch(`${deployUrl}/read-blob`).then((res) => res.text()) | ||
| t.expect(response).toEqual('hello from the blob') | ||
|
|
@@ -1374,14 +1341,10 @@ | |
| timeout: 300_000, | ||
| }, | ||
| async ({ fixture }) => { | ||
| const { deploy_url: deployUrl } = (await callCli( | ||
| ['deploy', '--json'], | ||
| { | ||
| cwd: fixture.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| true, | ||
| )) as unknown as { deploy_url: string } | ||
| const { deploy_url: deployUrl } = await callCli(['deploy', '--json'], { | ||
| cwd: fixture.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then(parseDeploy) | ||
|
|
||
| const html = await fetch(deployUrl).then((res) => res.text()) | ||
| const $ = load(html) | ||
|
|
@@ -1422,7 +1385,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build', '--dir', 'public', '--draft'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty( | ||
|
|
@@ -1486,7 +1449,7 @@ | |
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }, | ||
| ).then((output: string) => JSON.parse(output)) | ||
| ).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty( | ||
|
|
@@ -1515,7 +1478,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--no-build', '--dir', 'public', '--upload-source-zip'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty('source_zip_filename') | ||
|
|
@@ -1550,7 +1513,7 @@ | |
| const deploy = await callCli(['deploy', '--json', '--dir', 'public', '--upload-source-zip'], { | ||
| cwd: builder.directory, | ||
| env: { NETLIFY_SITE_ID: context.siteId }, | ||
| }).then((output: string) => JSON.parse(output)) | ||
| }).then(parseDeploy) | ||
|
|
||
| await validateDeploy({ deploy, siteName: SITE_NAME, content }) | ||
| expect(deploy).toHaveProperty('source_zip_filename') | ||
|
|
@@ -1614,9 +1577,7 @@ | |
| } | ||
|
|
||
| await callCli(['build'], options) | ||
| const deploy = (await callCli(['deploy', '--json', '--no-build'], options).then((output: string) => | ||
| JSON.parse(output), | ||
| )) as Deploy | ||
| const deploy = await callCli(['deploy', '--json', '--no-build'], options).then(parseDeploy) | ||
|
|
||
| await pause(500) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t skip site deletion by default — it will leak resources in CI.
Leaving test sites undeleted can accumulate and hit quotas or clutter shared accounts. Gate the skip behind an explicit env flag and keep deletion as the default behavior.
🧹 Suggested safeguard
afterAll(async () => { const { siteId } = context - // TODO: temporarily disabled to debug deploy test failures — re-enable after investigation - console.log(`skipping deletion of test site "${SITE_NAME}". ${siteId}`) - // await callCli(['sites:delete', siteId, '--force']) + if (process.env.NETLIFY_TEST_SKIP_SITE_DELETE === 'true') { + console.log(`skipping deletion of test site "${SITE_NAME}". ${siteId}`) + return + } + await callCli(['sites:delete', siteId, '--force']) })📝 Committable suggestion
🤖 Prompt for AI Agents