@camunda/e2e-test-suite 0.0.1286 → 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.
- 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.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/tests/SM-8.7/topology-orchestration-smoke.spec.js +11 -0
- 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
|
+
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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` +
|