@camunda/e2e-test-suite 0.0.1435 → 0.0.1436
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.
- package/dist/pages/8.8/AppsPage.js +11 -2
- package/dist/pages/8.8/ClusterDetailsPage.d.ts +2 -2
- package/dist/pages/8.8/ClusterDetailsPage.js +15 -7
- package/dist/pages/8.8/ClusterPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterPage.js +47 -12
- package/dist/pages/8.8/UtilitiesPage.js +8 -2
- package/dist/pages/8.9/ClusterDetailsPage.d.ts +2 -2
- package/dist/pages/8.9/ClusterDetailsPage.js +18 -9
- package/dist/pages/8.9/ClusterPage.d.ts +1 -0
- package/dist/pages/8.9/ClusterPage.js +56 -17
- package/dist/pages/8.9/UtilitiesPage.js +8 -2
- package/dist/tests/8.8/access-user-role-management.spec.js +14 -46
- package/dist/tests/8.9/access-user-role-management.spec.js +15 -16
- package/package.json +1 -1
|
@@ -204,10 +204,19 @@ class AppsPage {
|
|
|
204
204
|
// current cluster's own toggle name is not exactly clusterName either,
|
|
205
205
|
// and an exact-equality exclusion would wrongly treat it as "other" and
|
|
206
206
|
// immediately re-collapse the group this method just expanded.
|
|
207
|
-
|
|
207
|
+
// The sidebar's own rail toggle is also a button inside "Main navigation"
|
|
208
|
+
// with aria-expanded="true" once the rail is open ("Collapse sidebar").
|
|
209
|
+
// Without excluding it this loop clicked it right after the group was
|
|
210
|
+
// expanded, collapsing the rail and taking the app links with it -- the
|
|
211
|
+
// 8.8 nightly trace shows exactly that sequence (expand "Test Cluster",
|
|
212
|
+
// count 1, click "Collapse sidebar", app link never appears). Same
|
|
213
|
+
// exclusion as pages/8.9/AppsPage.ts.
|
|
214
|
+
const otherExpandedToggles = this.hubSidebar
|
|
215
|
+
.getByRole('button', {
|
|
208
216
|
expanded: true,
|
|
209
217
|
name: new RegExp(`^(?!.*${escapeRegExp(clusterName)}).+$`),
|
|
210
|
-
})
|
|
218
|
+
})
|
|
219
|
+
.and(this.page.locator(':not([data-slot="app-sidebar-collapse-toggle"])'));
|
|
211
220
|
for (let i = 0; i < 5 && (await otherExpandedToggles.count()) > 0; i++) {
|
|
212
221
|
// Let a failed collapse throw rather than swallowing it: the app link
|
|
213
222
|
// is intentionally sidebar-wide, so a group left expanded here produces
|
|
@@ -71,8 +71,8 @@ declare class ClusterDetailsPage {
|
|
|
71
71
|
clickSettingsTab(): Promise<void>;
|
|
72
72
|
private waitForAuthorizationsSection;
|
|
73
73
|
private setAuthorizationsViaSwitch;
|
|
74
|
-
enableAuthorizations(): Promise<
|
|
75
|
-
disableAuthorizations(): Promise<
|
|
74
|
+
enableAuthorizations(): Promise<boolean>;
|
|
75
|
+
disableAuthorizations(): Promise<boolean>;
|
|
76
76
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
77
77
|
deleteAPIClientsIfExist(name?: string): Promise<void>;
|
|
78
78
|
clickAlertsTab(): Promise<void>;
|
|
@@ -326,11 +326,11 @@ class ClusterDetailsPage {
|
|
|
326
326
|
// Carbon toggle.
|
|
327
327
|
async setAuthorizationsViaSwitch(enabled) {
|
|
328
328
|
if (!(await this.authorizationsSwitch.isVisible().catch(() => false))) {
|
|
329
|
-
return
|
|
329
|
+
return 'absent';
|
|
330
330
|
}
|
|
331
331
|
const isOn = (await this.authorizationsSwitch.getAttribute('aria-checked')) === 'true';
|
|
332
332
|
if (isOn === enabled) {
|
|
333
|
-
return
|
|
333
|
+
return 'unchanged';
|
|
334
334
|
}
|
|
335
335
|
await this.authorizationsSwitch.click({ timeout: 30000 });
|
|
336
336
|
if (await this.dialog.isVisible({ timeout: 15000 }).catch(() => false)) {
|
|
@@ -341,13 +341,14 @@ class ClusterDetailsPage {
|
|
|
341
341
|
await (0, test_1.expect)(this.page.getByText(enabled ? 'Enabling...' : 'Disabling...')).not.toBeVisible({ timeout: 60000 });
|
|
342
342
|
}
|
|
343
343
|
await (0, test_1.expect)(this.authorizationsSwitch).toHaveAttribute('aria-checked', String(enabled), { timeout: 120000 });
|
|
344
|
-
return
|
|
344
|
+
return 'changed';
|
|
345
345
|
}
|
|
346
346
|
async enableAuthorizations() {
|
|
347
347
|
await (0, sleep_1.sleep)(5000);
|
|
348
348
|
await this.waitForAuthorizationsSection();
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
const viaSwitch = await this.setAuthorizationsViaSwitch(true);
|
|
350
|
+
if (viaSwitch !== 'absent') {
|
|
351
|
+
return viaSwitch === 'changed';
|
|
351
352
|
}
|
|
352
353
|
// Locate all elements with class .cds--toggle__text
|
|
353
354
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
@@ -372,13 +373,16 @@ class ClusterDetailsPage {
|
|
|
372
373
|
}
|
|
373
374
|
await (0, sleep_1.sleep)(10000);
|
|
374
375
|
console.log('Second toggle switch clicked.');
|
|
376
|
+
return true;
|
|
375
377
|
}
|
|
376
378
|
else {
|
|
377
379
|
console.log('Second toggle switch not found or less than 2.');
|
|
380
|
+
return false;
|
|
378
381
|
}
|
|
379
382
|
}
|
|
380
383
|
else {
|
|
381
384
|
console.log("Second toggle text does not contain 'Disabled'.");
|
|
385
|
+
return false;
|
|
382
386
|
}
|
|
383
387
|
}
|
|
384
388
|
else {
|
|
@@ -395,8 +399,9 @@ class ClusterDetailsPage {
|
|
|
395
399
|
}
|
|
396
400
|
async disableAuthorizations() {
|
|
397
401
|
await this.waitForAuthorizationsSection();
|
|
398
|
-
|
|
399
|
-
|
|
402
|
+
const viaSwitch = await this.setAuthorizationsViaSwitch(false);
|
|
403
|
+
if (viaSwitch !== 'absent') {
|
|
404
|
+
return viaSwitch === 'changed';
|
|
400
405
|
}
|
|
401
406
|
// Locate all elements with class .cds--toggle__text
|
|
402
407
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
@@ -421,13 +426,16 @@ class ClusterDetailsPage {
|
|
|
421
426
|
}
|
|
422
427
|
console.log('Second toggle switch clicked.');
|
|
423
428
|
await (0, sleep_1.sleep)(10000);
|
|
429
|
+
return true;
|
|
424
430
|
}
|
|
425
431
|
else {
|
|
426
432
|
console.log('Second toggle switch not found or less than 2.');
|
|
433
|
+
return false;
|
|
427
434
|
}
|
|
428
435
|
}
|
|
429
436
|
else {
|
|
430
437
|
console.log("Second toggle text does not contain 'Enabled'.");
|
|
438
|
+
return false;
|
|
431
439
|
}
|
|
432
440
|
}
|
|
433
441
|
else {
|
|
@@ -58,6 +58,7 @@ declare class ClusterPage {
|
|
|
58
58
|
clickLatestStableForMinorVersion(): Promise<void>;
|
|
59
59
|
clickClusterOption(version: string): Promise<void>;
|
|
60
60
|
createClusterIfNotExists(clusterName: string, region?: string): Promise<void>;
|
|
61
|
+
waitForClusterRestartToStart(name: string, timeout?: number): Promise<void>;
|
|
61
62
|
assertClusterHealthyStatusWithRetry(name: string, visibilityTimeout?: number, totalTimeout?: number, isUpgradeScenario?: boolean): Promise<void>;
|
|
62
63
|
clickAlphaTab(): Promise<void>;
|
|
63
64
|
clickDevTab(): Promise<void>;
|
|
@@ -362,20 +362,18 @@ class ClusterPage {
|
|
|
362
362
|
await this.createClusterButton.click();
|
|
363
363
|
}
|
|
364
364
|
async clickClustersBreadcrumb(clusterName) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
365
|
+
// Wait for the new cluster's row, not for a status: Hub can list a
|
|
366
|
+
// just-created cluster as "unknown" for longer than this wait before it
|
|
367
|
+
// switches to "Creating…". Every caller asserts Healthy afterwards.
|
|
368
|
+
const clusterRow = this.clusterLink(clusterName);
|
|
369
369
|
try {
|
|
370
370
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
371
371
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
372
|
-
await (0, test_1.expect)(clusterRow
|
|
373
|
-
timeout: 30000,
|
|
374
|
-
});
|
|
372
|
+
await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
|
|
375
373
|
}
|
|
376
374
|
catch (error) {
|
|
377
375
|
await this.clickClusterBanner();
|
|
378
|
-
await (0, test_1.expect)(
|
|
376
|
+
await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
|
|
379
377
|
}
|
|
380
378
|
}
|
|
381
379
|
async clickClusterLink(name) {
|
|
@@ -465,14 +463,51 @@ class ClusterPage {
|
|
|
465
463
|
}
|
|
466
464
|
async createClusterIfNotExists(clusterName, region = 'GCP') {
|
|
467
465
|
await (0, sleep_1.sleep)(15000);
|
|
468
|
-
|
|
469
|
-
|
|
466
|
+
// isVisible() does not wait, so a table that was still loading read as
|
|
467
|
+
// "no cluster" and a retry recreated the cluster its first attempt had
|
|
468
|
+
// already created. Give the row time to render before trusting a miss.
|
|
469
|
+
const exists = await this.cluster(clusterName)
|
|
470
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
471
|
+
.then(() => true)
|
|
472
|
+
.catch(() => false);
|
|
473
|
+
if (!exists) {
|
|
474
|
+
await this.createCluster(clusterName, region);
|
|
475
|
+
}
|
|
476
|
+
else if (await this.clusterHealthiness(clusterName).isVisible()) {
|
|
470
477
|
console.log(`${clusterName} exists and is healthy.`);
|
|
478
|
+
return;
|
|
471
479
|
}
|
|
472
480
|
else {
|
|
473
|
-
|
|
474
|
-
|
|
481
|
+
// Still provisioning, typically from the attempt this retry replaces.
|
|
482
|
+
// Recreating it adds a second cluster of the same name, because the
|
|
483
|
+
// pre-clean in createCluster does not delete it, and every exact-name
|
|
484
|
+
// locator then fails on strict mode.
|
|
485
|
+
console.log(`${clusterName} exists but is not Healthy yet; waiting for it to come up.`);
|
|
475
486
|
}
|
|
487
|
+
await this.assertClusterHealthyStatusWithRetry(clusterName, 120000, 600000);
|
|
488
|
+
}
|
|
489
|
+
// A settings change that restarts the cluster, such as the authorizations
|
|
490
|
+
// toggle, leaves the status on "Healthy" until the operator reconciles the
|
|
491
|
+
// new spec. A Healthy check made straight after the change can therefore
|
|
492
|
+
// pass before the restart has begun, and the test then runs against a
|
|
493
|
+
// cluster that is about to restart. Wait for the status to leave Healthy
|
|
494
|
+
// first. The list updates over SSE, so no reload is needed while waiting.
|
|
495
|
+
async waitForClusterRestartToStart(name, timeout = 120000) {
|
|
496
|
+
const rowShown = await this.cluster(name)
|
|
497
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
498
|
+
.then(() => true)
|
|
499
|
+
.catch(() => false);
|
|
500
|
+
if (!rowShown) {
|
|
501
|
+
console.warn(`${name}: cluster row not shown, so the restart cannot be watched.`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const leftHealthy = await this.clusterHealthiness(name)
|
|
505
|
+
.waitFor({ state: 'hidden', timeout })
|
|
506
|
+
.then(() => true)
|
|
507
|
+
.catch(() => false);
|
|
508
|
+
console.log(leftHealthy
|
|
509
|
+
? `${name}: restart started, status left Healthy.`
|
|
510
|
+
: `${name}: status stayed Healthy for ${timeout / 1000}s after the change.`);
|
|
476
511
|
}
|
|
477
512
|
async assertClusterHealthyStatusWithRetry(name, visibilityTimeout = 100000, totalTimeout = 420000, isUpgradeScenario = false) {
|
|
478
513
|
// For upgrade scenarios, use much longer timeouts and more retries instead of fixed waits.
|
|
@@ -387,8 +387,11 @@ async function disableRBA(clusterName, homePage, clusterPage, clusterDetailsPage
|
|
|
387
387
|
timeout: 90000,
|
|
388
388
|
});
|
|
389
389
|
await clusterDetailsPage.clickSettingsTab();
|
|
390
|
-
await clusterDetailsPage.disableAuthorizations();
|
|
390
|
+
const changed = await clusterDetailsPage.disableAuthorizations();
|
|
391
391
|
await homePage.clickClusters();
|
|
392
|
+
if (changed) {
|
|
393
|
+
await clusterPage.waitForClusterRestartToStart(clusterName);
|
|
394
|
+
}
|
|
392
395
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
393
396
|
await clusterPage.clickClusterLink(clusterName);
|
|
394
397
|
await (0, test_1.expect)(clusterDetailsPage.rbaEnabledMessage.first()).not.toBeVisible({
|
|
@@ -407,8 +410,11 @@ async function enableAuthorizations(clusterName, homePage, clusterPage, clusterD
|
|
|
407
410
|
timeout: 90000,
|
|
408
411
|
});
|
|
409
412
|
await clusterDetailsPage.clickSettingsTab();
|
|
410
|
-
await clusterDetailsPage.enableAuthorizations();
|
|
413
|
+
const changed = await clusterDetailsPage.enableAuthorizations();
|
|
411
414
|
await homePage.clickClusters();
|
|
415
|
+
if (changed) {
|
|
416
|
+
await clusterPage.waitForClusterRestartToStart(clusterName);
|
|
417
|
+
}
|
|
412
418
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
413
419
|
await clusterPage.clickClusterLink(clusterName);
|
|
414
420
|
await clusterDetailsPage.assertComponentsHealth();
|
|
@@ -78,8 +78,8 @@ declare class ClusterDetailsPage {
|
|
|
78
78
|
clickSettingsTab(): Promise<void>;
|
|
79
79
|
private waitForAuthorizationsSection;
|
|
80
80
|
private setAuthorizationsViaSwitch;
|
|
81
|
-
enableAuthorizations(): Promise<
|
|
82
|
-
disableAuthorizations(): Promise<
|
|
81
|
+
enableAuthorizations(): Promise<boolean>;
|
|
82
|
+
disableAuthorizations(): Promise<boolean>;
|
|
83
83
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
84
84
|
deleteAPIClientsIfExist(name?: string): Promise<void>;
|
|
85
85
|
clickAlertsTab(): Promise<void>;
|
|
@@ -401,20 +401,21 @@ class ClusterDetailsPage {
|
|
|
401
401
|
},
|
|
402
402
|
});
|
|
403
403
|
}
|
|
404
|
-
// Drives the design-system Authorizations switch. Returns
|
|
404
|
+
// Drives the design-system Authorizations switch. Returns 'absent' when this
|
|
405
405
|
// shell does not render one, so the caller falls through to the legacy
|
|
406
|
-
// Carbon toggle
|
|
406
|
+
// Carbon toggle, and 'changed' only when it flipped the setting, which
|
|
407
|
+
// restarts the cluster.
|
|
407
408
|
async setAuthorizationsViaSwitch(enabled) {
|
|
408
409
|
const hasSwitch = await this.authorizationsSwitch
|
|
409
410
|
.waitFor({ state: 'visible', timeout: 10000 })
|
|
410
411
|
.then(() => true)
|
|
411
412
|
.catch(() => false);
|
|
412
413
|
if (!hasSwitch) {
|
|
413
|
-
return
|
|
414
|
+
return 'absent';
|
|
414
415
|
}
|
|
415
416
|
const isOn = (await this.authorizationsSwitch.getAttribute('aria-checked')) === 'true';
|
|
416
417
|
if (isOn === enabled) {
|
|
417
|
-
return
|
|
418
|
+
return 'unchanged';
|
|
418
419
|
}
|
|
419
420
|
await this.authorizationsSwitch.click({ timeout: 30000 });
|
|
420
421
|
const confirmOpened = await this.dialog
|
|
@@ -427,13 +428,14 @@ class ClusterDetailsPage {
|
|
|
427
428
|
await (0, test_1.expect)(this.page.getByText(enabled ? 'Enabling...' : 'Disabling...')).not.toBeVisible({ timeout: 60000 });
|
|
428
429
|
}
|
|
429
430
|
await (0, test_1.expect)(this.authorizationsSwitch).toHaveAttribute('aria-checked', String(enabled), { timeout: 120000 });
|
|
430
|
-
return
|
|
431
|
+
return 'changed';
|
|
431
432
|
}
|
|
432
433
|
async enableAuthorizations() {
|
|
433
434
|
await (0, sleep_1.sleep)(5000);
|
|
434
435
|
await this.waitForAuthorizationsSection();
|
|
435
|
-
|
|
436
|
-
|
|
436
|
+
const viaSwitch = await this.setAuthorizationsViaSwitch(true);
|
|
437
|
+
if (viaSwitch !== 'absent') {
|
|
438
|
+
return viaSwitch === 'changed';
|
|
437
439
|
}
|
|
438
440
|
// Locate all elements with class .cds--toggle__text
|
|
439
441
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
@@ -458,13 +460,16 @@ class ClusterDetailsPage {
|
|
|
458
460
|
}
|
|
459
461
|
await (0, sleep_1.sleep)(10000);
|
|
460
462
|
console.log('Second toggle switch clicked.');
|
|
463
|
+
return true;
|
|
461
464
|
}
|
|
462
465
|
else {
|
|
463
466
|
console.log('Second toggle switch not found or less than 2.');
|
|
467
|
+
return false;
|
|
464
468
|
}
|
|
465
469
|
}
|
|
466
470
|
else {
|
|
467
471
|
console.log("Second toggle text does not contain 'Disabled'.");
|
|
472
|
+
return false;
|
|
468
473
|
}
|
|
469
474
|
}
|
|
470
475
|
else {
|
|
@@ -477,8 +482,9 @@ class ClusterDetailsPage {
|
|
|
477
482
|
}
|
|
478
483
|
async disableAuthorizations() {
|
|
479
484
|
await this.waitForAuthorizationsSection();
|
|
480
|
-
|
|
481
|
-
|
|
485
|
+
const viaSwitch = await this.setAuthorizationsViaSwitch(false);
|
|
486
|
+
if (viaSwitch !== 'absent') {
|
|
487
|
+
return viaSwitch === 'changed';
|
|
482
488
|
}
|
|
483
489
|
// Locate all elements with class .cds--toggle__text
|
|
484
490
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
@@ -504,13 +510,16 @@ class ClusterDetailsPage {
|
|
|
504
510
|
}
|
|
505
511
|
console.log('Second toggle switch clicked.');
|
|
506
512
|
await (0, sleep_1.sleep)(10000);
|
|
513
|
+
return true;
|
|
507
514
|
}
|
|
508
515
|
else {
|
|
509
516
|
console.log('Second toggle switch not found or less than 2.');
|
|
517
|
+
return false;
|
|
510
518
|
}
|
|
511
519
|
}
|
|
512
520
|
else {
|
|
513
521
|
console.log("Second toggle text does not contain 'Enabled'.");
|
|
522
|
+
return false;
|
|
514
523
|
}
|
|
515
524
|
}
|
|
516
525
|
else {
|
|
@@ -56,6 +56,7 @@ declare class ClusterPage {
|
|
|
56
56
|
clickLatestStableForMinorVersion(): Promise<void>;
|
|
57
57
|
clickClusterOption(): Promise<void>;
|
|
58
58
|
createClusterIfNotExists(clusterName: string, region?: string): Promise<void>;
|
|
59
|
+
waitForClusterRestartToStart(name: string, timeout?: number): Promise<void>;
|
|
59
60
|
assertClusterHealthyStatusWithRetry(name: string, visibilityTimeout?: number, totalTimeout?: number, maxRetries?: number): Promise<void>;
|
|
60
61
|
clickAlphaTab(): Promise<void>;
|
|
61
62
|
clickDevTab(): Promise<void>;
|
|
@@ -314,17 +314,18 @@ class ClusterPage {
|
|
|
314
314
|
await this.createClusterButton.click();
|
|
315
315
|
}
|
|
316
316
|
async clickClustersBreadcrumb(clusterName) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
// Wait for the new cluster's row, not for a status: Hub can list a
|
|
318
|
+
// just-created cluster as "unknown" for longer than this wait before it
|
|
319
|
+
// switches to "Creating…". Every caller asserts Healthy afterwards.
|
|
320
|
+
const clusterRow = this.clusterLink(clusterName);
|
|
320
321
|
try {
|
|
321
322
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
322
323
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
323
|
-
await (0, test_1.expect)(
|
|
324
|
+
await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
|
|
324
325
|
}
|
|
325
326
|
catch (error) {
|
|
326
327
|
await this.clickClusterBanner();
|
|
327
|
-
await (0, test_1.expect)(
|
|
328
|
+
await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
|
|
328
329
|
}
|
|
329
330
|
}
|
|
330
331
|
async clickClusterLink(name) {
|
|
@@ -432,22 +433,60 @@ class ClusterPage {
|
|
|
432
433
|
}
|
|
433
434
|
async createClusterIfNotExists(clusterName, region = 'GCP') {
|
|
434
435
|
await (0, sleep_1.sleep)(15000);
|
|
435
|
-
|
|
436
|
-
|
|
436
|
+
// isVisible() does not wait, so a table that was still loading read as
|
|
437
|
+
// "no cluster" and a retry recreated the cluster its first attempt had
|
|
438
|
+
// already created. Give the row time to render before trusting a miss.
|
|
439
|
+
const exists = await this.cluster(clusterName)
|
|
440
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
441
|
+
.then(() => true)
|
|
442
|
+
.catch(() => false);
|
|
443
|
+
if (!exists) {
|
|
444
|
+
await this.createCluster(clusterName, region);
|
|
445
|
+
}
|
|
446
|
+
else if (await this.clusterHealthiness(clusterName).isVisible()) {
|
|
437
447
|
console.log(`${clusterName} exists and is healthy.`);
|
|
448
|
+
return;
|
|
438
449
|
}
|
|
439
450
|
else {
|
|
440
|
-
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
451
|
+
// Still provisioning, typically from the attempt this retry replaces.
|
|
452
|
+
// Recreating it is not an option: createCluster's pre-clean finds no
|
|
453
|
+
// per-row Delete button in Hub's table (only "Row actions"), so it
|
|
454
|
+
// leaves this cluster in place and adds a second one of the same name,
|
|
455
|
+
// which the apps menu can then no longer tell apart.
|
|
456
|
+
console.log(`${clusterName} exists but is not Healthy yet; waiting for it to come up.`);
|
|
457
|
+
}
|
|
458
|
+
// The Healthy badge is only re-read after a reload, and
|
|
459
|
+
// expectLocatorWithRetry reloads once per attempt -- so `maxRetries` is
|
|
460
|
+
// what sets the polling resolution, not `totalTimeout`. With the old
|
|
461
|
+
// 120s/600s/5 the loop reloaded 5 times and gave up after ~570s, short
|
|
462
|
+
// of its own 600s budget. Polling every 15s instead reloads ~45 times,
|
|
463
|
+
// and 900s gives a cluster provisioning alongside the other parallel
|
|
464
|
+
// workers' clusters the room it actually needs (still well inside the
|
|
465
|
+
// 36-minute test.slow() budget).
|
|
466
|
+
await this.assertClusterHealthyStatusWithRetry(clusterName, 15000, 900000, 60);
|
|
467
|
+
}
|
|
468
|
+
// A settings change that restarts the cluster, such as the authorizations
|
|
469
|
+
// toggle, leaves the status on "Healthy" until the operator reconciles the
|
|
470
|
+
// new spec. A Healthy check made straight after the change can therefore
|
|
471
|
+
// pass before the restart has begun, and the test then runs against a
|
|
472
|
+
// cluster that is about to restart. Wait for the status to leave Healthy
|
|
473
|
+
// first. The list updates over SSE, so no reload is needed while waiting.
|
|
474
|
+
async waitForClusterRestartToStart(name, timeout = 120000) {
|
|
475
|
+
const rowShown = await this.cluster(name)
|
|
476
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
477
|
+
.then(() => true)
|
|
478
|
+
.catch(() => false);
|
|
479
|
+
if (!rowShown) {
|
|
480
|
+
console.warn(`${name}: cluster row not shown, so the restart cannot be watched.`);
|
|
481
|
+
return;
|
|
450
482
|
}
|
|
483
|
+
const leftHealthy = await this.clusterHealthiness(name)
|
|
484
|
+
.waitFor({ state: 'hidden', timeout })
|
|
485
|
+
.then(() => true)
|
|
486
|
+
.catch(() => false);
|
|
487
|
+
console.log(leftHealthy
|
|
488
|
+
? `${name}: restart started, status left Healthy.`
|
|
489
|
+
: `${name}: status stayed Healthy for ${timeout / 1000}s after the change.`);
|
|
451
490
|
}
|
|
452
491
|
async assertClusterHealthyStatusWithRetry(name, visibilityTimeout = 100000, totalTimeout = 500000, maxRetries) {
|
|
453
492
|
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.clusterHealthiness(name), {
|
|
@@ -422,10 +422,13 @@ async function disableRBA(clusterName, homePage, clusterPage, clusterDetailsPage
|
|
|
422
422
|
timeout: 90000,
|
|
423
423
|
});
|
|
424
424
|
await clusterDetailsPage.clickSettingsTab();
|
|
425
|
-
await clusterDetailsPage.disableAuthorizations();
|
|
425
|
+
const changed = await clusterDetailsPage.disableAuthorizations();
|
|
426
426
|
// The toggle restarts the cluster; Console can crash out of the settings
|
|
427
427
|
// route while it does, taking the Clusters nav link with it.
|
|
428
428
|
await homePage.clickClustersWithReloadRecovery();
|
|
429
|
+
if (changed) {
|
|
430
|
+
await clusterPage.waitForClusterRestartToStart(clusterName);
|
|
431
|
+
}
|
|
429
432
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
430
433
|
await clusterPage.clickClusterLink(clusterName);
|
|
431
434
|
await (0, test_1.expect)(clusterDetailsPage.rbaEnabledMessage.first()).not.toBeVisible({
|
|
@@ -444,10 +447,13 @@ async function enableAuthorizations(clusterName, homePage, clusterPage, clusterD
|
|
|
444
447
|
timeout: 90000,
|
|
445
448
|
});
|
|
446
449
|
await clusterDetailsPage.clickSettingsTab();
|
|
447
|
-
await clusterDetailsPage.enableAuthorizations();
|
|
450
|
+
const changed = await clusterDetailsPage.enableAuthorizations();
|
|
448
451
|
// The toggle restarts the cluster; Console can crash out of the settings
|
|
449
452
|
// route while it does, taking the Clusters nav link with it.
|
|
450
453
|
await homePage.clickClustersWithReloadRecovery();
|
|
454
|
+
if (changed) {
|
|
455
|
+
await clusterPage.waitForClusterRestartToStart(clusterName);
|
|
456
|
+
}
|
|
451
457
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
452
458
|
await clusterPage.clickClusterLink(clusterName);
|
|
453
459
|
await clusterDetailsPage.assertComponentsHealth();
|
|
@@ -44,10 +44,8 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
44
44
|
});
|
|
45
45
|
// Workspace membership is durable organization state, so the Editor rights
|
|
46
46
|
// the Analyst flow grants must be handed back whether the test passed or
|
|
47
|
-
// failed. Left behind,
|
|
48
|
-
//
|
|
49
|
-
// would fail on the second attempt; the grant would also leak into every
|
|
50
|
-
// later nightly run against the same org.
|
|
47
|
+
// failed. Left behind, the grant would leak into every later nightly run
|
|
48
|
+
// against the same org.
|
|
51
49
|
_8_8_1.test.afterEach(async ({ page, loginPage }, testInfo) => {
|
|
52
50
|
if (!testInfo.title.startsWith('Analyst Role User Flow')) {
|
|
53
51
|
return;
|
|
@@ -169,10 +167,9 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
169
167
|
const processName = 'User Roles User Flow' + randomString;
|
|
170
168
|
const clusterName = clusterNames['Analyst Role User Flow'];
|
|
171
169
|
// A retry runs in a fresh worker, where analystWorkspaceGrantMade starts
|
|
172
|
-
// false even when the previous attempt's teardown failed to hand the
|
|
173
|
-
// back
|
|
174
|
-
//
|
|
175
|
-
// would then always fail, because the analyst is still an Editor.
|
|
170
|
+
// false even when the previous attempt's teardown failed to hand the
|
|
171
|
+
// grant back. Remove a leftover grant so the membership does not leak
|
|
172
|
+
// into later runs against the same org.
|
|
176
173
|
if (_8_8_1.test.info().retry > 0) {
|
|
177
174
|
await _8_8_1.test.step('Hand back a workspace grant left by the previous attempt', async () => {
|
|
178
175
|
await appsPage.clickModeler();
|
|
@@ -201,52 +198,23 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
201
198
|
password: process.env.C8_PASSWORD_TEST_9,
|
|
202
199
|
}, 1000);
|
|
203
200
|
});
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
// Non-admins do not have cluster visibility (they operate with
|
|
213
|
-
// environments instead)."
|
|
214
|
-
//
|
|
215
|
-
// Assert that gate before granting the membership the rest of this test
|
|
216
|
-
// depends on, so both halves are covered: the role alone grants no
|
|
217
|
-
// access, and the role plus workspace editorship does.
|
|
218
|
-
await _8_8_1.test.step('Ensure Optimize is not reachable without workspace access', async () => {
|
|
219
|
-
// Assert the cause, not just the consequence. Hub shows the analyst an
|
|
220
|
-
// empty Workspaces overview -- "You don't have explicit membership in
|
|
221
|
-
// any workspace in this organization" before the scope is widened, and
|
|
222
|
-
// "Your organization has no workspaces yet" after -- because the
|
|
223
|
-
// workspace belongs to whoever created it. With no workspace there is
|
|
224
|
-
// no environment, and with no environment no cluster app, so checking
|
|
225
|
-
// the Optimize banner alone would pass for unrelated reasons too.
|
|
201
|
+
// Every SaaS organization member sees every environment in the
|
|
202
|
+
// organization, whether or not they belong to a workspace. Hub decided
|
|
203
|
+
// this in camunda-hub#30270 and shipped it in camunda-hub#30271; the bug
|
|
204
|
+
// that challenged it, camunda-hub#30300, was closed as works-as-intended.
|
|
205
|
+
// Assert it, so a revert to the stricter "assigned only" rule is caught.
|
|
206
|
+
// What the analyst may do inside each app stays gated by the cluster's
|
|
207
|
+
// authorizations, which the steps below assert.
|
|
208
|
+
await _8_8_1.test.step('Ensure the cluster is listed without workspace access', async () => {
|
|
226
209
|
await appsPage.clickCamundaApps();
|
|
227
210
|
await (0, test_1.expect)(page
|
|
228
211
|
.getByText('Your organization has no workspaces yet')
|
|
229
212
|
.or(page.getByText("You don't have explicit membership in any workspace"))
|
|
230
213
|
.first()).toBeVisible({ timeout: 60000 });
|
|
231
|
-
// Assert the absence of THIS cluster's Optimize target directly rather
|
|
232
|
-
// than calling clickOptimize() and swallowing whatever it throws: a
|
|
233
|
-
// swallowed error would let this step pass because the hand-off helper
|
|
234
|
-
// itself broke and the page simply stayed on Hub, where the Optimize
|
|
235
|
-
// banner is absent for reasons that have nothing to do with the gate.
|
|
236
|
-
// On Hub a cluster's apps hang off the sidebar group named for the
|
|
237
|
-
// cluster, so with no environment neither the group nor the Optimize
|
|
238
|
-
// link inside it is ever rendered.
|
|
239
214
|
const hubSidebar = page.getByRole('navigation', {
|
|
240
215
|
name: 'Main navigation',
|
|
241
216
|
});
|
|
242
|
-
|
|
243
|
-
// toHaveCount(0) passes vacuously on a page that has not painted yet.
|
|
244
|
-
await (0, test_1.expect)(hubSidebar).toBeVisible({ timeout: 30000 });
|
|
245
|
-
await (0, test_1.expect)(hubSidebar.getByRole('button', { name: clusterName })).toHaveCount(0, { timeout: 30000 });
|
|
246
|
-
await (0, test_1.expect)(hubSidebar.getByRole('link', { name: /Optimize/ })).toHaveCount(0, { timeout: 30000 });
|
|
247
|
-
await (0, test_1.expect)(optimizeHomePage.optimizeBanner).not.toBeVisible({
|
|
248
|
-
timeout: 30000,
|
|
249
|
-
});
|
|
217
|
+
await (0, test_1.expect)(hubSidebar.getByRole('button', { name: clusterName })).toBeVisible({ timeout: 30000 });
|
|
250
218
|
});
|
|
251
219
|
await _8_8_1.test.step('Grant the analyst Editor rights on the shared workspace', async () => {
|
|
252
220
|
// Only an organization admin may change workspace membership, so this
|
|
@@ -40,9 +40,10 @@ _8_9_1.test.describe('User Roles User Flow', () => {
|
|
|
40
40
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
41
41
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
42
42
|
});
|
|
43
|
-
// Workspace membership is durable
|
|
44
|
-
//
|
|
45
|
-
//
|
|
43
|
+
// Workspace membership is durable organization state, so the Editor rights
|
|
44
|
+
// the Analyst flow grants must be handed back whether the test passed or
|
|
45
|
+
// failed. Left behind, the grant would leak into every later nightly run
|
|
46
|
+
// against the same org.
|
|
46
47
|
_8_9_1.test.afterEach(async ({ page, loginPage }, testInfo) => {
|
|
47
48
|
if (!testInfo.title.startsWith('Analyst Role User Flow')) {
|
|
48
49
|
return;
|
|
@@ -166,7 +167,8 @@ _8_9_1.test.describe('User Roles User Flow', () => {
|
|
|
166
167
|
const clusterName = clusterNames['Analyst Role User Flow'];
|
|
167
168
|
// A retry runs in a fresh worker, where analystWorkspaceGrantMade starts
|
|
168
169
|
// false even when the previous attempt's teardown failed to hand the
|
|
169
|
-
// grant back.
|
|
170
|
+
// grant back. Remove a leftover grant so the membership does not leak
|
|
171
|
+
// into later runs against the same org.
|
|
170
172
|
if (_8_9_1.test.info().retry > 0) {
|
|
171
173
|
await _8_9_1.test.step('Hand back a workspace grant left by the previous attempt', async () => {
|
|
172
174
|
await appsPage.clickModeler();
|
|
@@ -196,11 +198,14 @@ _8_9_1.test.describe('User Roles User Flow', () => {
|
|
|
196
198
|
password: process.env.C8_PASSWORD_TEST_9,
|
|
197
199
|
});
|
|
198
200
|
});
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
201
|
+
// Every SaaS organization member sees every environment in the
|
|
202
|
+
// organization, whether or not they belong to a workspace. Hub decided
|
|
203
|
+
// this in camunda-hub#30270 and shipped it in camunda-hub#30271; the bug
|
|
204
|
+
// that challenged it, camunda-hub#30300, was closed as works-as-intended.
|
|
205
|
+
// Assert it, so a revert to the stricter "assigned only" rule is caught.
|
|
206
|
+
// What the analyst may do inside each app stays gated by the cluster's
|
|
207
|
+
// authorizations, which the steps below assert.
|
|
208
|
+
await _8_9_1.test.step('Ensure the cluster is listed without workspace access', async () => {
|
|
204
209
|
await appsPage.clickCamundaApps();
|
|
205
210
|
await (0, test_1.expect)(page
|
|
206
211
|
.getByText('Your organization has no workspaces yet')
|
|
@@ -209,13 +214,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
|
|
|
209
214
|
const hubSidebar = page.getByRole('navigation', {
|
|
210
215
|
name: 'Main navigation',
|
|
211
216
|
});
|
|
212
|
-
|
|
213
|
-
await (0, test_1.expect)(hubSidebar).toBeVisible({ timeout: 30000 });
|
|
214
|
-
await (0, test_1.expect)(hubSidebar.getByRole('button', { name: clusterName })).toHaveCount(0, { timeout: 30000 });
|
|
215
|
-
await (0, test_1.expect)(hubSidebar.getByRole('link', { name: /Optimize/ })).toHaveCount(0, { timeout: 30000 });
|
|
216
|
-
await (0, test_1.expect)(optimizeHomePage.optimizeBanner).not.toBeVisible({
|
|
217
|
-
timeout: 30000,
|
|
218
|
-
});
|
|
217
|
+
await (0, test_1.expect)(hubSidebar.getByRole('button', { name: clusterName })).toBeVisible({ timeout: 30000 });
|
|
219
218
|
});
|
|
220
219
|
await _8_9_1.test.step('Grant the analyst Editor rights on the shared workspace', async () => {
|
|
221
220
|
// Membership and environment assignment are org-admin actions, so this
|