@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.
@@ -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
- await (0, test_1.expect)(toggle).toBeVisible({ timeout: 30000 });
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
- await this.expandHubClusterGroup(clusterName);
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
- const present = await this.isHubClusterAppPresent(clusterName, 'Operate');
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
- const present = await this.isHubClusterAppPresent(clusterName, 'Tasklist');
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
- const present = await this.isHubClusterAppPresent(clusterName, 'Optimize');
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 (0, test_1.expect)(this.rbaHeading).toBeVisible({ timeout: 60000 });
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 (0, test_1.expect)(this.rbaHeading).toBeVisible({ timeout: 60000 });
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
- this.idComboBox = page.getByRole('combobox', { name: 'ID' });
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
- (0, _8_7_1.test)('User Roles User Flow', async ({ page, modelerHomePage, appsPage, consoleOrganizationsPage, settingsPage, loginPage, homePage, modelerCreatePage, }) => {
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
- await (0, test_1.expect)(page.getByText(`You do not have the permissions needed to deploy`)).toBeVisible({ timeout: 60000 });
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1284",
3
+ "version": "0.0.1286",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",