diff --git a/packages/wb/src/commands/test.ts b/packages/wb/src/commands/test.ts index 7ae121a1..42871977 100644 --- a/packages/wb/src/commands/test.ts +++ b/packages/wb/src/commands/test.ts @@ -42,6 +42,10 @@ const builder = { description: 'Reduce redundant outputs', type: 'boolean', }, + bail: { + description: 'Stop tests after the first failure', + type: 'boolean', + }, 'unit-timeout': { description: 'Timeout for unit tests', type: 'number', diff --git a/packages/wb/src/scripts/execution/baseScripts.ts b/packages/wb/src/scripts/execution/baseScripts.ts index ca1d417f..5cb9f3b3 100644 --- a/packages/wb/src/scripts/execution/baseScripts.ts +++ b/packages/wb/src/scripts/execution/baseScripts.ts @@ -101,7 +101,7 @@ export abstract class BaseScripts { ): Promise { const port = await checkAndKillPortProcess(project.env.PORT, project); const suffix = project.packageJson.scripts?.['test/e2e-additional'] ? ' && YARN test/e2e-additional' : ''; - const playwrightCommand = buildPlaywrightCommand(playwrightArgs, argv.targets); + const playwrightCommand = buildPlaywrightCommand(playwrightArgs, argv.targets, argv.bail); if (project.skipLaunchingServerForPlaywright) { return `${playwrightCommand}${suffix}`; } @@ -116,10 +116,12 @@ export abstract class BaseScripts { testUnit(project: Project, argv: TestArgv): string { const testTarget = argv.targets?.join(' ') || 'test/unit/'; if (project.hasVitest) { + const bailOption = argv.bail ? ' --bail=1' : ''; // Since this command is referred from other commands, we have to use "vitest run" (non-interactive mode). - return `YARN vitest run ${testTarget} --color --passWithNoTests --allowOnly`; + return `YARN vitest run ${testTarget} --color --passWithNoTests --allowOnly${bailOption}`; } else if (project.isBunAvailable) { - return `bun test ${testTarget}`; + const bailOption = argv.bail ? ' --bail' : ''; + return `bun test ${testTarget}${bailOption}`; } return 'echo "No tests."'; } @@ -151,11 +153,11 @@ function findEcosystemConfigPath(project: Project): string | undefined { } } -function buildPlaywrightCommand(playwrightArgs: string, targets: TestArgv['targets']): string { +function buildPlaywrightCommand(playwrightArgs: string, targets: TestArgv['targets'], bail?: boolean): string { const base = 'BUN playwright'; const target = targets?.join(' ') || 'test/e2e/'; if (!playwrightArgs.startsWith('test ') || !targets?.length) { - return `${base} ${playwrightArgs}`; + return appendPlaywrightBailOption(`${base} ${playwrightArgs}`, bail); } const rest = playwrightArgs.slice('test '.length).trim(); @@ -165,5 +167,15 @@ function buildPlaywrightCommand(playwrightArgs: string, targets: TestArgv['targe } else { parts[0] = target; } - return `${base} test ${parts.join(' ')}`; + return appendPlaywrightBailOption(`${base} test ${parts.join(' ')}`, bail); +} + +function appendPlaywrightBailOption(command: string, bail?: boolean): string { + if (!bail || !command.includes('playwright test')) { + return command; + } + if (/--max-failures(?:=|\s)/.test(command)) { + return command; + } + return `${command} --max-failures=1`; } diff --git a/packages/wb/src/scripts/execution/httpServerScripts.ts b/packages/wb/src/scripts/execution/httpServerScripts.ts index 7ec608d0..6743ef34 100644 --- a/packages/wb/src/scripts/execution/httpServerScripts.ts +++ b/packages/wb/src/scripts/execution/httpServerScripts.ts @@ -22,11 +22,12 @@ class HttpServerScripts extends BaseScripts { const port = await checkAndKillPortProcess(project.env.PORT, project); const suffix = project.packageJson.scripts?.['test/e2e-additional'] ? ' && YARN test/e2e-additional' : ''; const testTarget = argv.targets && argv.targets.length > 0 ? argv.targets.join(' ') : 'test/e2e/'; + const bailOption = argv.bail ? ' --bail=1' : ''; return `YARN concurrently --kill-others --raw --success first "${startCommand} && exit 1" "wait-on -t 600000 -i 2000 http-get://127.0.0.1:${port} - && vitest run ${testTarget} --color --passWithNoTests --allowOnly${suffix}"`; + && vitest run ${testTarget} --color --passWithNoTests --allowOnly${bailOption}${suffix}"`; } }