diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index c29bb7d3c09..bde9d3b056b 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -34,6 +34,7 @@ import { Push } from '@/push'; import { CacheService } from '@/services/cache/cache.service'; import { FrontendService } from '@/services/frontend.service'; import { PasswordUtility } from '@/services/password.utility'; +import { TaskBroker } from '@/task-runners/task-broker/task-broker.service'; import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service'; if (!inE2ETests) { @@ -202,6 +203,7 @@ export class E2EController { private readonly scheduledJobRepository: ScheduledJobRepository, private readonly pollerStateRepository: PollerStateRepository, private readonly workflowStaticDataService: WorkflowStaticDataService, + private readonly taskBroker: TaskBroker, ) { license.isLicensed = (feature: BooleanLicenseFeature) => this.enabledFeatures[feature] ?? false; @@ -310,6 +312,16 @@ export class E2EController { return { success: true }; } + /** + * Number of task runners currently registered with the broker, so a test can + * wait for a runner to be ready instead of racing its startup. + */ + @Get('/task-runners/count', { skipAuth: true }) + countTaskRunners() { + const count = this.taskBroker.getKnownRunners().size; + return { count }; + } + @Get('/env-feature-flags', { skipAuth: true }) async getEnvFeatureFlags() { return (await this.frontendService.getSettings()).envFeatureFlags; diff --git a/packages/testing/playwright/services/api-helper.ts b/packages/testing/playwright/services/api-helper.ts index eec12dec4e1..cb14c0cd4e3 100644 --- a/packages/testing/playwright/services/api-helper.ts +++ b/packages/testing/playwright/services/api-helper.ts @@ -261,6 +261,15 @@ export class ApiHelpers { return data.cursor; } + async countTaskRunners(): Promise { + const response = await this.request.get('/rest/e2e/task-runners/count'); + if (!response.ok()) { + throw new TestError(`Failed to count task runners: ${await response.text()}`); + } + const { data } = (await response.json()) as { data: { count: number } }; + return data.count; + } + async clearWorkflowStaticData(workflowId: string): Promise { const response = await this.request.post('/rest/e2e/workflow-static-data/clear', { data: { workflowId }, diff --git a/packages/testing/playwright/tests/e2e/capabilities/task-runner.spec.ts b/packages/testing/playwright/tests/e2e/capabilities/task-runner.spec.ts index 9fe433f01b7..f672b418622 100644 --- a/packages/testing/playwright/tests/e2e/capabilities/task-runner.spec.ts +++ b/packages/testing/playwright/tests/e2e/capabilities/task-runner.spec.ts @@ -13,11 +13,18 @@ test.describe( annotation: [{ type: 'owner', description: 'Catalysts' }], }, () => { - test('should execute Javascript with task runner enabled', async ({ n8n }) => { + test('should execute Javascript with task runner enabled', async ({ n8n, api }) => { await n8n.start.fromBlankCanvas(); await n8n.canvas.addNode(MANUAL_TRIGGER_NODE_NAME); await n8n.canvas.addNode(CODE_NODE_NAME, { action: 'Code in JavaScript', closeNDV: true }); + // The server reports itself ready once the runner process is spawned, not + // once it has registered with the broker over WebSocket, so wait for an + // actual registration before triggering execution. + await expect + .poll(async () => await api.countTaskRunners(), { timeout: 10_000, intervals: [250] }) + .toBeGreaterThan(0); + await n8n.workflowComposer.executeWorkflowAndWaitForNotification( 'Workflow executed successfully', );