@camunda/e2e-test-suite 0.0.1413 → 0.0.1414

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.
@@ -69,6 +69,7 @@ declare class ClusterDetailsPage {
69
69
  clickSettingsTab(): Promise<void>;
70
70
  private waitForAuthorizationsSection;
71
71
  private setAuthorizationsViaSwitch;
72
+ private submitAuthorizationsConfirm;
72
73
  enableAuthorizations(): Promise<void>;
73
74
  disableAuthorizations(): Promise<void>;
74
75
  searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
@@ -369,13 +369,39 @@ class ClusterDetailsPage {
369
369
  .then(() => true)
370
370
  .catch(() => false);
371
371
  if (confirmOpened) {
372
- const confirm = enabled ? this.rbaEnableButton : this.rbaDisableButton;
373
- await confirm.click({ timeout: 30000 });
374
- await (0, test_1.expect)(this.page.getByText(enabled ? 'Enabling...' : 'Disabling...')).not.toBeVisible({ timeout: 60000 });
372
+ await this.submitAuthorizationsConfirm(enabled);
375
373
  }
376
374
  await (0, test_1.expect)(this.authorizationsSwitch).toHaveAttribute('aria-checked', String(enabled), { timeout: 120000 });
377
375
  return true;
378
376
  }
377
+ // The confirm dialog sends one PATCH to console-backend. When it fails, the
378
+ // dialog stays open with the error in an inline alert and does not retry by
379
+ // itself (`submitAuthorizationsConfirmation` in camunda-cloud-management-apps'
380
+ // c4 settings tab), so the switch behind the modal never becomes reachable.
381
+ // On INT console-backend intermittently answers without CORS headers, which
382
+ // the browser reports as "fetch error". Submit again, as a user would: the
383
+ // body names the target state, so a PATCH that did land is not applied twice.
384
+ async submitAuthorizationsConfirm(enabled) {
385
+ const confirm = enabled ? this.rbaEnableButton : this.rbaDisableButton;
386
+ const submitError = this.dialog.getByRole('alert');
387
+ await (0, test_1.expect)(async () => {
388
+ // A submit that was still in flight when the previous attempt gave up
389
+ // may have closed the dialog since.
390
+ if (await this.dialog.isHidden()) {
391
+ return;
392
+ }
393
+ if (await submitError.isVisible()) {
394
+ console.warn(`Console rejected the authorizations change ` +
395
+ `("${await submitError.textContent()}"); submitting it again.`);
396
+ }
397
+ await confirm.click({ timeout: 30000 });
398
+ // The submit settles either way: the dialog closes, or the alert shows.
399
+ await test_1.expect
400
+ .poll(async () => (await this.dialog.isHidden()) || (await submitError.isVisible()), { timeout: 60000 })
401
+ .toBe(true);
402
+ await (0, test_1.expect)(this.dialog, 'Console did not accept the authorizations change').toBeHidden({ timeout: 1000 });
403
+ }).toPass({ timeout: 300000, intervals: [5000, 10000, 20000] });
404
+ }
379
405
  async enableAuthorizations() {
380
406
  await (0, sleep_1.sleep)(5000);
381
407
  await this.waitForAuthorizationsSection();
@@ -275,7 +275,13 @@ class ClusterPage {
275
275
  timeout: 30000,
276
276
  });
277
277
  if (await deleteMenuItem.isVisible()) {
278
- await deleteMenuItem.click({ timeout: 30000 });
278
+ // Radix renders the menu at `translate(0, -200%)` until it has
279
+ // positioned it against the trigger (@radix-ui/react-popper). When
280
+ // that never happens the item is visible but outside the viewport, and
281
+ // a pointer click retried for the full 30s without landing. Enter on
282
+ // the item is the same selection: Radix's MenuItem turns it into a
283
+ // click.
284
+ await deleteMenuItem.press('Enter', { timeout: 30000 });
279
285
  }
280
286
  await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
281
287
  await (0, test_1.expect)(confirmInput.first()).toBeVisible({ timeout: 60000 });
@@ -19,7 +19,13 @@ class TaskProcessesPage {
19
19
  this.popupContinueButton = page.getByRole('button', { name: 'Continue' });
20
20
  this.popup = page.getByLabel('Start your process on demand');
21
21
  this.processes = page.locator('[data-testid="process-tile"]');
22
- this.availableTasks = page.getByTitle('Available tasks').first();
22
+ // Same dual-shell anchor as TaskPanelPage: the redesigned Tasklist shell
23
+ // dropped the `title="Available tasks"` wrapper, and the rollout is per
24
+ // app, so match the `Tasks side panel` region as well as the old title.
25
+ this.availableTasks = page
26
+ .getByRole('region', { name: 'Tasks side panel' })
27
+ .or(page.getByTitle('Available tasks'))
28
+ .first();
23
29
  this.waitingForTasksText = this.page.getByText('Waiting for tasks...');
24
30
  this.searchProcessesSearchbox = page.getByRole('searchbox', {
25
31
  name: 'Search processes',
@@ -79,7 +85,7 @@ class TaskProcessesPage {
79
85
  // `queryUserTasks({processInstanceKey})` every second under a hard
80
86
  // `TASKS_TIMEOUT: 15000`; when that elapses the machine goes straight to
81
87
  // `Succeeded` and performs no navigation at all, leaving the browser on the
82
- // Processes page -- which never renders the "Available tasks" panel. On
88
+ // Processes page -- which never renders the tasks side panel. On
83
89
  // these long-lived SaaS RBA clusters the exporter routinely lags past 15s,
84
90
  // so waiting 90s for a redirect the product gave up on after 15 can only
85
91
  // time out. Open the Tasks view ourselves when the redirect did not happen;
@@ -40,11 +40,18 @@ _8_10_1.test.describe('Console cluster duplication', () => {
40
40
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
41
41
  await homePage.clickClusters();
42
42
  });
43
- _8_10_1.test.afterEach(async ({ page, homePage, clusterPage }, testInfo) => {
43
+ _8_10_1.test.afterEach(async ({ page, loginPage, homePage, clusterPage }, testInfo) => {
44
44
  await (0, _setup_1.captureScreenshot)(page, testInfo);
45
45
  // Postcondition: no cluster left behind, source or duplicate.
46
46
  const namesToDelete = [...createdClusterNames].reverse();
47
47
  createdClusterNames.length = 0;
48
+ // A body that failed because the Hub session dropped ends on the identity
49
+ // provider's login form, where clickClusters() finds no navigation and
50
+ // every cluster below would leak.
51
+ if (namesToDelete.length > 0 &&
52
+ /^(weblogin|login|auth)\./.test(new URL(page.url()).host)) {
53
+ await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, 0, 3);
54
+ }
48
55
  const undeleted = [];
49
56
  for (const clusterName of namesToDelete) {
50
57
  try {
@@ -55,6 +62,10 @@ _8_10_1.test.describe('Console cluster duplication', () => {
55
62
  // Collect rather than throw: one cluster that will not delete must
56
63
  // not strand the ones after it.
57
64
  undeleted.push(`${clusterName} (${String(error)})`);
65
+ // A delete that failed half-way can leave the row-actions menu or
66
+ // the confirm dialog open, which blocks pointer events on the rest of
67
+ // the page and failed every delete after it.
68
+ await page.keyboard.press('Escape');
58
69
  }
59
70
  }
60
71
  // Soft, so the video capture below still runs, but still a failure: a
@@ -75,7 +86,11 @@ _8_10_1.test.describe('Console cluster duplication', () => {
75
86
  await clusterPage.clickClusterTab();
76
87
  createdClusterNames.push(sourceClusterName);
77
88
  await clusterPage.createCluster(sourceClusterName);
78
- await clusterPage.assertClusterHealthyStatusWithRetry(sourceClusterName);
89
+ // Every health poll in this spec gets session recovery: the Hub session
90
+ // can drop to the identity provider's login form during these
91
+ // multi-minute polls, and reloading only re-renders that form (a run's
92
+ // retry spent its whole 500s budget on it).
93
+ await clusterPage.assertClusterHealthyStatusWithRetry(sourceClusterName, undefined, undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
79
94
  // A connector secret, an API client and an alert, so the duplication has
80
95
  // real settings to carry over instead of copying nothing.
81
96
  await clusterSecretsPage.createNewSecret(`DUP_SECRET_${randomSuffix}`, `dup-secret-value-${randomSuffix}`, sourceClusterName);
@@ -140,10 +155,10 @@ _8_10_1.test.describe('Console cluster duplication', () => {
140
155
  await (0, test_1.expect)(clusterPage.clusterLink(duplicateClusterName)).toBeVisible({
141
156
  timeout: 60000,
142
157
  });
143
- await clusterPage.assertClusterHealthyStatusWithRetry(duplicateClusterName);
158
+ await clusterPage.assertClusterHealthyStatusWithRetry(duplicateClusterName, undefined, undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
144
159
  await clusterPage.clickClusterLink(duplicateClusterName);
145
160
  await clusterDetailsPage.dismissOopsIfPresent();
146
- await clusterDetailsPage.assertComponentsHealth();
161
+ await clusterDetailsPage.assertComponentsHealth(undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
147
162
  });
148
163
  });
149
164
  // Scoped to name and region. The epic's case also lists plan type, channel
@@ -168,7 +183,7 @@ _8_10_1.test.describe('Console cluster duplication', () => {
168
183
  // over". The second argument is the cloud provider, the fourth the
169
184
  // sub-region -- passing a region label as the provider matches no tab.
170
185
  await clusterPage.createCluster(sourceClusterName, 'GCP', undefined, SOURCE_SUB_REGION);
171
- await clusterPage.assertClusterHealthyStatusWithRetry(sourceClusterName);
186
+ await clusterPage.assertClusterHealthyStatusWithRetry(sourceClusterName, undefined, undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
172
187
  // A populated source - against an empty one this case would pass by
173
188
  // copying nothing at all.
174
189
  await clusterSecretsPage.createNewSecret(`DUP_SECRET_${randomSuffix}`, `dup-secret-value-${randomSuffix}`, sourceClusterName);
@@ -231,10 +246,10 @@ _8_10_1.test.describe('Console cluster duplication', () => {
231
246
  await (0, test_1.expect)(clusterPage.clusterRegion(sourceClusterName, SOURCE_REGION_LABEL)).toBeVisible();
232
247
  // Completion rule: a duplicate that is created but never healthy is the
233
248
  // failure this epic exists to prevent.
234
- await clusterPage.assertClusterHealthyStatusWithRetry(duplicateClusterName);
249
+ await clusterPage.assertClusterHealthyStatusWithRetry(duplicateClusterName, undefined, undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
235
250
  await clusterPage.clickClusterLink(duplicateClusterName);
236
251
  await clusterDetailsPage.dismissOopsIfPresent();
237
- await clusterDetailsPage.assertComponentsHealth();
252
+ await clusterDetailsPage.assertComponentsHealth(undefined, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url()));
238
253
  });
239
254
  });
240
255
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1413",
3
+ "version": "0.0.1414",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",