@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.
- 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/pages/8.9/AppsPage.d.ts +9 -3
- package/dist/pages/8.9/AppsPage.js +262 -47
- package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
- package/dist/pages/8.9/ClusterDetailsPage.js +92 -9
- package/dist/pages/8.9/ClusterSecretsPage.js +5 -1
- package/dist/pages/8.9/ConnectorMarketplacePage.d.ts +1 -0
- package/dist/pages/8.9/ConnectorMarketplacePage.js +46 -3
- package/dist/pages/8.9/ConsoleOrganizationPage.d.ts +3 -2
- package/dist/pages/8.9/ConsoleOrganizationPage.js +47 -22
- package/dist/pages/8.9/FormJsPage.d.ts +4 -0
- package/dist/pages/8.9/FormJsPage.js +53 -19
- package/dist/pages/8.9/IdpCreatePage.js +5 -0
- package/dist/pages/8.9/ModelerCreatePage.d.ts +5 -0
- package/dist/pages/8.9/ModelerCreatePage.js +153 -21
- package/dist/pages/8.9/ModelerHomePage.d.ts +1 -0
- package/dist/pages/8.9/ModelerHomePage.js +29 -4
- package/dist/pages/8.9/PlayPage.js +11 -1
- package/dist/pages/8.9/ProcessApplicationPage.js +48 -4
- package/dist/pages/8.9/UtilitiesPage.js +57 -17
- package/dist/pages/8.9/WorkspaceMembersPage.d.ts +21 -0
- package/dist/pages/8.9/WorkspaceMembersPage.js +264 -0
- 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/dist/tests/8.9/access-user-role-management.spec.js +111 -26
- package/dist/tests/8.9/console-user-flows.spec.js +4 -4
- package/dist/tests/8.9/idp-user-flows.spec.js +2 -1
- package/dist/tests/8.9/rba-enabled-v1-user-flows.spec.js +13 -4
- package/dist/tests/8.9/rba-enabled-v2-user-flows.spec.js +13 -4
- package/dist/tests/8.9/test-setup.spec.js +57 -4
- package/dist/tests/8.9/utr-enabled-user-flows.spec.js +18 -0
- package/dist/tests/8.9/web-modeler-user-flows.spec.js +6 -2
- package/dist/utils/consoleApiHelpers.d.ts +2 -0
- package/dist/utils/consoleApiHelpers.js +50 -4
- package/dist/utils/resetSession.js +6 -1
- 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,
|
|
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;
|