@camunda/e2e-test-suite 0.0.1171 → 0.0.1173
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/SM-8.10/OCIdentityRolesPage.d.ts +1 -0
- package/dist/pages/SM-8.10/OCIdentityRolesPage.js +29 -8
- package/dist/pages/SM-8.10/OCTenantPage.d.ts +2 -0
- package/dist/pages/SM-8.10/OCTenantPage.js +37 -7
- package/dist/pages/SM-8.10/TaskPanelPage.d.ts +2 -0
- package/dist/pages/SM-8.10/TaskPanelPage.js +60 -5
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ declare class OCIdentityRolesPage {
|
|
|
4
4
|
readonly selectResourceTypeTab: (name: string) => Locator;
|
|
5
5
|
readonly assignMappingRuleButton: Locator;
|
|
6
6
|
readonly mappingSearchBox: Locator;
|
|
7
|
+
readonly mappingSearchTrigger: Locator;
|
|
7
8
|
readonly assignMappingModal: Locator;
|
|
8
9
|
readonly assignMappingRuleOption: (name: string) => Locator;
|
|
9
10
|
readonly assignMappingModalAssignRuleButton: Locator;
|
|
@@ -12,6 +12,7 @@ class OCIdentityRolesPage {
|
|
|
12
12
|
selectResourceTypeTab;
|
|
13
13
|
assignMappingRuleButton;
|
|
14
14
|
mappingSearchBox;
|
|
15
|
+
mappingSearchTrigger;
|
|
15
16
|
assignMappingModal;
|
|
16
17
|
assignMappingRuleOption;
|
|
17
18
|
assignMappingModalAssignRuleButton;
|
|
@@ -49,20 +50,32 @@ class OCIdentityRolesPage {
|
|
|
49
50
|
this.assignMappingModal = page.getByRole('dialog', {
|
|
50
51
|
name: 'Assign mapping rule',
|
|
51
52
|
});
|
|
52
|
-
// The design-system modal
|
|
53
|
-
//
|
|
53
|
+
// The design-system modal renders a MultiSelect whose trigger is a
|
|
54
|
+
// div[role="combobox"], not the searchbox the pre design-system modal
|
|
54
55
|
// rendered; the Mapping rules list page keeps a page-level searchbox with
|
|
55
56
|
// the same accessible name, so scope this to the dialog and accept either
|
|
56
57
|
// role. The modal's field resolves its placeholder through the
|
|
57
58
|
// `entitySelection` i18n namespace, which carries no `searchByMappingRuleId`
|
|
58
59
|
// entry, so it is labelled with the raw key instead of the translated
|
|
59
60
|
// string the list page renders - match either form.
|
|
60
|
-
this.
|
|
61
|
+
this.mappingSearchTrigger = this.assignMappingModal
|
|
61
62
|
.getByRole('combobox', { name: SEARCH_MAPPING_RULE_LABEL })
|
|
62
63
|
.or(this.assignMappingModal.getByRole('searchbox', {
|
|
63
64
|
name: SEARCH_MAPPING_RULE_LABEL,
|
|
64
65
|
}))
|
|
65
66
|
.first();
|
|
67
|
+
// Clicking that trigger opens a Radix popover that portals to <body> as a
|
|
68
|
+
// sibling of the dialog, and the editable field is the cmdk <input> inside
|
|
69
|
+
// it - the trigger div carries the same role and accessible name but
|
|
70
|
+
// swallows key events, so typing at it leaves the query empty. Match the
|
|
71
|
+
// popover's input, falling back to the dialog-scoped searchbox that
|
|
72
|
+
// pre-design-system builds render instead of the trigger/popover pair.
|
|
73
|
+
this.mappingSearchBox = page
|
|
74
|
+
.locator('[data-slot="multi-select-content"] input[cmdk-input]')
|
|
75
|
+
.or(this.assignMappingModal.getByRole('searchbox', {
|
|
76
|
+
name: SEARCH_MAPPING_RULE_LABEL,
|
|
77
|
+
}))
|
|
78
|
+
.first();
|
|
66
79
|
this.createRoleButton = page.getByRole('button', {
|
|
67
80
|
name: /create role/i,
|
|
68
81
|
});
|
|
@@ -175,15 +188,23 @@ class OCIdentityRolesPage {
|
|
|
175
188
|
await (0, test_1.expect)(this.assignMappingModal).toBeVisible({ timeout: 60000 });
|
|
176
189
|
// The design-system search input is a cmdk combobox that only issues
|
|
177
190
|
// its server-side query on real keystrokes, so type the id in rather
|
|
178
|
-
// than setting the value in one shot.
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
await this.
|
|
191
|
+
// than setting the value in one shot. It lives in the popover the
|
|
192
|
+
// trigger opens, so wait for it before typing. Budgeted like every
|
|
193
|
+
// other step here rather than inheriting the 10s actionTimeout.
|
|
194
|
+
await this.mappingSearchTrigger.click({ timeout: 60000 });
|
|
195
|
+
await (0, test_1.expect)(this.mappingSearchBox).toBeVisible({ timeout: 60000 });
|
|
182
196
|
await this.mappingSearchBox.pressSequentially(mappingId, {
|
|
183
197
|
timeout: 60000,
|
|
184
198
|
});
|
|
185
199
|
await this.assignMappingRuleOption(mappingId).click({ timeout: 30000 });
|
|
186
|
-
|
|
200
|
+
// The submit button stays disabled until the MultiSelect commits the
|
|
201
|
+
// pick, which lands a tick after the option click - budget that wait
|
|
202
|
+
// like the rest of the method instead of leaving it to the 10s
|
|
203
|
+
// actionTimeout the bare click would inherit.
|
|
204
|
+
await (0, test_1.expect)(this.assignMappingModalAssignRuleButton).toBeEnabled({
|
|
205
|
+
timeout: 30000,
|
|
206
|
+
});
|
|
207
|
+
await this.assignMappingModalAssignRuleButton.click({ timeout: 30000 });
|
|
187
208
|
return;
|
|
188
209
|
}
|
|
189
210
|
catch (error) {
|
|
@@ -22,6 +22,7 @@ declare class OCTenantPage {
|
|
|
22
22
|
readonly rolesTab: Locator;
|
|
23
23
|
readonly assignRoleButton: Locator;
|
|
24
24
|
readonly roleIdSearchBox: Locator;
|
|
25
|
+
readonly roleIdSearchTrigger: Locator;
|
|
25
26
|
readonly assignRoleSubButton: Locator;
|
|
26
27
|
readonly assignRoleModal: Locator;
|
|
27
28
|
readonly cancelButton: Locator;
|
|
@@ -63,6 +64,7 @@ declare class OCTenantPage {
|
|
|
63
64
|
clickRolesTab(): Promise<void>;
|
|
64
65
|
clickAssignRoleButton(): Promise<void>;
|
|
65
66
|
clickRoleIdSearchBox(): Promise<void>;
|
|
67
|
+
private ensureRoleIdSearchBoxOpen;
|
|
66
68
|
fillRoleIdSearchBox(roleId: string, roleIdentifier: string): Promise<void>;
|
|
67
69
|
clickAssignRoleSubButton(): Promise<void>;
|
|
68
70
|
private isRoleAssigned;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OCTenantPage = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const expectTextWithPagination_1 = require("../../utils/assertionHelpers/expectTextWithPagination");
|
|
6
|
+
const ROLE_ID_SEARCH_LABEL = /^(Search by role ID|searchByRoleId)$/i;
|
|
6
7
|
class OCTenantPage {
|
|
7
8
|
page;
|
|
8
9
|
createTenantMainButton;
|
|
@@ -26,6 +27,7 @@ class OCTenantPage {
|
|
|
26
27
|
rolesTab;
|
|
27
28
|
assignRoleButton;
|
|
28
29
|
roleIdSearchBox;
|
|
30
|
+
roleIdSearchTrigger;
|
|
29
31
|
assignRoleSubButton;
|
|
30
32
|
assignRoleModal;
|
|
31
33
|
cancelButton;
|
|
@@ -71,12 +73,25 @@ class OCTenantPage {
|
|
|
71
73
|
this.rolesTab = page.getByRole('tab', { name: 'Roles' });
|
|
72
74
|
this.assignRoleButton = page.getByRole('button', { name: 'Assign role' });
|
|
73
75
|
// The design-system Admin shell renders the Assign role modal's search
|
|
74
|
-
// field as a
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// dual-shell pattern the table filter locator
|
|
76
|
+
// field as a MultiSelect whose trigger is a div[role="combobox"] carrying
|
|
77
|
+
// the label as a child span - it has no placeholder and is not editable,
|
|
78
|
+
// so scope the trigger to the dialog and keep the legacy searchbox match
|
|
79
|
+
// as a fallback, the same dual-shell pattern the table filter locator
|
|
80
|
+
// below uses.
|
|
81
|
+
this.roleIdSearchTrigger = page
|
|
82
|
+
.getByRole('dialog', { name: 'Assign role' })
|
|
83
|
+
.getByRole('combobox', { name: ROLE_ID_SEARCH_LABEL })
|
|
84
|
+
.or(page.getByRole('searchbox', { name: 'Search by Role ID' }))
|
|
85
|
+
.first();
|
|
86
|
+
// Clicking that trigger opens a Radix popover that portals to <body> as a
|
|
87
|
+
// sibling of the dialog, and the editable field is the cmdk <input> inside
|
|
88
|
+
// it. Nothing opened that popover before, so the placeholder-bearing input
|
|
89
|
+
// never existed and the click at it timed out. Match the popover's input,
|
|
90
|
+
// falling back to the placeholder/searchbox that pre-design-system builds
|
|
91
|
+
// render instead of the trigger/popover pair.
|
|
78
92
|
this.roleIdSearchBox = page
|
|
79
|
-
.
|
|
93
|
+
.locator('[data-slot="multi-select-content"] input[cmdk-input]')
|
|
94
|
+
.or(page.getByPlaceholder('Search by role ID'))
|
|
80
95
|
.or(page.getByRole('searchbox', { name: 'Search by Role ID' }))
|
|
81
96
|
.first();
|
|
82
97
|
this.assignRoleModal = page.getByLabel('Assign role');
|
|
@@ -273,7 +288,19 @@ class OCTenantPage {
|
|
|
273
288
|
await this.assignRoleButton.click();
|
|
274
289
|
}
|
|
275
290
|
async clickRoleIdSearchBox() {
|
|
276
|
-
await this.
|
|
291
|
+
await this.roleIdSearchTrigger.click({ timeout: 60000 });
|
|
292
|
+
await (0, test_1.expect)(this.roleIdSearchBox).toBeVisible({ timeout: 60000 });
|
|
293
|
+
}
|
|
294
|
+
// Selecting an option or losing focus dismisses the popover, which unmounts
|
|
295
|
+
// the search input, so reopen it before the next term is typed.
|
|
296
|
+
async ensureRoleIdSearchBoxOpen() {
|
|
297
|
+
const open = await this.roleIdSearchBox
|
|
298
|
+
.waitFor({ state: 'visible', timeout: 5000 })
|
|
299
|
+
.then(() => true)
|
|
300
|
+
.catch(() => false);
|
|
301
|
+
if (!open) {
|
|
302
|
+
await this.clickRoleIdSearchBox();
|
|
303
|
+
}
|
|
277
304
|
}
|
|
278
305
|
async fillRoleIdSearchBox(roleId, roleIdentifier) {
|
|
279
306
|
// The design-system shell portals the matches into a listbox rendered as
|
|
@@ -304,9 +331,12 @@ class OCTenantPage {
|
|
|
304
331
|
// between terms resets it before the next one is typed.
|
|
305
332
|
await (0, test_1.expect)(async () => {
|
|
306
333
|
for (const searchTerm of searchTerms) {
|
|
334
|
+
await this.ensureRoleIdSearchBoxOpen();
|
|
307
335
|
await this.roleIdSearchBox.fill('');
|
|
308
336
|
await this.page.waitForLoadState('networkidle');
|
|
309
|
-
|
|
337
|
+
// The cmdk input only issues its server-side query on real
|
|
338
|
+
// keystrokes, so type the term in rather than setting it in one shot.
|
|
339
|
+
await this.roleIdSearchBox.pressSequentially(searchTerm);
|
|
310
340
|
const offered = await result
|
|
311
341
|
.waitFor({ state: 'visible', timeout: 15000 })
|
|
312
342
|
.then(() => true)
|
|
@@ -14,6 +14,8 @@ declare class TaskPanelPage {
|
|
|
14
14
|
constructor(page: Page);
|
|
15
15
|
openTask(name: string, assignButton?: boolean): Promise<void>;
|
|
16
16
|
filterBy(option: 'All open' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
|
|
17
|
+
private expandFilterPanel;
|
|
18
|
+
private static appliedFilterUrl;
|
|
17
19
|
scrollToLastTask(name: string): Promise<void>;
|
|
18
20
|
scrollToFirstTask(name: string): Promise<void>;
|
|
19
21
|
clickCollapseFilter(): Promise<void>;
|
|
@@ -72,11 +72,66 @@ class TaskPanelPage {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
async filterBy(option) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
// The task list refetches every 5s behind a suspense boundary, so a poll
|
|
76
|
+
// landing on the filter click tears the floating filter panel down before
|
|
77
|
+
// the router navigates: the panel closes but the filter never changes.
|
|
78
|
+
// Nothing downstream notices, so the test fails much later on an assertion
|
|
79
|
+
// that reads the still-unfiltered list -- which is how a dropped click on
|
|
80
|
+
// "Completed" surfaced as the completed-filter heading never appearing.
|
|
81
|
+
// Confirm the route actually carries the filter, and re-pick if it does not.
|
|
82
|
+
const maxAttempts = 3;
|
|
83
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
84
|
+
await this.expandFilterPanel();
|
|
85
|
+
// Deliberately not `exact`: the panel spells the default filter
|
|
86
|
+
// "All open tasks" while callers pass "All open".
|
|
87
|
+
const filterLink = this.page.getByRole('link', { name: option });
|
|
88
|
+
// Only the first attempt waits out a slow first render. Later attempts
|
|
89
|
+
// re-pick from a panel that has already rendered once, so they stay
|
|
90
|
+
// short -- three attempts at the full budget would outlast the
|
|
91
|
+
// 12-minute test timeout and turn a dropped click into a timeout with
|
|
92
|
+
// no retry spent.
|
|
93
|
+
const renderTimeout = attempt === 1 ? 120000 : 15000;
|
|
94
|
+
await (0, test_1.expect)(filterLink).toBeVisible({ timeout: renderTimeout });
|
|
95
|
+
await filterLink.click({ timeout: renderTimeout });
|
|
96
|
+
const applied = await this.page
|
|
97
|
+
.waitForURL(TaskPanelPage.appliedFilterUrl(option), { timeout: 15000 })
|
|
98
|
+
.then(() => true)
|
|
99
|
+
.catch(() => false);
|
|
100
|
+
if (applied) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (attempt < maxAttempts) {
|
|
104
|
+
console.warn(`Attempt ${attempt} did not apply the "${option}" filter. Retrying...`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Filter "${option}" was not applied after ${maxAttempts} attempts`);
|
|
108
|
+
}
|
|
109
|
+
// The filter panel renders either the collapsed rail (which carries the
|
|
110
|
+
// expand control) or the expanded nav -- never both -- and picking a filter
|
|
111
|
+
// leaves it expanded, so a retry must not click "expand" a second time.
|
|
112
|
+
async expandFilterPanel() {
|
|
113
|
+
const alreadyExpanded = await this.collapseFilter
|
|
114
|
+
.waitFor({ state: 'visible', timeout: 2000 })
|
|
115
|
+
.then(() => true)
|
|
116
|
+
.catch(() => false);
|
|
117
|
+
if (!alreadyExpanded) {
|
|
118
|
+
await this.filterOptions.click({ timeout: 45000 });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Tasklist keeps the active filter in the `filter` search param. "All open"
|
|
122
|
+
// is the route's default and is stripped from the URL, so it is asserted as
|
|
123
|
+
// the absence of the param.
|
|
124
|
+
static appliedFilterUrl(option) {
|
|
125
|
+
const filterParam = {
|
|
126
|
+
'All open': undefined,
|
|
127
|
+
Unassigned: 'unassigned',
|
|
128
|
+
'Assigned to me': 'assigned-to-me',
|
|
129
|
+
Completed: 'completed',
|
|
130
|
+
};
|
|
131
|
+
const param = filterParam[option];
|
|
132
|
+
return param === undefined
|
|
133
|
+
? /\/tasklist(?:\/[^?]*)?(?:\?(?!.*\bfilter=).*)?$/
|
|
134
|
+
: new RegExp(`[?&]filter=${param}(?:&|$)`);
|
|
80
135
|
}
|
|
81
136
|
async scrollToLastTask(name) {
|
|
82
137
|
await this.page.getByText(name).last().scrollIntoViewIfNeeded();
|