@camunda/e2e-test-suite 0.0.1002 → 0.0.1003

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.
@@ -41,6 +41,8 @@ declare class ModelerCreatePage {
41
41
  readonly firstPlacedElement: Locator;
42
42
  readonly secondPlacedElement: Locator;
43
43
  readonly appendElementButton: Locator;
44
+ readonly appendElementIndicator: Locator;
45
+ readonly appendCreatePad: Locator;
44
46
  readonly payloadInput: Locator;
45
47
  readonly marketPlaceButton: Locator;
46
48
  readonly publicHolidayConnectorOption: Locator;
@@ -178,6 +180,7 @@ declare class ModelerCreatePage {
178
180
  clickFirstPlacedElement(): Promise<void>;
179
181
  clickSecondPlacedElement(): Promise<void>;
180
182
  clickSecondElement(): Promise<void>;
183
+ private openAppendCreatePad;
181
184
  clickAppendElementButton(): Promise<void>;
182
185
  clickMarketPlaceButton(): Promise<void>;
183
186
  clickPublicHolidayConnectorOption(): Promise<void>;
@@ -47,6 +47,8 @@ class ModelerCreatePage {
47
47
  firstPlacedElement;
48
48
  secondPlacedElement;
49
49
  appendElementButton;
50
+ appendElementIndicator;
51
+ appendCreatePad;
50
52
  payloadInput;
51
53
  marketPlaceButton;
52
54
  publicHolidayConnectorOption;
@@ -118,9 +120,22 @@ class ModelerCreatePage {
118
120
  this.generalPanel = page.locator('[data-group-id="group-general"]');
119
121
  this.processIdInput = page.getByLabel('ID', { exact: true });
120
122
  this.startEventElement = page.locator('.djs-hit').first();
123
+ // Match on the individual classes rather than the whole `class` attribute:
124
+ // an exact `[class="..."]` match breaks the moment bpmn-js reorders or adds
125
+ // a class, and it is the same shape camunda-hub's own e2e uses
126
+ // (`.djs-append-create-pad` + an `open` class assertion).
121
127
  this.appendElementButton = page
122
- .locator('[class="djs-create-pad open djs-append-create-pad"]')
128
+ .locator('.djs-append-create-pad.open')
123
129
  .first();
130
+ // improved-canvas 1.11.0 (camunda-hub fe2b961, 2026-08-25) stopped
131
+ // auto-opening the append create pad for an element that already has an
132
+ // outgoing sequence flow. Such an element now only carries a "+" append
133
+ // indicator overlay; moving the pointer onto it swaps the create pad in at
134
+ // the same spot, which is what reveals the append entries.
135
+ this.appendElementIndicator = page
136
+ .locator('.djs-append-indicator.selected')
137
+ .first();
138
+ this.appendCreatePad = page.locator('.djs-append-create-pad').first();
124
139
  this.appendTaskButton = page.getByRole('button', { name: 'Append task' });
125
140
  this.changeTypeButton = page.getByRole('button', {
126
141
  name: 'Change element',
@@ -539,7 +554,7 @@ class ModelerCreatePage {
539
554
  else if (parentElement == 'parallelGateway') {
540
555
  await this.clickSecondPlacedGateway();
541
556
  }
542
- await this.appendElementButton.hover();
557
+ await this.clickAppendElementButton();
543
558
  await this.appendEndEventButton.click({ timeout: 120000 });
544
559
  }
545
560
  }
@@ -893,6 +908,13 @@ class ModelerCreatePage {
893
908
  await this.endEventCanvas.click({ timeout: 60000 });
894
909
  }
895
910
  async clickConnectToOtherElementButton() {
911
+ // 'Connect to other element' is a create-pad entry
912
+ // (`data-entry-id="connect"`), so the pad has to be open and hovered before
913
+ // the entry exists at all. The user task this is called on already has an
914
+ // outgoing sequence flow, so the pad no longer auto-opens — go through the
915
+ // append indicator first, which is exactly what clickAppendElementButton
916
+ // does (with its own retries).
917
+ await this.clickAppendElementButton();
896
918
  await (0, test_1.expect)(this.connectToOtherElementButton).toBeVisible({
897
919
  timeout: 60000,
898
920
  });
@@ -909,11 +931,30 @@ class ModelerCreatePage {
909
931
  await (0, test_1.expect)(this.secondElement).toBeVisible({ timeout: 60000 });
910
932
  await this.secondElement.click({ timeout: 60000 });
911
933
  }
934
+ // The append create pad only auto-opens for an element with no outgoing
935
+ // sequence flow. Every other selected element gets the "+" append indicator
936
+ // overlay instead, and the pad is rendered only once the pointer hovers it —
937
+ // which is exactly what appending a second parallel branch off an
938
+ // already-connected gateway needs.
939
+ async openAppendCreatePad() {
940
+ const padOpen = await this.appendElementButton
941
+ .isVisible()
942
+ .catch(() => false);
943
+ if (padOpen)
944
+ return;
945
+ // Force past the actionability check on both hops: the indicator is
946
+ // replaced by the create pad the moment the pointer reaches it, so an
947
+ // unforced click/hover is reported as "intercepted by the create pad" and
948
+ // then as "element is not visible" once the swap has happened.
949
+ await this.appendElementIndicator.hover({ force: true, timeout: 10000 });
950
+ await this.appendCreatePad.hover({ force: true, timeout: 10000 });
951
+ }
912
952
  async clickAppendElementButton() {
913
953
  const maxRetries = 3;
914
954
  for (let retries = 0; retries < maxRetries; retries++) {
915
955
  try {
916
956
  await this.page.waitForTimeout(1000);
957
+ await this.openAppendCreatePad();
917
958
  await (0, test_1.expect)(this.appendElementButton).toBeVisible({ timeout: 10000 });
918
959
  await this.appendElementButton.hover({ force: true });
919
960
  return;
@@ -1123,7 +1164,9 @@ class ModelerCreatePage {
1123
1164
  async clickFirstPlacedGateway() {
1124
1165
  try {
1125
1166
  await this.firstPlacedGateway.click({ timeout: 60000 });
1126
- await (0, test_1.expect)(this.appendElementButton).toBeVisible({ timeout: 60000 });
1167
+ // The split gateway already has an outgoing flow, so selecting it renders
1168
+ // the append indicator rather than an open create pad — accept either.
1169
+ await (0, test_1.expect)(this.appendElementButton.or(this.appendElementIndicator)).toBeVisible({ timeout: 60000 });
1127
1170
  }
1128
1171
  catch (error) {
1129
1172
  await this.page.reload();
@@ -1159,7 +1202,7 @@ class ModelerCreatePage {
1159
1202
  await this.page.reload();
1160
1203
  await (0, sleep_1.sleep)(20000);
1161
1204
  await this.clickFirstPlacedElement();
1162
- await this.appendElementButton.hover();
1205
+ await this.clickAppendElementButton();
1163
1206
  }
1164
1207
  await this.clickAppendGatewayButton();
1165
1208
  await this.clickChangeTypeButton();
@@ -181,10 +181,7 @@ _8_10_1.test.describe('HTO User Flow Tests', () => {
181
181
  await (0, expectTextWithPagination_1.expectTextWithPagination)(page, processName + '2');
182
182
  });
183
183
  });
184
- // Temporarily skipped pending fix PR #3144:
185
- // https://github.com/camunda/c8-cross-component-e2e-tests/pull/3144
186
- // Re-enable in the same change that lands #3144.
187
- _8_10_1.test.skip('Zeebe User Task With Priority', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, taskPanelPage, taskDetailsPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
184
+ (0, _8_10_1.test)('Zeebe User Task With Priority', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, taskPanelPage, taskDetailsPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
188
185
  const randomName = await (0, _setup_1.generateRandomStringAsync)(3);
189
186
  const processName = 'Zeebe_User_Task_Process_With_Priority' + randomName;
190
187
  const taskName = 'priorityTest' + randomName;
@@ -32,7 +32,13 @@ const assertUnauthorizedResponseBody = async (response) => {
32
32
  }
33
33
  expectUnauthorizedErrorBody(body);
34
34
  };
35
- const createNumberReport = async (request, options) => {
35
+ // Optimize's report endpoint is fronted by the SaaS gateway, which
36
+ // intermittently answers a bare 520 for a request that succeeds on the next
37
+ // attempt. Every other creation helper this hook calls (createCollection,
38
+ // createDashboard) already goes through retryOnTransientApiError; these two
39
+ // spec-local ones did not, so a single gateway blip failed the whole
40
+ // beforeAll and with it every test the worker had picked up.
41
+ const createNumberReport = async (request, options) => (0, apiHelpers_1.retryOnTransientApiError)(async () => {
36
42
  const response = await request.post(`${options.baseUrl}/api/report/process/single`, {
37
43
  headers: {
38
44
  Cookie: options.optimizeCookie,
@@ -63,8 +69,9 @@ const createNumberReport = async (request, options) => {
63
69
  const body = await response.json();
64
70
  (0, test_1.expect)(body).toHaveProperty('id');
65
71
  return body.id;
66
- };
67
- const createRawDataReport = async (request, options) => {
72
+ }, 'createNumberReport');
73
+ // Same transient-520 guard as createNumberReport above.
74
+ const createRawDataReport = async (request, options) => (0, apiHelpers_1.retryOnTransientApiError)(async () => {
68
75
  const response = await request.post(`${options.baseUrl}/api/report/process/single`, {
69
76
  headers: {
70
77
  Cookie: options.optimizeCookie,
@@ -95,7 +102,7 @@ const createRawDataReport = async (request, options) => {
95
102
  const body = await response.json();
96
103
  (0, test_1.expect)(body).toHaveProperty('id');
97
104
  return body.id;
98
- };
105
+ }, 'createRawDataReport');
99
106
  _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sharing-alerts-export-api @8.10', () => {
100
107
  let optimizeCookie;
101
108
  let csrfToken;
@@ -27,10 +27,7 @@ _8_10_1.test.describe('Smoke Tests', () => {
27
27
  await (0, _setup_1.captureScreenshot)(page, testInfo);
28
28
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
29
29
  });
30
- // Temporarily skipped pending fix PR #3144:
31
- // https://github.com/camunda/c8-cross-component-e2e-tests/pull/3144
32
- // Re-enable in the same change that lands #3144.
33
- _8_10_1.test.skip('Most Common Flow User Flow With All Apps', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
30
+ (0, _8_10_1.test)('Most Common Flow User Flow With All Apps', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
34
31
  _8_10_1.test.slow();
35
32
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
36
33
  const formName = 'New form' + randomString;
@@ -7,6 +7,7 @@ export declare function expectUnauthorizedErrorBody(body: unknown): void;
7
7
  export declare function assertUnauthorizedResponseBody(response: APIResponse): Promise<void>;
8
8
  export declare function sendRequestAndAssertResponse(request: APIRequestContext, url: string, requestBody?: string | Buffer | Serializable, maxRetries?: number): Promise<void>;
9
9
  export declare function retryOn500(fn: () => Promise<APIResponse>, maxAttempts?: number, delayMs?: number): Promise<APIResponse>;
10
+ export declare function retryOnTransientApiError<T>(fn: () => Promise<T>, label: string, maxAttempts?: number, delayMs?: number): Promise<T>;
10
11
  export declare function buildZeebeApiUrl(resourcePath: string, environmentOverride?: 'saas' | 'sm', clusterType?: string): string;
11
12
  export declare function authAPI(environment: 'saas'): Promise<string>;
12
13
  export declare function authAPI(environment: 'sm'): Promise<string>;
@@ -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.setOptimizeSharing = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.loginToOptimizeSession = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForOrchestrationClusterReady = 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;
6
+ exports.updateClusterDataFilters = exports.getClusterDataFilters = exports.getTestClusterUuid = exports.authConsoleManagementAPI = exports.updateCollectionScope = exports.createSingleProcessReport = exports.setOptimizeSharing = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.loginToOptimizeSession = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForOrchestrationClusterReady = exports.waitForInboundWebhookRegistered = exports.waitForConnectorsReady = exports.validateMcpServerHealth = exports.authC8runAPI = exports.authSmAPI = exports.authSaasAPI = exports.authAPI = exports.buildZeebeApiUrl = exports.retryOnTransientApiError = 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 randomSleep_1 = require("./randomSleep");
@@ -97,7 +97,14 @@ async function retryOnTransientApiError(fn, label, maxAttempts = 3, delayMs = 30
97
97
  return await fn();
98
98
  }
99
99
  catch (error) {
100
- const message = error instanceof Error ? error.message : String(error);
100
+ const raw = error instanceof Error ? error.message : String(error);
101
+ // assertResponseStatus reports the status through expect(), which
102
+ // colourises it: the message is literally `Received: <SGR>520<SGR>`. The
103
+ // escape sits between `Received:` and the digits, so the 5xx branch below
104
+ // never matched and a genuine gateway blip (Optimize answering 520) was
105
+ // surfaced as a hard failure. Strip the escapes before matching.
106
+ // eslint-disable-next-line no-control-regex
107
+ const message = raw.replace(/\u001b\[\d+m/g, '');
101
108
  const isTransient = /Request timed out/i.test(message) ||
102
109
  /Received:\s*5\d{2}/.test(message) ||
103
110
  /socket hang up/i.test(message) ||
@@ -114,6 +121,7 @@ async function retryOnTransientApiError(fn, label, maxAttempts = 3, delayMs = 30
114
121
  }
115
122
  throw new Error('unreachable');
116
123
  }
124
+ exports.retryOnTransientApiError = retryOnTransientApiError;
117
125
  // ---- Cluster Credentials Helper ----
118
126
  function getClusterCredentials(clusterType) {
119
127
  if (!clusterType) {
@@ -17,10 +17,14 @@ exports.deletionAssertionOptions = {
17
17
  // Longest options for audit-log assertions: entries land in secondary storage
18
18
  // only after the audit config propagates to the exporters, which is markedly
19
19
  // slower on freshly-provisioned SaaS clusters. 90s occasionally lapsed before
20
- // the CLIENT-actor PROCESS_INSTANCE CREATE entry surfaced, so poll for 180s.
20
+ // the CLIENT-actor PROCESS_INSTANCE CREATE entry surfaced, then 180s did too
21
+ // (every audit test in the nightly timed out on the same fresh org, including
22
+ // the REST-triggered one, so nothing about the entry itself was wrong — the
23
+ // pipeline simply had not started exporting yet). Poll for 360s, which still
24
+ // leaves headroom inside the 12-minute test timeout after the ~60s beforeAll.
21
25
  exports.auditLogAssertionOptions = {
22
26
  intervals: [5000, 10000, 15000, 20000, 30000],
23
- timeout: 180000,
27
+ timeout: 360000,
24
28
  };
25
29
  exports.TIMEOUT = {
26
30
  short: 15000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1002",
3
+ "version": "0.0.1003",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",