@camunda/e2e-test-suite 0.0.1403 → 0.0.1405

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.
@@ -36,6 +36,7 @@ declare class ClusterPage {
36
36
  readonly dialog: Locator;
37
37
  constructor(page: Page);
38
38
  private createdClusterRow;
39
+ private filterClustersTable;
39
40
  clickClusterBanner(): Promise<void>;
40
41
  deleteCluster(name: string): Promise<void>;
41
42
  clickClusterTab(): Promise<void>;
@@ -131,6 +131,15 @@ class ClusterPage {
131
131
  .filter({ has: this.page.locator(`a[href$="/cluster/${clusterId}"]`) })
132
132
  .first();
133
133
  }
134
+ // Hub's clusters table shows 25 rows per page, so once the org holds more
135
+ // clusters than that the wanted row can sit on a later page and never match.
136
+ // Narrow the table to the cluster name via its search box first.
137
+ async filterClustersTable(name) {
138
+ const search = this.page.getByPlaceholder('Search', { exact: true });
139
+ if (await search.isVisible({ timeout: 10000 }).catch(() => false)) {
140
+ await search.fill(name);
141
+ }
142
+ }
134
143
  async clickClusterBanner() {
135
144
  // Camunda Hub has no `banner` landmark; "Clusters" instead lives in its
136
145
  // persistent "Main navigation" sidebar, and once the org has environments
@@ -162,10 +171,19 @@ class ClusterPage {
162
171
  // provider and /login-callback (~35s) before the SPA pushes its returnUrl
163
172
  // back to /clusters. Counting rows mid-bounce throws "Execution context
164
173
  // was destroyed", so wait for the clusters page to have settled first.
165
- await this.page.waitForURL((url) => /\/clusters\/?$/.test(url.pathname), {
166
- timeout: 120000,
167
- });
174
+ const onClustersPage = (url) => /\/clusters\/?$/.test(url.pathname);
175
+ try {
176
+ await this.page.waitForURL(onClustersPage, { timeout: 120000 });
177
+ }
178
+ catch {
179
+ // The bounce can hand back a different route, and a caller that failed
180
+ // mid-flow (e.g. the test's cleanup step) may sit on a cluster's detail
181
+ // page. Navigate to the clusters list rather than failing the cleanup.
182
+ await this.clickClusterBanner();
183
+ await this.page.waitForURL(onClustersPage, { timeout: 60000 });
184
+ }
168
185
  await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
186
+ await this.filterClustersTable(name);
169
187
  // Hub's clusters data table renders skeleton rows (no links) while it
170
188
  // fetches, and the "Create new cluster" button is visible before the data
171
189
  // arrives. Counting matching rows at that point always returned 0, so the
@@ -207,6 +225,8 @@ class ClusterPage {
207
225
  // A cluster in a transient state (e.g. just created by a concurrent
208
226
  // setup worker) may have its row present before the action renders.
209
227
  await this.page.reload({ waitUntil: 'domcontentloaded' });
228
+ await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
229
+ await this.filterClustersTable(name);
210
230
  await this.clustersList.getByRole('link').first().waitFor({
211
231
  timeout: 30000,
212
232
  });
@@ -217,23 +237,41 @@ class ClusterPage {
217
237
  const clusterHref = await row
218
238
  .getByRole('link', { name, exact: true })
219
239
  .getAttribute('href');
240
+ const pendingRow = this.clustersList
241
+ .filter({ has: this.page.locator(`a[href="${clusterHref}"]`) })
242
+ .filter({ hasNotText: /Deleting/ });
220
243
  // The kebab menu animates open and the table re-renders while polling
221
244
  // cluster status, detaching the menu item mid-click ("element is not
222
- // stable"). Reopen the menu and retry until the confirm dialog shows.
245
+ // stable"). The same re-render also unmounts the confirm dialog after
246
+ // DELETE is typed, leaving the danger button detached for good. Run the
247
+ // whole open-confirm-delete sequence as one retried unit, and stop once
248
+ // this cluster's row is gone or deleting (an earlier click landed).
223
249
  await (0, test_1.expect)(async () => {
250
+ if (!(await this.dialog.isVisible()) &&
251
+ (await pendingRow.count()) === 0) {
252
+ return;
253
+ }
224
254
  if (!(await this.dialog.isVisible())) {
225
255
  if (!(await deleteMenuItem.isVisible())) {
226
256
  await rowAction.click({ timeout: 10000 });
227
257
  }
228
258
  if (await deleteMenuItem.isVisible({ timeout: 5000 }).catch(() => false)) {
229
- await deleteMenuItem.click({ timeout: 10000 });
259
+ try {
260
+ await deleteMenuItem.click({ timeout: 10000 });
261
+ }
262
+ catch {
263
+ // For a row near the bottom of a long table the menu renders
264
+ // outside the viewport and cannot be scrolled to, so the click
265
+ // never lands. Select the focused item via keyboard instead.
266
+ await deleteMenuItem.press('Enter', { timeout: 10000 });
267
+ }
230
268
  }
231
269
  }
232
270
  await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 5000 });
233
- }).toPass({ timeout: 90000 });
234
- await (0, test_1.expect)(confirmInput.first()).toBeVisible({ timeout: 60000 });
235
- await confirmInput.first().fill('DELETE', { timeout: 60000 });
236
- await this.dangerDeleteButton.last().click({ timeout: 60000 });
271
+ await (0, test_1.expect)(confirmInput.first()).toBeVisible({ timeout: 10000 });
272
+ await confirmInput.first().fill('DELETE', { timeout: 10000 });
273
+ await this.dangerDeleteButton.last().click({ timeout: 10000 });
274
+ }).toPass({ timeout: 180000 });
237
275
  // When a concurrent worker already deleted this same cluster, the modal
238
276
  // stays open with "We're having trouble deleting the cluster". Close it;
239
277
  // the row check below still fails if the cluster was not removed.
@@ -247,6 +285,10 @@ class ClusterPage {
247
285
  if (await deleteError.isVisible()) {
248
286
  await this.dialog.getByRole('button', { name: 'Cancel' }).click();
249
287
  await this.page.reload({ waitUntil: 'domcontentloaded' });
288
+ await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({
289
+ timeout: 120000,
290
+ });
291
+ await this.filterClustersTable(name);
250
292
  await this.clustersList.getByRole('link').first().waitFor({
251
293
  timeout: 120000,
252
294
  });
@@ -255,9 +297,7 @@ class ClusterPage {
255
297
  // Wait until the deleted row is gone or flagged as deleting. The table
256
298
  // is paginated, so the row count alone does not drop: the next page's
257
299
  // rows slide in to fill the gap.
258
- await (0, test_1.expect)(this.clustersList
259
- .filter({ has: this.page.locator(`a[href="${clusterHref}"]`) })
260
- .filter({ hasNotText: /Deleting/ })).toHaveCount(0, { timeout: 60000 });
300
+ await (0, test_1.expect)(pendingRow).toHaveCount(0, { timeout: 60000 });
261
301
  }
262
302
  }
263
303
  async clickClusterTab() {
@@ -279,8 +319,13 @@ class ClusterPage {
279
319
  }
280
320
  async clickClusterNameInput() {
281
321
  await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.clusterNameInput, {
322
+ visibilityTimeout: 30000,
323
+ totalTimeout: 180000,
282
324
  postAction: async () => {
325
+ // Going back to the clusters list closes the create form, so reopen
326
+ // it; otherwise every later attempt looks for an input that is gone.
283
327
  await this.clickClusterBanner();
328
+ await this.clickCreateNewClusterButton();
284
329
  },
285
330
  });
286
331
  }
@@ -375,11 +420,22 @@ class ClusterPage {
375
420
  try {
376
421
  await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
377
422
  await this.clustersBreadcrumb.click({ timeout: 60000 });
423
+ await this.filterClustersTable(clusterName);
378
424
  await (0, test_1.expect)(creatingStatus).toBeVisible({ timeout: 30000 });
379
425
  }
380
426
  catch (error) {
381
- await this.clickClusterBanner();
382
- await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
427
+ // Hub serves the clusters table from its cached list after an in-app
428
+ // navigation, so the just-created cluster is missing until a reload.
429
+ // The new row can take more than one reload to show up, so retry.
430
+ await (0, test_1.expect)(async () => {
431
+ await this.clickClusterBanner();
432
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
433
+ await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({
434
+ timeout: 120000,
435
+ });
436
+ await this.filterClustersTable(clusterName);
437
+ await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 60000 });
438
+ }).toPass({ timeout: 420000 });
383
439
  }
384
440
  }
385
441
  async clickClusterLink(name) {
@@ -489,6 +545,8 @@ class ClusterPage {
489
545
  maxRetries: 5,
490
546
  preAction: async () => {
491
547
  await this.page.reload();
548
+ await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
549
+ await this.filterClustersTable(name);
492
550
  await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
493
551
  },
494
552
  });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OperateHomePage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
6
+ const sessionRecovery_1 = require("../../utils/sessionRecovery");
6
7
  class OperateHomePage {
7
8
  page;
8
9
  operateHeader;
@@ -37,10 +38,20 @@ class OperateHomePage {
37
38
  this.messageBanner = page.getByRole('button', { name: 'Close' });
38
39
  }
39
40
  async clickProcessesTab() {
41
+ // Reload between attempts: a click that keeps failing means the Operate
42
+ // shell is stuck (half-loaded, overlaid, or bounced to login), and
43
+ // re-clicking the same frozen page cannot recover from that.
40
44
  await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.processesTab, {
45
+ totalTimeout: 180000,
46
+ visibilityTimeout: 30000,
47
+ maxRetries: 4,
41
48
  preAction: async () => {
42
49
  await this.clickMessageBanner();
43
50
  },
51
+ postAction: async () => {
52
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
53
+ await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page);
54
+ },
44
55
  });
45
56
  }
46
57
  async closeInformationDialog() {
@@ -33,9 +33,21 @@ _8_7_1.test.describe('Console User Flow Tests', () => {
33
33
  await homePage.clickClusters();
34
34
  await clusterPage.createCluster(newClusterName);
35
35
  });
36
- await _8_7_1.test.step('Assert cluster is healthy', async () => {
37
- await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
38
- });
36
+ try {
37
+ await _8_7_1.test.step('Assert cluster is healthy', async () => {
38
+ await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
39
+ });
40
+ }
41
+ finally {
42
+ // Suffixed repeat names are never pre-deleted by a later run, so each
43
+ // repeat leaked one G3-Dev cluster until the org ran out of G3-Dev
44
+ // reservations and the type tile rendered disabled. Remove them here.
45
+ if (repeatIndex) {
46
+ await _8_7_1.test.step('Delete created cluster', async () => {
47
+ await clusterPage.deleteCluster(newClusterName);
48
+ });
49
+ }
50
+ }
39
51
  });
40
52
  //Waiting infra team to provide DNS configuration for email testing, skipping for now
41
53
  _8_7_1.test.skip('Alert Trigger Flow - Email Notification', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, connectorSettingsPage, }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1403",
3
+ "version": "0.0.1405",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",