chore: kanban template
This commit is contained in:
126
e2e/tests/support/hooks.ts
Normal file
126
e2e/tests/support/hooks.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Before,
|
||||
After,
|
||||
BeforeAll,
|
||||
AfterAll,
|
||||
setDefaultTimeout,
|
||||
} from '@cucumber/cucumber'
|
||||
import { spawn, type ChildProcess } from 'child_process'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import type { AppWorld } from './world.js'
|
||||
import { config } from './types.js'
|
||||
|
||||
setDefaultTimeout(config.responseTimeout)
|
||||
|
||||
let backendProcess: ChildProcess | undefined
|
||||
let frontendProcess: ChildProcess | undefined
|
||||
|
||||
async function waitForUrl(url: string, label: string, timeoutMs = 60_000) {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
if (res.status < 500) return
|
||||
} catch {
|
||||
// not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 500))
|
||||
}
|
||||
throw new Error(`[e2e] ${label} did not become ready at ${url} within ${timeoutMs}ms`)
|
||||
}
|
||||
|
||||
BeforeAll(async function () {
|
||||
if (!config.manageServers) {
|
||||
// Caller is expected to start backend + frontend (e.g. via pm2, docker,
|
||||
// or a separate `yarn dev` terminal) before running tests.
|
||||
return
|
||||
}
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = resolve(__dirname, '../../..')
|
||||
|
||||
// Project-specific: override these commands by setting E2E_BACKEND_CMD /
|
||||
// E2E_FRONTEND_CMD env vars. The defaults assume `yarn dev` at the repo
|
||||
// root spawns the frontend, and the project provides a `dev:backend`
|
||||
// script for the API.
|
||||
const backendCmd = process.env.E2E_BACKEND_CMD ?? 'yarn dev:backend'
|
||||
const frontendCmd = process.env.E2E_FRONTEND_CMD ?? 'yarn dev'
|
||||
|
||||
backendProcess = spawn(backendCmd, {
|
||||
cwd: repoRoot,
|
||||
env: { ...process.env, NODE_ENV: 'test' },
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
detached: true,
|
||||
})
|
||||
backendProcess.stderr?.on('data', (d: Buffer) =>
|
||||
process.stderr.write(`[backend] ${d}`),
|
||||
)
|
||||
backendProcess.stdout?.on('data', (d: Buffer) =>
|
||||
process.stdout.write(`[backend] ${d}`),
|
||||
)
|
||||
|
||||
frontendProcess = spawn(frontendCmd, {
|
||||
cwd: repoRoot,
|
||||
env: { ...process.env, NODE_ENV: 'test' },
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
detached: true,
|
||||
})
|
||||
frontendProcess.stderr?.on('data', (d: Buffer) =>
|
||||
process.stderr.write(`[frontend] ${d}`),
|
||||
)
|
||||
frontendProcess.stdout?.on('data', (d: Buffer) =>
|
||||
process.stdout.write(`[frontend] ${d}`),
|
||||
)
|
||||
|
||||
await Promise.all([
|
||||
waitForUrl(config.apiUrl, 'backend'),
|
||||
waitForUrl(config.appUrl, 'frontend'),
|
||||
])
|
||||
})
|
||||
|
||||
AfterAll(async function () {
|
||||
for (const p of [backendProcess, frontendProcess]) {
|
||||
if (!p?.pid) continue
|
||||
try {
|
||||
// Detached + pgid kill takes the whole tree (vite/wrangler spawn children).
|
||||
process.kill(-p.pid, 'SIGTERM')
|
||||
} catch {
|
||||
try {
|
||||
p.kill('SIGTERM')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Before(async function (this: AppWorld) {
|
||||
await this.openBrowser()
|
||||
if (config.resetUrl) {
|
||||
const body = config.resetRpcName
|
||||
? JSON.stringify({ rpcName: config.resetRpcName, data: {} })
|
||||
: undefined
|
||||
await fetch(config.resetUrl, {
|
||||
method: 'POST',
|
||||
headers: body ? { 'content-type': 'application/json' } : undefined,
|
||||
body,
|
||||
})
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
process.stderr.write(
|
||||
`[e2e] WARN: reset hook ${config.resetUrl} returned ${res.status}\n`,
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
process.stderr.write(`[e2e] WARN: reset hook ${config.resetUrl} unreachable\n`)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
After(async function (this: AppWorld) {
|
||||
await this.closeBrowser()
|
||||
})
|
||||
Reference in New Issue
Block a user