@camunda/e2e-test-suite 0.0.646 → 0.0.648

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.
@@ -100,7 +100,7 @@ class ClusterPage {
100
100
  .filter({ hasNotText: 'Generation' }); //Filter out header row
101
101
  this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
102
102
  this.tasklistV2Api = page.getByText('Tasklist API v2');
103
- this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName });
103
+ this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
104
104
  this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
105
105
  exact: true,
106
106
  });
@@ -128,31 +128,39 @@ class ClusterPage {
128
128
  });
129
129
  while ((await deleteButtons.count()) > 0) {
130
130
  const deleteButton = deleteButtons.nth(0);
131
- if (await deleteButton.isVisible()) {
132
- try {
133
- await deleteButton.click({ force: true, timeout: 60000 });
134
- }
135
- catch (error) {
136
- await this.clickClusterBanner();
137
- await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
138
- await deleteButton.click({ force: true, timeout: 60000 });
139
- }
140
- await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
141
- await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
142
- await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
143
- await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
144
- const dangerDeleteButtons = await this.dangerDeleteButton.all();
145
- const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
146
- await lastDangerDeleteButton.click();
147
- await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
148
- timeout: 60000,
149
- });
150
- await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
151
- // Refresh the list of delete buttons
131
+ // Wait for the Delete button to become visible — a cluster in a transient
132
+ // state (e.g. just created by a concurrent setup worker) may have the row
133
+ // present in the DOM but the button not yet rendered. Without this wait
134
+ // the while-loop would spin forever since count() stays > 0.
135
+ try {
136
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 10000 });
137
+ }
138
+ catch {
139
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
152
140
  deleteButtons = this.cluster(name).getByRole('button', {
153
141
  name: 'Delete',
154
142
  });
143
+ continue;
144
+ }
145
+ try {
146
+ await deleteButton.click({ force: true, timeout: 60000 });
155
147
  }
148
+ catch (error) {
149
+ await this.clickClusterBanner();
150
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
151
+ await deleteButton.click({ force: true, timeout: 60000 });
152
+ }
153
+ await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
154
+ await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
155
+ await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
156
+ await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
157
+ const dangerDeleteButtons = await this.dangerDeleteButton.all();
158
+ const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
159
+ await lastDangerDeleteButton.click();
160
+ await (0, test_1.expect)(this.cluster(name).getByText('Deleting...', { exact: true })).not.toBeVisible({ timeout: 60000 });
161
+ await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
162
+ // Refresh the list of delete buttons
163
+ deleteButtons = this.cluster(name).getByRole('button', { name: 'Delete' });
156
164
  }
157
165
  await this.page.waitForTimeout(500);
158
166
  }
@@ -201,6 +209,12 @@ class ClusterPage {
201
209
  async createCluster(name, region = 'GCP', tasklistV1Api) {
202
210
  await this.clickClusterTab();
203
211
  await this.deleteCluster(name);
212
+ // A concurrent setup worker (e.g. another version's test suite running in
213
+ // the same org) may have created a same-named cluster in the window between
214
+ // our deleteCluster check and now. Reload and delete again to catch it.
215
+ await this.page.waitForTimeout(3000);
216
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
217
+ await this.deleteCluster(name);
204
218
  await this.clickCreateNewClusterButton();
205
219
  await this.clickClusterNameInput();
206
220
  await this.fillClusterNameInput(name);
@@ -235,22 +249,18 @@ class ClusterPage {
235
249
  await this.createClusterButton.click();
236
250
  }
237
251
  async clickClustersBreadcrumb(clusterName) {
238
- const creatingCheckRegex = new RegExp(`${clusterName}.*?Creatingdev`);
239
- const healthyCheckRegex = new RegExp(`${clusterName}.*?Healthydev`);
252
+ const clusterRow = this.cluster(clusterName);
253
+ const clusterRowVisible = clusterRow
254
+ .getByText('Creating', { exact: true })
255
+ .or(clusterRow.getByText('Healthy', { exact: true }));
240
256
  try {
241
257
  await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
242
258
  await this.clustersBreadcrumb.click({ timeout: 60000 });
243
- await (0, test_1.expect)(this.page.getByText(creatingCheckRegex)).toBeVisible({
244
- timeout: 30000,
245
- });
259
+ await (0, test_1.expect)(clusterRow.getByText('Creating', { exact: true })).toBeVisible({ timeout: 30000 });
246
260
  }
247
261
  catch (error) {
248
262
  await this.clickClusterBanner();
249
- await (0, test_1.expect)(this.page
250
- .getByText(creatingCheckRegex)
251
- .or(this.page.getByText(healthyCheckRegex))).toBeVisible({
252
- timeout: 30000,
253
- });
263
+ await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
254
264
  }
255
265
  }
256
266
  async clickClusterLink(name) {
@@ -1023,6 +1023,61 @@ class ModelerCreatePage {
1023
1023
  }
1024
1024
  async completePlayConfiguration(clusterName) {
1025
1025
  const timeout = 30000;
1026
+ // New flow (8.10+): "Setup environment" panel with three steps:
1027
+ // 1. Connect cluster → "Configure environment" modal → select cluster → Save
1028
+ // 2. Deploy process → wait for success banner
1029
+ // 3. Configure scenario
1030
+ const setupDeployButton = this.page
1031
+ .getByText('Deploy process')
1032
+ .locator('..')
1033
+ .getByRole('button', { name: 'Deploy' });
1034
+ const configureScenarioButton = this.page.getByRole('button', {
1035
+ name: 'Configure scenario',
1036
+ });
1037
+ // Wait up to 15s for EITHER the new-flow setup panel OR the legacy Continue
1038
+ // button to appear. Using .or() avoids separate sequential timeouts that would
1039
+ // both expire before the page finishes loading on a slow cluster.
1040
+ const setupOrContinue = setupDeployButton.or(this.continueToPlayButton);
1041
+ const panelAppeared = await setupOrContinue
1042
+ .first()
1043
+ .isVisible({ timeout: 15000 })
1044
+ .catch(() => false);
1045
+ if (!panelAppeared) {
1046
+ // Neither appeared — Play is already configured and ready.
1047
+ return;
1048
+ }
1049
+ const isNewFlow = await setupDeployButton.isVisible().catch(() => false);
1050
+ if (isNewFlow) {
1051
+ // Step 1: connect cluster if not yet connected (modal with cluster dropdown)
1052
+ const connectClusterButton = this.page.getByRole('button', {
1053
+ name: 'Connect cluster',
1054
+ });
1055
+ const needsConnect = await connectClusterButton
1056
+ .isVisible({ timeout: 3000 })
1057
+ .catch(() => false);
1058
+ if (needsConnect) {
1059
+ await connectClusterButton.click({ timeout });
1060
+ const configureEnvDialog = this.page
1061
+ .getByRole('dialog')
1062
+ .filter({ hasText: 'Configure environment' });
1063
+ await configureEnvDialog.getByRole('combobox').click({ timeout });
1064
+ await this.page
1065
+ .getByRole('option', { name: new RegExp(clusterName, 'i') })
1066
+ .click({ timeout });
1067
+ await configureEnvDialog
1068
+ .getByRole('button', { name: 'Save' })
1069
+ .click({ timeout });
1070
+ }
1071
+ // Step 2: deploy — wait for enabled; button stays disabled until the cluster
1072
+ // connection is confirmed by the backend after the Save in step 1.
1073
+ await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 30000 });
1074
+ await setupDeployButton.click({ timeout });
1075
+ await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
1076
+ // Step 3: configure scenario
1077
+ await configureScenarioButton.click({ timeout });
1078
+ return;
1079
+ }
1080
+ // Legacy flow: select cluster via dialog, then click Continue.
1026
1081
  let attempts = 0;
1027
1082
  const maxRetries = 2;
1028
1083
  while (attempts < maxRetries) {
@@ -62,9 +62,9 @@ class ModelerHomePage {
62
62
  this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
63
63
  this.openOrganizationsButton = page.getByLabel('Open Organizations');
64
64
  this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
65
- this.crossComponentProjectFolder = page.getByTitle(this.defaultFolderName, {
66
- exact: true,
67
- });
65
+ this.crossComponentProjectFolder = page
66
+ .getByTitle(this.defaultFolderName, { exact: true })
67
+ .first();
68
68
  this.rows = page.getByRole('row');
69
69
  this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
70
70
  this.messageBanner = page.locator('[data-test="close-top-banner"]');
@@ -22,6 +22,7 @@ declare class ModelerCreatePage {
22
22
  readonly renameDiagramNameButton: Locator;
23
23
  readonly diagramNameInput: Locator;
24
24
  readonly variableInput: Locator;
25
+ readonly businessIdInput: Locator;
25
26
  readonly embedFormButton: Locator;
26
27
  readonly embedButton: Locator;
27
28
  readonly newForm: Locator;
@@ -92,7 +93,7 @@ declare class ModelerCreatePage {
92
93
  modelJobWorkerDiagram(processName: string): Promise<void>;
93
94
  assertThreeElementsVisible(): Promise<void>;
94
95
  clickForm(name: string): Promise<void>;
95
- runProcessInstance(variables?: string, tenant?: string): Promise<void>;
96
+ runProcessInstance(variables?: string, tenant?: string, businessId?: string): Promise<void>;
96
97
  switchToPlay(): Promise<void>;
97
98
  clickGeneralPropertiesPanel(): Promise<void>;
98
99
  clickIdInput(): Promise<void>;
@@ -27,6 +27,7 @@ class ModelerCreatePage {
27
27
  renameDiagramNameButton;
28
28
  diagramNameInput;
29
29
  variableInput;
30
+ businessIdInput;
30
31
  embedFormButton;
31
32
  embedButton;
32
33
  newForm;
@@ -136,6 +137,7 @@ class ModelerCreatePage {
136
137
  this.renameDiagramNameButton = page.getByText('Rename');
137
138
  this.diagramNameInput = page.locator('[data-test="editable-input"]');
138
139
  this.variableInput = page.locator('[id="variables-json"]');
140
+ this.businessIdInput = page.locator('[id^="business-id-input"]');
139
141
  this.embedFormButton = page.getByRole('button', { name: 'Link form' });
140
142
  this.embedButton = page.locator('[data-test="confirm-move"]');
141
143
  this.newForm = page.locator('[data-test="item-New form"]');
@@ -333,7 +335,7 @@ class ModelerCreatePage {
333
335
  .getByText(name)
334
336
  .click({ timeout: 90000 });
335
337
  }
336
- async runProcessInstance(variables = '', tenant = '') {
338
+ async runProcessInstance(variables = '', tenant = '', businessId = '') {
337
339
  await this.page.waitForTimeout(2000); // the canvas autosaves. This can take a second to save.
338
340
  const maxRetries = 3;
339
341
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -353,18 +355,24 @@ class ModelerCreatePage {
353
355
  await this.clickTenantIdInput();
354
356
  await this.fillTenantIdInput(tenant);
355
357
  }
358
+ if (businessId.length > 0) {
359
+ await (0, test_1.expect)(this.businessIdInput).toBeVisible({
360
+ timeout: 60000,
361
+ });
362
+ await this.businessIdInput.fill(businessId);
363
+ }
356
364
  await this.clickDeployAndRunSubButton();
357
365
  await this.instanceStartedAssertion();
358
366
  return;
359
367
  }
360
368
  catch (error) {
361
369
  if (attempt < maxRetries - 1) {
362
- console.warn(`Attempt ${attempt + 1} failed for deploy & run running process. Retrying...`);
370
+ console.warn(`Attempt ${attempt + 1} failed for deploy & run running process: ${error instanceof Error ? error.message : error}. Retrying...`);
363
371
  await this.page.reload();
364
372
  await (0, sleep_1.sleep)(10000);
365
373
  }
366
374
  else {
367
- throw new Error(`Assertion failed after ${maxRetries} attempts`);
375
+ throw new Error(`runProcessInstance failed after ${maxRetries} attempts. Last error: ${error instanceof Error ? error.message : error}`);
368
376
  }
369
377
  }
370
378
  }
@@ -24,6 +24,7 @@ declare class OperateProcessInstancePage {
24
24
  private captureVariableJsonValue;
25
25
  assertResultVariableVisibleWithRetry(variableName: string): Promise<void>;
26
26
  assertActiveTokenIsPresent(): Promise<void>;
27
+ assertBusinessIdVisible(businessId: string): Promise<void>;
27
28
  assertVariablesListVisible(timeout?: number): Promise<void>;
28
29
  }
29
30
  export { OperateProcessInstancePage };
@@ -234,6 +234,13 @@ class OperateProcessInstancePage {
234
234
  async assertActiveTokenIsPresent() {
235
235
  await (0, test_1.expect)(this.activeIcon).toBeVisible({ timeout: 60000 });
236
236
  }
237
+ async assertBusinessIdVisible(businessId) {
238
+ const instanceHeader = this.page.getByTestId('instance-header');
239
+ await (0, test_1.expect)(instanceHeader.getByText('Business ID')).toBeVisible({
240
+ timeout: 60000,
241
+ });
242
+ await (0, test_1.expect)(instanceHeader.getByText(businessId, { exact: true })).toBeVisible({ timeout: 60000 });
243
+ }
237
244
  async assertVariablesListVisible(timeout = 30000) {
238
245
  const startTime = Date.now();
239
246
  while (Date.now() - startTime < timeout) {
@@ -141,6 +141,35 @@ SM_8_10_1.test.describe('Web Modeler User Flow Tests', () => {
141
141
  await operateProcessInstancePage.completedIconAssertion();
142
142
  });
143
143
  });
144
+ (0, SM_8_10_1.test)('Deploy and run process with run configuration and verify in Operate', async ({ modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
145
+ SM_8_10_1.test.slow();
146
+ const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
147
+ const processName = 'Run_Config_Process_' + randomString;
148
+ const businessId = `bid-${Date.now()}-${randomString}`;
149
+ const variables = '{"orderNumber": "A12BH98", "amount": 185.34}';
150
+ await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
151
+ await modelerHomePage.clickCrossComponentProjectFolder();
152
+ await modelerHomePage.clickDiagramTypeDropdown();
153
+ await modelerHomePage.clickBpmnTemplateOption();
154
+ });
155
+ await SM_8_10_1.test.step('Model a simple job worker diagram', async () => {
156
+ await modelerCreatePage.modelJobWorkerDiagram(processName);
157
+ });
158
+ await SM_8_10_1.test.step('Deploy and run process with variables and Business ID', async () => {
159
+ await modelerCreatePage.runProcessInstance(variables, '', businessId);
160
+ });
161
+ await SM_8_10_1.test.step('View process instance in Operate and assert run configuration is visible', async () => {
162
+ await navigationPage.goToOperate();
163
+ await (0, test_1.expect)(operateHomePage.processesTab).toBeVisible({
164
+ timeout: 120000,
165
+ });
166
+ await operateHomePage.clickProcessesTab();
167
+ await operateProcessesPage.clickProcessInstanceLink(processName);
168
+ await operateProcessInstancePage.assertBusinessIdVisible(businessId);
169
+ await operateProcessInstancePage.assertProcessVariableContainsText('orderNumber', '"A12BH98"');
170
+ await operateProcessInstancePage.assertProcessVariableContainsText('amount', '185.34');
171
+ });
172
+ });
144
173
  (0, SM_8_10_1.test)('Conditional Events - Deploy, verify in Operate and verify process in Optimize dashboard @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeDashboardPage, }) => {
145
174
  SM_8_10_1.test.slow();
146
175
  const bpmnFileName = 'Conditional_Events_All.bpmn';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.646",
3
+ "version": "0.0.648",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",