@camunda/e2e-test-suite 0.0.1001 → 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();
@@ -36,6 +36,7 @@ declare class ModelerCreatePage {
36
36
  readonly firstPlacedElement: Locator;
37
37
  readonly secondPlacedElement: Locator;
38
38
  readonly appendElementButton: Locator;
39
+ readonly appendIndicator: Locator;
39
40
  readonly payloadInput: Locator;
40
41
  readonly marketPlaceButton: Locator;
41
42
  readonly publicHolidayConnectorOption: Locator;
@@ -164,6 +165,18 @@ declare class ModelerCreatePage {
164
165
  clickFirstPlacedElement(): Promise<void>;
165
166
  clickSecondPlacedElement(): Promise<void>;
166
167
  clickSecondElement(): Promise<void>;
168
+ /**
169
+ * Hovers the append ("+") pad of the current selection, opening it first
170
+ * when it is not already on screen.
171
+ *
172
+ * Since camunda/improved-canvas commit 3e2aa7a ("show append indicator
173
+ * instead of full pad when selection has outgoing flow") the full append pad
174
+ * only auto-opens for elements without an outgoing sequence flow. A selected
175
+ * element that already has one now shows a `.djs-append-indicator` "+"
176
+ * whose `mouseenter` opens the pad — see
177
+ * `lib/bpmn/appendIndicator/AppendIndicator.js` in that repo.
178
+ */
179
+ private hoverAppendPad;
167
180
  clickAppendElementButton(): Promise<void>;
168
181
  clickMarketPlaceButton(): Promise<void>;
169
182
  clickPublicHolidayConnectorOption(): Promise<void>;
@@ -41,6 +41,7 @@ class ModelerCreatePage {
41
41
  firstPlacedElement;
42
42
  secondPlacedElement;
43
43
  appendElementButton;
44
+ appendIndicator;
44
45
  payloadInput;
45
46
  marketPlaceButton;
46
47
  publicHolidayConnectorOption;
@@ -114,6 +115,12 @@ class ModelerCreatePage {
114
115
  this.appendElementButton = page
115
116
  .locator('[class="djs-create-pad open djs-append-create-pad"]')
116
117
  .first();
118
+ // Web Modeler renders a "+" indicator next to every appendable element and
119
+ // marks the selected element's one as `selected`; hovering it opens the
120
+ // append pad.
121
+ this.appendIndicator = page
122
+ .locator('.djs-append-indicator.selected:visible')
123
+ .first();
117
124
  this.appendTaskButton = page.getByRole('button', { name: 'Append task' });
118
125
  this.changeTypeButton = page.getByRole('button', {
119
126
  name: 'Change element',
@@ -449,7 +456,7 @@ class ModelerCreatePage {
449
456
  else if (parentElement == 'parallelGateway') {
450
457
  await this.clickSecondPlacedGateway();
451
458
  }
452
- await this.appendElementButton.hover();
459
+ await this.hoverAppendPad(60000);
453
460
  await this.appendEndEventButton.click({ timeout: 120000 });
454
461
  }
455
462
  }
@@ -736,16 +743,31 @@ class ModelerCreatePage {
736
743
  async clickSecondElement() {
737
744
  await this.secondElement.click({ timeout: 60000 });
738
745
  }
746
+ /**
747
+ * Hovers the append ("+") pad of the current selection, opening it first
748
+ * when it is not already on screen.
749
+ *
750
+ * Since camunda/improved-canvas commit 3e2aa7a ("show append indicator
751
+ * instead of full pad when selection has outgoing flow") the full append pad
752
+ * only auto-opens for elements without an outgoing sequence flow. A selected
753
+ * element that already has one now shows a `.djs-append-indicator` "+"
754
+ * whose `mouseenter` opens the pad — see
755
+ * `lib/bpmn/appendIndicator/AppendIndicator.js` in that repo.
756
+ */
757
+ async hoverAppendPad(timeout) {
758
+ if (!(await this.appendElementButton.isVisible())) {
759
+ await this.appendIndicator.hover({ timeout });
760
+ }
761
+ await this.appendElementButton.hover({ timeout, force: true });
762
+ }
739
763
  async clickAppendElementButton() {
740
764
  try {
741
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
742
- await sleep(10000);
743
- await this.appendElementButton.hover({ timeout: 60000, force: true });
765
+ await (0, sleep_1.sleep)(10000);
766
+ await this.hoverAppendPad(60000);
744
767
  }
745
768
  catch (error) {
746
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
747
- await sleep(10000);
748
- await this.appendElementButton.hover({ timeout: 90000, force: true });
769
+ await (0, sleep_1.sleep)(10000);
770
+ await this.hoverAppendPad(90000);
749
771
  }
750
772
  }
751
773
  async clickMarketPlaceButton() {
@@ -972,7 +994,7 @@ class ModelerCreatePage {
972
994
  await this.page.reload();
973
995
  await (0, sleep_1.sleep)(10000);
974
996
  await this.clickFirstPlacedElement();
975
- await this.appendElementButton.hover();
997
+ await this.hoverAppendPad(60000);
976
998
  }
977
999
  await this.clickAppendGatewayButton();
978
1000
  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.1001",
3
+ "version": "0.0.1003",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",