fix(ci): initialize cloud e2e command errors

This commit is contained in:
saltbo
2026-07-31 11:03:29 -04:00
parent 1f3e9b4bf5
commit 4a9588e551
3 changed files with 28 additions and 11 deletions
+7
View File
@@ -3,6 +3,13 @@ const CLIENT_TRANSPORT_FAILURE = /(?:\b502\b|ERR_(?:FAILED|TUNNEL_CONNECTION_FAI
const TUNNEL_CONTEXT_CANCELED = /(?:Incoming request ended abruptly|Request failed)[^\n]*context canceled/i
const QUICK_TUNNEL_REQUEST = /trycloudflare\.com/i
export class CloudE2eCommandError extends Error {
constructor(command, commandArgs, code, output) {
super(`${command} ${commandArgs.join(' ')} exited with ${code}`)
this.output = output
}
}
export function isRetryableQuickTunnelFailure({ commandOutput, tunnelOutput }) {
if (QUICK_TUNNEL_502.test(commandOutput)) return true
return (
+14 -1
View File
@@ -1,7 +1,20 @@
import { describe, expect, it } from 'vitest'
import { cloudE2eAttemptCount, isRetryableQuickTunnelFailure } from './cloud-e2e-resilience.mjs'
import {
CloudE2eCommandError,
cloudE2eAttemptCount,
isRetryableQuickTunnelFailure,
} from './cloud-e2e-resilience.mjs'
describe('cloud E2E resilience', () => {
it('provides the command error type before the runner executes', () => {
const error = new CloudE2eCommandError('node', ['playwright', 'test'], 1, 'gateway response')
expect(error).toBeInstanceOf(Error)
expect(error).toBeInstanceOf(CloudE2eCommandError)
expect(error.message).toBe('node playwright test exited with 1')
expect(error.output).toBe('gateway response')
})
it('retries a Cloudflare Quick Tunnel gateway page', () => {
expect(
isRetryableQuickTunnelFailure({
+7 -10
View File
@@ -2,7 +2,11 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { spawn } from 'node:child_process'
import { Resolver } from 'node:dns/promises'
import { createRequire } from 'node:module'
import { cloudE2eAttemptCount, isRetryableQuickTunnelFailure } from './cloud-e2e-resilience.mjs'
import {
CloudE2eCommandError,
cloudE2eAttemptCount,
isRetryableQuickTunnelFailure,
} from './cloud-e2e-resilience.mjs'
const args = process.argv.slice(2)
const require = createRequire(import.meta.url)
@@ -49,7 +53,7 @@ for (let attempt = 1; attempt <= maxRunAttempts; attempt += 1) {
break
} catch (error) {
const retryable =
error instanceof CommandError &&
error instanceof CloudE2eCommandError &&
tunnel &&
isRetryableQuickTunnelFailure({
commandOutput: error.output,
@@ -227,13 +231,6 @@ async function waitForPublicTunnelIp(hostname) {
throw new Error(`Timed out waiting for public tunnel DNS: ${hostname}`)
}
class CommandError extends Error {
constructor(command, commandArgs, code, output) {
super(`${command} ${commandArgs.join(' ')} exited with ${code}`)
this.output = output
}
}
function run(command, commandArgs, env = {}, captureOutput = false) {
return new Promise((resolve, reject) => {
const child = spawn(command, commandArgs, {
@@ -253,7 +250,7 @@ function run(command, commandArgs, env = {}, captureOutput = false) {
}
child.on('exit', (code) => {
if (code === 0) resolve()
else reject(new CommandError(command, commandArgs, code, output))
else reject(new CloudE2eCommandError(command, commandArgs, code, output))
})
})
}