diff --git a/packages/testing/playwright/fixtures/base.ts b/packages/testing/playwright/fixtures/base.ts index f52a3184afb..f39b70b9664 100644 --- a/packages/testing/playwright/fixtures/base.ts +++ b/packages/testing/playwright/fixtures/base.ts @@ -47,6 +47,7 @@ type WorkerFixtures = { backendUrl: string; frontendUrl: string; dbSetup: undefined; + n8nStackConfig: N8NConfig; n8nContainer: N8NStack; capability?: CapabilityOption; }; @@ -54,6 +55,25 @@ type WorkerFixtures = { type CapabilityOption = Capability | N8NConfig; type ProjectUse = { containerConfig?: N8NConfig }; +function parseGlobalTestEnv(): Record { + const raw = process.env.N8N_TEST_ENV; + if (!raw) return {}; + try { + return JSON.parse(raw) as Record; + } catch { + console.warn('[base.ts] Failed to parse N8N_TEST_ENV'); + return {}; + } +} + +function logKeepalive(container: N8NStack): void { + console.log('\n=== KEEPALIVE: Containers left running for debugging ==='); + console.log(` URL: ${container.baseUrl}`); + console.log(` Project: ${container.projectName}`); + console.log(' Cleanup: pnpm --filter n8n-containers stack:clean:all'); + console.log('=========================================================\n'); +} + export const test = base.extend< TestFixtures & CurrentsFixtures & ObservabilityTestFixtures & QuarantineTestFixtures, WorkerFixtures & CurrentsWorkerFixtures & QuarantineWorkerFixtures @@ -68,15 +88,11 @@ export const test = base.extend< // Option for test.use({ capability: 'proxy' }) - transformed into N8NStack by n8nContainer capability: [undefined, { scope: 'worker', option: true }], - // Creates container from: project.containerConfig (base) + capability (override) - // When N8N_BASE_URL is set, skips container creation for local testing - n8nContainer: [ + // Resolves the effective N8NConfig from project.containerConfig (base) + + // capability (override) + N8N_TEST_ENV (global). Topology-neutral: it + // always produces a config, even when a container will not be provisioned. + n8nStackConfig: [ async ({ capability }, use, workerInfo) => { - if (getBackendUrl()) { - await use(null!); - return; - } - const { containerConfig: base = {} } = workerInfo.project.use as ProjectUse; const override: N8NConfig = !capability ? {} @@ -84,16 +100,7 @@ export const test = base.extend< ? CAPABILITIES[capability] : capability; - const globalEnv: Record = (() => { - const raw = process.env.N8N_TEST_ENV; - if (!raw) return {}; - try { - return JSON.parse(raw) as Record; - } catch { - console.warn('[base.ts] Failed to parse N8N_TEST_ENV'); - return {}; - } - })(); + const globalEnv = parseGlobalTestEnv(); const config: N8NConfig = { ...base, @@ -108,15 +115,25 @@ export const test = base.extend< }, }; - const container = await createN8NStack(config); + await use(config); + }, + { scope: 'worker', box: true }, + ], + + // Creates container from n8nStackConfig. + // When N8N_BASE_URL is set, skips container creation for local testing. + n8nContainer: [ + async ({ n8nStackConfig }, use) => { + if (getBackendUrl()) { + await use(null!); + return; + } + + const container = await createN8NStack(n8nStackConfig); await use(container); if (process.env.N8N_CONTAINERS_KEEPALIVE === 'true') { - console.log('\n=== KEEPALIVE: Containers left running for debugging ==='); - console.log(` URL: ${container.baseUrl}`); - console.log(` Project: ${container.projectName}`); - console.log(' Cleanup: pnpm --filter n8n-containers stack:clean:all'); - console.log('=========================================================\n'); + logKeepalive(container); return; } @@ -313,11 +330,12 @@ export { expect }; /* Fixture Dependency Graph: -Worker: capability + project.containerConfig → n8nContainer → [backendUrl, frontendUrl, dbSetup] +Worker: capability + project.containerConfig → n8nStackConfig → n8nContainer → [backendUrl, frontendUrl, dbSetup] Test: frontendUrl + dbSetup → baseURL → n8n (uses backendUrl for API calls) backendUrl → api n8nContainer → services -services: Type-safe helpers (mailpit, gitea, proxy, observability, etc.) -n8nContainer: Container lifecycle (stop, containers, mainUrls, etc.) +n8nStackConfig: Resolved N8NConfig (topology-neutral, always produced) +n8nContainer: Container lifecycle (stop, containers, mainUrls, etc.) +services: Type-safe helpers (mailpit, gitea, proxy, observability, etc.) */