mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-31 02:04:40 +08:00
b92df828ab
* ci: parallelize and isolate test suites * ci: avoid unavailable Playwright video runtime * ci: shard coverage and cache docker smoke * ci: balance Playwright shards by test * ci: smoke test the CLI container * ci: enforce merged coverage thresholds * ci: ratchet canonical coverage baseline * ci: make coverage ratchet lossless * ci: organize parallel gates by responsibility * perf(ci): reduce total runner time * perf(ci): balance runner cost and latency * perf(ci): suppress passing test logs * fix(test): make coverage sorting proof deterministic * perf(docker): exclude test-only build inputs * perf(ci): scope Docker smokes to packaging changes * refactor(test): enforce fast test boundaries * test: isolate coverage ownership * perf(test): run backend projects concurrently * perf(ci): separate test layers by runtime * perf(test): separate integration boundaries * perf(ci): prioritize test runners * docs(ci): clarify package scheduling * test: restore shared Cloudflare mocks * fix(preview): isolate Cloudflare E2E build config * fix(auth): bind preview sessions to request origin * revert: remove ineffective preview auth workaround * fix(auth): stop signing JWTs on session reads
98 lines
3.0 KiB
JavaScript
98 lines
3.0 KiB
JavaScript
import { spawn } from 'node:child_process'
|
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
import { createServer } from 'node:net'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { createRequire } from 'node:module'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const stateDir = mkdtempSync(join(tmpdir(), 'zpan-e2e-'))
|
|
const shardArg = process.argv.slice(2).find((arg) => arg.startsWith('--shard='))
|
|
const artifactSuffix = `${shardArg?.slice('--shard='.length).replace('/', '-of-') ?? 'run'}-${process.pid}`
|
|
const [appPort, apiPort, s3Port, cloudPort] = await Promise.all([
|
|
reservePort(),
|
|
reservePort(),
|
|
reservePort(),
|
|
reservePort(),
|
|
])
|
|
|
|
const appOrigin = `http://127.0.0.1:${appPort}`
|
|
const cloudOrigin = `http://127.0.0.1:${cloudPort}`
|
|
const env = {
|
|
...process.env,
|
|
DATABASE_URL: join(stateDir, 'zpan.db'),
|
|
E2E_STATE_DIR: stateDir,
|
|
E2E_ARTIFACT_SUFFIX: artifactSuffix,
|
|
E2E_APP_PORT: String(appPort),
|
|
E2E_API_PORT: String(apiPort),
|
|
E2E_S3_MOCK_PORT: String(s3Port),
|
|
E2E_CLOUD_FAKE_PORT: String(cloudPort),
|
|
E2E_BASE_URL: appOrigin,
|
|
E2E_LOCAL_BASE_URL: appOrigin,
|
|
E2E_PUBLIC_BASE_URL: appOrigin,
|
|
E2E_S3_MOCK: '1',
|
|
E2E_CLOUD_FAKE: '1',
|
|
E2E_STORAGE_ENDPOINT: `http://127.0.0.1:${s3Port}`,
|
|
E2E_STORAGE_BUCKET: 'e2e-test',
|
|
E2E_STORAGE_REGION: 'auto',
|
|
E2E_STORAGE_ACCESS_KEY: 'e2e-access-key',
|
|
E2E_STORAGE_SECRET_KEY: 'e2e-secret-key',
|
|
ZPAN_CLOUD_URL: cloudOrigin,
|
|
VITE_ZPAN_CLOUD_URL: cloudOrigin,
|
|
ZPAN_LICENSE_PUBLIC_KEYS: 'k4.public.H2gYKGNtxgWbMuwgPdDuHoM_sOLzFC-khe23pz2IZfM',
|
|
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET ?? 'e2e-auth-secret-that-is-at-least-32-characters',
|
|
BETTER_AUTH_URL: appOrigin,
|
|
TRUSTED_ORIGINS: appOrigin,
|
|
}
|
|
|
|
try {
|
|
if (process.env.E2E_RUNTIME === 'cf') {
|
|
await run(
|
|
process.execPath,
|
|
[
|
|
require.resolve('wrangler/bin/wrangler.js'),
|
|
'd1',
|
|
'migrations',
|
|
'apply',
|
|
'DB',
|
|
'--local',
|
|
'--persist-to',
|
|
stateDir,
|
|
],
|
|
{ ...env, CI: 'true' },
|
|
['ignore', 'ignore', 'inherit'],
|
|
)
|
|
}
|
|
await run(process.execPath, [require.resolve('@playwright/test/cli'), 'test', ...process.argv.slice(2)], env)
|
|
} finally {
|
|
rmSync(stateDir, { recursive: true, force: true })
|
|
}
|
|
|
|
function reservePort() {
|
|
return new Promise((resolve, reject) => {
|
|
const server = createServer()
|
|
server.unref()
|
|
server.on('error', reject)
|
|
server.listen(0, '127.0.0.1', () => {
|
|
const address = server.address()
|
|
if (!address || typeof address === 'string') {
|
|
server.close()
|
|
reject(new Error('Could not allocate an E2E port'))
|
|
return
|
|
}
|
|
server.close((error) => (error ? reject(error) : resolve(address.port)))
|
|
})
|
|
})
|
|
}
|
|
|
|
function run(command, args, childEnv, stdio = 'inherit') {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, { env: childEnv, stdio })
|
|
child.on('error', reject)
|
|
child.on('exit', (code, signal) => {
|
|
if (code === 0) resolve()
|
|
else reject(new Error(`Playwright exited with ${signal ?? code}`))
|
|
})
|
|
})
|
|
}
|