@camunda/e2e-test-suite 0.0.1611 → 0.0.1613

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.
@@ -24,6 +24,6 @@ declare class HomePage {
24
24
  closeInformationDialog(): Promise<void>;
25
25
  organizationUuid(): string;
26
26
  clickOpenOrganizationButton(): Promise<void>;
27
- clickSkipCustomization(): Promise<void>;
27
+ clickSkipCustomization(timeout?: number): Promise<void>;
28
28
  }
29
29
  export { HomePage };
@@ -155,11 +155,9 @@ class HomePage {
155
155
  async clickOpenOrganizationButton() {
156
156
  await this.openOrganizationButton.click({ timeout: 60000 });
157
157
  }
158
- async clickSkipCustomization() {
158
+ async clickSkipCustomization(timeout = 30000) {
159
159
  try {
160
- await (0, test_1.expect)(this.buttonSkipCustomization).toBeVisible({
161
- timeout: 30000,
162
- });
160
+ await (0, test_1.expect)(this.buttonSkipCustomization).toBeVisible({ timeout });
163
161
  // Best-effort: dismiss any Carbon modal that has a named close button.
164
162
  await this.closeInformationDialog();
165
163
  // force: true bypasses the "pointer events intercepted by modal backdrop"
@@ -38,7 +38,10 @@ export declare function runMultipleProcesses(clusterName: string, page: Page, mo
38
38
  processName?: string;
39
39
  uploadFromFile?: string;
40
40
  }): Promise<void>;
41
- export declare function consoleSessionRecovery(page: Page, clusterUrl: string): () => Promise<void>;
41
+ export declare function consoleSessionRecovery(page: Page, clusterUrl: string, user?: {
42
+ username: string;
43
+ password: string;
44
+ }): () => Promise<void>;
42
45
  export declare function goBackWithRetry(page: Page, maxRetries?: number): Promise<void>;
43
46
  export declare function disableRBA(clusterName: string, homePage: HomePage, clusterPage: ClusterPage, clusterDetailsPage: ClusterDetailsPage, appsPage: AppsPage): Promise<void>;
44
47
  export declare function enableAuthorizations(clusterName: string, homePage: HomePage, clusterPage: ClusterPage, clusterDetailsPage: ClusterDetailsPage, appsPage: AppsPage): Promise<void>;
@@ -649,13 +649,13 @@ exports.runMultipleProcesses = runMultipleProcesses;
649
649
  // component-health poll below, after which reloading only ever re-renders the
650
650
  // login form. Re-authenticate as the console user and return to the page that
651
651
  // was being polled, so the health assertion carries on unchanged.
652
- function consoleSessionRecovery(page, clusterUrl) {
652
+ function consoleSessionRecovery(page, clusterUrl, user = {
653
+ username: process.env.C8_USERNAME,
654
+ password: process.env.C8_PASSWORD,
655
+ }) {
653
656
  return async () => {
654
657
  console.warn(`Console session lost while polling component health on ${clusterUrl}; re-authenticating.`);
655
- await loginWithRetry(page, new LoginPage_1.LoginPage(page), {
656
- username: process.env.C8_USERNAME,
657
- password: process.env.C8_PASSWORD,
658
- }, 0, 3);
658
+ await loginWithRetry(page, new LoginPage_1.LoginPage(page), user, 0, 3);
659
659
  await page.goto(clusterUrl, {
660
660
  waitUntil: 'domcontentloaded',
661
661
  timeout: 60000,
@@ -9,6 +9,7 @@ declare class ProcessApplicationPage {
9
9
  readonly cancelButton: Locator;
10
10
  readonly breadcrumb: Locator;
11
11
  readonly folderExplorer: Locator;
12
+ readonly loadErrorState: Locator;
12
13
  readonly createNewDropdown: Locator;
13
14
  readonly inlineNameInput: Locator;
14
15
  readonly defineStagesDialog: Locator;
@@ -33,6 +33,7 @@ class ProcessApplicationPage {
33
33
  cancelButton;
34
34
  breadcrumb;
35
35
  folderExplorer;
36
+ loadErrorState;
36
37
  createNewDropdown;
37
38
  inlineNameInput;
38
39
  // "Define stages" dialog, reached from the process application's "Connected
@@ -85,6 +86,11 @@ class ProcessApplicationPage {
85
86
  });
86
87
  this.breadcrumb = page.locator('[data-test="breadcrumb-process-application-menu"]');
87
88
  this.folderExplorer = page.getByRole('region', { name: 'Folder explorer' });
89
+ // camunda-hub app/components/page/Page.tsx ErrorState: the design-system
90
+ // EmptyState that replaces the page body when its fetches fail.
91
+ this.loadErrorState = page.getByRole('heading', {
92
+ name: /^(Something went wrong|Not found)$/,
93
+ });
88
94
  // The process application page renders more than one diagram-dropdown
89
95
  // (hub's own unit tests reach for it with getAllByTestId), so the dropdown
90
96
  // has to be resolved inside the folder explorer rather than page-wide.
@@ -133,30 +139,50 @@ class ProcessApplicationPage {
133
139
  // absolute deadline: reload() otherwise inherits the 120s navigationTimeout
134
140
  // and two slow reloads would eat the test budget during an outage. The last
135
141
  // attempt is a plain assertion so a persistent outage still fails loudly.
142
+ //
143
+ // Reload only when it can help: as soon as Hub has rendered its error view
144
+ // (Page.tsx ErrorState -- "Something went wrong" or a "Not found" that a
145
+ // just-created application can briefly answer), or when one load has sat
146
+ // on the spinner for `stallAfter`. A fixed per-attempt cut-off used to
147
+ // reload pages that were still loading, so under load a ~40s render was
148
+ // restarted twice and the last attempt got ~37s -- not enough for a third
149
+ // render from scratch.
136
150
  async waitWithReload(target, timeout, attempts = 3) {
137
151
  const deadline = Date.now() + timeout;
138
- const perAttempt = Math.ceil(timeout / attempts);
152
+ const stallAfter = Math.ceil(timeout / 2);
139
153
  const remaining = () => Math.max(deadline - Date.now(), 1000);
140
- for (let attempt = 1; attempt < attempts; attempt++) {
154
+ const settled = target.or(this.loadErrorState).first();
155
+ let reloadsLeft = attempts - 1;
156
+ let loadStartedAt = Date.now();
157
+ while (reloadsLeft > 0 && deadline - Date.now() > 1000) {
141
158
  // Hub can drop its session on a navigation and bounce through
142
159
  // /login-callback to the identity provider, where neither reloading nor
143
160
  // waiting renders the content -- re-authenticate first.
144
- await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
145
- await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page);
146
- const ready = await target
147
- .waitFor({ state: 'visible', timeout: Math.min(perAttempt, remaining()) })
148
- .then(() => true)
149
- .catch(() => false);
150
- if (ready) {
161
+ const recoveredLogin = await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
162
+ const restoredSession = await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page);
163
+ if (recoveredLogin || restoredSession) {
164
+ loadStartedAt = Date.now();
165
+ }
166
+ // Short slices so a session dropped mid-wait is still recovered above.
167
+ await settled
168
+ .waitFor({ state: 'visible', timeout: Math.min(15000, remaining()) })
169
+ .catch(() => undefined);
170
+ if (await target.isVisible()) {
151
171
  return;
152
172
  }
153
- console.warn(`Process application content did not render (attempt ${attempt}/${attempts}); reloading.`);
173
+ const errored = await this.loadErrorState.isVisible();
174
+ if (!errored && Date.now() - loadStartedAt < stallAfter) {
175
+ continue;
176
+ }
177
+ reloadsLeft--;
178
+ console.warn(`Process application content did not render (${errored ? 'Hub showed its error view' : 'still loading'}); reloading, ${reloadsLeft} reload(s) left.`);
154
179
  // 'domcontentloaded': a reload that bounces through /login-callback
155
180
  // never reaches "load", and recovery above needs the page back.
156
181
  await this.page.reload({
157
182
  waitUntil: 'domcontentloaded',
158
183
  timeout: remaining(),
159
184
  });
185
+ loadStartedAt = Date.now();
160
186
  }
161
187
  await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
162
188
  await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page);
@@ -31,8 +31,12 @@ const OVERRIDE_REGION_LABEL = 'Finland, Europe (europe-north1)';
31
31
  * Isolation: every case creates its own source cluster with a randomised name and
32
32
  * deletes every cluster it created in teardown - a leaked cluster consumes a
33
33
  * reservation and would make the reservation case pass for the wrong reason.
34
+ *
35
+ * Parallel: no case reads state another writes (randomised names, per-test
36
+ * teardown), and each spends 10-14 minutes on cluster health polls, so serial
37
+ * mode stacked them into the longest chain of the full-suite pass.
34
38
  */
35
- _8_10_1.test.describe.configure({ mode: 'serial' });
39
+ _8_10_1.test.describe.configure({ mode: 'parallel' });
36
40
  _8_10_1.test.describe('Console cluster duplication', () => {
37
41
  // Everything this spec creates is tracked here so teardown runs even on failure.
38
42
  const createdClusterNames = [];
@@ -71,7 +71,7 @@ _8_10_1.test.describe('Console User Flow Tests @tasklistV2', () => {
71
71
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'completed icon in Operate', 60000);
72
72
  });
73
73
  });
74
- (0, _8_10_1.test)('Create Cluster', async ({ homePage, clusterPage }) => {
74
+ (0, _8_10_1.test)('Create Cluster', async ({ page, homePage, clusterPage }) => {
75
75
  _8_10_1.test.slow();
76
76
  // createCluster pre-deletes every cluster carrying this name, so a fixed
77
77
  // name let concurrent runs of this test (--repeat-each, retries racing a
@@ -83,7 +83,9 @@ _8_10_1.test.describe('Console User Flow Tests @tasklistV2', () => {
83
83
  await clusterPage.createCluster(newClusterName);
84
84
  });
85
85
  await _8_10_1.test.step('Assert cluster is healthy', async () => {
86
- await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
86
+ // Without recovery a dropped Hub session left the poll reloading the
87
+ // login form for its whole 25-minute budget.
88
+ await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000, (0, UtilitiesPage_1.consoleSessionRecovery)(page, page.url(), testUser));
87
89
  });
88
90
  await _8_10_1.test.step('Delete the created cluster', async () => {
89
91
  await homePage.clickClusters();
@@ -227,7 +227,9 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
227
227
  });
228
228
  await _8_10_1.test.step('Create Cross Component Project Folder', async () => {
229
229
  await (0, UtilitiesPage_1.withSessionRecovery)(page, loginPage, appsPage, modelerHomePage.modelerPageBanner, user, Math.min((testInfo.workerIndex + 1) * 5000, 60000), async () => {
230
- await homePage.clickSkipCustomization();
230
+ // The Modeler banner is already visible here, so a customization
231
+ // prompt would have rendered with it; Hub-only orgs never show one.
232
+ await homePage.clickSkipCustomization(3000);
231
233
  // Reverted the "only User 1 creates" leader-election attempt: it made
232
234
  // things worse, not better. Run 34191243820 showed followers still
233
235
  // timing out waiting for the folder even well after User 1 (2.9min)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1611",
3
+ "version": "0.0.1613",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",