@camunda/e2e-test-suite 0.0.1287 → 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.
Files changed (45) hide show
  1. package/dist/pages/8.8/AppsPage.d.ts +7 -3
  2. package/dist/pages/8.8/AppsPage.js +96 -26
  3. package/dist/pages/8.8/ModelerCreatePage.d.ts +3 -0
  4. package/dist/pages/8.8/ModelerCreatePage.js +25 -0
  5. package/dist/pages/8.8/UtilitiesPage.js +34 -17
  6. package/dist/pages/8.8/WorkspaceMembersPage.d.ts +1 -0
  7. package/dist/pages/8.8/WorkspaceMembersPage.js +21 -10
  8. package/dist/pages/8.9/AppsPage.d.ts +9 -3
  9. package/dist/pages/8.9/AppsPage.js +262 -47
  10. package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
  11. package/dist/pages/8.9/ClusterDetailsPage.js +92 -9
  12. package/dist/pages/8.9/ClusterSecretsPage.js +5 -1
  13. package/dist/pages/8.9/ConnectorMarketplacePage.d.ts +1 -0
  14. package/dist/pages/8.9/ConnectorMarketplacePage.js +46 -3
  15. package/dist/pages/8.9/ConsoleOrganizationPage.d.ts +3 -2
  16. package/dist/pages/8.9/ConsoleOrganizationPage.js +47 -22
  17. package/dist/pages/8.9/FormJsPage.d.ts +4 -0
  18. package/dist/pages/8.9/FormJsPage.js +53 -19
  19. package/dist/pages/8.9/IdpCreatePage.js +5 -0
  20. package/dist/pages/8.9/ModelerCreatePage.d.ts +5 -0
  21. package/dist/pages/8.9/ModelerCreatePage.js +153 -21
  22. package/dist/pages/8.9/ModelerHomePage.d.ts +1 -0
  23. package/dist/pages/8.9/ModelerHomePage.js +29 -4
  24. package/dist/pages/8.9/PlayPage.js +11 -1
  25. package/dist/pages/8.9/ProcessApplicationPage.js +48 -4
  26. package/dist/pages/8.9/UtilitiesPage.js +57 -17
  27. package/dist/pages/8.9/WorkspaceMembersPage.d.ts +21 -0
  28. package/dist/pages/8.9/WorkspaceMembersPage.js +264 -0
  29. package/dist/tests/8.8/access-user-role-management.spec.js +57 -29
  30. package/dist/tests/8.8/console-user-flows.spec.js +22 -1
  31. package/dist/tests/8.8/rba-enabled-v1-user-flows.spec.js +13 -4
  32. package/dist/tests/8.8/rba-enabled-v2-user-flows.spec.js +50 -14
  33. package/dist/tests/8.8/test-setup.spec.js +49 -4
  34. package/dist/tests/8.9/access-user-role-management.spec.js +111 -26
  35. package/dist/tests/8.9/console-user-flows.spec.js +4 -4
  36. package/dist/tests/8.9/idp-user-flows.spec.js +2 -1
  37. package/dist/tests/8.9/rba-enabled-v1-user-flows.spec.js +13 -4
  38. package/dist/tests/8.9/rba-enabled-v2-user-flows.spec.js +13 -4
  39. package/dist/tests/8.9/test-setup.spec.js +57 -4
  40. package/dist/tests/8.9/utr-enabled-user-flows.spec.js +18 -0
  41. package/dist/tests/8.9/web-modeler-user-flows.spec.js +6 -2
  42. package/dist/utils/consoleApiHelpers.d.ts +2 -0
  43. package/dist/utils/consoleApiHelpers.js +50 -4
  44. package/dist/utils/resetSession.js +6 -1
  45. 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
+ }
@@ -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, browser }, testInfo) => {
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
- await (0, resetSession_1.resetSession)(browser, page);
58
- await new LoginPage_1.LoginPage(page).loginWithTestUser({
59
- username: process.env.C8_USERNAME,
60
- password: process.env.C8_PASSWORD,
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 page.goBack();
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 page.goBack();
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 page.goBack();
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 page.goBack();
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 page.goBack();
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 page.goBack();
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 page.goBack();
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 Failed To Deploy', async () => {
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
- await modelerCreatePage.runProcessInstance(clusterName, '', {}, '', true);
343
- await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
344
- await modelerCreatePage.clickCloseButton();
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 page.goBack();
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 page.goBack();
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 page.goBack();
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
- await (0, sleep_1.sleep)(10000);
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;