@camunda/e2e-test-suite 0.0.1081 → 0.0.1083
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.
|
@@ -70,9 +70,15 @@ class OCTenantPage {
|
|
|
70
70
|
.getByRole('button', { name: 'Close' });
|
|
71
71
|
this.rolesTab = page.getByRole('tab', { name: 'Roles' });
|
|
72
72
|
this.assignRoleButton = page.getByRole('button', { name: 'Assign role' });
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// The design-system Admin shell renders the Assign role modal's search
|
|
74
|
+
// field as a cmdk command input (role="combobox", type="text"), so the
|
|
75
|
+
// Carbon searchbox role no longer resolves - match the new field by its
|
|
76
|
+
// placeholder and keep the legacy role match as a fallback, the same
|
|
77
|
+
// dual-shell pattern the table filter locator below uses.
|
|
78
|
+
this.roleIdSearchBox = page
|
|
79
|
+
.getByPlaceholder('Search by role ID')
|
|
80
|
+
.or(page.getByRole('searchbox', { name: 'Search by Role ID' }))
|
|
81
|
+
.first();
|
|
76
82
|
this.assignRoleModal = page.getByLabel('Assign role');
|
|
77
83
|
this.assignRoleSubButton = this.assignRoleModal.getByRole('button', {
|
|
78
84
|
name: 'Assign role',
|
|
@@ -267,13 +273,20 @@ class OCTenantPage {
|
|
|
267
273
|
await this.assignRoleButton.click();
|
|
268
274
|
}
|
|
269
275
|
async clickRoleIdSearchBox() {
|
|
270
|
-
await this.roleIdSearchBox.click();
|
|
276
|
+
await this.roleIdSearchBox.click({ timeout: 60000 });
|
|
271
277
|
}
|
|
272
278
|
async fillRoleIdSearchBox(roleId, roleIdentifier) {
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
279
|
+
// The design-system shell portals the matches into a listbox rendered as
|
|
280
|
+
// a sibling of the dialog, so they cannot be scoped to the modal. The
|
|
281
|
+
// legacy Carbon list stays inside it and must remain scoped: the tenant's
|
|
282
|
+
// own roles table sits behind the modal and carries the same role text, so
|
|
283
|
+
// an unscoped match resolves to that row and its click is intercepted by
|
|
284
|
+
// the modal overlay.
|
|
285
|
+
const result = this.page
|
|
286
|
+
.getByRole('option')
|
|
287
|
+
.filter({ hasText: roleIdentifier })
|
|
288
|
+
.or(this.assignRoleModal.getByText(roleIdentifier))
|
|
289
|
+
.first();
|
|
277
290
|
// The dropdown matches against the roles fetched with an empty search
|
|
278
291
|
// filter, so clearing the box re-triggers that fetch before retyping.
|
|
279
292
|
await (0, test_1.expect)(async () => {
|
|
@@ -62,13 +62,16 @@ class PlayPage {
|
|
|
62
62
|
})
|
|
63
63
|
.getByRole('button', { name: 'Delete', exact: true });
|
|
64
64
|
// After saving a scenario, the Scenarios panel is in detail-view mode.
|
|
65
|
-
// To get back to the list (where "Run all
|
|
66
|
-
//
|
|
67
|
-
//
|
|
65
|
+
// To get back to the list (where "Run all test cases" lives in
|
|
66
|
+
// TestCaseTopBar), click the ghost button at the top of the detail
|
|
67
|
+
// panel — see TestCaseDetail.tsx Header.
|
|
68
68
|
// This replaces the legacy bottom-bar `(View all)` link that used to
|
|
69
69
|
// close the saved-scenario view.
|
|
70
|
+
// camunda-hub cfaea03 (#28259, "rename play package to test-studio")
|
|
71
|
+
// relabelled it "Back to all test cases"; the anchored regex below must
|
|
72
|
+
// keep the optional "all " or it matches nothing on current builds.
|
|
70
73
|
this.viewAllScenariosButton = page.getByRole('button', {
|
|
71
|
-
name: /^Back to (scenarios|test cases)$/i,
|
|
74
|
+
name: /^Back to (all )?(scenarios|test cases)$/i,
|
|
72
75
|
});
|
|
73
76
|
this.getScenarioRowByName = (scenarioName) => page.locator('tr', { hasText: scenarioName });
|
|
74
77
|
this.getScenarioRow = (scenarioName) => page.locator('tr', { hasText: scenarioName });
|
|
@@ -128,14 +131,19 @@ class PlayPage {
|
|
|
128
131
|
name: /^(Scenarios|Test cases)$/i,
|
|
129
132
|
});
|
|
130
133
|
const backToScenariosButton = scenariosRegion.getByRole('button', {
|
|
131
|
-
name: /^Back to (scenarios|test cases)$/i,
|
|
134
|
+
name: /^Back to (all )?(scenarios|test cases)$/i,
|
|
132
135
|
});
|
|
133
|
-
|
|
136
|
+
// TestPanel.tsx labels this `Close ${title} panel`, so match on the
|
|
137
|
+
// substring rather than the exact accessible name.
|
|
138
|
+
const closeScenariosButton = scenariosRegion
|
|
139
|
+
.getByRole('button', {
|
|
134
140
|
name: 'Close',
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
// the
|
|
141
|
+
})
|
|
142
|
+
.first();
|
|
143
|
+
// When "Configure test case" is clicked with a prior test case already
|
|
144
|
+
// saved, the Test Studio UI opens test-case DETAILS (with "Back to all test
|
|
145
|
+
// cases") instead of the input panel. Close the Test cases panel to reveal
|
|
146
|
+
// the canvas start controls.
|
|
139
147
|
const isDetailsView = await backToScenariosButton
|
|
140
148
|
.waitFor({ state: 'visible', timeout: 3000 })
|
|
141
149
|
.then(() => true)
|
|
@@ -245,8 +253,8 @@ class PlayPage {
|
|
|
245
253
|
}
|
|
246
254
|
async clickViewAllScenariosButton() {
|
|
247
255
|
// This method's job is to reach the scenario LIST. Saving can already leave
|
|
248
|
-
// Play there (see assertSavedScenarioNameInPanel), and the "Back to
|
|
249
|
-
// cases" control exists only inside a scenario's detail view -- so on the
|
|
256
|
+
// Play there (see assertSavedScenarioNameInPanel), and the "Back to all
|
|
257
|
+
// test cases" control exists only inside a scenario's detail view -- so on the
|
|
250
258
|
// list it never renders and waiting for it just burns the retry budget.
|
|
251
259
|
// "Run all test cases" lives on the list and is what the caller needs next,
|
|
252
260
|
// so treat it as proof the destination is already reached.
|
|
@@ -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);
|
|
@@ -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
|
});
|