@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.
|
|
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
|
-
|
|
113
|
-
|
|
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
|
}
|