@camunda/e2e-test-suite 0.0.1287 → 0.0.1288

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.
Files changed (33) hide show
  1. package/dist/pages/8.9/AppsPage.d.ts +9 -3
  2. package/dist/pages/8.9/AppsPage.js +262 -47
  3. package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
  4. package/dist/pages/8.9/ClusterDetailsPage.js +92 -9
  5. package/dist/pages/8.9/ClusterSecretsPage.js +5 -1
  6. package/dist/pages/8.9/ConnectorMarketplacePage.d.ts +1 -0
  7. package/dist/pages/8.9/ConnectorMarketplacePage.js +46 -3
  8. package/dist/pages/8.9/ConsoleOrganizationPage.d.ts +3 -2
  9. package/dist/pages/8.9/ConsoleOrganizationPage.js +47 -22
  10. package/dist/pages/8.9/FormJsPage.d.ts +4 -0
  11. package/dist/pages/8.9/FormJsPage.js +53 -19
  12. package/dist/pages/8.9/IdpCreatePage.js +5 -0
  13. package/dist/pages/8.9/ModelerCreatePage.d.ts +5 -0
  14. package/dist/pages/8.9/ModelerCreatePage.js +153 -21
  15. package/dist/pages/8.9/ModelerHomePage.d.ts +1 -0
  16. package/dist/pages/8.9/ModelerHomePage.js +29 -4
  17. package/dist/pages/8.9/PlayPage.js +11 -1
  18. package/dist/pages/8.9/ProcessApplicationPage.js +48 -4
  19. package/dist/pages/8.9/UtilitiesPage.js +57 -17
  20. package/dist/pages/8.9/WorkspaceMembersPage.d.ts +21 -0
  21. package/dist/pages/8.9/WorkspaceMembersPage.js +264 -0
  22. package/dist/tests/8.9/access-user-role-management.spec.js +111 -26
  23. package/dist/tests/8.9/console-user-flows.spec.js +4 -4
  24. package/dist/tests/8.9/idp-user-flows.spec.js +2 -1
  25. package/dist/tests/8.9/rba-enabled-v1-user-flows.spec.js +13 -4
  26. package/dist/tests/8.9/rba-enabled-v2-user-flows.spec.js +13 -4
  27. package/dist/tests/8.9/test-setup.spec.js +57 -4
  28. package/dist/tests/8.9/utr-enabled-user-flows.spec.js +18 -0
  29. package/dist/tests/8.9/web-modeler-user-flows.spec.js +6 -2
  30. package/dist/utils/consoleApiHelpers.d.ts +2 -0
  31. package/dist/utils/consoleApiHelpers.js +50 -4
  32. package/dist/utils/resetSession.js +6 -1
  33. package/package.json +1 -1
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkspaceMembersPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ // Camunda Hub's workspace "Manage members" view: Members / Environments /
6
+ // General tabs over a member table of Name | Email | Workspace role | Joined,
7
+ // an "Add members" panel, and a role cell that becomes a select once clicked.
8
+ //
9
+ // This page matters to the role flows because an organization role alone no
10
+ // longer decides what a non-admin can reach. Since camunda-hub's environments
11
+ // work, an environment -- and so the cluster app behind it -- is visible only
12
+ // to a user who is Editor or Workspace Admin on a workspace it is assigned to
13
+ // (pdp-3715); organization admins and owners see every environment.
14
+ class WorkspaceMembersPage {
15
+ page;
16
+ membersTab;
17
+ manageMembersHeading;
18
+ addMembersButton;
19
+ addMemberSearchbox;
20
+ addMemberSubmitButton;
21
+ constructor(page) {
22
+ this.page = page;
23
+ this.membersTab = page.getByRole('tab', { name: 'Members' });
24
+ this.manageMembersHeading = page.getByRole('heading', {
25
+ name: 'Manage members',
26
+ });
27
+ this.addMembersButton = page
28
+ .locator('[data-test="add-members-button"]')
29
+ .or(page.getByRole('button', { name: 'Add members' }))
30
+ .first();
31
+ // A cmdk combobox: <input cmdk-input role="combobox"
32
+ // aria-label="Search members" placeholder="Search members or enter an
33
+ // email to invite">. Prefer the accessible name over the placeholder,
34
+ // which is the longer and more churn-prone of the two.
35
+ this.addMemberSearchbox = page
36
+ .getByRole('combobox', { name: 'Search members' })
37
+ .or(page.getByPlaceholder('Search members or enter an email to invite'))
38
+ .first();
39
+ // `exact` matters: the panel's own trigger is "Add members", which a
40
+ // substring match on "Add" would also resolve.
41
+ this.addMemberSubmitButton = page.getByRole('button', {
42
+ name: 'Add',
43
+ exact: true,
44
+ });
45
+ }
46
+ // Hub's Workspaces overview defaults to "My workspaces" -- only the ones the
47
+ // current user is an explicit member of -- and resets to it on every load,
48
+ // so a workspace created by another user is invisible until the scope is
49
+ // widened. The empty state offers an explicit button; the populated view
50
+ // offers the filter instead. A no-op where neither is rendered.
51
+ async showAllWorkspaces() {
52
+ const switchButton = this.page.getByRole('button', {
53
+ name: 'Switch to All workspaces',
54
+ });
55
+ const scopeFilter = this.page.getByRole('combobox', {
56
+ name: 'Filter workspaces',
57
+ });
58
+ const appeared = await switchButton
59
+ .or(scopeFilter)
60
+ .first()
61
+ .waitFor({ state: 'visible', timeout: 15000 })
62
+ .then(() => true)
63
+ .catch(() => false);
64
+ if (!appeared) {
65
+ return;
66
+ }
67
+ if (await switchButton.isVisible().catch(() => false)) {
68
+ await switchButton.click({ timeout: 10000 });
69
+ await (0, test_1.expect)(switchButton).toBeHidden({ timeout: 10000 });
70
+ return;
71
+ }
72
+ if ((await scopeFilter.innerText()).includes('All workspaces')) {
73
+ return;
74
+ }
75
+ await scopeFilter.click({ timeout: 10000 });
76
+ await this.page
77
+ .getByRole('option', { name: 'All workspaces' })
78
+ .click({ timeout: 10000 });
79
+ await (0, test_1.expect)(scopeFilter).toContainText('All workspaces', {
80
+ timeout: 10000,
81
+ });
82
+ }
83
+ async openManageMembers(workspaceName) {
84
+ // `.first()`: an org can hold several workspaces sharing a name -- the
85
+ // nightly's environments card lists "Cross Component Test Project" twice
86
+ // -- and an unqualified match is then a strict-mode violation.
87
+ const manageButton = this.page
88
+ .getByRole('button', { name: `Manage workspace ${workspaceName}` })
89
+ .first();
90
+ // Keep Hub's default "My workspaces" scope, which is what a real user
91
+ // sees, and widen only when the target is not in it -- e.g. it was
92
+ // created by another user and this one holds no explicit membership.
93
+ const visibleUnderDefaultScope = await manageButton
94
+ .waitFor({ state: 'visible', timeout: 15000 })
95
+ .then(() => true)
96
+ .catch(() => false);
97
+ if (!visibleUnderDefaultScope) {
98
+ await this.showAllWorkspaces();
99
+ }
100
+ await (0, test_1.expect)(manageButton).toBeVisible({ timeout: 60000 });
101
+ await manageButton.click({ timeout: 30000 });
102
+ await (0, test_1.expect)(this.manageMembersHeading).toBeVisible({ timeout: 60000 });
103
+ if ((await this.membersTab
104
+ .getAttribute('aria-selected')
105
+ .catch(() => null)) !== 'true') {
106
+ await this.membersTab.click({ timeout: 30000 });
107
+ }
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
+ }
117
+ memberRow(email) {
118
+ return this.page.getByRole('row').filter({ hasText: email }).first();
119
+ }
120
+ async isMember(email) {
121
+ return this.memberRow(email)
122
+ .waitFor({ state: 'visible', timeout: 15000 })
123
+ .then(() => true)
124
+ .catch(() => false);
125
+ }
126
+ // Adds a user who is not on the member list yet. The panel is collapsed
127
+ // until its trigger is clicked, and its own "Workspace role" select already
128
+ // defaults to Editor, so it is only driven when a different role is wanted.
129
+ async addMember(email, role) {
130
+ await this.addMembersButton.click({ timeout: 30000 });
131
+ await (0, test_1.expect)(this.addMemberSearchbox).toBeVisible({ timeout: 30000 });
132
+ await this.addMemberSearchbox.click({ timeout: 30000 });
133
+ await this.addMemberSearchbox.fill(email);
134
+ // An org member shows up as a pickable result; picking it is what enables
135
+ // the submit. Typing alone leaves an invite-by-email candidate that some
136
+ // builds require to be committed the same way.
137
+ // cmdk renders its results as [cmdk-item] nodes with role="option",
138
+ // filtered live against what was typed.
139
+ const result = this.page
140
+ .getByRole('option', { name: new RegExp(escapeRegExp(email), 'i') })
141
+ .or(this.page.locator('[cmdk-item]').filter({ hasText: email }))
142
+ .first();
143
+ // The result list is a multi-select, and its checkbox is NOT a checkbox:
144
+ // a trace of the live DOM shows each row as
145
+ // <div cmdk-item role="option" data-staged="false" aria-selected="true">
146
+ // <svg aria-hidden="true"><rect .../></svg> <div>name / email</div>
147
+ // so getByRole('checkbox') matches nothing, and clicking the row only
148
+ // sets cmdk's highlight -- aria-selected and data-selected both read
149
+ // "true" while data-staged stays "false" and "Add" stays disabled.
150
+ // Click the leading svg, which is the box itself, and gate on the one
151
+ // attribute that actually tracks the pick.
152
+ await (0, test_1.expect)(result).toBeVisible({ timeout: 30000 });
153
+ // Neither the row nor its box can be clicked into a staged state: the box
154
+ // is a decorative svg the design system gives `pointer-events: none`, so
155
+ // that click lands on the row, and a row click only moves cmdk's
156
+ // highlight -- across three live runs `data-staged` never left "false"
157
+ // while `aria-selected` read "true" and "Add" stayed disabled. The
158
+ // list's "Select all" control is what actually stages. It is a
159
+ // <button data-test="members-picker-select-all">, not a cmdk item, and
160
+ // its label is split across text nodes ("Select all (", "1", ")"), so it
161
+ // is addressed by its test id. The search box above has already been
162
+ // filled with this one address, so the guard below holds it to exactly
163
+ // the intended user rather than trusting the label's count.
164
+ const candidates = this.page
165
+ .locator('[cmdk-item]')
166
+ .filter({ hasText: email });
167
+ await (0, test_1.expect)(candidates).toHaveCount(1, { timeout: 15000 });
168
+ await this.page
169
+ .locator('[data-test="members-picker-select-all"]')
170
+ .click({ timeout: 15000 });
171
+ await (0, test_1.expect)(result).toHaveAttribute('data-staged', 'true', {
172
+ timeout: 15000,
173
+ });
174
+ if (role !== 'Editor') {
175
+ await this.selectRoleIn(this.page.getByRole('combobox', { name: 'Workspace role' }).first(), role);
176
+ }
177
+ await (0, test_1.expect)(this.addMemberSubmitButton).toBeEnabled({ timeout: 30000 });
178
+ await this.addMemberSubmitButton.click({ timeout: 30000 });
179
+ await (0, test_1.expect)(this.memberRow(email)).toBeVisible({ timeout: 60000 });
180
+ }
181
+ async selectRoleIn(control, role) {
182
+ await control.click({ timeout: 30000 });
183
+ await this.page
184
+ .getByRole('option', { name: role, exact: true })
185
+ .or(this.page.getByRole('menuitem', { name: role, exact: true }))
186
+ .first()
187
+ .click({ timeout: 30000 });
188
+ }
189
+ // Ensures the user holds `role` on this workspace, adding them first when
190
+ // they are not a member at all -- which is the usual case for a role flow,
191
+ // since the shared workspace is created by whichever setup user won the
192
+ // race and nobody else is added to it.
193
+ async setMemberRole(email, role) {
194
+ if (!(await this.isMember(email))) {
195
+ await this.addMember(email, role);
196
+ return;
197
+ }
198
+ const row = this.memberRow(email);
199
+ if ((await row.getByText(role, { exact: true }).count()) > 0) {
200
+ return;
201
+ }
202
+ // The role cell renders as a static pill until clicked, then becomes a
203
+ // select with its own confirm control; leaving it unconfirmed silently
204
+ // reverts the row to its previous role.
205
+ const roleControl = row
206
+ .getByRole('combobox')
207
+ .or(row.getByRole('button', {
208
+ name: /^(Workspace Admin|Editor|Commenter|Viewer)$/,
209
+ }))
210
+ .first();
211
+ await roleControl.click({ timeout: 30000 });
212
+ await this.selectRoleIn(roleControl, role);
213
+ // Clicked unconditionally, not behind an `isVisible()` probe: the
214
+ // confirmation is what persists the change, and the control can render a
215
+ // beat after the select. A probe would read `false` in that window, skip
216
+ // the click, and let the assertion below match the still-uncommitted
217
+ // value in the open editor just before the row reverts. Clicking outright
218
+ // makes Playwright wait for the control and fail here when it never
219
+ // arrives, rather than passing on an edit that was never saved.
220
+ await row
221
+ .getByRole('button', { name: /^(Confirm|Save|Apply|Update)$/i })
222
+ .or(row.locator('button[type="submit"]'))
223
+ .first()
224
+ .click({ timeout: 30000 });
225
+ await (0, test_1.expect)(row.getByText(role, { exact: true })).toBeVisible({
226
+ timeout: 60000,
227
+ });
228
+ }
229
+ // Undoes `setMemberRole`/`addMember`. Workspace membership is durable org
230
+ // state: left behind, it survives the test that granted it, so a Playwright
231
+ // retry starts with the user already an Editor and any assertion about the
232
+ // un-granted state fails on the second attempt. A no-op when the user is
233
+ // not on the list, so it is safe to call from teardown after a failure at
234
+ // any point in the flow.
235
+ async removeMember(email) {
236
+ if (!(await this.isMember(email))) {
237
+ return;
238
+ }
239
+ const row = this.memberRow(email);
240
+ // camunda-hub MembersTable.tsx: the row's icon trigger is
241
+ // aria-label="Actions for <name>", data-test="member-actions-trigger".
242
+ await row
243
+ .locator('[data-test="member-actions-trigger"]')
244
+ .or(row.getByRole('button', { name: /^Actions for / }))
245
+ .first()
246
+ .click({ timeout: 30000 });
247
+ await this.page
248
+ .getByRole('menuitem', { name: /^Remove( member)?$/i })
249
+ .or(this.page.getByRole('option', { name: /^Remove( member)?$/i }))
250
+ .first()
251
+ .click({ timeout: 30000 });
252
+ // RemoveMemberDialog.tsx is a Radix AlertDialog, i.e. role="alertdialog",
253
+ // which getByRole('dialog') does not match.
254
+ await this.page
255
+ .getByRole('alertdialog', { name: 'Remove member' })
256
+ .getByRole('button', { name: 'Remove', exact: true })
257
+ .click({ timeout: 30000 });
258
+ await (0, test_1.expect)(row).toBeHidden({ timeout: 60000 });
259
+ }
260
+ }
261
+ exports.WorkspaceMembersPage = WorkspaceMembersPage;
262
+ function escapeRegExp(value) {
263
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
264
+ }
@@ -6,9 +6,15 @@ const UtilitiesPage_1 = require("../../pages/8.9/UtilitiesPage");
6
6
  const sleep_1 = require("../../utils/sleep");
7
7
  const resetSession_1 = require("../../utils/resetSession");
8
8
  const LoginPage_1 = require("../../pages/8.9/LoginPage");
9
+ const WorkspaceMembersPage_1 = require("../../pages/8.9/WorkspaceMembersPage");
9
10
  const test_1 = require("@playwright/test");
10
11
  const users_1 = require("../../utils/users");
12
+ const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
11
13
  const testUser = (0, users_1.getTestUser)('mainUser');
14
+ // The workspace the Analyst flow borrows Editor rights on, and whether that
15
+ // grant is currently outstanding (read by the teardown below).
16
+ const analystWorkspaceName = 'Cross Component Test Project';
17
+ let analystWorkspaceGrantMade = false;
12
18
  _8_9_1.test.describe.configure({ mode: 'parallel' });
13
19
  _8_9_1.test.describe('User Roles User Flow', () => {
14
20
  const clusterNames = {
@@ -17,6 +23,10 @@ _8_9_1.test.describe('User Roles User Flow', () => {
17
23
  'Admin Role User Flow': 'Admin Role Cluster',
18
24
  };
19
25
  _8_9_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage, clusterDetailsPage, appsPage }, testInfo) => {
26
+ // Claimed here, not in each body: creating the cluster and waiting for
27
+ // it to come back Healthy after the authorizations toggle can outlast
28
+ // the default per-test timeout before the body starts. Same as 8.8.
29
+ _8_9_1.test.slow();
20
30
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
21
31
  await (0, test_1.expect)(homePage.clusterTab).toBeVisible({ timeout: 120000 });
22
32
  await homePage.clickClusters();
@@ -30,9 +40,30 @@ _8_9_1.test.describe('User Roles User Flow', () => {
30
40
  await (0, _setup_1.captureScreenshot)(page, testInfo);
31
41
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
32
42
  });
43
+ // Workspace membership is durable org state: left behind, a retry starts
44
+ // with the analyst already an Editor and its "not reachable without
45
+ // workspace access" step fails. Hand it back whether the test passed or not.
46
+ _8_9_1.test.afterEach(async ({ page, loginPage }, testInfo) => {
47
+ if (!testInfo.title.startsWith('Analyst Role User Flow')) {
48
+ return;
49
+ }
50
+ if (!analystWorkspaceGrantMade) {
51
+ return;
52
+ }
53
+ // Leave Hub before the analyst's session is dropped. A Hub deep link
54
+ // reloaded signed out is persisted as the next login's destination
55
+ // (camunda-hub auth-service.saas.ts persistReturnTo), so the admin landed
56
+ // on the analyst's diagram, whose sidebar has no org link for
57
+ // assertTestUsesCorrectOrganization to find.
58
+ await page.goto('about:blank');
59
+ await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
60
+ const workspaceMembersPage = new WorkspaceMembersPage_1.WorkspaceMembersPage(page);
61
+ await workspaceMembersPage.openManageMembers(analystWorkspaceName);
62
+ await workspaceMembersPage.removeMember(process.env.C8_USERNAME_TEST_9);
63
+ analystWorkspaceGrantMade = false;
64
+ });
33
65
  //Fix will be implemented in https://github.com/camunda/c8-cross-component-e2e-tests/issues/1236
34
66
  _8_9_1.test.skip('Modeler Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, context, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
35
- _8_9_1.test.slow();
36
67
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
37
68
  const processName = 'User Roles User Flow' + randomString;
38
69
  const clusterName = clusterNames['Modeler Role User Flow'];
@@ -65,9 +96,9 @@ _8_9_1.test.describe('User Roles User Flow', () => {
65
96
  timeout: 60000,
66
97
  });
67
98
  await appsPage.clickCamundaApps();
68
- await appsPage.assertOptimizePresent();
69
- await appsPage.assertOperatePresent();
70
- await appsPage.assertTasklistPresent();
99
+ await appsPage.assertOptimizePresent(clusterName);
100
+ await appsPage.assertOperatePresent(clusterName);
101
+ await appsPage.assertTasklistPresent(clusterName);
71
102
  await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
72
103
  await appsPage.clickCamundaApps();
73
104
  });
@@ -79,7 +110,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
79
110
  });
80
111
  });
81
112
  await _8_9_1.test.step('Navigate to Tasklist and Ensure Tasklist is not Accessible', async () => {
82
- await page.goBack();
113
+ await appsPage.goBackToHub();
83
114
  await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
84
115
  await appsPage.clickCamundaApps();
85
116
  await appsPage.clickTasklist(clusterName);
@@ -88,7 +119,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
88
119
  });
89
120
  });
90
121
  await _8_9_1.test.step('Navigate to Identity and Ensure Identity is not Accessible', async () => {
91
- await page.goBack();
122
+ await appsPage.goBackToHub();
92
123
  await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
93
124
  await appsPage.clickCamundaApps();
94
125
  await appsPage.clickAdmin(clusterName);
@@ -97,7 +128,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
97
128
  });
98
129
  });
99
130
  await _8_9_1.test.step('Navigate to Optimize and Ensure Optimize is Not Accessible', async () => {
100
- await page.goBack();
131
+ await appsPage.goBackToHub();
101
132
  await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
102
133
  await appsPage.clickCamundaApps();
103
134
  await appsPage.clickOptimize(clusterName);
@@ -106,7 +137,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
106
137
  });
107
138
  });
108
139
  await _8_9_1.test.step('Navigate to Modeler and Ensure Diagram Failed To Deploy', async () => {
109
- await page.goBack();
140
+ await appsPage.goBackToHub();
110
141
  await (0, UtilitiesPage_1.waitForLoadingToFinish)(page);
111
142
  await appsPage.clickCamundaApps();
112
143
  await appsPage.clickModeler();
@@ -130,10 +161,20 @@ _8_9_1.test.describe('User Roles User Flow', () => {
130
161
  });
131
162
  });
132
163
  (0, _8_9_1.test)('Analyst Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, browser, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
133
- _8_9_1.test.slow();
134
164
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
135
165
  const processName = 'User Roles User Flow' + randomString;
136
166
  const clusterName = clusterNames['Analyst Role User Flow'];
167
+ // A retry runs in a fresh worker, where analystWorkspaceGrantMade starts
168
+ // false even when the previous attempt's teardown failed to hand the
169
+ // grant back. The "not reachable" step below would then always fail.
170
+ if (_8_9_1.test.info().retry > 0) {
171
+ await _8_9_1.test.step('Hand back a workspace grant left by the previous attempt', async () => {
172
+ await appsPage.clickModeler();
173
+ const workspaceMembersPage = new WorkspaceMembersPage_1.WorkspaceMembersPage(page);
174
+ await workspaceMembersPage.openManageMembers(analystWorkspaceName);
175
+ await workspaceMembersPage.removeMember(process.env.C8_USERNAME_TEST_9);
176
+ });
177
+ }
137
178
  await _8_9_1.test.step('Navigate to Console and Enable RBA', async () => {
138
179
  await (0, UtilitiesPage_1.enableAuthorizations)(clusterName, homePage, clusterPage, clusterDetailsPage, appsPage);
139
180
  await (0, sleep_1.sleep)(15000);
@@ -155,6 +196,50 @@ _8_9_1.test.describe('User Roles User Flow', () => {
155
196
  password: process.env.C8_PASSWORD_TEST_9,
156
197
  });
157
198
  });
199
+ // Hub no longer lets an organization role alone reach a cluster's apps: a
200
+ // non-admin sees only environments assigned to a workspace they are Editor
201
+ // or Workspace Admin on (camunda-hub EnvironmentVisibilityResolver,
202
+ // ProjectOperation.VIEW_ENVIRONMENTS). Assert that gate, then grant it.
203
+ await _8_9_1.test.step('Ensure Optimize is not reachable without workspace access', async () => {
204
+ await appsPage.clickCamundaApps();
205
+ await (0, test_1.expect)(page
206
+ .getByText('Your organization has no workspaces yet')
207
+ .or(page.getByText("You don't have explicit membership in any workspace"))
208
+ .first()).toBeVisible({ timeout: 60000 });
209
+ const hubSidebar = page.getByRole('navigation', {
210
+ name: 'Main navigation',
211
+ });
212
+ // The negative counts below mean nothing before the shell has rendered.
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
+ });
219
+ });
220
+ await _8_9_1.test.step('Grant the analyst Editor rights on the shared workspace', async () => {
221
+ // Membership and environment assignment are org-admin actions, so this
222
+ // runs as the main user and switches back afterwards.
223
+ await (0, resetSession_1.resetSession)(browser, page);
224
+ await new LoginPage_1.LoginPage(page).loginWithTestUser({
225
+ username: process.env.C8_USERNAME,
226
+ password: process.env.C8_PASSWORD,
227
+ });
228
+ const workspaceMembersPage = new WorkspaceMembersPage_1.WorkspaceMembersPage(page);
229
+ await workspaceMembersPage.openManageMembers(analystWorkspaceName);
230
+ // Set before the grant: a failure partway through can still have added
231
+ // the member, and teardown must undo that too.
232
+ analystWorkspaceGrantMade = true;
233
+ await workspaceMembersPage.setMemberRole(process.env.C8_USERNAME_TEST_9, 'Editor');
234
+ // Editorship alone surfaces nothing: this cluster's environment must
235
+ // also be assigned to that same workspace, and nothing else assigns it.
236
+ await (0, consoleApiHelpers_1.linkClusterToWorkspaceViaApi)(page, workspaceMembersPage.workspaceId(), clusterName);
237
+ await (0, resetSession_1.resetSession)(browser, page);
238
+ await new LoginPage_1.LoginPage(page).loginWithTestUser({
239
+ username: process.env.C8_USERNAME_TEST_9,
240
+ password: process.env.C8_PASSWORD_TEST_9,
241
+ });
242
+ });
158
243
  await _8_9_1.test.step('Wait for the role change to reach Optimize', async () => {
159
244
  // Replaces a blind `sleep(600000)`. The role change only takes effect in
160
245
  // Optimize once its permission cache refreshes, and reaching Optimize is
@@ -188,9 +273,9 @@ _8_9_1.test.describe('User Roles User Flow', () => {
188
273
  timeout: 60000,
189
274
  });
190
275
  await appsPage.clickCamundaApps();
191
- await appsPage.assertOptimizePresent();
192
- await appsPage.assertOperatePresent();
193
- await appsPage.assertTasklistPresent();
276
+ await appsPage.assertOptimizePresent(clusterName);
277
+ await appsPage.assertOperatePresent(clusterName);
278
+ await appsPage.assertTasklistPresent(clusterName);
194
279
  await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
195
280
  await appsPage.clickCamundaApps();
196
281
  });
@@ -200,7 +285,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
200
285
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(page, operateHomePage.noPermissionsMessage, 'Operate No Permission message');
201
286
  });
202
287
  await _8_9_1.test.step('Navigate to Tasklist and Ensure Tasklist is not Accessible', async () => {
203
- await page.goBack();
288
+ await appsPage.goBackToHub();
204
289
  await appsPage.clickCamundaApps();
205
290
  await appsPage.clickTasklist(clusterName);
206
291
  await (0, test_1.expect)(taskPanelPage.noPermissionsMessage).toBeVisible({
@@ -208,7 +293,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
208
293
  });
209
294
  });
210
295
  await _8_9_1.test.step('Navigate to Identity and Ensure Identity is not Accessible', async () => {
211
- await page.goBack();
296
+ await appsPage.goBackToHub();
212
297
  await appsPage.clickCamundaApps();
213
298
  await appsPage.clickAdmin(clusterName);
214
299
  await (0, test_1.expect)(ocIdentityHomePage.noPermissionsMessage).toBeVisible({
@@ -216,14 +301,14 @@ _8_9_1.test.describe('User Roles User Flow', () => {
216
301
  });
217
302
  });
218
303
  await _8_9_1.test.step('Navigate to Optimize and Ensure Optimize is Accessible', async () => {
219
- await page.goBack();
304
+ await appsPage.goBackToHub();
220
305
  await appsPage.clickCamundaApps();
221
306
  await appsPage.clickOptimize(clusterName);
222
307
  await (0, test_1.expect)(optimizeHomePage.optimizeBanner).toBeVisible({
223
308
  timeout: 60000,
224
309
  });
225
310
  });
226
- await _8_9_1.test.step('Navigate to Modeler and Ensure Diagram Failed To Deploy', async () => {
311
+ await _8_9_1.test.step('Navigate to Modeler and Ensure Diagram Cannot Be Deployed & Run', async () => {
227
312
  await appsPage.clickCamundaApps();
228
313
  await appsPage.clickModeler();
229
314
  await homePage.clickSkipCustomization();
@@ -240,13 +325,13 @@ _8_9_1.test.describe('User Roles User Flow', () => {
240
325
  await modelerCreatePage.clickAppendElementButton();
241
326
  await modelerCreatePage.clickAppendEndEventButton();
242
327
  await (0, sleep_1.sleep)(20000);
243
- await modelerCreatePage.runProcessInstance(clusterName, '', {}, '', false);
244
- await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
245
- await modelerCreatePage.clickCloseButton();
328
+ // Hub gates this in the UI: an Analyst holds no instance or workflow
329
+ // create permission, so "Deploy & run" is offered only disabled, where
330
+ // Console let the attempt through and reported a failed deployment.
331
+ await modelerCreatePage.assertDeployAndRunNotPermitted();
246
332
  });
247
333
  });
248
334
  (0, _8_9_1.test)('Admin Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, browser, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
249
- _8_9_1.test.slow();
250
335
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
251
336
  const processName = 'User Roles User Flow' + randomString;
252
337
  const clusterName = clusterNames['Admin Role User Flow'];
@@ -304,9 +389,9 @@ _8_9_1.test.describe('User Roles User Flow', () => {
304
389
  timeout: 60000,
305
390
  });
306
391
  await appsPage.clickCamundaApps();
307
- await appsPage.assertOptimizePresent();
308
- await appsPage.assertOperatePresent();
309
- await appsPage.assertTasklistPresent();
392
+ await appsPage.assertOptimizePresent(clusterName);
393
+ await appsPage.assertOperatePresent(clusterName);
394
+ await appsPage.assertTasklistPresent(clusterName);
310
395
  await (0, test_1.expect)(appsPage.modelerLink).toBeVisible();
311
396
  await appsPage.clickCamundaApps();
312
397
  });
@@ -318,7 +403,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
318
403
  });
319
404
  });
320
405
  await _8_9_1.test.step('Navigate to Tasklist and Ensure Tasklist is Accessible', async () => {
321
- await page.goBack();
406
+ await appsPage.goBackToHub();
322
407
  await appsPage.clickCamundaApps();
323
408
  await appsPage.clickTasklist(clusterName);
324
409
  await (0, test_1.expect)(taskPanelPage.taskListPageBanner).toBeVisible({
@@ -326,7 +411,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
326
411
  });
327
412
  });
328
413
  await _8_9_1.test.step('Navigate to Identity and Ensure Identity is Accessible', async () => {
329
- await page.goBack();
414
+ await appsPage.goBackToHub();
330
415
  await appsPage.clickCamundaApps();
331
416
  await appsPage.clickAdmin(clusterName);
332
417
  await (0, test_1.expect)(ocIdentityHomePage.adminBanner).toBeVisible({
@@ -337,7 +422,7 @@ _8_9_1.test.describe('User Roles User Flow', () => {
337
422
  });
338
423
  });
339
424
  await _8_9_1.test.step('Navigate to Optimize and Ensure Optimize is Accessible', async () => {
340
- await page.goBack();
425
+ await appsPage.goBackToHub();
341
426
  await appsPage.clickCamundaApps();
342
427
  await appsPage.clickOptimize(clusterName);
343
428
  await (0, test_1.expect)(optimizeHomePage.optimizeBanner).toBeVisible({
@@ -154,7 +154,7 @@ _8_9_1.test.describe('Console User Flow Tests @tasklistV2', () => {
154
154
  await _8_9_1.test.step('Verify Alert Count Increases to 1 After Trigger', async () => {
155
155
  await clusterDetailsPage.clickAlertsTab();
156
156
  await (0, test_1.expect)(async () => {
157
- await page.reload();
157
+ await clusterDetailsPage.reopenAlertsTab();
158
158
  await (0, test_1.expect)(clusterDetailsPage.alertsList.first()).toBeVisible({
159
159
  timeout: 20000,
160
160
  });
@@ -162,7 +162,7 @@ _8_9_1.test.describe('Console User Flow Tests @tasklistV2', () => {
162
162
  (0, test_1.expect)(await clusterDetailsPage.alertsList.count()).toBeGreaterThan(1);
163
163
  await (0, test_1.expect)(clusterDetailsPage.alertsList.nth(0)).toContainText(`Email`);
164
164
  await clusterDetailsPage.assertAlertText('Amount triggered1');
165
- }).toPass({ timeout: 60000 });
165
+ }).toPass({ timeout: 240000 });
166
166
  });
167
167
  await _8_9_1.test.step('Verify Alerts Received via Email for Incident', async () => {
168
168
  await (0, sleep_1.sleep)(60000);
@@ -194,13 +194,13 @@ _8_9_1.test.describe('Console User Flow Tests @tasklistV2', () => {
194
194
  await _8_9_1.test.step('Verify Alert Count Increases to 3 After Trigger', async () => {
195
195
  await clusterDetailsPage.clickAlertsTab();
196
196
  await (0, test_1.expect)(async () => {
197
- await page.reload();
197
+ await clusterDetailsPage.reopenAlertsTab();
198
198
  await (0, test_1.expect)(clusterDetailsPage.alertsList.first()).toBeVisible({
199
199
  timeout: 20000,
200
200
  });
201
201
  await clusterDetailsPage.expandAlerts();
202
202
  (0, test_1.expect)(await clusterDetailsPage.alertsList.count()).toBeGreaterThan(1);
203
- }).toPass({ timeout: 60000 });
203
+ }).toPass({ timeout: 240000 });
204
204
  await clusterDetailsPage.assertAlertText('Amount triggered3');
205
205
  });
206
206
  await _8_9_1.test.step('Verify Alerts Received via Email for Incident', async () => {
@@ -237,7 +237,8 @@ _8_9_1.test.describe('IDP User Flow Tests', () => {
237
237
  });
238
238
  _8_9_1.test.describe('Classification Template', () => {
239
239
  for (const params of classificationTestCases) {
240
- (0, _8_9_1.test)(`${params.provider} classification - User creates an idp application with a classification template`, async ({ page, idpCreatePage, modelerHomePage, homePage, appsPage, clusterPage, clusterSecretsPage, }) => {
240
+ // Skipped due to bug #29644: https://github.com/camunda/camunda-hub/issues/29644
241
+ _8_9_1.test.skip(`${params.provider} classification - User creates an idp application with a classification template`, async ({ page, idpCreatePage, modelerHomePage, homePage, appsPage, clusterPage, clusterSecretsPage, }) => {
241
242
  _8_9_1.test.slow();
242
243
  const idpApplicationName = await (0, randomName_1.randomNameAgregator)(`${params.provider} classification` + ' - IDP_Application_Name');
243
244
  const idpClassificationTemplateName = await (0, randomName_1.randomNameAgregator)(`${params.provider} classification` +