(
opts: RunTestRunAllOptions,
deps: TestDeps = {},
)
| 4975 | * With `--wait`, polls each accepted runId. |
| 4976 | */ |
| 4977 | export async function runTestRunAll( |
| 4978 | opts: RunTestRunAllOptions, |
| 4979 | deps: TestDeps = {}, |
| 4980 | ): Promise<BatchRunFreshResponse | undefined> { |
| 4981 | assertIdempotencyKey(opts.idempotencyKey); |
| 4982 | requireProjectId(opts.projectId); |
| 4983 | if ( |
| 4984 | !Number.isInteger(opts.maxConcurrency) || |
| 4985 | opts.maxConcurrency < 1 || |
| 4986 | opts.maxConcurrency > MAX_BATCH_CONCURRENCY |
| 4987 | ) { |
| 4988 | throw localValidationError('max-concurrency', 'must be an integer between 1 and 100'); |
| 4989 | } |
| 4990 | |
| 4991 | const stderrFn = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 4992 | const out = makeOutput(opts.output, deps); |
| 4993 | |
| 4994 | // --- Dry-run path --- |
| 4995 | if (opts.dryRun) { |
| 4996 | const idempotencyKey = opts.idempotencyKey ?? `dry-run-${randomUUID()}`; |
| 4997 | const batchRunSample = findSample('POST', '/api/cli/v1/tests/batch/run')?.body(); |
| 4998 | const envelope = { |
| 4999 | dryRun: true, |
| 5000 | method: 'POST', |
| 5001 | path: '/api/cli/v1/tests/batch/run', |
| 5002 | body: { |
| 5003 | projectId: opts.projectId, |
| 5004 | testIds: opts.nameFilter ? ['<filtered by --filter>'] : undefined, |
| 5005 | source: 'cli' as const, |
| 5006 | }, |
| 5007 | idempotencyKey, |
| 5008 | ...(opts.wait ? { thenPoll: '/api/cli/v1/runs/<run-id>?waitSeconds=25' } : {}), |
| 5009 | }; |
| 5010 | out.print(batchRunSample ?? envelope); |
| 5011 | return undefined; |
| 5012 | } |
| 5013 | |
| 5014 | // D4: under --wait, raise per-request timeout to cover --timeout. |
| 5015 | const client = makeClient({ ...opts, requestTimeoutMs: resolveWaitRequestTimeoutMs(opts) }, deps); |
| 5016 | |
| 5017 | // Portal deep links for batch output: every test in the batch belongs to |
| 5018 | // opts.projectId, so per-item dashboardUrl needs no extra wire data. The |
| 5019 | // project-level URL closes out text-mode output ("watch the wave here"). |
| 5020 | // Both stay undefined for unknown API hosts (resolvePortalBase contract). |
| 5021 | const batchApiUrl = resolveApiUrl(opts, deps); |
| 5022 | const batchPortalBase = resolvePortalBase(batchApiUrl); |
| 5023 | const projectDashboardUrl = |
| 5024 | batchPortalBase === undefined |
| 5025 | ? undefined |
| 5026 | : `${batchPortalBase}/dashboard/tests/${encodeURIComponent(opts.projectId)}`; |
| 5027 | const withBatchDashboardUrl = <T extends { testId: string }>(item: T): T => { |
| 5028 | const dashboardUrl = resolvePortalUrl(batchApiUrl, opts.projectId, item.testId); |
| 5029 | return dashboardUrl !== undefined ? { ...item, dashboardUrl } : item; |
| 5030 | }; |
| 5031 | |
| 5032 | const idempotencyKey = opts.idempotencyKey ?? `cli-batch-run-fresh-${randomUUID()}`; |
| 5033 | if (opts.idempotencyKey === undefined && opts.debug) { |
| 5034 | stderrFn(`idempotency-key: ${idempotencyKey}`); |
no test coverage detected