@camunda/e2e-test-suite 0.0.1288 → 0.0.1289
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.d.ts +7 -3
- package/dist/pages/8.8/AppsPage.js +96 -26
- package/dist/pages/8.8/ModelerCreatePage.d.ts +3 -0
- package/dist/pages/8.8/ModelerCreatePage.js +25 -0
- package/dist/pages/8.8/UtilitiesPage.js +34 -17
- package/dist/pages/8.8/WorkspaceMembersPage.d.ts +1 -0
- package/dist/pages/8.8/WorkspaceMembersPage.js +21 -10
- package/dist/tests/8.8/access-user-role-management.spec.js +57 -29
- package/dist/tests/8.8/console-user-flows.spec.js +22 -1
- package/dist/tests/8.8/rba-enabled-v1-user-flows.spec.js +13 -4
- package/dist/tests/8.8/rba-enabled-v2-user-flows.spec.js +50 -14
- package/dist/tests/8.8/test-setup.spec.js +49 -4
- package/package.json +1 -1
|
@@ -19,11 +19,13 @@ declare class AppsPage {
|
|
|
19
19
|
readonly camundaComponentsButton: Locator;
|
|
20
20
|
readonly camundaAppsButton: Locator;
|
|
21
21
|
readonly hubSidebar: Locator;
|
|
22
|
+
private hubReturnUrl?;
|
|
22
23
|
constructor(page: Page);
|
|
23
24
|
clickCluster(component: Locator, name: string): Promise<void>;
|
|
24
25
|
private expandHubClusterGroup;
|
|
25
26
|
private showAllWorkspaces;
|
|
26
27
|
private clickHubClusterApp;
|
|
28
|
+
goBackToHub(): Promise<void>;
|
|
27
29
|
private isOnHub;
|
|
28
30
|
clickModeler(): Promise<void>;
|
|
29
31
|
private clickHubModelerHome;
|
|
@@ -54,8 +56,10 @@ declare class AppsPage {
|
|
|
54
56
|
clickCamundaApps(maxRetries?: number): Promise<void>;
|
|
55
57
|
private dismissBlockingModal;
|
|
56
58
|
clickConsoleLink(): Promise<void>;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
private hubClusterAppLink;
|
|
60
|
+
private assertHubClusterAppPresent;
|
|
61
|
+
assertOperatePresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
62
|
+
assertTasklistPresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
63
|
+
assertOptimizePresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
60
64
|
}
|
|
61
65
|
export { AppsPage };
|
|
@@ -27,9 +27,19 @@ class AppsPage {
|
|
|
27
27
|
camundaComponentsButton;
|
|
28
28
|
camundaAppsButton;
|
|
29
29
|
hubSidebar;
|
|
30
|
+
hubReturnUrl;
|
|
30
31
|
constructor(page) {
|
|
31
32
|
this.page = page;
|
|
32
|
-
|
|
33
|
+
// Hub has no "Modeler" switcher entry; Modeler is Hub's home, reached
|
|
34
|
+
// through the header logo link clickHubModelerHome() uses
|
|
35
|
+
// (NavShellDs.common.tsx). The final .first() keeps this strict-safe on a
|
|
36
|
+
// page that carries both.
|
|
37
|
+
this.modelerLink = page
|
|
38
|
+
.getByRole('link', { name: 'Modeler', exact: true })
|
|
39
|
+
.or(page
|
|
40
|
+
.locator('header[data-slot="app-header"]')
|
|
41
|
+
.getByRole('link', { name: /^Camunda (Modeler|Hub) home$/ }))
|
|
42
|
+
.first();
|
|
33
43
|
this.appSwitcherButton = page.getByLabel('App Switcher');
|
|
34
44
|
this.tasklistLink = page.getByRole('link', { name: 'Tasklist', exact: true });
|
|
35
45
|
this.identityLink = page.getByRole('link', { name: /^(Admin|Identity)$/ });
|
|
@@ -253,14 +263,8 @@ class AppsPage {
|
|
|
253
263
|
// cluster name and portalled OUTSIDE the "Main navigation" <nav> -- so a
|
|
254
264
|
// sidebar-scoped lookup finds nothing there. Look in that popover first;
|
|
255
265
|
// keep the sidebar-scoped lookup for the expanded-sidebar layout, where
|
|
256
|
-
// the links render inline.
|
|
257
|
-
|
|
258
|
-
// (opens in a new tab)").
|
|
259
|
-
const appLink = this.page
|
|
260
|
-
.getByRole('dialog', { name: clusterName, exact: true })
|
|
261
|
-
.getByRole('link', { name: appName })
|
|
262
|
-
.or(this.hubSidebar.getByRole('link', { name: appName }))
|
|
263
|
-
.first();
|
|
266
|
+
// the links render inline.
|
|
267
|
+
const appLink = this.hubClusterAppLink(clusterName, appName);
|
|
264
268
|
// Decide whether to expand from clusterName's OWN toggle state, not from
|
|
265
269
|
// whether "some" appLink is already visible sidebar-wide: in a
|
|
266
270
|
// multi-cluster org another cluster's group can already be expanded with
|
|
@@ -279,11 +283,22 @@ class AppsPage {
|
|
|
279
283
|
// navigation the Console app switcher gave these callers.
|
|
280
284
|
const href = await appLink.getAttribute('href');
|
|
281
285
|
if (href) {
|
|
286
|
+
this.hubReturnUrl = this.page.url();
|
|
282
287
|
await this.page.goto(new URL(href, this.page.url()).toString());
|
|
283
288
|
return;
|
|
284
289
|
}
|
|
285
290
|
await appLink.click({ timeout: 10000 });
|
|
286
291
|
}
|
|
292
|
+
// Returns from an app that clickHubClusterApp opened. page.goBack() alone
|
|
293
|
+
// can stop on one of the app's own login-redirect history entries, and the
|
|
294
|
+
// next Hub sidebar lookup then runs against the app instead of Hub.
|
|
295
|
+
async goBackToHub() {
|
|
296
|
+
await this.page.goBack();
|
|
297
|
+
if (this.hubReturnUrl &&
|
|
298
|
+
!new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
299
|
+
await this.page.goto(this.hubReturnUrl);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
287
302
|
// True once the tab is on a Camunda Hub host, where Modeler home is Hub's
|
|
288
303
|
// own home rather than a modeler.* hostname.
|
|
289
304
|
isOnHub() {
|
|
@@ -459,10 +474,7 @@ class AppsPage {
|
|
|
459
474
|
}
|
|
460
475
|
async clickIdentity(clusterName) {
|
|
461
476
|
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
462
|
-
//
|
|
463
|
-
// for Operate/Tasklist/Optimize -- an anchored pattern fails the same
|
|
464
|
-
// way an exact-string match did for those apps if the rendered link's
|
|
465
|
-
// accessible name carries any extra content (icon/badge).
|
|
477
|
+
// Identity should be removed once camunda/camunda#46447 is fixed.
|
|
466
478
|
await this.clickHubClusterApp(clusterName, /Admin|Identity/);
|
|
467
479
|
return;
|
|
468
480
|
}
|
|
@@ -716,17 +728,26 @@ class AppsPage {
|
|
|
716
728
|
.catch(() => { });
|
|
717
729
|
}
|
|
718
730
|
async clickConsoleLink() {
|
|
719
|
-
// Camunda Hub
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
//
|
|
731
|
+
// Camunda Hub folded Console into the same persistent shell as
|
|
732
|
+
// Modeler/home: Console's Dashboard/Clusters/Organization links are just
|
|
733
|
+
// another section of the same "Main navigation" sidebar, not a separate
|
|
734
|
+
// app to switch into. Its header banner (homePage.consoleBanner, via
|
|
735
|
+
// hubHeader) is already visible from any Hub page, so there is no
|
|
736
|
+
// navigation action needed here -- mirrors the same hostname guard in
|
|
737
|
+
// clickModeler(). Clicking the sidebar's Clusters/Environments link is
|
|
738
|
+
// not just unnecessary but wrong: Hub shows that section only to org
|
|
739
|
+
// admins, so requiring it broke every RBA step that reaches here as a
|
|
740
|
+
// handed-off non-admin test user. Callers that do need the Clusters page
|
|
741
|
+
// call clickClusters() themselves; the rest assert consoleBanner.
|
|
742
|
+
//
|
|
743
|
+
// The diagram editor is the exception: it has no "Main navigation"
|
|
744
|
+
// sidebar at all, so a caller arriving from "Deploy & run" would find
|
|
745
|
+
// nothing here. Go home first there.
|
|
723
746
|
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
724
|
-
|
|
725
|
-
.
|
|
726
|
-
.
|
|
727
|
-
|
|
728
|
-
await (0, test_1.expect)(consoleArea).toBeVisible({ timeout: 30000 });
|
|
729
|
-
await consoleArea.click({ timeout: 30000 });
|
|
747
|
+
if (!(await this.hubSidebar.isVisible().catch(() => false))) {
|
|
748
|
+
await this.clickModeler();
|
|
749
|
+
await (0, test_1.expect)(this.hubSidebar).toBeVisible({ timeout: 30000 });
|
|
750
|
+
}
|
|
730
751
|
return;
|
|
731
752
|
}
|
|
732
753
|
const maxRetries = 3;
|
|
@@ -750,7 +771,46 @@ class AppsPage {
|
|
|
750
771
|
}
|
|
751
772
|
throw new Error(`Failed to click the console link after ${maxRetries} attempts.`);
|
|
752
773
|
}
|
|
753
|
-
|
|
774
|
+
// Hub's replacement for the app switcher: a cluster's apps are the child
|
|
775
|
+
// links of its own sidebar group. Hub names every app link "<app> in
|
|
776
|
+
// <environment>", plus " (opens in a new tab)" when it leads to the live app
|
|
777
|
+
// (camunda-hub environment-sidebar-rows.ts) -- the nightly traces carry both
|
|
778
|
+
// shapes ("Operate in Third RBA V2 Cluster", "Admin in Admin Role Cluster
|
|
779
|
+
// (opens in a new tab)"). Anchor both halves rather than matching the bare
|
|
780
|
+
// app name: the flyout's first link is "Details of <environment>", so for
|
|
781
|
+
// "Admin Role Cluster" a bare /Admin|Identity/ resolved to that link, and
|
|
782
|
+
// inline it resolved to "Operate in Admin Role Cluster (opens in a new
|
|
783
|
+
// tab)", which precedes the Admin link. Anchoring on the environment also
|
|
784
|
+
// stops "Test Cluster" matching "UI Test Cluster".
|
|
785
|
+
hubClusterAppLink(clusterName, appName) {
|
|
786
|
+
const appPattern = typeof appName === 'string' ? escapeRegExp(appName) : appName.source;
|
|
787
|
+
const cluster = escapeRegExp(clusterName);
|
|
788
|
+
const appLinkName = new RegExp(`^(?:${appPattern}) in ${cluster}(?: \\(opens in a new tab\\))?$`);
|
|
789
|
+
return this.page
|
|
790
|
+
.getByRole('dialog', { name: clusterName, exact: true })
|
|
791
|
+
.getByRole('link', { name: appLinkName })
|
|
792
|
+
.or(this.hubSidebar.getByRole('link', { name: appLinkName }))
|
|
793
|
+
.first();
|
|
794
|
+
}
|
|
795
|
+
async assertHubClusterAppPresent(clusterName, appName, shouldBeVisible) {
|
|
796
|
+
const appLink = this.hubClusterAppLink(clusterName, appName);
|
|
797
|
+
// Not expanded for an absence check: expandHubClusterGroup throws when
|
|
798
|
+
// the user cannot see the environment at all, which is a pass here.
|
|
799
|
+
if (!shouldBeVisible) {
|
|
800
|
+
await (0, test_1.expect)(appLink).toBeHidden({ timeout: 30000 });
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
await this.expandHubClusterGroup(clusterName);
|
|
804
|
+
await (0, test_1.expect)(appLink).toBeVisible({ timeout: 30000 });
|
|
805
|
+
}
|
|
806
|
+
// On Hub the cluster name is what scopes the lookup: there is no app
|
|
807
|
+
// switcher panel listing apps org-wide, only each cluster's own sidebar
|
|
808
|
+
// group.
|
|
809
|
+
async assertOperatePresent(clusterName, shouldBeVisible = true) {
|
|
810
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
811
|
+
await this.assertHubClusterAppPresent(clusterName, 'Operate', shouldBeVisible);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
754
814
|
const maxRetries = 3;
|
|
755
815
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
756
816
|
try {
|
|
@@ -779,7 +839,12 @@ class AppsPage {
|
|
|
779
839
|
}
|
|
780
840
|
throw new Error(`Failed to assert the operate link visibility after ${maxRetries} attempts.`);
|
|
781
841
|
}
|
|
782
|
-
|
|
842
|
+
// See assertOperatePresent for the clusterName/Hub note.
|
|
843
|
+
async assertTasklistPresent(clusterName, shouldBeVisible = true) {
|
|
844
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
845
|
+
await this.assertHubClusterAppPresent(clusterName, 'Tasklist', shouldBeVisible);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
783
848
|
const maxRetries = 3;
|
|
784
849
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
785
850
|
try {
|
|
@@ -808,7 +873,12 @@ class AppsPage {
|
|
|
808
873
|
}
|
|
809
874
|
throw new Error(`Failed to assert the tasklist link visibility after ${maxRetries} attempts.`);
|
|
810
875
|
}
|
|
811
|
-
|
|
876
|
+
// See assertOperatePresent for the clusterName/Hub note.
|
|
877
|
+
async assertOptimizePresent(clusterName, shouldBeVisible = true) {
|
|
878
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
879
|
+
await this.assertHubClusterAppPresent(clusterName, 'Optimize', shouldBeVisible);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
812
882
|
const maxRetries = 3;
|
|
813
883
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
814
884
|
try {
|
|
@@ -103,6 +103,8 @@ declare class ModelerCreatePage {
|
|
|
103
103
|
readonly timerValue: Locator;
|
|
104
104
|
readonly eighthPlacedElement: Locator;
|
|
105
105
|
readonly deployAndRunMainButton: Locator;
|
|
106
|
+
readonly deployRunActions: Locator;
|
|
107
|
+
readonly deployAndRunMenuItem: Locator;
|
|
106
108
|
readonly deployAndRunSubButton: Locator;
|
|
107
109
|
readonly dialog: Locator;
|
|
108
110
|
readonly backupDeployButton: Locator;
|
|
@@ -141,6 +143,7 @@ declare class ModelerCreatePage {
|
|
|
141
143
|
clickAdditionalActionsButton(): Promise<void>;
|
|
142
144
|
clickDeployMenuItem(): Promise<void>;
|
|
143
145
|
deployDiagram(clusterName: string): Promise<void>;
|
|
146
|
+
assertDeployAndRunNotPermitted(): Promise<void>;
|
|
144
147
|
clickStartInstanceMainButton(): Promise<void>;
|
|
145
148
|
clickStartInstanceSubButton(): Promise<void>;
|
|
146
149
|
runProcessInstance(clusterName: string, variables?: string, formVariables?: {
|
|
@@ -115,6 +115,8 @@ class ModelerCreatePage {
|
|
|
115
115
|
timerValue;
|
|
116
116
|
eighthPlacedElement;
|
|
117
117
|
deployAndRunMainButton;
|
|
118
|
+
deployRunActions;
|
|
119
|
+
deployAndRunMenuItem;
|
|
118
120
|
deployAndRunSubButton;
|
|
119
121
|
dialog;
|
|
120
122
|
backupDeployButton;
|
|
@@ -160,6 +162,13 @@ class ModelerCreatePage {
|
|
|
160
162
|
name: 'Deploy & run',
|
|
161
163
|
exact: true,
|
|
162
164
|
});
|
|
165
|
+
// camunda-hub deploy-run-combo-action: both the Carbon ComboButton and
|
|
166
|
+
// the design-system SplitButton carry this test id.
|
|
167
|
+
this.deployRunActions = page.getByTestId('deploy-run-actions');
|
|
168
|
+
this.deployAndRunMenuItem = page.getByRole('menuitem', {
|
|
169
|
+
name: 'Deploy & run',
|
|
170
|
+
exact: true,
|
|
171
|
+
});
|
|
163
172
|
this.deployAndRunSubButton = this.dialog.getByRole('button', {
|
|
164
173
|
name: 'Deploy & run',
|
|
165
174
|
exact: true,
|
|
@@ -674,6 +683,22 @@ class ModelerCreatePage {
|
|
|
674
683
|
await (0, test_1.expect)(this.viewProcessLink).toBeVisible({ timeout: 30000 });
|
|
675
684
|
await (0, test_1.expect)(this.viewProcessLink).toBeHidden({ timeout: 30000 });
|
|
676
685
|
}
|
|
686
|
+
// For a user whose org role lacks instance/workflow create permissions,
|
|
687
|
+
// Hub's checkIfAllowedToRun is false: the split button's primary action
|
|
688
|
+
// becomes "Deploy" and "Deploy & run" moves into its menu, disabled
|
|
689
|
+
// (camunda-hub BPMNDeployAndRunButtonsDs.tsx, deploy-run-permission.saas.ts).
|
|
690
|
+
// The Console-era expectation of a deploy that is attempted and then fails
|
|
691
|
+
// cannot happen, so assert the gate itself.
|
|
692
|
+
async assertDeployAndRunNotPermitted() {
|
|
693
|
+
await (0, test_1.expect)(this.deployRunActions.getByRole('button', { name: 'Deploy', exact: true })).toBeVisible({ timeout: 60000 });
|
|
694
|
+
await (0, test_1.expect)(this.deployAndRunMainButton).toHaveCount(0);
|
|
695
|
+
await this.deployRunActions
|
|
696
|
+
.getByRole('button', { name: 'Additional actions' })
|
|
697
|
+
.click({ timeout: 30000 });
|
|
698
|
+
await (0, test_1.expect)(this.deployAndRunMenuItem).toBeVisible({ timeout: 30000 });
|
|
699
|
+
await (0, test_1.expect)(this.deployAndRunMenuItem).toBeDisabled();
|
|
700
|
+
await this.page.keyboard.press('Escape');
|
|
701
|
+
}
|
|
677
702
|
async clickStartInstanceMainButton() {
|
|
678
703
|
// ensureStageCluster runs immediately before this on every runProcessInstance
|
|
679
704
|
// attempt and navigates twice, so the diagram's action bar may belong to a
|
|
@@ -83,8 +83,13 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
83
83
|
// for the tab's whole lifetime; without clearing it here alongside
|
|
84
84
|
// cookies/storage, a flow that logs a different user into the same
|
|
85
85
|
// page could have getUserToken() return the PREVIOUS user's token.
|
|
86
|
-
|
|
86
|
+
// Listen from before Hub loads: an API helper called later on an idle
|
|
87
|
+
// Hub page would otherwise find no request carrying the token.
|
|
88
|
+
(0, consoleApiHelpers_1.observeBearerTokens)(page);
|
|
87
89
|
await page.goto('about:blank');
|
|
90
|
+
// After about:blank: until then the previous user's Hub page can still
|
|
91
|
+
// send a request, and its token must not count for the new session.
|
|
92
|
+
(0, consoleApiHelpers_1.resetHubBearerToken)(page);
|
|
88
93
|
await page
|
|
89
94
|
.evaluate(() => {
|
|
90
95
|
try {
|
|
@@ -409,24 +414,36 @@ async function enableAuthorizations(clusterName, homePage, clusterPage, clusterD
|
|
|
409
414
|
}
|
|
410
415
|
exports.enableAuthorizations = enableAuthorizations;
|
|
411
416
|
async function assertLatestAlertEmail(id, mailSlurp, processName, alertText) {
|
|
412
|
-
|
|
413
|
-
for
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
417
|
+
// Search the whole inbox for an email carrying alertText rather than asking
|
|
418
|
+
// for the "latest" one. waitForLatestEmail is documented as returning the
|
|
419
|
+
// last received email immediately when the inbox is NOT empty ("If you need
|
|
420
|
+
// to wait for an email for a non-empty inbox see the other receive methods"
|
|
421
|
+
// -- mailslurp-client's own JSDoc), and the alert flow calls this twice on
|
|
422
|
+
// the same inbox: the second call kept resolving to the first alert's mail
|
|
423
|
+
// ("New Incidents: 1") and failed its "New Incidents: 2" assertion on every
|
|
424
|
+
// retry, since retrying re-read the same already-delivered email. Matching
|
|
425
|
+
// on content also tolerates the alert sending more mails than the two this
|
|
426
|
+
// flow asserts on, and ordering between them.
|
|
427
|
+
let body;
|
|
428
|
+
await (0, test_1.expect)(async () => {
|
|
429
|
+
// minCount/retryTimeout make the server itself hold the request open until
|
|
430
|
+
// the inbox has grown, so this does not busy-poll between attempts.
|
|
431
|
+
const previews = await mailSlurp.getEmails(id, {
|
|
432
|
+
minCount: 1,
|
|
433
|
+
retryTimeout: 60000,
|
|
434
|
+
});
|
|
435
|
+
for (const preview of previews) {
|
|
436
|
+
const email = await mailSlurp.getEmail(preview.id);
|
|
437
|
+
if (email?.body?.includes(processName) &&
|
|
438
|
+
email.body.includes(alertText)) {
|
|
439
|
+
body = email.body;
|
|
440
|
+
return;
|
|
423
441
|
}
|
|
424
442
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
throw new Error(`Failed to get latest alert (${processName}) email for ${id} after ${maxRetries} attempts.`);
|
|
443
|
+
throw new Error(`No alert email for "${processName}" containing "${alertText}" in ` +
|
|
444
|
+
`inbox ${id} yet (${previews.length} mail(s) present)`);
|
|
445
|
+
}).toPass({ timeout: 360000, intervals: [5000, 10000, 20000] });
|
|
446
|
+
return body;
|
|
430
447
|
}
|
|
431
448
|
exports.assertLatestAlertEmail = assertLatestAlertEmail;
|
|
432
449
|
async function clickInvitationLinkInEmail(page, id, mailSlurp) {
|
|
@@ -10,6 +10,7 @@ declare class WorkspaceMembersPage {
|
|
|
10
10
|
constructor(page: Page);
|
|
11
11
|
showAllWorkspaces(): Promise<void>;
|
|
12
12
|
openManageMembers(workspaceName: string): Promise<void>;
|
|
13
|
+
workspaceId(): string;
|
|
13
14
|
private memberRow;
|
|
14
15
|
isMember(email: string): Promise<boolean>;
|
|
15
16
|
addMember(email: string, role: WorkspaceRole): Promise<void>;
|
|
@@ -106,6 +106,14 @@ class WorkspaceMembersPage {
|
|
|
106
106
|
await this.membersTab.click({ timeout: 30000 });
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
// Valid once openManageMembers has landed on /workspaces/<id>/manage.
|
|
110
|
+
workspaceId() {
|
|
111
|
+
const id = this.page.url().match(/\/workspaces\/([^/?#]+)\/manage/)?.[1];
|
|
112
|
+
if (!id) {
|
|
113
|
+
throw new Error(`Not on a workspace Manage page, cannot read its id: ${this.page.url()}`);
|
|
114
|
+
}
|
|
115
|
+
return decodeURIComponent(id);
|
|
116
|
+
}
|
|
109
117
|
memberRow(email) {
|
|
110
118
|
return this.page.getByRole('row').filter({ hasText: email }).first();
|
|
111
119
|
}
|
|
@@ -229,12 +237,11 @@ class WorkspaceMembersPage {
|
|
|
229
237
|
return;
|
|
230
238
|
}
|
|
231
239
|
const row = this.memberRow(email);
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// rather than on text.
|
|
240
|
+
// camunda-hub MembersTable.tsx: the row's icon trigger is
|
|
241
|
+
// aria-label="Actions for <name>", data-test="member-actions-trigger".
|
|
235
242
|
await row
|
|
236
|
-
.
|
|
237
|
-
.or(row.
|
|
243
|
+
.locator('[data-test="member-actions-trigger"]')
|
|
244
|
+
.or(row.getByRole('button', { name: /^Actions for / }))
|
|
238
245
|
.first()
|
|
239
246
|
.click({ timeout: 30000 });
|
|
240
247
|
await this.page
|
|
@@ -242,11 +249,15 @@ class WorkspaceMembersPage {
|
|
|
242
249
|
.or(this.page.getByRole('option', { name: /^Remove( member)?$/i }))
|
|
243
250
|
.first()
|
|
244
251
|
.click({ timeout: 30000 });
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
// RemoveMemberDialog.tsx is a Radix AlertDialog, i.e. role="alertdialog",
|
|
253
|
+
// which getByRole('dialog') does NOT match -- the confirm button was
|
|
254
|
+
// unreachable. Prefer its own test id and keep the role lookup as a
|
|
255
|
+
// fallback.
|
|
256
|
+
await this.page
|
|
257
|
+
.locator('[data-test="remove-member-confirm"]')
|
|
258
|
+
.or(this.page
|
|
259
|
+
.getByRole('alertdialog', { name: 'Remove member' })
|
|
260
|
+
.getByRole('button', { name: 'Remove', exact: true }))
|
|
250
261
|
.first()
|
|
251
262
|
.click({ timeout: 30000 });
|
|
252
263
|
await (0, test_1.expect)(row).toBeHidden({ timeout: 60000 });
|
|
@@ -7,6 +7,7 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
7
7
|
const resetSession_1 = require("../../utils/resetSession");
|
|
8
8
|
const LoginPage_1 = require("../../pages/8.8/LoginPage");
|
|
9
9
|
const WorkspaceMembersPage_1 = require("../../pages/8.8/WorkspaceMembersPage");
|
|
10
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
10
11
|
const test_1 = require("@playwright/test");
|
|
11
12
|
const users_1 = require("../../utils/users");
|
|
12
13
|
const testUser = (0, users_1.getTestUser)('mainUser');
|
|
@@ -47,18 +48,20 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
47
48
|
// Editor and its own "not reachable without workspace access" assertion
|
|
48
49
|
// would fail on the second attempt; the grant would also leak into every
|
|
49
50
|
// later nightly run against the same org.
|
|
50
|
-
_8_8_1.test.afterEach(async ({ page,
|
|
51
|
+
_8_8_1.test.afterEach(async ({ page, loginPage }, testInfo) => {
|
|
51
52
|
if (!testInfo.title.startsWith('Analyst Role User Flow')) {
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
if (!analystWorkspaceGrantMade) {
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
// Leave Hub before the analyst's session is dropped. A Hub deep link
|
|
59
|
+
// reloaded signed out is persisted as the next login's destination
|
|
60
|
+
// (camunda-hub auth-service.saas.ts persistReturnTo), so the admin landed
|
|
61
|
+
// on the analyst's diagram, whose sidebar has no org link for
|
|
62
|
+
// assertTestUsesCorrectOrganization to find.
|
|
63
|
+
await page.goto('about:blank');
|
|
64
|
+
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
|
|
62
65
|
const workspaceMembersPage = new WorkspaceMembersPage_1.WorkspaceMembersPage(page);
|
|
63
66
|
await workspaceMembersPage.openManageMembers(analystWorkspaceName);
|
|
64
67
|
await workspaceMembersPage.removeMember(process.env.C8_USERNAME_TEST_9);
|
|
@@ -98,9 +101,9 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
98
101
|
timeout: 60000,
|
|
99
102
|
});
|
|
100
103
|
await appsPage.clickCamundaApps();
|
|
101
|
-
await appsPage.assertOptimizePresent();
|
|
102
|
-
await appsPage.assertOperatePresent();
|
|
103
|
-
await appsPage.assertTasklistPresent();
|
|
104
|
+
await appsPage.assertOptimizePresent(clusterName);
|
|
105
|
+
await appsPage.assertOperatePresent(clusterName);
|
|
106
|
+
await appsPage.assertTasklistPresent(clusterName);
|
|
104
107
|
await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
|
|
105
108
|
await appsPage.clickCamundaApps();
|
|
106
109
|
});
|
|
@@ -112,7 +115,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
112
115
|
});
|
|
113
116
|
});
|
|
114
117
|
await _8_8_1.test.step('Navigate to Tasklist and Ensure Tasklist is not Accessible', async () => {
|
|
115
|
-
await
|
|
118
|
+
await appsPage.goBackToHub();
|
|
116
119
|
await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
|
|
117
120
|
await appsPage.clickCamundaApps();
|
|
118
121
|
await appsPage.clickTasklist(clusterName);
|
|
@@ -121,7 +124,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
121
124
|
});
|
|
122
125
|
});
|
|
123
126
|
await _8_8_1.test.step('Navigate to Identity and Ensure Identity is not Accessible', async () => {
|
|
124
|
-
await
|
|
127
|
+
await appsPage.goBackToHub();
|
|
125
128
|
await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
|
|
126
129
|
await appsPage.clickCamundaApps();
|
|
127
130
|
await appsPage.clickIdentity(clusterName);
|
|
@@ -130,7 +133,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
130
133
|
});
|
|
131
134
|
});
|
|
132
135
|
await _8_8_1.test.step('Navigate to Optimize and Ensure Optimize is Not Accessible', async () => {
|
|
133
|
-
await
|
|
136
|
+
await appsPage.goBackToHub();
|
|
134
137
|
await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
|
|
135
138
|
await appsPage.clickCamundaApps();
|
|
136
139
|
await appsPage.clickOptimize(clusterName);
|
|
@@ -139,7 +142,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
139
142
|
});
|
|
140
143
|
});
|
|
141
144
|
await _8_8_1.test.step('Navigate to Modeler and Ensure Diagram Failed To Deploy', async () => {
|
|
142
|
-
await
|
|
145
|
+
await appsPage.goBackToHub();
|
|
143
146
|
await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
|
|
144
147
|
await appsPage.clickCamundaApps();
|
|
145
148
|
await appsPage.clickModeler();
|
|
@@ -166,6 +169,19 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
166
169
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
167
170
|
const processName = 'User Roles User Flow' + randomString;
|
|
168
171
|
const clusterName = clusterNames['Analyst Role User Flow'];
|
|
172
|
+
// A retry runs in a fresh worker, where analystWorkspaceGrantMade starts
|
|
173
|
+
// false even when the previous attempt's teardown failed to hand the grant
|
|
174
|
+
// back -- which is exactly what happens when that teardown dies on its
|
|
175
|
+
// admin re-login. The "not reachable without workspace access" step below
|
|
176
|
+
// would then always fail, because the analyst is still an Editor.
|
|
177
|
+
if (_8_8_1.test.info().retry > 0) {
|
|
178
|
+
await _8_8_1.test.step('Hand back a workspace grant left by the previous attempt', async () => {
|
|
179
|
+
await appsPage.clickModeler();
|
|
180
|
+
const workspaceMembersPage = new WorkspaceMembersPage_1.WorkspaceMembersPage(page);
|
|
181
|
+
await workspaceMembersPage.openManageMembers(analystWorkspaceName);
|
|
182
|
+
await workspaceMembersPage.removeMember(process.env.C8_USERNAME_TEST_9);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
169
185
|
await _8_8_1.test.step('Navigate to Console and Enable RBA', async () => {
|
|
170
186
|
await (0, UtilitiesPage_1.enableAuthorizations)(clusterName, homePage, clusterPage, clusterDetailsPage, appsPage);
|
|
171
187
|
await (0, sleep_1.sleep)(15000);
|
|
@@ -225,6 +241,9 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
225
241
|
const hubSidebar = page.getByRole('navigation', {
|
|
226
242
|
name: 'Main navigation',
|
|
227
243
|
});
|
|
244
|
+
// The negative counts below mean nothing before the shell has rendered:
|
|
245
|
+
// toHaveCount(0) passes vacuously on a page that has not painted yet.
|
|
246
|
+
await (0, test_1.expect)(hubSidebar).toBeVisible({ timeout: 30000 });
|
|
228
247
|
await (0, test_1.expect)(hubSidebar.getByRole('button', { name: clusterName })).toHaveCount(0, { timeout: 30000 });
|
|
229
248
|
await (0, test_1.expect)(hubSidebar.getByRole('link', { name: /Optimize/ })).toHaveCount(0, { timeout: 30000 });
|
|
230
249
|
await (0, test_1.expect)(optimizeHomePage.optimizeBanner).not.toBeVisible({
|
|
@@ -247,6 +266,14 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
247
266
|
// clean that up too.
|
|
248
267
|
analystWorkspaceGrantMade = true;
|
|
249
268
|
await workspaceMembersPage.setMemberRole(process.env.C8_USERNAME_TEST_9, 'Editor');
|
|
269
|
+
// Editorship alone surfaces nothing: an environment is reachable only
|
|
270
|
+
// when it is assigned to a workspace the user is Editor or Workspace
|
|
271
|
+
// Admin on, and nothing else in this flow assigns this cluster's
|
|
272
|
+
// environment to the shared workspace. Without it the analyst holds the
|
|
273
|
+
// membership but the sidebar still renders no group for the cluster,
|
|
274
|
+
// which is where the nightly timed out (expandHubClusterGroup waiting on
|
|
275
|
+
// button "Analyst Role Cluster").
|
|
276
|
+
await (0, consoleApiHelpers_1.linkClusterToWorkspaceViaApi)(page, workspaceMembersPage.workspaceId(), clusterName);
|
|
250
277
|
await (0, resetSession_1.resetSession)(browser, page);
|
|
251
278
|
await new LoginPage_1.LoginPage(page).loginWithTestUser({
|
|
252
279
|
username: process.env.C8_USERNAME_TEST_9,
|
|
@@ -287,9 +314,9 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
287
314
|
timeout: 60000,
|
|
288
315
|
});
|
|
289
316
|
await appsPage.clickCamundaApps();
|
|
290
|
-
await appsPage.assertOptimizePresent();
|
|
291
|
-
await appsPage.assertOperatePresent();
|
|
292
|
-
await appsPage.assertTasklistPresent();
|
|
317
|
+
await appsPage.assertOptimizePresent(clusterName);
|
|
318
|
+
await appsPage.assertOperatePresent(clusterName);
|
|
319
|
+
await appsPage.assertTasklistPresent(clusterName);
|
|
293
320
|
await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
|
|
294
321
|
await appsPage.clickCamundaApps();
|
|
295
322
|
});
|
|
@@ -299,7 +326,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
299
326
|
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(page, operateHomePage.noPermissionsMessage, 'Operate No Permission message');
|
|
300
327
|
});
|
|
301
328
|
await _8_8_1.test.step('Navigate to Tasklist and Ensure Tasklist is not Accessible', async () => {
|
|
302
|
-
await
|
|
329
|
+
await appsPage.goBackToHub();
|
|
303
330
|
await appsPage.clickCamundaApps();
|
|
304
331
|
await appsPage.clickTasklist(clusterName);
|
|
305
332
|
await (0, test_1.expect)(taskPanelPage.noPermissionsMessage).toBeVisible({
|
|
@@ -307,7 +334,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
307
334
|
});
|
|
308
335
|
});
|
|
309
336
|
await _8_8_1.test.step('Navigate to Identity and Ensure Identity is not Accessible', async () => {
|
|
310
|
-
await
|
|
337
|
+
await appsPage.goBackToHub();
|
|
311
338
|
await appsPage.clickCamundaApps();
|
|
312
339
|
await appsPage.clickIdentity(clusterName);
|
|
313
340
|
await (0, test_1.expect)(ocIdentityHomePage.noPermissionsMessage).toBeVisible({
|
|
@@ -315,14 +342,14 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
315
342
|
});
|
|
316
343
|
});
|
|
317
344
|
await _8_8_1.test.step('Navigate to Optimize and Ensure Optimize is Accessible', async () => {
|
|
318
|
-
await
|
|
345
|
+
await appsPage.goBackToHub();
|
|
319
346
|
await appsPage.clickCamundaApps();
|
|
320
347
|
await appsPage.clickOptimize(clusterName);
|
|
321
348
|
await (0, test_1.expect)(optimizeHomePage.optimizeBanner).toBeVisible({
|
|
322
349
|
timeout: 60000,
|
|
323
350
|
});
|
|
324
351
|
});
|
|
325
|
-
await _8_8_1.test.step('Navigate to Modeler and Ensure Diagram
|
|
352
|
+
await _8_8_1.test.step('Navigate to Modeler and Ensure Diagram Cannot Be Deployed & Run', async () => {
|
|
326
353
|
await appsPage.clickCamundaApps();
|
|
327
354
|
await appsPage.clickModeler();
|
|
328
355
|
await homePage.clickSkipCustomization();
|
|
@@ -339,9 +366,10 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
339
366
|
await modelerCreatePage.clickAppendElementButton();
|
|
340
367
|
await modelerCreatePage.clickAppendEndEventButton();
|
|
341
368
|
await (0, sleep_1.sleep)(20000);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
369
|
+
// Hub gates this in the UI: an Analyst holds no instance or workflow
|
|
370
|
+
// create permission, so "Deploy & run" is offered only disabled, where
|
|
371
|
+
// Console let the attempt through and reported a failed deployment.
|
|
372
|
+
await modelerCreatePage.assertDeployAndRunNotPermitted();
|
|
345
373
|
});
|
|
346
374
|
});
|
|
347
375
|
(0, _8_8_1.test)('Admin Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, browser, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
|
|
@@ -403,9 +431,9 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
403
431
|
timeout: 60000,
|
|
404
432
|
});
|
|
405
433
|
await appsPage.clickCamundaApps();
|
|
406
|
-
await appsPage.assertOptimizePresent();
|
|
407
|
-
await appsPage.assertOperatePresent();
|
|
408
|
-
await appsPage.assertTasklistPresent();
|
|
434
|
+
await appsPage.assertOptimizePresent(clusterName);
|
|
435
|
+
await appsPage.assertOperatePresent(clusterName);
|
|
436
|
+
await appsPage.assertTasklistPresent(clusterName);
|
|
409
437
|
await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
|
|
410
438
|
await appsPage.clickCamundaApps();
|
|
411
439
|
});
|
|
@@ -417,7 +445,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
417
445
|
});
|
|
418
446
|
});
|
|
419
447
|
await _8_8_1.test.step('Navigate to Tasklist and Ensure Tasklist is Accessible', async () => {
|
|
420
|
-
await
|
|
448
|
+
await appsPage.goBackToHub();
|
|
421
449
|
await appsPage.clickCamundaApps();
|
|
422
450
|
await appsPage.clickTasklist(clusterName);
|
|
423
451
|
await (0, test_1.expect)(taskPanelPage.taskListPageBanner).toBeVisible({
|
|
@@ -425,7 +453,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
425
453
|
});
|
|
426
454
|
});
|
|
427
455
|
await _8_8_1.test.step('Navigate to Identity and Ensure Identity is Accessible', async () => {
|
|
428
|
-
await
|
|
456
|
+
await appsPage.goBackToHub();
|
|
429
457
|
await appsPage.clickCamundaApps();
|
|
430
458
|
await appsPage.clickIdentity(clusterName);
|
|
431
459
|
await (0, test_1.expect)(ocIdentityHomePage.identityBanner).toBeVisible({
|
|
@@ -436,7 +464,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
436
464
|
});
|
|
437
465
|
});
|
|
438
466
|
await _8_8_1.test.step('Navigate to Optimize and Ensure Optimize is Accessible', async () => {
|
|
439
|
-
await
|
|
467
|
+
await appsPage.goBackToHub();
|
|
440
468
|
await appsPage.clickCamundaApps();
|
|
441
469
|
await appsPage.clickOptimize(clusterName);
|
|
442
470
|
await (0, test_1.expect)(optimizeHomePage.optimizeBanner).toBeVisible({
|
|
@@ -192,7 +192,28 @@ _8_8_1.test.describe('Console User Flow Tests @tasklistV2', () => {
|
|
|
192
192
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
193
193
|
await modelerHomePage.clickProcessDiagram(processName);
|
|
194
194
|
await modelerCreatePage.runProcessInstance(alertFlowClusterName);
|
|
195
|
-
|
|
195
|
+
// Straight into the next run, no wait. The email assertion below expects
|
|
196
|
+
// ONE mail reporting "New Incidents: 2", which only happens when both
|
|
197
|
+
// incidents fall inside the same alert evaluation cycle; the 10s sleep
|
|
198
|
+
// that used to be here pushed them into separate cycles and Console sent
|
|
199
|
+
// a "New Incidents: 1" mail for each (observed live: three mails at
|
|
200
|
+
// 15:09:20, 15:11:51 and 15:13:02, all reporting 1).
|
|
201
|
+
//
|
|
202
|
+
// Close the first run's toast before clicking again: camunda-hub 6813a7e
|
|
203
|
+
// made the "Instance started!" toast persistent whenever it carries the
|
|
204
|
+
// Operate action, so it stays up and its action button covers
|
|
205
|
+
// "Deploy & run", swallowing the second click ("<button
|
|
206
|
+
// data-action=\"true\"> ... intercepts pointer events", observed live
|
|
207
|
+
// once the sleep was gone).
|
|
208
|
+
const instanceStartedToast = page
|
|
209
|
+
.locator('[data-sonner-toast]')
|
|
210
|
+
.filter({ has: modelerCreatePage.viewProcessInstanceLink });
|
|
211
|
+
await instanceStartedToast
|
|
212
|
+
.locator('[data-close-button="true"]')
|
|
213
|
+
.click({ timeout: 30000 });
|
|
214
|
+
await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeHidden({
|
|
215
|
+
timeout: 30000,
|
|
216
|
+
});
|
|
196
217
|
await modelerCreatePage.runProcessInstance(alertFlowClusterName);
|
|
197
218
|
});
|
|
198
219
|
await _8_8_1.test.step('Navigate to Cluster Details', async () => {
|
|
@@ -9,6 +9,7 @@ const roleAuthorizations_1 = require("../../utils/roleAuthorizations");
|
|
|
9
9
|
const expectTextWithRetry_1 = require("../../utils/assertionHelpers/expectTextWithRetry");
|
|
10
10
|
const expectTextWithPagination_1 = require("../../utils/assertionHelpers/expectTextWithPagination");
|
|
11
11
|
const users_1 = require("../../utils/users");
|
|
12
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
12
13
|
const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
|
|
13
14
|
const mainUser = (0, users_1.getTestUser)('twentyFifthUser');
|
|
14
15
|
_8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV1', () => {
|
|
@@ -19,19 +20,30 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV1', () => {
|
|
|
19
20
|
'RBA On User Flow - Permission for Selected Processes Only': 'Fourth RBA V1 Cluster',
|
|
20
21
|
};
|
|
21
22
|
_8_8_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage, clusterDetailsPage, appsPage }, testInfo) => {
|
|
23
|
+
// Claimed here, not in each body: creating the cluster and waiting for
|
|
24
|
+
// it to come back Healthy after the authorizations toggle can outlast
|
|
25
|
+
// the default per-test timeout before the body starts.
|
|
26
|
+
_8_8_1.test.slow();
|
|
22
27
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
23
28
|
await homePage.clickClusters();
|
|
24
29
|
const clusterName = clusterNames[testInfo.title];
|
|
25
30
|
(0, test_1.expect)(clusterName, `No cluster mapping for test title: "${testInfo.title}")`).toBeTruthy();
|
|
26
31
|
await clusterPage.createClusterIfNotExists(clusterName);
|
|
27
32
|
await (0, UtilitiesPage_1.disableRBA)(clusterName, homePage, clusterPage, clusterDetailsPage, appsPage);
|
|
33
|
+
// The flows below hand off to test users with no elevated or DevOps org
|
|
34
|
+
// role, and Hub only lets those roles assign an environment to a
|
|
35
|
+
// workspace (camunda-hub#28955). Without this, the test user's deploy
|
|
36
|
+
// dialog never offers this cluster and every runProcessInstance retry
|
|
37
|
+
// fails until the test times out. Assign it now, while the elevated
|
|
38
|
+
// main user's session is active. test-setup creates the test users'
|
|
39
|
+
// workspaces up front so that they exist at this point.
|
|
40
|
+
await (0, consoleApiHelpers_1.linkClusterToWorkspacesNamedViaApi)(page, clusterName);
|
|
28
41
|
});
|
|
29
42
|
_8_8_1.test.afterEach(async ({ page }, testInfo) => {
|
|
30
43
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
31
44
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
32
45
|
});
|
|
33
46
|
(0, _8_8_1.test)('RBA On User Flow - No User Permission', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, loginPage, settingsPage, }, testInfo) => {
|
|
34
|
-
_8_8_1.test.slow();
|
|
35
47
|
const clusterName = clusterNames[testInfo.title];
|
|
36
48
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
37
49
|
const processName = 'RBA_NO_PERM' + randomString;
|
|
@@ -153,7 +165,6 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV1', () => {
|
|
|
153
165
|
});
|
|
154
166
|
});
|
|
155
167
|
(0, _8_8_1.test)('RBA On User Flow - Permission for One Process', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, loginPage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, settingsPage, }, testInfo) => {
|
|
156
|
-
_8_8_1.test.slow();
|
|
157
168
|
const clusterName = clusterNames[testInfo.title];
|
|
158
169
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
159
170
|
const processName = 'RBA_ONE_PERM' + randomString;
|
|
@@ -305,7 +316,6 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV1', () => {
|
|
|
305
316
|
});
|
|
306
317
|
});
|
|
307
318
|
(0, _8_8_1.test)('RBA On User Flow - Permission for All Processes with Full Permissions', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, }, testInfo) => {
|
|
308
|
-
_8_8_1.test.slow();
|
|
309
319
|
const clusterName = clusterNames[testInfo.title];
|
|
310
320
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
311
321
|
const processName = 'AFTER_RBA' + randomString;
|
|
@@ -375,7 +385,6 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV1', () => {
|
|
|
375
385
|
});
|
|
376
386
|
});
|
|
377
387
|
(0, _8_8_1.test)('RBA On User Flow - Permission for Selected Processes Only', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, loginPage, settingsPage, }, testInfo) => {
|
|
378
|
-
_8_8_1.test.slow();
|
|
379
388
|
const clusterName = clusterNames[testInfo.title];
|
|
380
389
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
381
390
|
const processName = 'RBA_SELECTED_PERM' + randomString;
|
|
@@ -9,6 +9,7 @@ const roleAuthorizations_1 = require("../../utils/roleAuthorizations");
|
|
|
9
9
|
const expectTextWithRetry_1 = require("../../utils/assertionHelpers/expectTextWithRetry");
|
|
10
10
|
const expectTextWithPagination_1 = require("../../utils/assertionHelpers/expectTextWithPagination");
|
|
11
11
|
const users_1 = require("../../utils/users");
|
|
12
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
12
13
|
const mainUser = (0, users_1.getTestUser)('twentySixthUser');
|
|
13
14
|
_8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
14
15
|
const clusterNames = {
|
|
@@ -18,6 +19,10 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
18
19
|
'RBA On User Flow - Permission for Selected Processes Only': 'Fourth RBA V2 Cluster',
|
|
19
20
|
};
|
|
20
21
|
_8_8_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage, clusterDetailsPage, appsPage }, testInfo) => {
|
|
22
|
+
// Claimed here, not in each body: creating the cluster and waiting for
|
|
23
|
+
// it to come back Healthy after the authorizations toggle can outlast
|
|
24
|
+
// the default per-test timeout before the body starts.
|
|
25
|
+
_8_8_1.test.slow();
|
|
21
26
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
22
27
|
await (0, test_1.expect)(homePage.clusterTab).toBeVisible({ timeout: 120000 });
|
|
23
28
|
await homePage.clickClusters();
|
|
@@ -25,13 +30,20 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
25
30
|
(0, test_1.expect)(clusterName, `No cluster mapping for test title: "${testInfo.title}")`).toBeTruthy();
|
|
26
31
|
await clusterPage.createClusterIfNotExists(clusterName);
|
|
27
32
|
await (0, UtilitiesPage_1.disableRBA)(clusterName, homePage, clusterPage, clusterDetailsPage, appsPage);
|
|
33
|
+
// The flows below hand off to test users with no elevated or DevOps org
|
|
34
|
+
// role, and Hub only lets those roles assign an environment to a
|
|
35
|
+
// workspace (camunda-hub#28955). Without this, the test user's deploy
|
|
36
|
+
// dialog never offers this cluster and every runProcessInstance retry
|
|
37
|
+
// fails until the test times out. Assign it now, while the elevated
|
|
38
|
+
// main user's session is active. test-setup creates the test users'
|
|
39
|
+
// workspaces up front so that they exist at this point.
|
|
40
|
+
await (0, consoleApiHelpers_1.linkClusterToWorkspacesNamedViaApi)(page, clusterName);
|
|
28
41
|
});
|
|
29
42
|
_8_8_1.test.afterEach(async ({ page }, testInfo) => {
|
|
30
43
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
31
44
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
32
45
|
});
|
|
33
46
|
(0, _8_8_1.test)('RBA On User Flow - No User Permission', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, loginPage, settingsPage, }, testInfo) => {
|
|
34
|
-
_8_8_1.test.slow();
|
|
35
47
|
const clusterName = clusterNames[testInfo.title];
|
|
36
48
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
37
49
|
const processName = 'RBA_NO_PERM' + randomString;
|
|
@@ -68,10 +80,19 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
68
80
|
await ocIdentityAuthorizationsPage.createAuthorization((0, roleAuthorizations_1.authorizationAllPermissions)(role));
|
|
69
81
|
});
|
|
70
82
|
await _8_8_1.test.step('Clear cookies and reset session', async () => {
|
|
83
|
+
// Reset the session directly rather than driving Hub's user-menu
|
|
84
|
+
// logout: that menu is not reachable from every page this step can be
|
|
85
|
+
// reached from, and clickOpenSettingsButton then burns all three of its
|
|
86
|
+
// retries (nightly: "Failed to click button after 3 retries"). Clearing
|
|
87
|
+
// cookies and storage is what the step is actually for, and is what
|
|
88
|
+
// pages/8.9's copy of this spec does.
|
|
89
|
+
await page.context().clearCookies();
|
|
90
|
+
await page.evaluate(() => {
|
|
91
|
+
localStorage.clear();
|
|
92
|
+
sessionStorage.clear();
|
|
93
|
+
});
|
|
71
94
|
await page.goto('/');
|
|
72
|
-
await
|
|
73
|
-
await settingsPage.clickLogoutButton();
|
|
74
|
-
await loginPage.loginWithTestUser(testUser);
|
|
95
|
+
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, 1000);
|
|
75
96
|
});
|
|
76
97
|
await _8_8_1.test.step('Navigate to Web Modeler', async () => {
|
|
77
98
|
await appsPage.clickCamundaApps();
|
|
@@ -131,7 +152,6 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
131
152
|
});
|
|
132
153
|
});
|
|
133
154
|
(0, _8_8_1.test)('RBA On User Flow - Permission for One Process', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, loginPage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, settingsPage, }, testInfo) => {
|
|
134
|
-
_8_8_1.test.slow();
|
|
135
155
|
const clusterName = clusterNames[testInfo.title];
|
|
136
156
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
137
157
|
const processName = 'RBA_ONE_PERM' + randomString;
|
|
@@ -175,12 +195,21 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
175
195
|
await ocIdentityAuthorizationsPage.createAuthorization((0, roleAuthorizations_1.roleAllAuthorization)(role1));
|
|
176
196
|
});
|
|
177
197
|
await _8_8_1.test.step('Clear cookies and reset session', async () => {
|
|
198
|
+
// Reset the session directly rather than driving Hub's user-menu
|
|
199
|
+
// logout: that menu is not reachable from every page this step can be
|
|
200
|
+
// reached from, and clickOpenSettingsButton then burns all three of its
|
|
201
|
+
// retries (nightly: "Failed to click button after 3 retries"). Clearing
|
|
202
|
+
// cookies and storage is what the step is actually for, and is what
|
|
203
|
+
// pages/8.9's copy of this spec does.
|
|
204
|
+
await page.context().clearCookies();
|
|
205
|
+
await page.evaluate(() => {
|
|
206
|
+
localStorage.clear();
|
|
207
|
+
sessionStorage.clear();
|
|
208
|
+
});
|
|
178
209
|
await page.goto('/');
|
|
179
|
-
await settingsPage.clickOpenSettingsButton();
|
|
180
|
-
await settingsPage.clickLogoutButton();
|
|
181
210
|
});
|
|
182
211
|
await _8_8_1.test.step('Login as Test User', async () => {
|
|
183
|
-
await
|
|
212
|
+
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, 1000);
|
|
184
213
|
});
|
|
185
214
|
await _8_8_1.test.step('Navigate to Web Modeler', async () => {
|
|
186
215
|
await appsPage.clickCamundaApps();
|
|
@@ -280,7 +309,6 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
280
309
|
});
|
|
281
310
|
});
|
|
282
311
|
(0, _8_8_1.test)('RBA On User Flow - Permission for All Processes with Full Permissions', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, }, testInfo) => {
|
|
283
|
-
_8_8_1.test.slow();
|
|
284
312
|
const clusterName = clusterNames[testInfo.title];
|
|
285
313
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
286
314
|
const processName = 'AFTER_RBA' + randomString;
|
|
@@ -352,8 +380,7 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
352
380
|
await (0, expectTextWithRetry_1.expectTextWithRetry)(page, process2);
|
|
353
381
|
});
|
|
354
382
|
});
|
|
355
|
-
(0, _8_8_1.test)('RBA On User Flow - Permission for Selected Processes Only', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, loginPage,
|
|
356
|
-
_8_8_1.test.slow();
|
|
383
|
+
(0, _8_8_1.test)('RBA On User Flow - Permission for Selected Processes Only', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, taskPanelPage, taskProcessesPage, operateHomePage, ocIdentityHomePage, ocIdentityAuthorizationsPage, ocIdentityRolesPage, loginPage, }, testInfo) => {
|
|
357
384
|
const clusterName = clusterNames[testInfo.title];
|
|
358
385
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
359
386
|
const processName = 'RBA_SELECTED_PERM' + randomString;
|
|
@@ -392,10 +419,19 @@ _8_8_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () => {
|
|
|
392
419
|
await (0, sleep_1.sleep)(20000);
|
|
393
420
|
});
|
|
394
421
|
await _8_8_1.test.step('Clear cookies and reset session', async () => {
|
|
422
|
+
// Reset the session directly rather than driving Hub's user-menu
|
|
423
|
+
// logout: that menu is not reachable from every page this step can be
|
|
424
|
+
// reached from, and clickOpenSettingsButton then burns all three of its
|
|
425
|
+
// retries (nightly: "Failed to click button after 3 retries"). Clearing
|
|
426
|
+
// cookies and storage is what the step is actually for, and is what
|
|
427
|
+
// pages/8.9's copy of this spec does.
|
|
428
|
+
await page.context().clearCookies();
|
|
429
|
+
await page.evaluate(() => {
|
|
430
|
+
localStorage.clear();
|
|
431
|
+
sessionStorage.clear();
|
|
432
|
+
});
|
|
395
433
|
await page.goto('/');
|
|
396
|
-
await
|
|
397
|
-
await settingsPage.clickLogoutButton();
|
|
398
|
-
await loginPage.loginWithTestUser(testUser);
|
|
434
|
+
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, 1000);
|
|
399
435
|
});
|
|
400
436
|
await _8_8_1.test.step('Navigate to Web Modeler', async () => {
|
|
401
437
|
await appsPage.clickCamundaApps();
|
|
@@ -118,11 +118,56 @@ _8_8_1.test.describe('Cluster Setup Tests', () => {
|
|
|
118
118
|
await clusterSecretsPage.createSetOfSecrets(clusterName, connectorSecrets_1.connectorSecretsValues);
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
// The RBA specs hand off to these developer-role users, who cannot assign an
|
|
122
|
+
// environment to a workspace (camunda-hub#28955). Their workspaces must exist
|
|
123
|
+
// before the RBA beforeEach links its cluster to them as the admin main user.
|
|
124
|
+
const rbaHandoffUsers = process.env.IS_AG === 'true' && process.env.RUN_FULL_SUITE !== 'true'
|
|
125
|
+
? []
|
|
126
|
+
: ['4', '5', '6']
|
|
127
|
+
.map((n) => ({
|
|
128
|
+
username: process.env[`C8_USERNAME_TEST_${n}`] ?? '',
|
|
129
|
+
password: process.env[`C8_PASSWORD_TEST_${n}`] ?? '',
|
|
130
|
+
title: `Create Project Folder for RBA hand-off user TEST_${n}`,
|
|
131
|
+
}))
|
|
132
|
+
.filter((user) => user.username !== '' && user.password !== '');
|
|
133
|
+
for (const [index, user] of [
|
|
134
|
+
...(0, users_1.getTestUsers)().map((u, i) => ({
|
|
135
|
+
...u,
|
|
136
|
+
title: `Create Project Folder for User ${i + 1}`,
|
|
137
|
+
})),
|
|
138
|
+
...rbaHandoffUsers,
|
|
139
|
+
].entries()) {
|
|
140
|
+
const isRbaHandoffUser = index >= (0, users_1.getTestUsers)().length;
|
|
141
|
+
(0, _8_8_1.test)(user.title, async ({ page, loginPage, homePage, appsPage, modelerHomePage }, testInfo) => {
|
|
142
|
+
const loggedIn = await _8_8_1.test.step('Log in', async () => {
|
|
143
|
+
try {
|
|
144
|
+
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, user, (testInfo.workerIndex + 1) * 1000);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
// Every spec in the project depends on this setup project, so a
|
|
149
|
+
// hand-off slot the org did not provision must not block the whole
|
|
150
|
+
// suite (an 8.9 PR run lost 162 tests to exactly that). Nothing but
|
|
151
|
+
// the RBA specs reads these three workspaces, and each of those logs
|
|
152
|
+
// in as its own user, so it fails on its own login instead.
|
|
153
|
+
//
|
|
154
|
+
// Any failure, not just a credential rejection: unlike 8.9/8.10,
|
|
155
|
+
// this version's LoginPage does not run classifyLoginRejection, so
|
|
156
|
+
// no AUTH0_INVALID_CREDENTIALS marker ever reaches here to match on.
|
|
157
|
+
if (!isRbaHandoffUser) {
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
console.warn(`${user.title}: ${String(error)}`);
|
|
161
|
+
testInfo.annotations.push({
|
|
162
|
+
type: 'warning',
|
|
163
|
+
description: 'Could not log in as this hand-off user; no workspace',
|
|
164
|
+
});
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
125
167
|
});
|
|
168
|
+
if (!loggedIn) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
126
171
|
await _8_8_1.test.step('Navigate to Web Modeler', async () => {
|
|
127
172
|
// Camunda Hub replaced Console + Web Modeler on SaaS: it lands on the
|
|
128
173
|
// Modeler home directly and has no app switcher to wait for.
|