mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
test: Wait for backend readiness before the Playwright database reset (#38366)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Declan Carroll <declan@n8n.io>
This commit is contained in:
parent
1abdc9222b
commit
ec267acdbc
|
|
@ -73,6 +73,7 @@ describe('useWorkflowPublicationStatusSync', () => {
|
|||
settingsStore = mockedStore(useSettingsStore);
|
||||
workflowDocumentStore = useWorkflowDocumentStore(TEST_DOCUMENT_ID);
|
||||
|
||||
workflowsStore.isNewWorkflow = false;
|
||||
settingsStore.isWorkflowPublicationServiceEnabled = true;
|
||||
|
||||
vi.spyOn(workflowsStore, 'fetchPublicationStatus').mockResolvedValue(makeStatus('published'));
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ export function useWorkflowPublicationStatusSync(documentId: MaybeRefOrGetter<Wo
|
|||
}
|
||||
|
||||
async function refetch() {
|
||||
if (disposed || !settingsStore.isWorkflowPublicationServiceEnabled) return;
|
||||
if (
|
||||
disposed ||
|
||||
!settingsStore.isWorkflowPublicationServiceEnabled ||
|
||||
workflowsStore.isNewWorkflow
|
||||
)
|
||||
return;
|
||||
|
||||
// Resolve the store from the current documentId on every call so a
|
||||
// workflow switch is immediately reflected without remounting.
|
||||
|
|
|
|||
|
|
@ -137,9 +137,9 @@ situations where `test:local`'s defaults aren't enough:
|
|||
`@db:reset` tests are picked up by the local `e2e` project. Their fixtures
|
||||
are responsible for detecting the missing container and skipping or falling
|
||||
back.
|
||||
- **Self-managed n8n.** Boots n8n with a real readiness check against
|
||||
`/rest/e2e/reset` (Playwright's default `webServer` favicon check is racy
|
||||
with slower module startups) and skips Playwright's own webServer.
|
||||
- **Self-managed n8n.** Boots n8n with a readiness check against
|
||||
`/rest/e2e/reset`, so the run waits for the E2E controller itself, and skips
|
||||
Playwright's own webServer.
|
||||
|
||||
Pass extra n8n env via `N8N_TEST_ENV` (the same convention `test:local` uses):
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import path from 'path';
|
|||
|
||||
import currentsConfig from './currents.config';
|
||||
import { getProjects } from './playwright-projects';
|
||||
import { getBackendUrl, getFrontendUrl, getPortFromUrl } from './utils/url-helper';
|
||||
import { getBackendUrl, getFrontendUrl, getPortFromUrl, getReadinessUrl } from './utils/url-helper';
|
||||
|
||||
const IS_CI = !!process.env.CI;
|
||||
const IS_DEV = !!process.env.N8N_EDITOR_URL;
|
||||
|
|
@ -68,10 +68,19 @@ if (BACKEND_URL && !SKIP_WEB_SERVER) {
|
|||
process.env.N8N_TEST_USER_FOLDER =
|
||||
typeof envUserFolder === 'string' ? envUserFolder : USER_FOLDER;
|
||||
}
|
||||
// Probe readiness, not a static asset: the startup middleware answers assets
|
||||
// with 200 while the migrations still run, so global setup could reset the
|
||||
// database before n8n registered the E2E controller.
|
||||
const envHealthEndpoint: unknown =
|
||||
getTestEnv().N8N_ENDPOINT_HEALTH ?? process.env.N8N_ENDPOINT_HEALTH;
|
||||
webServer.push({
|
||||
command: 'pnpm --dir ../../.. start',
|
||||
url: `${BACKEND_URL}/favicon.ico`,
|
||||
timeout: 30000,
|
||||
url: getReadinessUrl(
|
||||
BACKEND_URL,
|
||||
typeof envHealthEndpoint === 'string' ? envHealthEndpoint : undefined,
|
||||
),
|
||||
// Readiness lands later than a bound port: allow for the migrations too.
|
||||
timeout: 120000,
|
||||
reuseExistingServer: IS_DEV ? false : true,
|
||||
env: {
|
||||
DB_SQLITE_POOL_SIZE: '40',
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@
|
|||
* - **Throwaway `N8N_USER_FOLDER`** under the OS temp dir (cleaned up on
|
||||
* exit). n8n creates `.n8n/` (sqlite DB, encryption key) inside it, fully
|
||||
* isolated from your local `~/.n8n` install.
|
||||
* - **Self-managed n8n process** with a real readiness check against
|
||||
* `/rest/e2e/reset` (Playwright's default `webServer` favicon check is
|
||||
* racy with slower module startups). Sets `PLAYWRIGHT_SKIP_WEBSERVER=true`
|
||||
* so Playwright doesn't race to spawn its own n8n.
|
||||
* - **Self-managed n8n process** with a readiness check against
|
||||
* `/rest/e2e/reset`, so the run waits for the E2E controller itself. Sets
|
||||
* `PLAYWRIGHT_SKIP_WEBSERVER=true` so Playwright doesn't race to spawn its
|
||||
* own n8n.
|
||||
* - **Container-only tests included.** Sets `PLAYWRIGHT_ALLOW_CONTAINER_ONLY=true`
|
||||
* so `@capability:*`, `@licensed`, and `@db:reset` tests are picked up by
|
||||
* the local `e2e` project. Their fixtures are responsible for detecting
|
||||
|
|
@ -157,8 +157,8 @@ n8n.on('exit', (code, signal) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Poll an actual REST route, not just /favicon.ico, so we know controllers are
|
||||
// registered. We POST `/rest/e2e/reset` with no body — a registered route
|
||||
// Poll the actual REST route, not just a health endpoint, so we know controllers
|
||||
// are registered. We POST `/rest/e2e/reset` with no body — a registered route
|
||||
// returns 4xx/5xx, an unregistered one returns 404 with an HTML body.
|
||||
async function waitForN8n(timeoutMs = 120_000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { expect, test } from '../../fixtures/base';
|
|||
* USER_FOLDER in playwright.config.ts), not in the default ~/.n8n. Guards the
|
||||
* smoke suite against silently polluting the developer's own install.
|
||||
*
|
||||
* Playwright polls the backend's favicon and global-setup resets its database
|
||||
* Playwright waits for backend readiness and global-setup resets its database
|
||||
* before any test runs, so the file exists by now without visiting the app.
|
||||
*/
|
||||
test.describe(
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@ From the isolated runner you get:
|
|||
`~/.n8n/database.sqlite` is never touched.
|
||||
- **`PLAYWRIGHT_ALLOW_CONTAINER_ONLY=true`** so the `@capability:proxy` tag
|
||||
is honoured locally.
|
||||
- **Self-managed n8n** with a real `/rest/e2e/reset` readiness check (not the
|
||||
racy default favicon poll), `PLAYWRIGHT_SKIP_WEBSERVER=true` to stop
|
||||
Playwright from spawning a duplicate, and process-group cleanup so
|
||||
`node ./n8n` doesn't get orphaned.
|
||||
- **Self-managed n8n** with a `/rest/e2e/reset` readiness check that waits for
|
||||
the E2E controller, `PLAYWRIGHT_SKIP_WEBSERVER=true` to stop Playwright from
|
||||
spawning a duplicate, and process-group cleanup so `node ./n8n` doesn't get
|
||||
orphaned.
|
||||
|
||||
The `instanceAiProxySetup` fixture (`fixtures.ts`) detects the missing
|
||||
`n8nContainer` and short-circuits all proxy + tool-trace setup, so every LLM
|
||||
|
|
|
|||
29
packages/testing/playwright/utils/url-helper.test.ts
Normal file
29
packages/testing/playwright/utils/url-helper.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { getReadinessUrl } from './url-helper';
|
||||
|
||||
describe('getReadinessUrl', () => {
|
||||
test('uses the default health endpoint', () => {
|
||||
expect(getReadinessUrl('http://localhost:5678')).toBe(
|
||||
'http://localhost:5678/healthz/readiness',
|
||||
);
|
||||
});
|
||||
|
||||
test('uses a custom health endpoint', () => {
|
||||
expect(getReadinessUrl('http://localhost:5678', '/health')).toBe(
|
||||
'http://localhost:5678/health/readiness',
|
||||
);
|
||||
});
|
||||
|
||||
test('adds the leading slash a custom health endpoint may omit', () => {
|
||||
expect(getReadinessUrl('http://localhost:5678', 'health')).toBe(
|
||||
'http://localhost:5678/health/readiness',
|
||||
);
|
||||
});
|
||||
|
||||
test('drops a trailing slash of the base URL', () => {
|
||||
expect(getReadinessUrl('http://localhost:5678/')).toBe(
|
||||
'http://localhost:5678/healthz/readiness',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -22,3 +22,14 @@ export function getBackendUrl(): string | undefined {
|
|||
export function getFrontendUrl(): string | undefined {
|
||||
return process.env.N8N_EDITOR_URL ?? process.env.N8N_BASE_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the readiness URL of a backend.
|
||||
* The backend serves readiness at `<N8N_ENDPOINT_HEALTH>/readiness` and adds a
|
||||
* missing leading slash to that variable, so mirror both rules here.
|
||||
*/
|
||||
export function getReadinessUrl(baseUrl: string, healthEndpoint?: string): string {
|
||||
const health = healthEndpoint ?? '/healthz';
|
||||
const healthPath = health.startsWith('/') ? health : `/${health}`;
|
||||
return `${baseUrl.replace(/\/+$/, '')}${healthPath}/readiness`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default defineConfig({
|
|||
'scripts/**/*.test.ts',
|
||||
'fixtures/**/*.test.ts',
|
||||
'reporters/**/*.test.ts',
|
||||
'utils/**/*.test.ts',
|
||||
'tests/framework/telemetry.test.ts',
|
||||
'tests/framework/telemetry-integration.test.ts',
|
||||
'*.test.ts',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user