@camunda/e2e-test-suite 0.0.883 → 0.0.885

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +33,7 @@ declare class ModelerCreatePage {
33
33
  readonly createTask: Locator;
34
34
  readonly createEndEvent: Locator;
35
35
  readonly canvas: Locator;
36
+ readonly zoomOutButton: Locator;
36
37
  readonly endEventCanvas: Locator;
37
38
  readonly connectToOtherElementButton: Locator;
38
39
  readonly firstPlacedElement: Locator;
@@ -162,6 +163,8 @@ declare class ModelerCreatePage {
162
163
  clickDeploySubButtonWithRetry(clusterName: string): Promise<void>;
163
164
  clickCreateTask(): Promise<void>;
164
165
  clickCreateEndEvent(): Promise<void>;
166
+ fitElementIntoCanvas(element: Locator, margin?: number, maxZoomOutSteps?: number): Promise<void>;
167
+ clickShapeBelowContextPad(element: Locator): Promise<void>;
165
168
  clickCanvas(): Promise<void>;
166
169
  clickEndEventCanvas(): Promise<void>;
167
170
  clickConnectToOtherElementButton(): Promise<void>;
@@ -39,6 +39,7 @@ class ModelerCreatePage {
39
39
  createTask;
40
40
  createEndEvent;
41
41
  canvas;
42
+ zoomOutButton;
42
43
  endEventCanvas;
43
44
  connectToOtherElementButton;
44
45
  firstPlacedElement;
@@ -182,6 +183,7 @@ class ModelerCreatePage {
182
183
  this.createTask = page.getByTitle('Create task');
183
184
  this.createEndEvent = page.getByTitle('Create end event');
184
185
  this.canvas = page.locator('.djs-container');
186
+ this.zoomOutButton = page.locator('[data-test="zoom-out"]');
185
187
  this.endEventCanvas = page.locator('[class="bjs-container"]');
186
188
  this.connectToOtherElementButton = page.getByRole('button', {
187
189
  name: 'Connect to other element',
@@ -773,6 +775,39 @@ class ModelerCreatePage {
773
775
  async clickCreateEndEvent() {
774
776
  await this.createEndEvent.click({ timeout: 60000 });
775
777
  }
778
+ // The canvas does not scroll with the DOM, so an element the diagram has
779
+ // pushed past the canvas edge cannot be clicked at all — the hit test returns
780
+ // the canvas container (or the panel toggle bar beside it). Zoom out until
781
+ // the element and one element-width of margin fit inside the canvas.
782
+ async fitElementIntoCanvas(element, margin = 120, maxZoomOutSteps = 3) {
783
+ for (let step = 0; step < maxZoomOutSteps; step++) {
784
+ const canvasBox = await this.canvas.boundingBox();
785
+ const elementBox = await element.boundingBox();
786
+ if (canvasBox === null || elementBox === null) {
787
+ return;
788
+ }
789
+ const elementRightEdge = elementBox.x + elementBox.width + margin;
790
+ if (elementRightEdge <= canvasBox.x + canvasBox.width) {
791
+ return;
792
+ }
793
+ await this.zoomOutButton.click({ timeout: 30000 });
794
+ }
795
+ }
796
+ // A context pad is a fixed-size strip anchored to the top-right of its
797
+ // element, so once the diagram is zoomed out it can reach across the gap and
798
+ // cover the top of the next shape. Click the bottom edge of the target, below
799
+ // the strip, so a neighbour's open pad cannot swallow the click.
800
+ async clickShapeBelowContextPad(element) {
801
+ const box = await element.boundingBox();
802
+ if (box === null) {
803
+ await element.click({ timeout: 60000 });
804
+ return;
805
+ }
806
+ await element.click({
807
+ timeout: 60000,
808
+ position: { x: box.width / 2, y: box.height - 2 },
809
+ });
810
+ }
776
811
  async clickCanvas() {
777
812
  // Clicking the canvas centre lands on a shape (or an open create pad) as
778
813
  // soon as the diagram has more than a couple of elements, and the click is
@@ -1027,16 +1062,22 @@ class ModelerCreatePage {
1027
1062
  }
1028
1063
  async clickSecondPlacedGateway(userTaskId = '') {
1029
1064
  try {
1030
- await this.secondPlacedGateway.click({ timeout: 60000 });
1065
+ await this.clickShapeBelowContextPad(this.secondPlacedGateway);
1031
1066
  }
1032
1067
  catch (error) {
1033
1068
  await this.page.reload();
1034
1069
  await (0, sleep_1.sleep)(10000);
1070
+ // The reload resets the canvas viewbox to the origin, discarding the
1071
+ // auto-scroll that kept the right-most gateway on screen.
1072
+ await this.fitElementIntoCanvas(this.secondPlacedGateway);
1073
+ // Clear any selection the reload restored, so its context pad is closed
1074
+ // before the gateway is clicked.
1075
+ await this.clickCanvas();
1035
1076
  if (userTaskId != '') {
1036
1077
  await this.clickUserTask(userTaskId);
1037
1078
  await this.clickConnectToOtherElementButton();
1038
1079
  }
1039
- await this.secondPlacedGateway.click({ timeout: 60000 });
1080
+ await this.clickShapeBelowContextPad(this.secondPlacedGateway);
1040
1081
  }
1041
1082
  }
1042
1083
  async addParallelUserTasks(numberOfTasks, taskName) {
@@ -1083,6 +1124,7 @@ class ModelerCreatePage {
1083
1124
  await this.clickCanvas();
1084
1125
  await (0, sleep_1.sleep)(1000);
1085
1126
  }
1127
+ await this.fitElementIntoCanvas(this.secondPlacedGateway);
1086
1128
  await this.clickSecondPlacedGateway();
1087
1129
  }
1088
1130
  async clickServiceTaskOption() {
@@ -11,8 +11,13 @@ const webhookAssertionOptions = {
11
11
  intervals: [10000, 15000, 30000, 30000],
12
12
  timeout: 120000,
13
13
  };
14
+ // The REST connector tasks in this suite are all io.camunda:http-json:1, so the
15
+ // suite cannot pass unless that worker is subscribed. Requiring it here turns a
16
+ // half-started connectors runtime into one fast, explicit setup failure instead
17
+ // of five tests independently timing out after 12 minutes each.
18
+ const requiredConnectorJobTypes = ['io.camunda:http-json:1'];
14
19
  c8Run_8_9_1.test.beforeAll(async () => {
15
- await (0, apiHelpers_1.waitForConnectorsReady)();
20
+ await (0, apiHelpers_1.waitForConnectorsReady)(undefined, 900000, requiredConnectorJobTypes);
16
21
  await Promise.all([
17
22
  (0, zeebeClient_1.deploy)('./resources/Basic_Auth_REST_Connector.bpmn'),
18
23
  (0, zeebeClient_1.deploy)('./resources/Intermediate_Event_Webhook_Connector_No_Auth_Process.bpmn'),
@@ -77,7 +82,7 @@ c8Run_8_9_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
77
82
  });
78
83
  (0, c8Run_8_9_1.test)('Start Event Webhook Connector No Auth User Flow', async ({ page, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, }) => {
79
84
  await c8Run_8_9_1.test.step('Make Authorization Request', async () => {
80
- await (0, sleep_1.sleep)(300000);
85
+ await (0, apiHelpers_1.waitForInboundWebhookRegistered)('test-webhook-id');
81
86
  await (0, test_1.expect)(async () => {
82
87
  const response = await request.get(process.env.C8RUN_CONNECTORS_API_URL_LATEST +
83
88
  '/inbound/test-webhook-id');
@@ -95,7 +100,7 @@ c8Run_8_9_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
95
100
  });
96
101
  (0, c8Run_8_9_1.test)('Intermediate Event Webhook Connector No Auth User Flow', async ({ request, operateHomePage, operateProcessInstancePage, operateProcessesPage, }) => {
97
102
  await c8Run_8_9_1.test.step('Make Authorization Request', async () => {
98
- await (0, sleep_1.sleep)(60000);
103
+ await (0, apiHelpers_1.waitForInboundWebhookRegistered)('test-webhook-intermediate');
99
104
  await (0, test_1.expect)(async () => {
100
105
  const response = await request.post(process.env.C8RUN_CONNECTORS_API_URL_LATEST +
101
106
  '/inbound/test-webhook-intermediate', {
@@ -31,7 +31,12 @@ c8Run_8_9_1.test.beforeAll(async () => {
31
31
  await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12001'); // No auth
32
32
  await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12002'); // Basic auth
33
33
  await (0, apiHelpers_1.validateMcpServerHealth)('http://127.0.0.1:12004'); // OAuth
34
- await (0, apiHelpers_1.waitForConnectorsReady)();
34
+ // Every process in this suite drives io.camunda.agenticai:mcpremoteclient:1,
35
+ // so gate on that worker actually being subscribed rather than on the runtime
36
+ // merely reporting UP.
37
+ await (0, apiHelpers_1.waitForConnectorsReady)(undefined, 600000, [
38
+ 'io.camunda.agenticai:mcpremoteclient:1',
39
+ ]);
35
40
  await (0, keycloakHelpers_1.validateKeycloakHealth)();
36
41
  bearerToken = await (0, keycloakHelpers_1.getAccessToken)();
37
42
  await (0, zeebeClient_1.deploy)([
@@ -12,7 +12,11 @@ c8Run_8_9_1.test.beforeAll(async () => {
12
12
  return;
13
13
  }
14
14
  await (0, apiHelpers_1.validateMcpServerHealth)();
15
- await (0, apiHelpers_1.waitForConnectorsReady)();
15
+ // The MCP client connector task cannot complete unless this worker is
16
+ // subscribed, so require it instead of only a healthy runtime.
17
+ await (0, apiHelpers_1.waitForConnectorsReady)(undefined, 600000, [
18
+ 'io.camunda.agenticai:mcpremoteclient:1',
19
+ ]);
16
20
  await (0, zeebeClient_1.deploy)(['./resources/mcp_server/mcp_remote_client_operations.bpmn']);
17
21
  await (0, zeebeClient_1.createInstances)('mcp_remote_client', 1, 1);
18
22
  await (0, sleep_1.sleep)(2000);
@@ -16,7 +16,8 @@ export declare function authSaasAPI(audience?: string, clusterType?: string): Pr
16
16
  export declare function authSmAPI(): Promise<string>;
17
17
  export declare function authC8runAPI(name: string, password: string): Promise<void>;
18
18
  export declare function validateMcpServerHealth(serverUrl?: string): Promise<APIResponse>;
19
- export declare function waitForConnectorsReady(baseUrl?: string | undefined, timeoutMs?: number): Promise<void>;
19
+ export declare function waitForConnectorsReady(baseUrl?: string | undefined, timeoutMs?: number, requiredJobTypes?: string[]): Promise<void>;
20
+ export declare function waitForInboundWebhookRegistered(webhookPath: string, baseUrl?: string | undefined, timeoutMs?: number): Promise<void>;
20
21
  export declare function deployProcess(filePath: string, authToken?: string, environment?: 'saas' | 'sm', clusterType?: string): Promise<number | null>;
21
22
  export declare function createProcessInstance(processDefinitionKey: string, authToken?: string, environment?: 'saas' | 'sm', clusterType?: string, variables?: Record<string, unknown>): Promise<string>;
22
23
  export declare function waitForProcessInstanceVisible(processInstanceKey: string, authToken?: string, environment?: 'saas' | 'sm', clusterType?: string, maxAttempts?: number, retryDelayMs?: number): Promise<void>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.updateClusterDataFilters = exports.getClusterDataFilters = exports.getTestClusterUuid = exports.authConsoleManagementAPI = exports.updateCollectionScope = exports.createSingleProcessReport = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForConnectorsReady = exports.validateMcpServerHealth = exports.authC8runAPI = exports.authSmAPI = exports.authSaasAPI = exports.authAPI = exports.buildZeebeApiUrl = exports.retryOn500 = exports.sendRequestAndAssertResponse = exports.assertUnauthorizedResponseBody = exports.expectUnauthorizedErrorBody = exports.assertResponseStatus = exports.getApiRequestContext = void 0;
6
+ exports.updateClusterDataFilters = exports.getClusterDataFilters = exports.getTestClusterUuid = exports.authConsoleManagementAPI = exports.updateCollectionScope = exports.createSingleProcessReport = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForInboundWebhookRegistered = exports.waitForConnectorsReady = exports.validateMcpServerHealth = exports.authC8runAPI = exports.authSmAPI = exports.authSaasAPI = exports.authAPI = exports.buildZeebeApiUrl = exports.retryOn500 = exports.sendRequestAndAssertResponse = exports.assertUnauthorizedResponseBody = exports.expectUnauthorizedErrorBody = exports.assertResponseStatus = exports.getApiRequestContext = void 0;
7
7
  const test_1 = require("@playwright/test");
8
8
  const sleep_1 = require("./sleep");
9
9
  const fs_1 = __importDefault(require("fs"));
@@ -316,7 +316,7 @@ exports.validateMcpServerHealth = validateMcpServerHealth;
316
316
  // Guards against the c8Run startup race where the connectors runtime on :8086
317
317
  // comes up later than Zeebe/Operate, leaving process instances stuck "active"
318
318
  // and webhook endpoints unregistered.
319
- async function waitForConnectorsReady(baseUrl = process.env.C8RUN_CONNECTORS_API_URL_LATEST, timeoutMs = 300000) {
319
+ async function waitForConnectorsReady(baseUrl = process.env.C8RUN_CONNECTORS_API_URL_LATEST, timeoutMs = 300000, requiredJobTypes = []) {
320
320
  if (!baseUrl) {
321
321
  throw new Error('waitForConnectorsReady: missing connectors API URL ' +
322
322
  '(C8RUN_CONNECTORS_API_URL_LATEST).');
@@ -343,11 +343,70 @@ async function waitForConnectorsReady(baseUrl = process.env.C8RUN_CONNECTORS_API
343
343
  consecutiveHealthy = 0;
344
344
  throw new Error(`connectors runtime health returned ${response.status()}`);
345
345
  }
346
+ if (requiredJobTypes.length > 0) {
347
+ const registered = await registeredConnectorJobTypes(baseUrl);
348
+ const missing = requiredJobTypes.filter((type) => !registered.has(type));
349
+ if (missing.length > 0) {
350
+ consecutiveHealthy = 0;
351
+ throw new Error('connectors runtime has not subscribed job workers for: ' +
352
+ missing.join(', '));
353
+ }
354
+ }
346
355
  consecutiveHealthy += 1;
347
356
  (0, test_1.expect)(consecutiveHealthy).toBeGreaterThanOrEqual(requiredConsecutive);
348
357
  }).toPass({ intervals: [5000, 10000, 15000], timeout: timeoutMs });
349
358
  }
350
359
  exports.waitForConnectorsReady = waitForConnectorsReady;
360
+ // `/actuator/health` flips to UP as soon as the web layer is serving and the
361
+ // Zeebe topology is reachable — it says nothing about whether the runtime
362
+ // actually subscribed its connector job workers. A runtime that started while
363
+ // the broker was still installing its partition can sit at UP indefinitely with
364
+ // no worker subscribed, which leaves every connector task active and inbound
365
+ // webhooks unregistered. The runtime publishes one
366
+ // camunda_client_worker_job_activated_total series per subscribed worker type,
367
+ // so the presence of that series is the real readiness signal.
368
+ async function registeredConnectorJobTypes(baseUrl) {
369
+ const response = await apiRequestContext.get(`${baseUrl}/actuator/prometheus`, { timeout: 15000 });
370
+ if (response.status() !== 200) {
371
+ throw new Error(`connectors runtime metrics returned ${response.status()}`);
372
+ }
373
+ const metrics = await response.text();
374
+ const pattern = /camunda_client_worker_job_activated_total\{type="([^"]+)"\}/g;
375
+ const types = new Set();
376
+ let match;
377
+ while ((match = pattern.exec(metrics)) !== null) {
378
+ types.add(match[1]);
379
+ }
380
+ return types;
381
+ }
382
+ // Waits until the connectors runtime reports a healthy inbound subscription for
383
+ // `webhookPath`. Registration happens asynchronously after deployment, so
384
+ // calling the endpoint before it lands returns 404 for a start-event webhook and
385
+ // 500 for an intermediate-event one — exactly what the nightly observed. Polling
386
+ // the runtime's own registration list makes that a checked precondition instead
387
+ // of a fixed sleep that silently expires.
388
+ async function waitForInboundWebhookRegistered(webhookPath, baseUrl = process.env.C8RUN_CONNECTORS_API_URL_LATEST, timeoutMs = 300000) {
389
+ if (!baseUrl) {
390
+ throw new Error('waitForInboundWebhookRegistered: missing connectors API URL ' +
391
+ '(C8RUN_CONNECTORS_API_URL_LATEST).');
392
+ }
393
+ apiRequestContext = await getApiRequestContext();
394
+ await (0, test_1.expect)(async () => {
395
+ const response = await apiRequestContext.get(`${baseUrl}/inbound`, {
396
+ timeout: 15000,
397
+ });
398
+ if (response.status() !== 200) {
399
+ throw new Error(`connectors runtime /inbound returned ${response.status()}`);
400
+ }
401
+ const registrations = (await response.json());
402
+ const registered = registrations.some((registration) => registration.data?.path === webhookPath &&
403
+ registration.health?.status === 'UP');
404
+ if (!registered) {
405
+ throw new Error(`inbound webhook '${webhookPath}' is not registered and healthy yet`);
406
+ }
407
+ }).toPass({ intervals: [5000, 10000, 15000], timeout: timeoutMs });
408
+ }
409
+ exports.waitForInboundWebhookRegistered = waitForInboundWebhookRegistered;
351
410
  // ---- Zeebe: API helpers ----
352
411
  async function deployProcess(filePath, authToken, environment, clusterType) {
353
412
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.883",
3
+ "version": "0.0.885",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",