@camunda/e2e-test-suite 0.0.874 → 0.0.876

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.
@@ -10,6 +10,8 @@ declare class NavigationPage {
10
10
  readonly keyboardPageBanner: Locator;
11
11
  readonly managementIdentityPageBanner: Locator;
12
12
  constructor(page: Page);
13
+ private isRetriableNavigationNetworkError;
14
+ private gotoWithNetworkRetry;
13
15
  goTo(url: string, banner: Locator, sleepTimeout?: number, { username, password, }?: {
14
16
  username?: string;
15
17
  password?: string;
@@ -15,6 +15,22 @@ const CONSOLE_CONTEXT_PATH = process.env.CONSOLE_CONTEXT_PATH ?? '/console';
15
15
  const MANAGEMENT_IDENTITY_CONTEXT_PATH = process.env.MANAGEMENT_IDENTITY_CONTEXT_PATH ?? '/identity';
16
16
  const IDENTITY_FIRSTUSER_USERNAME = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_USERNAME || 'demo';
17
17
  const IDENTITY_FIRSTUSER_PASSWORD = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD || 'demo';
18
+ // Navigation errors raised by Chromium's network stack rather than by the app:
19
+ // the cluster host stopped resolving or stopped accepting connections. These
20
+ // are not login problems, so retrying the login flow does nothing for them —
21
+ // they need wall-clock waiting instead (see `gotoWithNetworkRetry`).
22
+ const RETRIABLE_NAVIGATION_NETWORK_ERRORS = [
23
+ 'ERR_NAME_NOT_RESOLVED',
24
+ 'ERR_CONNECTION_REFUSED',
25
+ 'ERR_CONNECTION_RESET',
26
+ 'ERR_CONNECTION_CLOSED',
27
+ 'ERR_CONNECTION_TIMED_OUT',
28
+ 'ERR_ADDRESS_UNREACHABLE',
29
+ 'ERR_EMPTY_RESPONSE',
30
+ 'ERR_SOCKET_NOT_CONNECTED',
31
+ ];
32
+ const NETWORK_OUTAGE_BUDGET = constants_1._1_MINUTE_IN_MS * 5;
33
+ const NETWORK_OUTAGE_POLL_INTERVAL = constants_1._1_SECOND_IN_MS * 10;
18
34
  class NavigationPage {
19
35
  page;
20
36
  modelerPageBanner;
@@ -53,8 +69,40 @@ class NavigationPage {
53
69
  .first();
54
70
  this.keyboardPageBanner = page.locator('#keycloak-bg');
55
71
  }
72
+ isRetriableNavigationNetworkError(error) {
73
+ const message = error instanceof Error ? error.message : String(error);
74
+ return RETRIABLE_NAVIGATION_NETWORK_ERRORS.some((code) => message.includes(code));
75
+ }
76
+ // Navigate, absorbing DNS/connection-level failures until
77
+ // `networkOutageDeadline` passes. The nightly cluster's ingress hostname can
78
+ // stop resolving mid-run (run 30329717154: every navigation to /modeler died
79
+ // instantly — 5 attempts in ~200ms — while the same suite had passed against
80
+ // the same host 35 minutes earlier). The login retry loop below fails fast on
81
+ // each of its attempts, so it burns its whole budget inside such a window.
82
+ // Waiting on a shared deadline lets the host come back while a sustained
83
+ // outage still exhausts the budget and surfaces as a failed login.
84
+ async gotoWithNetworkRetry(url, networkOutageDeadline) {
85
+ for (;;) {
86
+ try {
87
+ await this.page.goto(url, {
88
+ timeout: 60000,
89
+ waitUntil: 'domcontentloaded',
90
+ });
91
+ return;
92
+ }
93
+ catch (error) {
94
+ if (!this.isRetriableNavigationNetworkError(error) ||
95
+ Date.now() >= networkOutageDeadline) {
96
+ throw error;
97
+ }
98
+ console.warn(`[${new Date().toISOString()}] [NavigationPage] ${url} unreachable at the network level (${error instanceof Error ? error.message : error}); waiting for the cluster host to come back`);
99
+ await (0, sleep_1.sleep)(NETWORK_OUTAGE_POLL_INTERVAL);
100
+ }
101
+ }
102
+ }
56
103
  async goTo(url, banner, sleepTimeout, { username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}, maxRetries = 5) {
57
104
  const startTime = Date.now();
105
+ const networkOutageDeadline = startTime + NETWORK_OUTAGE_BUDGET;
58
106
  let timeout = constants_1._1_SECOND_IN_MS * 10;
59
107
  if (url === '/modeler') {
60
108
  timeout = constants_1._1_SECOND_IN_MS * 20;
@@ -64,10 +112,7 @@ class NavigationPage {
64
112
  if (sleepTimeout) {
65
113
  await (0, sleep_1.sleep)(sleepTimeout);
66
114
  }
67
- await this.page.goto(url, {
68
- timeout: 60000,
69
- waitUntil: 'domcontentloaded',
70
- });
115
+ await this.gotoWithNetworkRetry(url, networkOutageDeadline);
71
116
  const loginPage = new LoginPage_1.LoginPage(this.page);
72
117
  const loginEntryPoint = banner
73
118
  .or(loginPage.usernameInput)
@@ -109,8 +154,13 @@ class NavigationPage {
109
154
  }
110
155
  }
111
156
  else {
112
- console.error(`[${now.toISOString()}] (+${elapsed}ms) [NavigationPage] Login (${url}) failed after ${maxRetries} attempts`);
113
- throw new Error(`Login (${url}) failed after ${maxRetries} attempts`);
157
+ // Carry the last underlying failure into the message: without it the
158
+ // report only shows "failed after N attempts", and the screenshot of
159
+ // a failed navigation is a blank page, so the real cause (a Keycloak
160
+ // 5xx vs. net::ERR_NAME_NOT_RESOLVED) is invisible in triage.
161
+ const cause = error instanceof Error ? error.message : String(error);
162
+ console.error(`[${now.toISOString()}] (+${elapsed}ms) [NavigationPage] Login (${url}) failed after ${maxRetries} attempts: ${cause}`);
163
+ throw new Error(`Login (${url}) failed after ${maxRetries} attempts: ${cause}`);
114
164
  }
115
165
  }
116
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.874",
3
+ "version": "0.0.876",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",