@camunda/e2e-test-suite 0.0.1040 → 0.0.1042
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.10/UtilitiesPage.js +14 -1
- package/dist/pages/8.7/UtilitiesPage.js +21 -2
- package/dist/pages/8.8/UtilitiesPage.js +14 -2
- package/dist/pages/8.9/UtilitiesPage.js +14 -1
- package/dist/pages/SM-8.10/Authorization.d.ts +2 -0
- package/dist/pages/SM-8.10/Authorization.js +80 -6
- package/dist/pages/SM-8.10/OCIdentityMappingRulesPage.js +8 -3
- package/dist/pages/SM-8.10/OCIdentityRolesPage.js +10 -0
- package/dist/tests/SM-8.10/rba-enabled-user-flows.spec.js +1 -0
- package/package.json +1 -1
|
@@ -100,8 +100,19 @@ async function waitForLoginForm(page, loginPage, totalTimeout = 90000, roundTime
|
|
|
100
100
|
}
|
|
101
101
|
throw new Error(`Auth0 login form did not render within ${totalTimeout}ms: ${lastState}`);
|
|
102
102
|
}
|
|
103
|
+
// Tracks which worker-derived `timeout` values have already paid the
|
|
104
|
+
// stagger sleep below, so a worker only pays it once per process lifetime
|
|
105
|
+
// rather than on every call -- see the comment at the call site.
|
|
106
|
+
const staggeredWorkerTimeouts = new Set();
|
|
103
107
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
|
|
104
108
|
let lastError;
|
|
109
|
+
// `timeout` is a stable per-worker identifier (derived from workerIndex),
|
|
110
|
+
// and a worker is a long-lived process across every test/repeat it runs, so
|
|
111
|
+
// the stagger below only needs to happen for that worker's first call in
|
|
112
|
+
// the whole job. A --repeat-each validation pass otherwise pays it on
|
|
113
|
+
// every repeat instead of just the first (team-test-automation#78).
|
|
114
|
+
const isWorkersFirstCall = !staggeredWorkerTimeouts.has(timeout);
|
|
115
|
+
staggeredWorkerTimeouts.add(timeout);
|
|
105
116
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
106
117
|
try {
|
|
107
118
|
await page.context().clearCookies();
|
|
@@ -150,7 +161,9 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
150
161
|
await page
|
|
151
162
|
.waitForLoadState('networkidle', { timeout: 30000 })
|
|
152
163
|
.catch(() => { });
|
|
153
|
-
|
|
164
|
+
if (isWorkersFirstCall) {
|
|
165
|
+
await (0, sleep_1.sleep)(timeout);
|
|
166
|
+
}
|
|
154
167
|
await waitForLoginForm(page, loginPage);
|
|
155
168
|
await loginPage.loginWithTestUser(testUser);
|
|
156
169
|
return;
|
|
@@ -7,11 +7,28 @@ const HomePage_1 = require("./HomePage");
|
|
|
7
7
|
const sleep_1 = require("../../utils/sleep");
|
|
8
8
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
9
9
|
const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
|
|
10
|
+
// Tracks which worker-derived `timeout` values have already paid the
|
|
11
|
+
// pre-flight stagger below, so a worker only pays it once per process
|
|
12
|
+
// lifetime rather than on every call -- see the comment at the call sites.
|
|
13
|
+
const staggeredWorkerTimeouts = new Set();
|
|
10
14
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5, skipOrgAssertion = false) {
|
|
15
|
+
// Callers derive `timeout` as (workerIndex + 1) * 1000, so it's a stable
|
|
16
|
+
// per-worker identifier: a Playwright worker is a long-lived process that
|
|
17
|
+
// calls loginWithRetry once per test/repeat it runs, and this stagger only
|
|
18
|
+
// needs to happen for that worker's FIRST call in the whole job -- by the
|
|
19
|
+
// time it calls again, it's no longer starting simultaneously with anyone
|
|
20
|
+
// else's first call, which is the only scenario the stagger protects
|
|
21
|
+
// against. Without this, a --repeat-each validation pass pays the full
|
|
22
|
+
// stagger on every single repeat instead of once, which is pure dead time
|
|
23
|
+
// on the critical path (team-test-automation#78).
|
|
24
|
+
const isWorkersFirstCall = !staggeredWorkerTimeouts.has(timeout);
|
|
25
|
+
staggeredWorkerTimeouts.add(timeout);
|
|
11
26
|
// Pre-flight worker stagger: spread the initial Auth0 hits across parallel
|
|
12
27
|
// workers BEFORE the first goto, otherwise all workers slam the tenant at
|
|
13
28
|
// the same instant and several get throttled into the retry loop.
|
|
14
|
-
|
|
29
|
+
if (isWorkersFirstCall) {
|
|
30
|
+
await (0, sleep_1.sleep)(timeout);
|
|
31
|
+
}
|
|
15
32
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
16
33
|
try {
|
|
17
34
|
await page.context().clearCookies();
|
|
@@ -36,7 +53,9 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
36
53
|
await page
|
|
37
54
|
.waitForLoadState('networkidle', { timeout: 30000 })
|
|
38
55
|
.catch(() => { });
|
|
39
|
-
|
|
56
|
+
if (isWorkersFirstCall) {
|
|
57
|
+
await (0, sleep_1.sleep)(timeout);
|
|
58
|
+
}
|
|
40
59
|
// Always detect SSO re-auth: after a logout + clearCookies, Auth0 may
|
|
41
60
|
// silently log the previous user back in without showing the login form.
|
|
42
61
|
// Force logout until the Auth0 email input appears.
|
|
@@ -32,14 +32,26 @@ async function forceLogoutIfNeeded(page) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
exports.forceLogoutIfNeeded = forceLogoutIfNeeded;
|
|
35
|
+
// Tracks which worker-derived `timeout` values have already paid the
|
|
36
|
+
// pre-flight stagger below, so a worker only pays it once per process
|
|
37
|
+
// lifetime rather than on every call -- see the comment at the call site.
|
|
38
|
+
const staggeredWorkerTimeouts = new Set();
|
|
35
39
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
|
|
36
40
|
let lastError;
|
|
37
41
|
// Stagger the first navigation across parallel workers. Without this, every
|
|
38
42
|
// worker's initial page.goto('/') fires within the same second -- with 18+
|
|
39
43
|
// workers logging in at once, that burst is enough to make the navigation
|
|
40
44
|
// itself time out well before any login form ever renders. Retries already
|
|
41
|
-
// back off via randomSleep below, so this only needs to run once
|
|
42
|
-
|
|
45
|
+
// back off via randomSleep below, so this only needs to run once -- and,
|
|
46
|
+
// since `timeout` is a stable per-worker identifier (derived from
|
|
47
|
+
// workerIndex) and a worker is a long-lived process across every
|
|
48
|
+
// test/repeat it runs, "once" means once per worker for the whole job, not
|
|
49
|
+
// once per call. A --repeat-each validation pass otherwise pays this on
|
|
50
|
+
// every repeat instead of just the first (team-test-automation#78).
|
|
51
|
+
if (!staggeredWorkerTimeouts.has(timeout)) {
|
|
52
|
+
staggeredWorkerTimeouts.add(timeout);
|
|
53
|
+
await (0, sleep_1.sleep)(timeout);
|
|
54
|
+
}
|
|
43
55
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
44
56
|
try {
|
|
45
57
|
await page.context().clearCookies();
|
|
@@ -8,8 +8,19 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
8
8
|
const randomSleep_1 = require("../../utils/randomSleep");
|
|
9
9
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
10
10
|
const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
|
|
11
|
+
// Tracks which worker-derived `timeout` values have already paid the
|
|
12
|
+
// stagger sleep below, so a worker only pays it once per process lifetime
|
|
13
|
+
// rather than on every call -- see the comment at the call site.
|
|
14
|
+
const staggeredWorkerTimeouts = new Set();
|
|
11
15
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
|
|
12
16
|
let lastError;
|
|
17
|
+
// `timeout` is a stable per-worker identifier (derived from workerIndex),
|
|
18
|
+
// and a worker is a long-lived process across every test/repeat it runs, so
|
|
19
|
+
// the stagger below only needs to happen for that worker's first call in
|
|
20
|
+
// the whole job. A --repeat-each validation pass otherwise pays it on
|
|
21
|
+
// every repeat instead of just the first (team-test-automation#78).
|
|
22
|
+
const isWorkersFirstCall = !staggeredWorkerTimeouts.has(timeout);
|
|
23
|
+
staggeredWorkerTimeouts.add(timeout);
|
|
13
24
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
14
25
|
try {
|
|
15
26
|
await page.context().clearCookies();
|
|
@@ -34,7 +45,9 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
34
45
|
await page
|
|
35
46
|
.waitForLoadState('networkidle', { timeout: 30000 })
|
|
36
47
|
.catch(() => { });
|
|
37
|
-
|
|
48
|
+
if (isWorkersFirstCall) {
|
|
49
|
+
await (0, sleep_1.sleep)(timeout);
|
|
50
|
+
}
|
|
38
51
|
// The identity provider occasionally returns a generic error / rate
|
|
39
52
|
// limit page that has none of the usual auth controls. Bail out early
|
|
40
53
|
// when that page is visible so the outer retry can back off.
|
|
@@ -29,6 +29,8 @@ declare class Authorization {
|
|
|
29
29
|
resourceId: string;
|
|
30
30
|
accessPermissions: string[];
|
|
31
31
|
}): Promise<void>;
|
|
32
|
+
private expectAuthorizationListed;
|
|
33
|
+
private filterAuthorizationsByOwner;
|
|
32
34
|
deleteAuthorization(resourceId: string, resourceType: string): Promise<void>;
|
|
33
35
|
}
|
|
34
36
|
export { Authorization };
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Authorization = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const UtilitiesPage_1 = require("../SM-8.10/UtilitiesPage");
|
|
6
|
+
const findLocatorWithPagination_1 = require("../../utils/assertionHelpers/findLocatorWithPagination");
|
|
6
7
|
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
7
8
|
const sleep_1 = require("../../utils/sleep");
|
|
8
9
|
class Authorization {
|
|
@@ -89,7 +90,13 @@ class Authorization {
|
|
|
89
90
|
this.deleteAuthorizationModal.getByRole('button', {
|
|
90
91
|
name: /delete authorization/i,
|
|
91
92
|
});
|
|
92
|
-
|
|
93
|
+
// Carbon labels the table toolbar search "Filter table"; the
|
|
94
|
+
// design-system shell the Admin is migrating onto exposes the placeholder
|
|
95
|
+
// as the accessible name instead. Match either.
|
|
96
|
+
this.tableFilter = page
|
|
97
|
+
.getByRole('searchbox', { name: 'Filter table' })
|
|
98
|
+
.or(page.getByRole('searchbox', { name: 'Search by owner ID' }))
|
|
99
|
+
.first();
|
|
93
100
|
}
|
|
94
101
|
// Released 8.10 alpha images still render the Carbon combobox that lists
|
|
95
102
|
// every owner, so accept whichever owner field this build shows.
|
|
@@ -162,14 +169,81 @@ class Authorization {
|
|
|
162
169
|
}
|
|
163
170
|
await (0, submitModalWithRetry_1.submitModalWithRetry)(this.createAuthorizationSubmitButton, this.createAuthorizationModal);
|
|
164
171
|
await this.selectResourceTypeTab(authorization.resourceType).click();
|
|
165
|
-
const item = this.page
|
|
172
|
+
const item = this.page
|
|
173
|
+
.getByRole('row')
|
|
174
|
+
.filter({
|
|
166
175
|
hasText: `${authorization.ownerId}${authorization.resourceId}`,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
await this.
|
|
170
|
-
|
|
176
|
+
})
|
|
177
|
+
.first();
|
|
178
|
+
await this.expectAuthorizationListed(authorization, item);
|
|
179
|
+
}
|
|
180
|
+
// The toolbar search filters by owner ID, so filling it with the resource
|
|
181
|
+
// type narrowed nothing, and the retry helper it fed reloads before it
|
|
182
|
+
// asserts, which dropped the filter entirely. That left the new row to be
|
|
183
|
+
// found on page 1 of the unfiltered table - and the authorizations table
|
|
184
|
+
// paginates, so with the RBA specs creating owners in parallel it is
|
|
185
|
+
// regularly on a later page. Filter by owner, then page through what is
|
|
186
|
+
// left; fall back to the unfiltered table for builds whose search has not
|
|
187
|
+
// caught up with a just-created owner yet.
|
|
188
|
+
async expectAuthorizationListed(authorization, item) {
|
|
189
|
+
const deadline = Date.now() + 180000;
|
|
190
|
+
const budget = (max) => Math.max(5000, Math.min(max, deadline - Date.now()));
|
|
191
|
+
const maxAttempts = 5;
|
|
192
|
+
let lastError;
|
|
193
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
194
|
+
if (attempt > 0 && Date.now() >= deadline) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const resourceTab = this.selectResourceTypeTab(authorization.resourceType);
|
|
199
|
+
await (0, test_1.expect)(resourceTab).toBeVisible({ timeout: budget(30000) });
|
|
200
|
+
await resourceTab.click({ timeout: budget(30000) });
|
|
201
|
+
for (const filterValue of [authorization.ownerId, '']) {
|
|
202
|
+
await this.filterAuthorizationsByOwner(filterValue);
|
|
203
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, item, {
|
|
204
|
+
timeout: budget(filterValue === '' ? 45000 : 15000),
|
|
205
|
+
});
|
|
206
|
+
if (found) {
|
|
207
|
+
await (0, test_1.expect)(item).toBeVisible({ timeout: 5000 });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`No row for owner "${authorization.ownerId}" and resource ` +
|
|
212
|
+
`"${authorization.resourceId}" in the ` +
|
|
213
|
+
`${authorization.resourceType} table`);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
lastError = error;
|
|
217
|
+
if (attempt < maxAttempts - 1) {
|
|
218
|
+
console.warn(`Attempt ${attempt + 1} did not list the ` +
|
|
219
|
+
`${authorization.resourceType} authorization for ` +
|
|
220
|
+
`"${authorization.ownerId}". Retrying...`);
|
|
221
|
+
await this.page.reload();
|
|
222
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Authorization for "${authorization.ownerId}" on ` +
|
|
227
|
+
`"${authorization.resourceId}" was not listed: ${String(lastError)}`);
|
|
228
|
+
}
|
|
229
|
+
async filterAuthorizationsByOwner(ownerId) {
|
|
230
|
+
const hasFilter = await this.tableFilter
|
|
231
|
+
.isVisible({ timeout: 5000 })
|
|
232
|
+
.catch(() => false);
|
|
233
|
+
if (!hasFilter) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await this.tableFilter.fill(ownerId);
|
|
237
|
+
await (0, sleep_1.sleep)(1000);
|
|
171
238
|
}
|
|
172
239
|
async deleteAuthorization(resourceId, resourceType) {
|
|
240
|
+
// Same paginated table as createAuthorization: the row being deleted is
|
|
241
|
+
// not necessarily on page 1, and the toolbar search cannot narrow by
|
|
242
|
+
// resource ID.
|
|
243
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, this.deleteAuthorizationButton(resourceId), { timeout: 60000 });
|
|
244
|
+
if (!found) {
|
|
245
|
+
throw new Error(`No ${resourceType} authorization row for resource "${resourceId}"`);
|
|
246
|
+
}
|
|
173
247
|
await this.deleteAuthorizationButton(resourceId).click();
|
|
174
248
|
await (0, test_1.expect)(this.deleteAuthorizationModal).toBeVisible();
|
|
175
249
|
await this.deleteAuthorizationModalDeleteButton.click();
|
|
@@ -29,9 +29,14 @@ class OCIdentityMappingRulesPage {
|
|
|
29
29
|
this.deleteMappingRuleDialog = page.getByRole('dialog', {
|
|
30
30
|
name: 'Delete Mapping rule',
|
|
31
31
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// The design-system Admin shell replaced Carbon's table toolbar search
|
|
33
|
+
// (labelled "Filter table") with an input whose aria-label is its
|
|
34
|
+
// placeholder, so match the new shell first and fall back to the legacy
|
|
35
|
+
// one - the same dual-shell pattern the Admin nav locators use.
|
|
36
|
+
this.filterTableSearchbox = page
|
|
37
|
+
.getByRole('searchbox', { name: 'Search by mapping rule ID' })
|
|
38
|
+
.or(page.getByRole('searchbox', { name: 'Filter table' }))
|
|
39
|
+
.first();
|
|
35
40
|
this.deleteMappingRuleButton = this.deleteMappingRuleDialog
|
|
36
41
|
.getByRole('button', {
|
|
37
42
|
name: 'Delete Mapping rule',
|
|
@@ -172,6 +172,16 @@ class OCIdentityRolesPage {
|
|
|
172
172
|
async deleteRole(name, options) {
|
|
173
173
|
const { throwError = true } = options || {};
|
|
174
174
|
try {
|
|
175
|
+
// The roles table paginates at 10 rows and the RBA specs run in
|
|
176
|
+
// parallel against a cluster that already ships six built-in roles, so
|
|
177
|
+
// a role created by this test is regularly on page 2 by the time it is
|
|
178
|
+
// deleted. Without walking the pages the row - and therefore its Delete
|
|
179
|
+
// button - simply never resolves, which is what the 10s click timeout
|
|
180
|
+
// reported.
|
|
181
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, this.deleteRoleButton(name), { timeout: 30000 });
|
|
182
|
+
if (!found) {
|
|
183
|
+
throw new Error(`Role "${name}" was not found on any page of the roles table`);
|
|
184
|
+
}
|
|
175
185
|
await this.deleteRoleButton(name).click();
|
|
176
186
|
await (0, test_1.expect)(this.deleteRoleModal).toBeVisible();
|
|
177
187
|
await this.deleteRoleModalDeleteButton.click();
|
|
@@ -25,6 +25,7 @@ if (node_process_1.default.env.IS_RBA === 'true') {
|
|
|
25
25
|
await navigationPage.goToOCAdmin();
|
|
26
26
|
const ids = idsToBeDeleted[testInfo.title];
|
|
27
27
|
if (ids['mappingIds'].length > 0) {
|
|
28
|
+
await ocIdentityHomePage.clickMappingRulesTab();
|
|
28
29
|
for (const id of ids['mappingIds']) {
|
|
29
30
|
await ocIdentityMappingRulesPage.deleteMappingRule(id);
|
|
30
31
|
}
|