@camunda/e2e-test-suite 0.0.1080 → 0.0.1082
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.9/AppsPage.js +15 -1
- package/dist/pages/8.9/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.9/ClusterDetailsPage.js +21 -2
- package/dist/pages/8.9/ClusterPage.js +9 -0
- package/dist/pages/SM-8.9/NavigationPage.d.ts +5 -0
- package/dist/pages/SM-8.9/NavigationPage.js +37 -16
- package/dist/tests/8.9/connectors-user-flows.spec.js +2 -1
- package/dist/tests/SM-8.9/identity-user-flows.spec.js +1 -1
- package/package.json +1 -1
|
@@ -314,7 +314,21 @@ class AppsPage {
|
|
|
314
314
|
}
|
|
315
315
|
catch (error) {
|
|
316
316
|
console.warn(`Click attempt ${retries + 1} failed: ${error}`);
|
|
317
|
-
|
|
317
|
+
// The switcher renders its component list from data Console fetches
|
|
318
|
+
// once; when that render comes back empty the panel stays empty for
|
|
319
|
+
// the rest of the page's life, and re-clicking the switcher button
|
|
320
|
+
// only toggles that same empty panel shut and open again. Reload so
|
|
321
|
+
// Console refetches and rebuilds the list — the same recovery
|
|
322
|
+
// `clickTasklist` and `clickOptimize` already use — which also leaves
|
|
323
|
+
// the panel closed, so the next `clickCamundaApps()` reopens it
|
|
324
|
+
// instead of closing an already-open one.
|
|
325
|
+
if (!this.page.isClosed()) {
|
|
326
|
+
await this.page
|
|
327
|
+
.waitForLoadState('domcontentloaded', { timeout: 20000 })
|
|
328
|
+
.catch(() => { });
|
|
329
|
+
await this.page.reload().catch(() => { });
|
|
330
|
+
await (0, sleep_1.sleep)(20000);
|
|
331
|
+
}
|
|
318
332
|
}
|
|
319
333
|
}
|
|
320
334
|
throw new Error(`Failed to click the console link after ${maxRetries} attempts.`);
|
|
@@ -65,6 +65,7 @@ declare class ClusterDetailsPage {
|
|
|
65
65
|
performClusterUpdate(targetVersion?: string): Promise<void>;
|
|
66
66
|
clickCloseModalButton(): Promise<void>;
|
|
67
67
|
clickSettingsTab(): Promise<void>;
|
|
68
|
+
private waitForAuthorizationsSection;
|
|
68
69
|
enableAuthorizations(): Promise<void>;
|
|
69
70
|
disableAuthorizations(): Promise<void>;
|
|
70
71
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
@@ -241,9 +241,28 @@ class ClusterDetailsPage {
|
|
|
241
241
|
async clickSettingsTab() {
|
|
242
242
|
await this.settingsTab.click({ timeout: 60000 });
|
|
243
243
|
}
|
|
244
|
+
// Console remounts the cluster-detail tab bar while the cluster payload is
|
|
245
|
+
// still loading, which silently discards a Settings click made in that
|
|
246
|
+
// window: the nightly trace shows the clicked tab left on
|
|
247
|
+
// `aria-selected="false"` with Overview still selected, so the Authorizations
|
|
248
|
+
// section never mounts and a single-shot wait on the heading can never
|
|
249
|
+
// succeed no matter how long it waits. Reload and re-select Settings between
|
|
250
|
+
// attempts so the click lands on a settled tab bar.
|
|
251
|
+
async waitForAuthorizationsSection() {
|
|
252
|
+
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.authorizationsHeading, {
|
|
253
|
+
visibilityTimeout: 60000,
|
|
254
|
+
totalTimeout: 240000,
|
|
255
|
+
maxRetries: 4,
|
|
256
|
+
postAction: async () => {
|
|
257
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
258
|
+
await this.settingsTab.waitFor({ state: 'visible', timeout: 60000 });
|
|
259
|
+
await this.clickSettingsTab();
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
244
263
|
async enableAuthorizations() {
|
|
245
264
|
await (0, sleep_1.sleep)(5000);
|
|
246
|
-
await
|
|
265
|
+
await this.waitForAuthorizationsSection();
|
|
247
266
|
// Locate all elements with class .cds--toggle__text
|
|
248
267
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
249
268
|
// Check if toggleTextElements has at least 2 elements
|
|
@@ -281,7 +300,7 @@ class ClusterDetailsPage {
|
|
|
281
300
|
}
|
|
282
301
|
}
|
|
283
302
|
async disableAuthorizations() {
|
|
284
|
-
await
|
|
303
|
+
await this.waitForAuthorizationsSection();
|
|
285
304
|
// Locate all elements with class .cds--toggle__text
|
|
286
305
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
287
306
|
// Check if toggleTextElements has at least 2 elements
|
|
@@ -274,7 +274,16 @@ class ClusterPage {
|
|
|
274
274
|
}
|
|
275
275
|
async clickClusterLink(name) {
|
|
276
276
|
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.clusterLink(name), {
|
|
277
|
+
// A reload per retry needs more headroom than the 60s default, otherwise
|
|
278
|
+
// the loop runs out of budget before the last attempt.
|
|
279
|
+
totalTimeout: 180000,
|
|
277
280
|
postAction: async () => {
|
|
281
|
+
// When Console's cluster fetch comes back empty it renders the
|
|
282
|
+
// "Processes run on clusters" empty state and keeps it: clicking the
|
|
283
|
+
// Clusters banner is an in-app route change to the page that is
|
|
284
|
+
// already mounted, so nothing refetches and the row never appears.
|
|
285
|
+
// Reload first to force the list to be fetched again.
|
|
286
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
278
287
|
await this.clickClusterBanner();
|
|
279
288
|
},
|
|
280
289
|
});
|
|
@@ -32,10 +32,15 @@ declare class NavigationPage {
|
|
|
32
32
|
username: string;
|
|
33
33
|
password: string;
|
|
34
34
|
}): Promise<void>;
|
|
35
|
+
private goToWithoutAccess;
|
|
35
36
|
goToOptimizeWithoutAccess({ username, password, }?: {
|
|
36
37
|
username?: string;
|
|
37
38
|
password?: string;
|
|
38
39
|
}): Promise<void>;
|
|
40
|
+
goToModelerWithoutAccess({ username, password, }?: {
|
|
41
|
+
username?: string;
|
|
42
|
+
password?: string;
|
|
43
|
+
}): Promise<void>;
|
|
39
44
|
goToKeycloak(): Promise<void>;
|
|
40
45
|
goToOCIdentity(sleepTimeout?: number, credentials?: {
|
|
41
46
|
username: string;
|
|
@@ -35,6 +35,10 @@ const RETRIABLE_NAVIGATION_NETWORK_ERRORS = [
|
|
|
35
35
|
];
|
|
36
36
|
const NETWORK_OUTAGE_BUDGET = constants_1._1_MINUTE_IN_MS * 5;
|
|
37
37
|
const NETWORK_OUTAGE_POLL_INTERVAL = constants_1._1_SECOND_IN_MS * 10;
|
|
38
|
+
// Statuses the ingress returns on its own behalf while a component has no
|
|
39
|
+
// ready backend — the component is restarting, not denying the request.
|
|
40
|
+
const GATEWAY_ERROR_STATUSES = [502, 503, 504];
|
|
41
|
+
const COMPONENT_UNAVAILABLE_BUDGET = constants_1._1_MINUTE_IN_MS * 3;
|
|
38
42
|
class NavigationPage {
|
|
39
43
|
page;
|
|
40
44
|
modelerPageBanner;
|
|
@@ -88,11 +92,10 @@ class NavigationPage {
|
|
|
88
92
|
async gotoWithNetworkRetry(url, networkOutageDeadline) {
|
|
89
93
|
for (;;) {
|
|
90
94
|
try {
|
|
91
|
-
await this.page.goto(url, {
|
|
95
|
+
return await this.page.goto(url, {
|
|
92
96
|
timeout: 60000,
|
|
93
97
|
waitUntil: 'domcontentloaded',
|
|
94
98
|
});
|
|
95
|
-
return;
|
|
96
99
|
}
|
|
97
100
|
catch (error) {
|
|
98
101
|
if (!this.isRetriableNavigationNetworkError(error) ||
|
|
@@ -181,15 +184,37 @@ class NavigationPage {
|
|
|
181
184
|
async goToOptimize(sleepTimeout, credentials) {
|
|
182
185
|
await this.goTo(OPTIMIZE_CONTEXT_PATH, this.optimizePageBanner, sleepTimeout, credentials);
|
|
183
186
|
}
|
|
184
|
-
// `goTo` ends by asserting the app banner, so it cannot be used to reach
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
// `goTo` ends by asserting the app banner, so it cannot be used to reach a
|
|
188
|
+
// component the user has no role for: the denied page carries neither the
|
|
189
|
+
// banner nor a login form, so every retry burns a full 60s timeout.
|
|
190
|
+
// Navigate and log in only; the caller asserts what the denied page shows.
|
|
191
|
+
// A gateway error is the ingress speaking, not the component (run
|
|
192
|
+
// 33703764920: nginx served "503 Service Temporarily Unavailable" for the
|
|
193
|
+
// whole Optimize step while its pod was rescheduled), so keep re-navigating
|
|
194
|
+
// until the component itself answers.
|
|
195
|
+
async goToWithoutAccess(url, username, password) {
|
|
196
|
+
const deadline = Date.now() + COMPONENT_UNAVAILABLE_BUDGET;
|
|
197
|
+
await (0, test_1.expect)(async () => {
|
|
198
|
+
const response = await this.gotoWithNetworkRetry(url, deadline);
|
|
199
|
+
(0, test_1.expect)(GATEWAY_ERROR_STATUSES).not.toContain(response?.status());
|
|
200
|
+
}).toPass({
|
|
201
|
+
timeout: COMPONENT_UNAVAILABLE_BUDGET,
|
|
202
|
+
intervals: [5000, 10000, 15000],
|
|
203
|
+
});
|
|
191
204
|
await new LoginPage_1.LoginPage(this.page).login(username, password);
|
|
192
205
|
}
|
|
206
|
+
// Optimize denies a user without the Optimize role with a `text/plain` 403
|
|
207
|
+
// page.
|
|
208
|
+
async goToOptimizeWithoutAccess({ username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}) {
|
|
209
|
+
await this.goToWithoutAccess(OPTIMIZE_CONTEXT_PATH, username, password);
|
|
210
|
+
}
|
|
211
|
+
// Web Modeler denies a user without the Web Modeler role by bouncing back
|
|
212
|
+
// to Keycloak, sometimes after briefly rendering its banner, so `goTo`'s
|
|
213
|
+
// closing banner assertion either never passes or passes by winning that
|
|
214
|
+
// race.
|
|
215
|
+
async goToModelerWithoutAccess({ username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}) {
|
|
216
|
+
await this.goToWithoutAccess(MODELER_CONTEXT_PATH, username, password);
|
|
217
|
+
}
|
|
193
218
|
async goToKeycloak() {
|
|
194
219
|
const keycloakUrl = process.env.KEYCLOAK_URL
|
|
195
220
|
? `${process.env.KEYCLOAK_URL}/auth`
|
|
@@ -206,14 +231,10 @@ class NavigationPage {
|
|
|
206
231
|
async goToConsole(sleepTimeout, credentials) {
|
|
207
232
|
await this.goTo(CONSOLE_CONTEXT_PATH, this.consolePageBanner, sleepTimeout, credentials);
|
|
208
233
|
}
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
// Console's own "Something went wrong" error page, which has neither the
|
|
212
|
-
// banner nor a login form, and every retry burns a full 60s timeout.
|
|
213
|
-
// Navigate and log in only; the caller asserts what the denied page shows.
|
|
234
|
+
// Console denies a user without the Console role with its own
|
|
235
|
+
// "Something went wrong" error page.
|
|
214
236
|
async goToConsoleWithoutAccess({ username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}) {
|
|
215
|
-
await this.
|
|
216
|
-
await new LoginPage_1.LoginPage(this.page).login(username, password);
|
|
237
|
+
await this.goToWithoutAccess(CONSOLE_CONTEXT_PATH, username, password);
|
|
217
238
|
}
|
|
218
239
|
async goToManagementIdentity(sleepTimeout, credentials) {
|
|
219
240
|
await this.goTo(MANAGEMENT_IDENTITY_CONTEXT_PATH, this.managementIdentityPageBanner, sleepTimeout, credentials);
|
|
@@ -168,7 +168,8 @@ _8_9_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
|
|
|
168
168
|
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateProcessInstancePage, operateProcessInstancePage.completedIcon, 'Completed icon', 60000);
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
|
-
|
|
171
|
+
// Skipped due to bug #8500: https://github.com/camunda/connectors/issues/8500
|
|
172
|
+
_8_9_1.test.skip('Connector Secrets User Flow - GCP', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, clusterPage, clusterSecretsPage, }) => {
|
|
172
173
|
_8_9_1.test.slow();
|
|
173
174
|
const processName = 'REST_Connector_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
174
175
|
await _8_9_1.test.step('Navigate to Connectors Secrets', async () => {
|
|
@@ -74,7 +74,7 @@ SM_8_9_1.test.describe('Identity User Flow Tests', () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
await SM_8_9_1.test.step('Ensure Modeler is not Accessible', async () => {
|
|
77
|
-
await navigationPage.
|
|
77
|
+
await navigationPage.goToModelerWithoutAccess(credentials);
|
|
78
78
|
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).not.toBeVisible({
|
|
79
79
|
timeout: 60000,
|
|
80
80
|
});
|