@camunda/e2e-test-suite 0.0.1413 → 0.0.1415
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
});
|