@camunda/e2e-test-suite 0.0.1284 → 0.0.1286
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.7/AppsPage.d.ts +1 -0
- package/dist/pages/8.7/AppsPage.js +60 -8
- package/dist/pages/8.7/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.7/ClusterDetailsPage.js +48 -2
- package/dist/pages/8.7/ConsoleOrganizationPage.js +9 -1
- package/dist/tests/8.7/web-modeler-user-flows.spec.js +12 -2
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ declare class AppsPage {
|
|
|
32
32
|
clickOptimize(clusterName: string): Promise<void>;
|
|
33
33
|
clickCamundaApps(maxRetries?: number): Promise<void>;
|
|
34
34
|
clickConsoleLink(): Promise<void>;
|
|
35
|
+
private assertHubClusterAppPresence;
|
|
35
36
|
assertOperatePresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
36
37
|
assertTasklistPresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
37
38
|
assertOptimizePresent(clusterName: string, shouldBeVisible?: boolean): Promise<void>;
|
|
@@ -9,6 +9,19 @@ function escapeRegExp(value) {
|
|
|
9
9
|
function isHubUrl(url) {
|
|
10
10
|
return new URL(url).hostname.startsWith('hub.');
|
|
11
11
|
}
|
|
12
|
+
// Thrown by expandHubClusterGroup only when the sidebar shell has rendered but
|
|
13
|
+
// this cluster's own group is absent from it -- Hub's server-side Environments
|
|
14
|
+
// filtering hides a cluster a user cannot access, so the group never appears
|
|
15
|
+
// (e.g. a downgraded Analyst on the "no workspaces yet" home). This is distinct
|
|
16
|
+
// from a navigation, sidebar-readiness, or toggle-interaction failure, which
|
|
17
|
+
// stay as their original errors so callers never mistake a broken flow for a
|
|
18
|
+
// legitimately absent app (see isHubClusterAppPresent).
|
|
19
|
+
class HubClusterGroupNotFoundError extends Error {
|
|
20
|
+
constructor(clusterName) {
|
|
21
|
+
super(`Hub sidebar has no group for cluster "${clusterName}"`);
|
|
22
|
+
this.name = 'HubClusterGroupNotFoundError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
12
25
|
class AppsPage {
|
|
13
26
|
page;
|
|
14
27
|
modelerLink;
|
|
@@ -119,9 +132,26 @@ class AppsPage {
|
|
|
119
132
|
.then(() => true)
|
|
120
133
|
.catch(() => false));
|
|
121
134
|
if (!sidebarReady) {
|
|
135
|
+
// clickModeler() and this sidebar-shell assertion stay as their own
|
|
136
|
+
// errors: a navigation failure or a sidebar that never renders is a
|
|
137
|
+
// broken flow, not an absent cluster group.
|
|
122
138
|
await this.clickModeler();
|
|
123
139
|
await (0, test_1.expect)(this.hubSidebar).toBeVisible({ timeout: 30000 });
|
|
124
|
-
|
|
140
|
+
// The shell is up now. If this cluster's toggle still isn't in it, the
|
|
141
|
+
// group is genuinely absent for this user (see HubClusterGroupNotFoundError)
|
|
142
|
+
// rather than slow -- surface that as the typed signal so a negative
|
|
143
|
+
// authorization check can conclude "app absent" while every other failure
|
|
144
|
+
// above keeps propagating.
|
|
145
|
+
try {
|
|
146
|
+
await toggle.waitFor({ state: 'visible', timeout: 30000 });
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (!(error instanceof Error && error.name === 'TimeoutError')) {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
await (0, test_1.expect)(this.hubSidebar).toBeVisible({ timeout: 3000 });
|
|
153
|
+
throw new HubClusterGroupNotFoundError(clusterName);
|
|
154
|
+
}
|
|
125
155
|
}
|
|
126
156
|
// camunda-hub's own nav-shell-ds.spec.saas.tsx documents the actual
|
|
127
157
|
// shape: "the design system splits the group row into a label control
|
|
@@ -264,7 +294,24 @@ class AppsPage {
|
|
|
264
294
|
// which on Hub renders as the link never appearing in the expanded group
|
|
265
295
|
// rather than a disabled control -- so this never throws on a miss.
|
|
266
296
|
async isHubClusterAppPresent(clusterName, appName) {
|
|
267
|
-
|
|
297
|
+
try {
|
|
298
|
+
await this.expandHubClusterGroup(clusterName);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
// Only a confirmed-absent cluster group counts as "app not present": Hub
|
|
302
|
+
// filters Environments server-side (see the catch block in
|
|
303
|
+
// clickHubClusterApp), and a downgraded Analyst re-logging in even lands
|
|
304
|
+
// on the workspace-agnostic "no workspaces yet" home whose rail shows no
|
|
305
|
+
// cluster group to expand (confirmed from a nightly trace). That absence
|
|
306
|
+
// is the authorization-negative outcome this method reports -- with no
|
|
307
|
+
// group, the app link cannot be present. Every other failure (navigation,
|
|
308
|
+
// sidebar readiness, toggle click) is a real UI/test problem and must
|
|
309
|
+
// propagate, so a negative check can never pass on a broken flow.
|
|
310
|
+
if (error instanceof HubClusterGroupNotFoundError) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
268
315
|
// Same collapsed-sidebar popover / environment-row fallbacks as
|
|
269
316
|
// clickHubClusterApp -- see hubAppLink.
|
|
270
317
|
const appLink = this.hubAppLink(clusterName, appName);
|
|
@@ -549,14 +596,21 @@ class AppsPage {
|
|
|
549
596
|
}
|
|
550
597
|
throw new Error(`Failed to click the console link after ${maxRetries} attempts.`);
|
|
551
598
|
}
|
|
599
|
+
// Asserts a cluster app's presence in the Hub sidebar. isHubClusterAppPresent
|
|
600
|
+
// reports the app absent (rather than throwing) when the cluster group is not
|
|
601
|
+
// in the sidebar, so both a present and an absent expectation read as a plain
|
|
602
|
+
// boolean here.
|
|
603
|
+
async assertHubClusterAppPresence(clusterName, appName, shouldBeVisible) {
|
|
604
|
+
const present = await this.isHubClusterAppPresent(clusterName, appName);
|
|
605
|
+
(0, test_1.expect)(present).toBe(shouldBeVisible);
|
|
606
|
+
}
|
|
552
607
|
// Asserts whether the current user's role grants them Operate access.
|
|
553
608
|
// clusterName is required on Hub (see isHubClusterAppPresent) but unused on
|
|
554
609
|
// the legacy Console app-switcher, which has no per-cluster concept here --
|
|
555
610
|
// the caller's single active cluster is implicit in operateLink/operateButton.
|
|
556
611
|
async assertOperatePresent(clusterName, shouldBeVisible = true) {
|
|
557
612
|
if (isHubUrl(this.page.url())) {
|
|
558
|
-
|
|
559
|
-
(0, test_1.expect)(present).toBe(shouldBeVisible);
|
|
613
|
+
await this.assertHubClusterAppPresence(clusterName, 'Operate', shouldBeVisible);
|
|
560
614
|
return;
|
|
561
615
|
}
|
|
562
616
|
const maxRetries = 3;
|
|
@@ -590,8 +644,7 @@ class AppsPage {
|
|
|
590
644
|
// See assertOperatePresent for the clusterName/Hub note.
|
|
591
645
|
async assertTasklistPresent(clusterName, shouldBeVisible = true) {
|
|
592
646
|
if (isHubUrl(this.page.url())) {
|
|
593
|
-
|
|
594
|
-
(0, test_1.expect)(present).toBe(shouldBeVisible);
|
|
647
|
+
await this.assertHubClusterAppPresence(clusterName, 'Tasklist', shouldBeVisible);
|
|
595
648
|
return;
|
|
596
649
|
}
|
|
597
650
|
const maxRetries = 3;
|
|
@@ -625,8 +678,7 @@ class AppsPage {
|
|
|
625
678
|
// See assertOperatePresent for the clusterName/Hub note.
|
|
626
679
|
async assertOptimizePresent(clusterName, shouldBeVisible = true) {
|
|
627
680
|
if (isHubUrl(this.page.url())) {
|
|
628
|
-
|
|
629
|
-
(0, test_1.expect)(present).toBe(shouldBeVisible);
|
|
681
|
+
await this.assertHubClusterAppPresence(clusterName, 'Optimize', shouldBeVisible);
|
|
630
682
|
return;
|
|
631
683
|
}
|
|
632
684
|
const maxRetries = 3;
|
|
@@ -63,6 +63,7 @@ declare class ClusterDetailsPage {
|
|
|
63
63
|
clickCloseModalButton(): Promise<void>;
|
|
64
64
|
clickSettingsTab(): Promise<void>;
|
|
65
65
|
private isRBAToggleInState;
|
|
66
|
+
private waitForRbaSettings;
|
|
66
67
|
enableRBA(): Promise<void>;
|
|
67
68
|
disableRBA(): Promise<void>;
|
|
68
69
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
@@ -243,8 +243,54 @@ class ClusterDetailsPage {
|
|
|
243
243
|
const checked = await this.rbaToggle.getAttribute('aria-checked');
|
|
244
244
|
return checked !== null && (checked === 'true') === enabled;
|
|
245
245
|
}
|
|
246
|
+
// The Hub cluster-details Settings panel intermittently renders blank -- the
|
|
247
|
+
// Settings tab is selected but its body (the "Resource-based authorizations"
|
|
248
|
+
// section) never populates; a single wait then times out on a heading a
|
|
249
|
+
// reload would have shown. Reload and re-open the Settings tab between
|
|
250
|
+
// attempts -- the same recovery clickUsersTab/assertComponentsHealth already
|
|
251
|
+
// use -- so the toggle work below always runs against a rendered panel.
|
|
252
|
+
//
|
|
253
|
+
// The recovery only makes sense while we are still on this cluster's details
|
|
254
|
+
// route. A mid-flow re-auth can bounce the page to another view, where a
|
|
255
|
+
// reload just reloads that route and clickSettingsTab() cannot reopen this
|
|
256
|
+
// cluster's Settings; retrying blind would loop until timeout. Detect that
|
|
257
|
+
// bounce (the pathname changed) and surface it promptly instead. Transient
|
|
258
|
+
// reload/tab failures while still on-route (e.g. ERR_CONNECTION_REFUSED as
|
|
259
|
+
// the upstream restarts) are still ridden out across attempts.
|
|
260
|
+
async waitForRbaSettings() {
|
|
261
|
+
const clusterDetailsPath = new URL(this.page.url()).pathname;
|
|
262
|
+
const maxRetries = 4;
|
|
263
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
264
|
+
try {
|
|
265
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
266
|
+
await this.rbaHeading.waitFor({ state: 'visible', timeout: 60000 });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
if (attempt === maxRetries) {
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
const currentPath = new URL(this.page.url()).pathname;
|
|
274
|
+
if (currentPath !== clusterDetailsPath) {
|
|
275
|
+
throw new Error(`Left cluster-details route (${clusterDetailsPath}) before the RBA ` +
|
|
276
|
+
`Settings panel rendered; now at ${currentPath}. Aborting the ` +
|
|
277
|
+
`blank-panel recovery so the navigation failure surfaces.`);
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
await this.page.reload();
|
|
281
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
282
|
+
await this.clickSettingsTab();
|
|
283
|
+
}
|
|
284
|
+
catch (recoveryErr) {
|
|
285
|
+
console.warn(`waitForRbaSettings: recovery (attempt ${attempt}) threw and will ` +
|
|
286
|
+
`be retried:`, recoveryErr instanceof Error ? recoveryErr.message : recoveryErr);
|
|
287
|
+
}
|
|
288
|
+
await this.page.waitForTimeout(500);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
246
292
|
async enableRBA() {
|
|
247
|
-
await
|
|
293
|
+
await this.waitForRbaSettings();
|
|
248
294
|
await (0, test_1.expect)(this.rbaToggle).toBeVisible({ timeout: 30000 });
|
|
249
295
|
if (await this.isRBAToggleInState(true)) {
|
|
250
296
|
return;
|
|
@@ -257,7 +303,7 @@ class ClusterDetailsPage {
|
|
|
257
303
|
});
|
|
258
304
|
}
|
|
259
305
|
async disableRBA() {
|
|
260
|
-
await
|
|
306
|
+
await this.waitForRbaSettings();
|
|
261
307
|
await (0, test_1.expect)(this.rbaToggle).toBeVisible({ timeout: 30000 });
|
|
262
308
|
if (await this.isRBAToggleInState(false)) {
|
|
263
309
|
return;
|
|
@@ -105,7 +105,15 @@ class ConsoleOrganizationPage {
|
|
|
105
105
|
this.createButton = this.createResourceAuthorizationDialog.getByRole('button', {
|
|
106
106
|
name: 'Create',
|
|
107
107
|
});
|
|
108
|
-
|
|
108
|
+
// Hub's design-system "Create resource authorizations" dialog renders the
|
|
109
|
+
// process/decision ID field on its "Enter an ID" step as a free-text input
|
|
110
|
+
// (label "ID", placeholder "Enter ID"), not the type-ahead combobox the
|
|
111
|
+
// legacy Console used -- confirmed from the nightly trace's step-2 DOM. Match
|
|
112
|
+
// the textbox by its "ID" label, keeping the combobox as a fallback for any
|
|
113
|
+
// surface still on the old control.
|
|
114
|
+
this.idComboBox = page
|
|
115
|
+
.getByRole('textbox', { name: 'ID' })
|
|
116
|
+
.or(page.getByRole('combobox', { name: 'ID' }));
|
|
109
117
|
this.readPermissionCheckbox = page.getByRole('checkbox', {
|
|
110
118
|
name: 'Read definition',
|
|
111
119
|
});
|
|
@@ -95,7 +95,8 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
|
-
|
|
98
|
+
// Skipped due to bug #64384: https://github.com/camunda/camunda/issues/64384
|
|
99
|
+
_8_7_1.test.skip('User Roles User Flow', async ({ page, modelerHomePage, appsPage, consoleOrganizationsPage, settingsPage, loginPage, homePage, modelerCreatePage, }) => {
|
|
99
100
|
_8_7_1.test.slow();
|
|
100
101
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
101
102
|
const processName = 'User Roles User Flow' + randomString;
|
|
@@ -160,7 +161,16 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
160
161
|
await modelerCreatePage.clickAppendEndEventButton();
|
|
161
162
|
await (0, sleep_1.sleep)(20000);
|
|
162
163
|
await modelerCreatePage.clickSecondDeployButton();
|
|
163
|
-
|
|
164
|
+
// Hub's Environments migration blocks an Analyst's deploy differently from
|
|
165
|
+
// the legacy modeler: the downgrade leaves the user's workspace with no
|
|
166
|
+
// deployment environment assigned, so the Deploy dialog shows "No
|
|
167
|
+
// deployment environments available" (Deploy disabled) instead of the old
|
|
168
|
+
// "You do not have the permissions needed to deploy" message -- confirmed
|
|
169
|
+
// from the nightly trace. Accept either wording so the check still proves
|
|
170
|
+
// the Analyst cannot deploy across both surfaces.
|
|
171
|
+
await (0, test_1.expect)(page
|
|
172
|
+
.getByText('No deployment environments available')
|
|
173
|
+
.or(page.getByText('You do not have the permissions needed to deploy'))).toBeVisible({ timeout: 60000 });
|
|
164
174
|
await modelerCreatePage.clickCancelButton();
|
|
165
175
|
await (0, test_1.expect)(settingsPage.openSettingsButton).toBeVisible({
|
|
166
176
|
timeout: 60000,
|