@camunda/e2e-test-suite 0.0.648 → 0.0.650

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.
@@ -96,7 +96,7 @@ class ClusterPage {
96
96
  this.clustersList = page
97
97
  .getByRole('row')
98
98
  .filter({ hasNotText: 'Generation' }); //Filter out header row
99
- this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName });
99
+ this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
100
100
  this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
101
101
  exact: true,
102
102
  });
@@ -122,32 +122,41 @@ class ClusterPage {
122
122
  });
123
123
  while ((await deleteButtons.count()) > 0) {
124
124
  const deleteButton = deleteButtons.nth(0);
125
- if (await deleteButton.isVisible()) {
126
- try {
127
- await deleteButton.click({ force: true, timeout: 60000 });
128
- }
129
- catch (error) {
130
- await this.clickClusterBanner();
131
- await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
132
- await deleteButton.click({ force: true, timeout: 60000 });
133
- }
134
- await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
135
- await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
136
- await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
137
- await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
138
- const dangerDeleteButtons = await this.dangerDeleteButton.all();
139
- const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
140
- await lastDangerDeleteButton.click();
141
- await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
142
- timeout: 60000,
143
- });
144
- await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
145
- // Refresh the list of delete buttons
125
+ // Wait for the Delete button to become visible — a cluster in a transient
126
+ // state (e.g. just created by a concurrent setup worker) may have the row
127
+ // present in the DOM but the button not yet rendered. Without this wait
128
+ // the while-loop would spin forever since count() stays > 0.
129
+ try {
130
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 10000 });
131
+ }
132
+ catch {
133
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
146
134
  deleteButtons = this.cluster(name).getByRole('button', {
147
135
  name: 'Delete',
148
136
  });
137
+ continue;
149
138
  }
139
+ try {
140
+ await deleteButton.click({ force: true, timeout: 60000 });
141
+ }
142
+ catch (error) {
143
+ await this.clickClusterBanner();
144
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
145
+ await deleteButton.click({ force: true, timeout: 60000 });
146
+ }
147
+ await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
148
+ await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
149
+ await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
150
+ await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
151
+ const dangerDeleteButtons = await this.dangerDeleteButton.all();
152
+ const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
153
+ await lastDangerDeleteButton.click();
154
+ await (0, test_1.expect)(this.cluster(name).getByText('Deleting...', { exact: true })).not.toBeVisible({ timeout: 60000 });
155
+ await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
156
+ // Refresh the list of delete buttons
157
+ deleteButtons = this.cluster(name).getByRole('button', { name: 'Delete' });
150
158
  }
159
+ await this.page.waitForTimeout(500);
151
160
  }
152
161
  async clickClusterTab() {
153
162
  await Promise.race([
@@ -179,6 +188,12 @@ class ClusterPage {
179
188
  async createCluster(name, region = 'GCP') {
180
189
  await this.clickClusterTab();
181
190
  await this.deleteCluster(name);
191
+ // A concurrent setup worker (e.g. another version's test suite running in
192
+ // the same org) may have created a same-named cluster in the window between
193
+ // our deleteCluster check and now. Reload and delete again to catch it.
194
+ await this.page.waitForTimeout(3000);
195
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
196
+ await this.deleteCluster(name);
182
197
  await this.clickCreateNewClusterButton();
183
198
  await this.clickClusterNameInput();
184
199
  await this.fillClusterNameInput(name);
@@ -233,22 +248,18 @@ class ClusterPage {
233
248
  await this.createClusterButton.click();
234
249
  }
235
250
  async clickClustersBreadcrumb(clusterName) {
236
- const creatingCheckRegex = new RegExp(`${clusterName}.*?Creatingdev`);
237
- const healthyCheckRegex = new RegExp(`${clusterName}.*?Healthydev`);
251
+ const clusterRow = this.cluster(clusterName);
252
+ const clusterRowVisible = clusterRow
253
+ .getByText('Creating', { exact: true })
254
+ .or(clusterRow.getByText('Healthy', { exact: true }));
238
255
  try {
239
256
  await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
240
257
  await this.clustersBreadcrumb.click({ timeout: 60000 });
241
- await (0, test_1.expect)(this.page.getByText(creatingCheckRegex)).toBeVisible({
242
- timeout: 30000,
243
- });
258
+ await (0, test_1.expect)(clusterRow.getByText('Creating', { exact: true })).toBeVisible({ timeout: 30000 });
244
259
  }
245
260
  catch (error) {
246
261
  await this.clickClusterBanner();
247
- await (0, test_1.expect)(this.page
248
- .getByText(creatingCheckRegex)
249
- .or(this.page.getByText(healthyCheckRegex))).toBeVisible({
250
- timeout: 30000,
251
- });
262
+ await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
252
263
  }
253
264
  }
254
265
  async clickClusterLink(name) {
@@ -1010,6 +1010,59 @@ class ModelerCreatePage {
1010
1010
  }
1011
1011
  async completePlayConfiguration(clusterName) {
1012
1012
  const timeout = 30000;
1013
+ // New flow (8.10+): "Setup environment" panel with three steps:
1014
+ // 1. Connect cluster → "Configure environment" modal → select cluster → Save
1015
+ // 2. Deploy process → wait for success banner
1016
+ // 3. Configure scenario
1017
+ const setupDeployButton = this.page
1018
+ .getByText('Deploy process')
1019
+ .locator('..')
1020
+ .getByRole('button', { name: 'Deploy' });
1021
+ const configureScenarioButton = this.page.getByRole('button', {
1022
+ name: 'Configure scenario',
1023
+ });
1024
+ // Wait up to 15s for EITHER the new-flow setup panel OR the legacy Continue
1025
+ // button to appear.
1026
+ const setupOrContinue = setupDeployButton.or(this.continueToPlayButton);
1027
+ const panelAppeared = await setupOrContinue
1028
+ .first()
1029
+ .isVisible({ timeout: 15000 })
1030
+ .catch(() => false);
1031
+ if (!panelAppeared) {
1032
+ return;
1033
+ }
1034
+ const isNewFlow = await setupDeployButton.isVisible().catch(() => false);
1035
+ if (isNewFlow) {
1036
+ // Step 1: connect cluster if not yet connected (modal with cluster dropdown)
1037
+ const connectClusterButton = this.page.getByRole('button', {
1038
+ name: 'Connect cluster',
1039
+ });
1040
+ const needsConnect = await connectClusterButton
1041
+ .isVisible({ timeout: 3000 })
1042
+ .catch(() => false);
1043
+ if (needsConnect) {
1044
+ await connectClusterButton.click({ timeout });
1045
+ const configureEnvDialog = this.page
1046
+ .getByRole('dialog')
1047
+ .filter({ hasText: 'Configure environment' });
1048
+ await configureEnvDialog.getByRole('combobox').click({ timeout });
1049
+ await this.page
1050
+ .getByRole('option', { name: new RegExp(clusterName, 'i') })
1051
+ .click({ timeout });
1052
+ await configureEnvDialog
1053
+ .getByRole('button', { name: 'Save' })
1054
+ .click({ timeout });
1055
+ }
1056
+ // Step 2: deploy — wait for enabled; button stays disabled until the cluster
1057
+ // connection is confirmed by the backend after the Save in step 1.
1058
+ await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 60000 });
1059
+ await setupDeployButton.click({ timeout });
1060
+ await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
1061
+ // Step 3: configure scenario
1062
+ await configureScenarioButton.click({ timeout });
1063
+ return;
1064
+ }
1065
+ // Legacy flow: select cluster via dialog, then click Continue.
1013
1066
  let attempts = 0;
1014
1067
  const maxRetries = 2;
1015
1068
  while (attempts < maxRetries) {
@@ -101,7 +101,7 @@ class ClusterPage {
101
101
  .filter({ hasNotText: 'Generation' }); //Filter out header row
102
102
  this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
103
103
  this.tasklistV2Api = page.getByText('Tasklist API v2');
104
- this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName });
104
+ this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
105
105
  this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
106
106
  exact: true,
107
107
  });
@@ -129,32 +129,41 @@ class ClusterPage {
129
129
  });
130
130
  while ((await deleteButtons.count()) > 0) {
131
131
  const deleteButton = deleteButtons.nth(0);
132
- if (await deleteButton.isVisible()) {
133
- try {
134
- await deleteButton.click({ force: true, timeout: 60000 });
135
- }
136
- catch (error) {
137
- await this.clickClusterBanner();
138
- await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
139
- await deleteButton.click({ force: true, timeout: 60000 });
140
- }
141
- await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
142
- await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
143
- await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
144
- await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
145
- const dangerDeleteButtons = await this.dangerDeleteButton.all();
146
- const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
147
- await lastDangerDeleteButton.click();
148
- await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
149
- timeout: 60000,
150
- });
151
- await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
152
- // Refresh the list of delete buttons
132
+ // Wait for the Delete button to become visible — a cluster in a transient
133
+ // state (e.g. just created by a concurrent setup worker) may have the row
134
+ // present in the DOM but the button not yet rendered. Without this wait
135
+ // the while-loop would spin forever since count() stays > 0.
136
+ try {
137
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 10000 });
138
+ }
139
+ catch {
140
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
153
141
  deleteButtons = this.cluster(name).getByRole('button', {
154
142
  name: 'Delete',
155
143
  });
144
+ continue;
156
145
  }
146
+ try {
147
+ await deleteButton.click({ force: true, timeout: 60000 });
148
+ }
149
+ catch (error) {
150
+ await this.clickClusterBanner();
151
+ await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
152
+ await deleteButton.click({ force: true, timeout: 60000 });
153
+ }
154
+ await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
155
+ await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
156
+ await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
157
+ await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
158
+ const dangerDeleteButtons = await this.dangerDeleteButton.all();
159
+ const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
160
+ await lastDangerDeleteButton.click();
161
+ await (0, test_1.expect)(this.cluster(name).getByText('Deleting...', { exact: true })).not.toBeVisible({ timeout: 60000 });
162
+ await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
163
+ // Refresh the list of delete buttons
164
+ deleteButtons = this.cluster(name).getByRole('button', { name: 'Delete' });
157
165
  }
166
+ await this.page.waitForTimeout(500);
158
167
  }
159
168
  async clickClusterTab() {
160
169
  await Promise.race([
@@ -201,6 +210,12 @@ class ClusterPage {
201
210
  async createCluster(name, region = 'GCP', tasklistV1Api) {
202
211
  await this.clickClusterTab();
203
212
  await this.deleteCluster(name);
213
+ // A concurrent setup worker (e.g. another version's test suite running in
214
+ // the same org) may have created a same-named cluster in the window between
215
+ // our deleteCluster check and now. Reload and delete again to catch it.
216
+ await this.page.waitForTimeout(3000);
217
+ await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
218
+ await this.deleteCluster(name);
204
219
  await this.clickCreateNewClusterButton();
205
220
  await this.clickClusterNameInput();
206
221
  await this.fillClusterNameInput(name);
@@ -253,22 +268,18 @@ class ClusterPage {
253
268
  await this.createClusterButton.click();
254
269
  }
255
270
  async clickClustersBreadcrumb(clusterName) {
256
- const creatingCheckRegex = new RegExp(`${clusterName}.*?Creatingdev`);
257
- const healthyCheckRegex = new RegExp(`${clusterName}.*?Healthydev`);
271
+ const clusterRow = this.cluster(clusterName);
272
+ const clusterRowVisible = clusterRow
273
+ .getByText('Creating', { exact: true })
274
+ .or(clusterRow.getByText('Healthy', { exact: true }));
258
275
  try {
259
276
  await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
260
277
  await this.clustersBreadcrumb.click({ timeout: 60000 });
261
- await (0, test_1.expect)(this.page.getByText(creatingCheckRegex)).toBeVisible({
262
- timeout: 30000,
263
- });
278
+ await (0, test_1.expect)(clusterRow.getByText('Creating', { exact: true })).toBeVisible({ timeout: 30000 });
264
279
  }
265
280
  catch (error) {
266
281
  await this.clickClusterBanner();
267
- await (0, test_1.expect)(this.page
268
- .getByText(creatingCheckRegex)
269
- .or(this.page.getByText(healthyCheckRegex))).toBeVisible({
270
- timeout: 30000,
271
- });
282
+ await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
272
283
  }
273
284
  }
274
285
  async clickClusterLink(name) {
@@ -1076,6 +1076,61 @@ class ModelerCreatePage {
1076
1076
  }
1077
1077
  async completePlayConfiguration(clusterName) {
1078
1078
  const timeout = 30000;
1079
+ // New flow (8.10+): "Setup environment" panel with three steps:
1080
+ // 1. Connect cluster → "Configure environment" modal → select cluster → Save
1081
+ // 2. Deploy process → wait for success banner
1082
+ // 3. Configure scenario
1083
+ const setupDeployButton = this.page
1084
+ .getByText('Deploy process')
1085
+ .locator('..')
1086
+ .getByRole('button', { name: 'Deploy' });
1087
+ const configureScenarioButton = this.page.getByRole('button', {
1088
+ name: 'Configure scenario',
1089
+ });
1090
+ // Wait up to 15s for EITHER the new-flow setup panel OR the legacy Continue
1091
+ // button to appear. Using .or() avoids separate sequential timeouts that would
1092
+ // both expire before the page finishes loading on a slow cluster.
1093
+ const setupOrContinue = setupDeployButton.or(this.continueToPlayButton);
1094
+ const panelAppeared = await setupOrContinue
1095
+ .first()
1096
+ .isVisible({ timeout: 15000 })
1097
+ .catch(() => false);
1098
+ if (!panelAppeared) {
1099
+ // Neither appeared — Play is already configured and ready.
1100
+ return;
1101
+ }
1102
+ const isNewFlow = await setupDeployButton.isVisible().catch(() => false);
1103
+ if (isNewFlow) {
1104
+ // Step 1: connect cluster if not yet connected (modal with cluster dropdown)
1105
+ const connectClusterButton = this.page.getByRole('button', {
1106
+ name: 'Connect cluster',
1107
+ });
1108
+ const needsConnect = await connectClusterButton
1109
+ .isVisible({ timeout: 3000 })
1110
+ .catch(() => false);
1111
+ if (needsConnect) {
1112
+ await connectClusterButton.click({ timeout });
1113
+ const configureEnvDialog = this.page
1114
+ .getByRole('dialog')
1115
+ .filter({ hasText: 'Configure environment' });
1116
+ await configureEnvDialog.getByRole('combobox').click({ timeout });
1117
+ await this.page
1118
+ .getByRole('option', { name: new RegExp(clusterName, 'i') })
1119
+ .click({ timeout });
1120
+ await configureEnvDialog
1121
+ .getByRole('button', { name: 'Save' })
1122
+ .click({ timeout });
1123
+ }
1124
+ // Step 2: deploy — wait for enabled; button stays disabled until the cluster
1125
+ // connection is confirmed by the backend after the Save in step 1.
1126
+ await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 30000 });
1127
+ await setupDeployButton.click({ timeout });
1128
+ await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
1129
+ // Step 3: configure scenario
1130
+ await configureScenarioButton.click({ timeout });
1131
+ return;
1132
+ }
1133
+ // Legacy flow: select cluster via dialog, then click Continue.
1079
1134
  let attempts = 0;
1080
1135
  const maxRetries = 2;
1081
1136
  while (attempts < maxRetries) {
@@ -65,9 +65,9 @@ class ModelerHomePage {
65
65
  this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
66
66
  this.openOrganizationsButton = page.getByLabel('Open Organizations');
67
67
  this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
68
- this.crossComponentProjectFolder = page.getByTitle(this.defaultFolderName, {
69
- exact: true,
70
- });
68
+ this.crossComponentProjectFolder = page
69
+ .getByTitle(this.defaultFolderName, { exact: true })
70
+ .first();
71
71
  this.rows = page.getByRole('row');
72
72
  this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
73
73
  this.messageBanner = page.locator('[data-test="close-top-banner"]');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.648",
3
+ "version": "0.0.650",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",