@camunda/e2e-test-suite 0.0.1443 → 0.0.1445
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/.github/c8_versions.json +1 -0
- package/README.md +2 -0
- package/dist/fixtures/SM-8.11.d.ts +77 -0
- package/dist/fixtures/SM-8.11.js +176 -0
- package/dist/pages/SM-8.11/Authorization.d.ts +39 -0
- package/dist/pages/SM-8.11/Authorization.js +349 -0
- package/dist/pages/SM-8.11/ClusterDetailsPage.d.ts +8 -0
- package/dist/pages/SM-8.11/ClusterDetailsPage.js +65 -0
- package/dist/pages/SM-8.11/ClusterPage.d.ts +11 -0
- package/dist/pages/SM-8.11/ClusterPage.js +37 -0
- package/dist/pages/SM-8.11/ConnectorMarketplacePage.d.ts +27 -0
- package/dist/pages/SM-8.11/ConnectorMarketplacePage.js +187 -0
- package/dist/pages/SM-8.11/ConnectorSettingsPage.d.ts +41 -0
- package/dist/pages/SM-8.11/ConnectorSettingsPage.js +153 -0
- package/dist/pages/SM-8.11/ConnectorTemplatePage.d.ts +11 -0
- package/dist/pages/SM-8.11/ConnectorTemplatePage.js +34 -0
- package/dist/pages/SM-8.11/FormJsPage.d.ts +33 -0
- package/dist/pages/SM-8.11/FormJsPage.js +109 -0
- package/dist/pages/SM-8.11/HomePage.d.ts +20 -0
- package/dist/pages/SM-8.11/HomePage.js +49 -0
- package/dist/pages/SM-8.11/IdentityTenantPage.d.ts +53 -0
- package/dist/pages/SM-8.11/IdentityTenantPage.js +264 -0
- package/dist/pages/SM-8.11/IdentityUsersPage.d.ts +92 -0
- package/dist/pages/SM-8.11/IdentityUsersPage.js +485 -0
- package/dist/pages/SM-8.11/KeycloakAdminPage.d.ts +17 -0
- package/dist/pages/SM-8.11/KeycloakAdminPage.js +78 -0
- package/dist/pages/SM-8.11/KeycloakLoginPage.d.ts +12 -0
- package/dist/pages/SM-8.11/KeycloakLoginPage.js +32 -0
- package/dist/pages/SM-8.11/KeycloakUtils.d.ts +17 -0
- package/dist/pages/SM-8.11/KeycloakUtils.js +94 -0
- package/dist/pages/SM-8.11/LoginPage.d.ts +21 -0
- package/dist/pages/SM-8.11/LoginPage.js +112 -0
- package/dist/pages/SM-8.11/ManagementIdentityPage.d.ts +44 -0
- package/dist/pages/SM-8.11/ManagementIdentityPage.js +176 -0
- package/dist/pages/SM-8.11/ModelerCreatePage.d.ts +238 -0
- package/dist/pages/SM-8.11/ModelerCreatePage.js +1950 -0
- package/dist/pages/SM-8.11/ModelerHomePage.d.ts +97 -0
- package/dist/pages/SM-8.11/ModelerHomePage.js +956 -0
- package/dist/pages/SM-8.11/NavigationPage.d.ts +58 -0
- package/dist/pages/SM-8.11/NavigationPage.js +389 -0
- package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.d.ts +10 -0
- package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.js +22 -0
- package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.d.ts +22 -0
- package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.js +120 -0
- package/dist/pages/SM-8.11/OCIdentityGroupsPage.d.ts +39 -0
- package/dist/pages/SM-8.11/OCIdentityGroupsPage.js +154 -0
- package/dist/pages/SM-8.11/OCIdentityHomePage.d.ts +23 -0
- package/dist/pages/SM-8.11/OCIdentityHomePage.js +115 -0
- package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.d.ts +23 -0
- package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.js +116 -0
- package/dist/pages/SM-8.11/OCIdentityRolesPage.d.ts +61 -0
- package/dist/pages/SM-8.11/OCIdentityRolesPage.js +433 -0
- package/dist/pages/SM-8.11/OCTenantPage.d.ts +73 -0
- package/dist/pages/SM-8.11/OCTenantPage.js +399 -0
- package/dist/pages/SM-8.11/OperateHomePage.d.ts +26 -0
- package/dist/pages/SM-8.11/OperateHomePage.js +139 -0
- package/dist/pages/SM-8.11/OperateProcessInstancePage.d.ts +30 -0
- package/dist/pages/SM-8.11/OperateProcessInstancePage.js +276 -0
- package/dist/pages/SM-8.11/OperateProcessesPage.d.ts +38 -0
- package/dist/pages/SM-8.11/OperateProcessesPage.js +383 -0
- package/dist/pages/SM-8.11/OptimizeCollectionsPage.d.ts +17 -0
- package/dist/pages/SM-8.11/OptimizeCollectionsPage.js +62 -0
- package/dist/pages/SM-8.11/OptimizeDashboardPage.d.ts +14 -0
- package/dist/pages/SM-8.11/OptimizeDashboardPage.js +63 -0
- package/dist/pages/SM-8.11/OptimizeHomePage.d.ts +19 -0
- package/dist/pages/SM-8.11/OptimizeHomePage.js +76 -0
- package/dist/pages/SM-8.11/OptimizeReportPage.d.ts +50 -0
- package/dist/pages/SM-8.11/OptimizeReportPage.js +209 -0
- package/dist/pages/SM-8.11/PlayPage.d.ts +47 -0
- package/dist/pages/SM-8.11/PlayPage.js +389 -0
- package/dist/pages/SM-8.11/ProcessApplicationPage.d.ts +35 -0
- package/dist/pages/SM-8.11/ProcessApplicationPage.js +340 -0
- package/dist/pages/SM-8.11/SettingsPage.d.ts +12 -0
- package/dist/pages/SM-8.11/SettingsPage.js +34 -0
- package/dist/pages/SM-8.11/TaskDetailsPage.d.ts +70 -0
- package/dist/pages/SM-8.11/TaskDetailsPage.js +273 -0
- package/dist/pages/SM-8.11/TaskPanelPage.d.ts +29 -0
- package/dist/pages/SM-8.11/TaskPanelPage.js +320 -0
- package/dist/pages/SM-8.11/TaskProcessesPage.d.ts +15 -0
- package/dist/pages/SM-8.11/TaskProcessesPage.js +77 -0
- package/dist/pages/SM-8.11/UtilitiesPage.d.ts +38 -0
- package/dist/pages/SM-8.11/UtilitiesPage.js +573 -0
- package/dist/pages/SM-8.11/optimizeReportUtils.d.ts +6 -0
- package/dist/pages/SM-8.11/optimizeReportUtils.js +77 -0
- package/dist/tests/SM-8.11/cluster-management-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/cluster-management-user-flows.spec.js +48 -0
- package/dist/tests/SM-8.11/cluster-variables.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/cluster-variables.spec.js +226 -0
- package/dist/tests/SM-8.11/connectors-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/connectors-user-flows.spec.js +307 -0
- package/dist/tests/SM-8.11/document-handling-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/document-handling-user-flows.spec.js +174 -0
- package/dist/tests/SM-8.11/hto-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/hto-user-flows.spec.js +410 -0
- package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.js +24 -0
- package/dist/tests/SM-8.11/identity-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/identity-user-flows.spec.js +220 -0
- package/dist/tests/SM-8.11/license-key-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/license-key-user-flows.spec.js +461 -0
- package/dist/tests/SM-8.11/login.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/login.spec.js +24 -0
- package/dist/tests/SM-8.11/migration-path-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/migration-path-user-flows.spec.js +263 -0
- package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.js +607 -0
- package/dist/tests/SM-8.11/optimize-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/optimize-user-flows.spec.js +505 -0
- package/dist/tests/SM-8.11/play.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/play.spec.js +144 -0
- package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.js +712 -0
- package/dist/tests/SM-8.11/smoke-tests.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/smoke-tests.spec.js +167 -0
- package/dist/tests/SM-8.11/test-setup.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/test-setup.spec.js +24 -0
- package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.js +58 -0
- package/dist/tests/SM-8.11/web-modeler-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/web-modeler-user-flows.spec.js +223 -0
- package/package.json +1 -1
- package/playwright.config.ts +21 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OCIdentityRolesPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const UtilitiesPage_1 = require("../SM-8.11/UtilitiesPage");
|
|
6
|
+
const expectLocatorWithPagination_1 = require("../../utils/assertionHelpers/expectLocatorWithPagination");
|
|
7
|
+
const findLocatorWithPagination_1 = require("../../utils/assertionHelpers/findLocatorWithPagination");
|
|
8
|
+
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
9
|
+
const SEARCH_MAPPING_RULE_LABEL = /^(Search by mapping rule ID|searchByMappingRuleId)$/;
|
|
10
|
+
class OCIdentityRolesPage {
|
|
11
|
+
page;
|
|
12
|
+
selectResourceTypeTab;
|
|
13
|
+
assignMappingRuleButton;
|
|
14
|
+
mappingSearchBox;
|
|
15
|
+
mappingSearchTrigger;
|
|
16
|
+
assignMappingModal;
|
|
17
|
+
assignMappingRuleOption;
|
|
18
|
+
assignMappingModalAssignRuleButton;
|
|
19
|
+
rolesList;
|
|
20
|
+
roleCell;
|
|
21
|
+
mappingRuleRow;
|
|
22
|
+
createRoleButton;
|
|
23
|
+
createRoleModal;
|
|
24
|
+
createNameField;
|
|
25
|
+
createIdField;
|
|
26
|
+
createRoleModalCreateButton;
|
|
27
|
+
deleteRoleButton;
|
|
28
|
+
deleteRoleModal;
|
|
29
|
+
closeDeleteRoleModal;
|
|
30
|
+
deleteRoleModalCancelButton;
|
|
31
|
+
deleteRoleModalDeleteButton;
|
|
32
|
+
adminRole;
|
|
33
|
+
rolesNavTab;
|
|
34
|
+
assignUserButton;
|
|
35
|
+
usernameTextBox;
|
|
36
|
+
assignUserSubButton;
|
|
37
|
+
clientsTab;
|
|
38
|
+
assignClientsButton;
|
|
39
|
+
clientIdTextBox;
|
|
40
|
+
assignClientIdSubButton;
|
|
41
|
+
notFoundText;
|
|
42
|
+
constructor(page) {
|
|
43
|
+
this.page = page;
|
|
44
|
+
this.rolesList = page.getByRole('table');
|
|
45
|
+
this.notFoundText = page.getByText('404 not found');
|
|
46
|
+
this.selectResourceTypeTab = (resourceType) => page.getByRole('tab', { name: resourceType });
|
|
47
|
+
this.assignMappingRuleButton = page.getByRole('button', {
|
|
48
|
+
name: /^(Assign mapping rule|assignMapping)$/,
|
|
49
|
+
});
|
|
50
|
+
this.assignMappingModal = page.getByRole('dialog', {
|
|
51
|
+
name: 'Assign mapping rule',
|
|
52
|
+
});
|
|
53
|
+
// The design-system modal renders a MultiSelect whose trigger is a
|
|
54
|
+
// div[role="combobox"], not the searchbox the pre design-system modal
|
|
55
|
+
// rendered; the Mapping rules list page keeps a page-level searchbox with
|
|
56
|
+
// the same accessible name, so scope this to the dialog and accept either
|
|
57
|
+
// role. The modal's field resolves its placeholder through the
|
|
58
|
+
// `entitySelection` i18n namespace, which carries no `searchByMappingRuleId`
|
|
59
|
+
// entry, so it is labelled with the raw key instead of the translated
|
|
60
|
+
// string the list page renders - match either form.
|
|
61
|
+
this.mappingSearchTrigger = this.assignMappingModal
|
|
62
|
+
.getByRole('combobox', { name: SEARCH_MAPPING_RULE_LABEL })
|
|
63
|
+
.or(this.assignMappingModal.getByRole('searchbox', {
|
|
64
|
+
name: SEARCH_MAPPING_RULE_LABEL,
|
|
65
|
+
}))
|
|
66
|
+
.first();
|
|
67
|
+
// The current design-system modal renders the cmdk <input cmdk-input> inline
|
|
68
|
+
// inside the dialog (data-slot="command-input", role="combobox"), so the
|
|
69
|
+
// trigger and the editable field are the same element - match that input
|
|
70
|
+
// scoped to the dialog. Older builds render a MultiSelect whose trigger div
|
|
71
|
+
// opens a Radix popover that portals to <body> as a sibling of the dialog,
|
|
72
|
+
// with the editable cmdk <input> inside it; that popover input is outside
|
|
73
|
+
// the dialog subtree, so match it page-wide. Pre-design-system builds render
|
|
74
|
+
// a dialog-scoped searchbox instead of either - fall back to that last.
|
|
75
|
+
this.mappingSearchBox = page
|
|
76
|
+
.locator('[data-slot="multi-select-content"] input[cmdk-input]')
|
|
77
|
+
.or(this.assignMappingModal.locator('input[cmdk-input]'))
|
|
78
|
+
.or(this.assignMappingModal.getByRole('searchbox', {
|
|
79
|
+
name: SEARCH_MAPPING_RULE_LABEL,
|
|
80
|
+
}))
|
|
81
|
+
.first();
|
|
82
|
+
this.createRoleButton = page.getByRole('button', {
|
|
83
|
+
name: /create role/i,
|
|
84
|
+
});
|
|
85
|
+
// The design-system assign-mapping-rule modal renders its result list in a
|
|
86
|
+
// Radix popover that portals to <body> as a sibling of the dialog, so the
|
|
87
|
+
// options are not inside assignMappingModal's DOM subtree - query them from
|
|
88
|
+
// the page. Each option repeats the id as both title and subtitle, so match
|
|
89
|
+
// on contained text rather than the accessible name.
|
|
90
|
+
//
|
|
91
|
+
// Builds predating the design-system migration (released alpha
|
|
92
|
+
// generations such as 8.10.0-alpha5) still render the Carbon
|
|
93
|
+
// `DropdownSearch` list inside the dialog as a role="listbox", with the
|
|
94
|
+
// mapping rule id as the item title - accept either shape.
|
|
95
|
+
this.assignMappingRuleOption = (name) => page
|
|
96
|
+
.getByRole('option')
|
|
97
|
+
.filter({ hasText: name })
|
|
98
|
+
.or(this.assignMappingModal.getByRole('listbox').getByText(name))
|
|
99
|
+
.first();
|
|
100
|
+
this.assignMappingModalAssignRuleButton = this.assignMappingModal.getByRole('button', {
|
|
101
|
+
name: 'Assign mapping rule',
|
|
102
|
+
});
|
|
103
|
+
this.roleCell = (roleID) => this.rolesList.getByRole('cell', { name: roleID, exact: true });
|
|
104
|
+
this.mappingRuleRow = (name) => page.getByRole('row').filter({ hasText: name });
|
|
105
|
+
this.createRoleModal = page.getByRole('dialog', {
|
|
106
|
+
name: 'Create role',
|
|
107
|
+
});
|
|
108
|
+
this.createIdField = this.createRoleModal.getByRole('textbox', {
|
|
109
|
+
name: 'Role ID',
|
|
110
|
+
exact: true,
|
|
111
|
+
});
|
|
112
|
+
this.createNameField = this.createRoleModal.getByRole('textbox', {
|
|
113
|
+
name: 'Role name',
|
|
114
|
+
exact: true,
|
|
115
|
+
});
|
|
116
|
+
this.createRoleModalCreateButton = this.createRoleModal.getByRole('button', { name: 'Create role' });
|
|
117
|
+
// The design-system DataTable renders the row's dangerous action as a
|
|
118
|
+
// labelled button - the trash icon is aria-hidden and the accessible name
|
|
119
|
+
// comes from the visible "Delete" text - while Carbon rendered an icon
|
|
120
|
+
// button carrying aria-label="Delete". getByLabel only matches the latter,
|
|
121
|
+
// so match the accessible name instead, which covers both DOMs.
|
|
122
|
+
this.deleteRoleButton = (rowName) => this.rolesList
|
|
123
|
+
.getByRole('row', { name: rowName })
|
|
124
|
+
.getByRole('button', { name: /^delete/i });
|
|
125
|
+
this.deleteRoleModal = page.getByRole('dialog', { name: /delete role/i });
|
|
126
|
+
this.closeDeleteRoleModal = this.deleteRoleModal.getByRole('button', {
|
|
127
|
+
name: 'Close',
|
|
128
|
+
});
|
|
129
|
+
this.deleteRoleModalCancelButton = this.deleteRoleModal.getByRole('button', {
|
|
130
|
+
name: 'Cancel',
|
|
131
|
+
});
|
|
132
|
+
this.deleteRoleModalDeleteButton = this.deleteRoleModal.getByRole('button', {
|
|
133
|
+
name: /delete role/i,
|
|
134
|
+
});
|
|
135
|
+
this.adminRole = page.getByRole('cell', { name: 'admin', exact: true });
|
|
136
|
+
this.rolesNavTab = page
|
|
137
|
+
.getByRole('navigation', { name: 'Main navigation' })
|
|
138
|
+
.getByRole('link', { name: 'Roles', exact: true })
|
|
139
|
+
.or(page
|
|
140
|
+
.getByRole('banner')
|
|
141
|
+
.locator('a')
|
|
142
|
+
.filter({ hasText: /^Roles$/ }))
|
|
143
|
+
.first();
|
|
144
|
+
this.assignUserButton = page.getByRole('button', { name: 'Assign user' });
|
|
145
|
+
this.usernameTextBox = page.getByRole('textbox', { name: 'Username' });
|
|
146
|
+
this.assignUserSubButton = page
|
|
147
|
+
.getByLabel('Assign user')
|
|
148
|
+
.getByRole('button', { name: 'Assign user' });
|
|
149
|
+
this.clientsTab = page.getByRole('tab', { name: 'Clients' });
|
|
150
|
+
this.assignClientsButton = page.getByRole('button', {
|
|
151
|
+
name: 'Assign client',
|
|
152
|
+
});
|
|
153
|
+
this.clientIdTextBox = page.getByPlaceholder('Enter client ID');
|
|
154
|
+
this.assignClientIdSubButton = page
|
|
155
|
+
.getByLabel('Assign client')
|
|
156
|
+
.getByRole('button', { name: 'Assign client' });
|
|
157
|
+
}
|
|
158
|
+
async clickMappingRulesTab() {
|
|
159
|
+
await (0, test_1.expect)(this.selectResourceTypeTab('Mapping Rules')).toBeVisible({
|
|
160
|
+
timeout: 60000,
|
|
161
|
+
});
|
|
162
|
+
await this.selectResourceTypeTab('Mapping Rules').click();
|
|
163
|
+
}
|
|
164
|
+
async clickRole(roleID) {
|
|
165
|
+
await (0, test_1.expect)(this.roleCell(roleID).first()).toBeVisible({ timeout: 60000 });
|
|
166
|
+
await this.roleCell(roleID).first().click();
|
|
167
|
+
}
|
|
168
|
+
async assignMappingRuleToRole(roleId, mappingId) {
|
|
169
|
+
const maxRetries = 3;
|
|
170
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
171
|
+
try {
|
|
172
|
+
// Inside the try so a retry that lands on the role detail page rather
|
|
173
|
+
// than the roles list reports the failure that caused it, instead of
|
|
174
|
+
// masking it as a roles-table timeout from this lookup.
|
|
175
|
+
await this.clickRole(roleId);
|
|
176
|
+
// After clicking the role, the detail page may show a 404 if the role
|
|
177
|
+
// was just created and the backend hasn't fully provisioned it yet.
|
|
178
|
+
const gotNotFound = await this.notFoundText
|
|
179
|
+
.isVisible({ timeout: 3000 })
|
|
180
|
+
.catch(() => false);
|
|
181
|
+
if (gotNotFound) {
|
|
182
|
+
console.warn(`Attempt ${attempt + 1}: Role detail page returned 404 for "${roleId}", retrying...`);
|
|
183
|
+
await this.recoverToRolesList();
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
await this.clickMappingRulesTab();
|
|
187
|
+
await (0, test_1.expect)(this.assignMappingRuleButton).toBeVisible({
|
|
188
|
+
timeout: 60000,
|
|
189
|
+
});
|
|
190
|
+
await this.assignMappingRuleButton.click({ timeout: 60000 });
|
|
191
|
+
await (0, test_1.expect)(this.assignMappingModal).toBeVisible({ timeout: 60000 });
|
|
192
|
+
// The design-system search input is a cmdk combobox that only issues
|
|
193
|
+
// its server-side query on real keystrokes, so type the id in rather
|
|
194
|
+
// than setting the value in one shot. It lives in the popover the
|
|
195
|
+
// trigger opens, so wait for it before typing. Budgeted like every
|
|
196
|
+
// other step here rather than inheriting the 10s actionTimeout.
|
|
197
|
+
await this.mappingSearchTrigger.click({ timeout: 60000 });
|
|
198
|
+
await (0, test_1.expect)(this.mappingSearchBox).toBeVisible({ timeout: 60000 });
|
|
199
|
+
await this.mappingSearchBox.pressSequentially(mappingId, {
|
|
200
|
+
timeout: 60000,
|
|
201
|
+
});
|
|
202
|
+
await this.assignMappingRuleOption(mappingId).click({ timeout: 30000 });
|
|
203
|
+
// The submit button stays disabled until the MultiSelect commits the
|
|
204
|
+
// pick, which lands a tick after the option click - budget that wait
|
|
205
|
+
// like the rest of the method instead of leaving it to the 10s
|
|
206
|
+
// actionTimeout the bare click would inherit.
|
|
207
|
+
await (0, test_1.expect)(this.assignMappingModalAssignRuleButton).toBeEnabled({
|
|
208
|
+
timeout: 30000,
|
|
209
|
+
});
|
|
210
|
+
await this.assignMappingModalAssignRuleButton.click({ timeout: 30000 });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (attempt < maxRetries - 1) {
|
|
215
|
+
console.warn(`Attempt ${attempt + 1} failed to assign mapping rule to role "${roleId}". Retrying...`);
|
|
216
|
+
await this.recoverToRolesList();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async createRole(role) {
|
|
225
|
+
await this.createRoleButton.click();
|
|
226
|
+
await (0, test_1.expect)(this.createRoleModal).toBeVisible();
|
|
227
|
+
await this.createIdField.fill(role.id);
|
|
228
|
+
await this.createNameField.fill(role.name);
|
|
229
|
+
await (0, submitModalWithRetry_1.submitModalWithRetry)(this.createRoleModalCreateButton, this.createRoleModal);
|
|
230
|
+
await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, this.rolesList
|
|
231
|
+
.getByRole('cell', {
|
|
232
|
+
name: role.name,
|
|
233
|
+
})
|
|
234
|
+
.first(), { maxRetries: 10 });
|
|
235
|
+
}
|
|
236
|
+
async deleteRole(name, options) {
|
|
237
|
+
const { throwError = true } = options || {};
|
|
238
|
+
try {
|
|
239
|
+
// The roles table paginates at 10 rows and the RBA specs run in
|
|
240
|
+
// parallel against a cluster that already ships six built-in roles, so
|
|
241
|
+
// a role created by this test is regularly on page 2 by the time it is
|
|
242
|
+
// deleted. Without walking the pages the row - and therefore its Delete
|
|
243
|
+
// button - simply never resolves, which is what the 10s click timeout
|
|
244
|
+
// reported.
|
|
245
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, this.deleteRoleButton(name), { timeout: 30000 });
|
|
246
|
+
if (!found) {
|
|
247
|
+
throw new Error(`Role "${name}" was not found on any page of the roles table`);
|
|
248
|
+
}
|
|
249
|
+
await this.deleteRoleButton(name).click();
|
|
250
|
+
await (0, test_1.expect)(this.deleteRoleModal).toBeVisible();
|
|
251
|
+
await this.deleteRoleModalDeleteButton.click();
|
|
252
|
+
await (0, test_1.expect)(this.deleteRoleModal).not.toBeVisible();
|
|
253
|
+
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(this.page, this.rolesList
|
|
254
|
+
.getByRole('cell', {
|
|
255
|
+
name,
|
|
256
|
+
})
|
|
257
|
+
.first(), name, 5000, true);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (throwError) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.warn(`Role "${name}" could not be deleted or did not exist.`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async isRolesListOpen(timeout) {
|
|
269
|
+
return this.createRoleButton
|
|
270
|
+
.first()
|
|
271
|
+
.waitFor({ state: 'visible', timeout })
|
|
272
|
+
.then(() => true)
|
|
273
|
+
.catch(() => false);
|
|
274
|
+
}
|
|
275
|
+
// Re-enters the roles list through the in-app nav rather than reloading.
|
|
276
|
+
// `page.reload()` re-runs the OIDC round-trip and its post-login redirect
|
|
277
|
+
// drops the deep path, so a reload-based retry re-ran against whatever the
|
|
278
|
+
// Admin root rendered: a view with no table at all, or the wrong table.
|
|
279
|
+
// Clicking the banner's Roles link is a client-side route change, so it
|
|
280
|
+
// cannot lose the session; the reload is kept only for the case where the
|
|
281
|
+
// app shell itself is gone.
|
|
282
|
+
async openRolesList(timeout) {
|
|
283
|
+
if (await this.isRolesListOpen(Math.min(timeout, 15000))) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const shellVisible = await this.rolesNavTab
|
|
287
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
288
|
+
.then(() => true)
|
|
289
|
+
.catch(() => false);
|
|
290
|
+
if (!shellVisible) {
|
|
291
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
292
|
+
await (0, test_1.expect)(this.rolesNavTab).toBeVisible({ timeout });
|
|
293
|
+
}
|
|
294
|
+
await this.rolesNavTab.click({ timeout });
|
|
295
|
+
await (0, test_1.expect)(this.createRoleButton.first()).toBeVisible({ timeout });
|
|
296
|
+
}
|
|
297
|
+
// `page.goBack()` is one history entry, and the role detail page's tab routes
|
|
298
|
+
// are entries of their own, so backing out of a failed assign-mapping step
|
|
299
|
+
// landed on the same role's Users tab - where the next attempt's roles-table
|
|
300
|
+
// lookup could only time out. Re-enter the list through the nav instead, and
|
|
301
|
+
// dismiss the dialog first: an open design-system dialog sets
|
|
302
|
+
// `pointer-events: none` on the body, which makes the nav link unclickable.
|
|
303
|
+
async recoverToRolesList() {
|
|
304
|
+
const modalOpen = await this.assignMappingModal
|
|
305
|
+
.isVisible()
|
|
306
|
+
.catch(() => false);
|
|
307
|
+
if (modalOpen) {
|
|
308
|
+
await this.page.keyboard.press('Escape');
|
|
309
|
+
const closed = await this.assignMappingModal
|
|
310
|
+
.waitFor({ state: 'hidden', timeout: 15000 })
|
|
311
|
+
.then(() => true)
|
|
312
|
+
.catch(() => false);
|
|
313
|
+
if (!closed) {
|
|
314
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await this.openRolesList(60000);
|
|
318
|
+
}
|
|
319
|
+
async clickAdminRole() {
|
|
320
|
+
// The roles table is fetched after the Roles tab click, and other specs in
|
|
321
|
+
// the nightly add roles that can push `admin` onto a later page, so a bare
|
|
322
|
+
// click had only the 10s actionTimeout to cover both the load and the
|
|
323
|
+
// lookup. Use the same pagination-aware lookup createRole/clickRole use.
|
|
324
|
+
//
|
|
325
|
+
// Bounded overall: this runs in the smoke suite's beforeEach, whose time is
|
|
326
|
+
// charged to the test, and three attempts of the previous per-step budgets
|
|
327
|
+
// could occupy 540s of the 720s test timeout on their own — which is why a
|
|
328
|
+
// stuck recovery here surfaced as a timeout in the test body rather than as
|
|
329
|
+
// this method's own error.
|
|
330
|
+
const deadline = Date.now() + 300000;
|
|
331
|
+
const budget = (max) => Math.max(5000, Math.min(max, deadline - Date.now()));
|
|
332
|
+
const adminCell = this.adminRole.first();
|
|
333
|
+
const maxRetries = 3;
|
|
334
|
+
let lastError;
|
|
335
|
+
for (let attempt = 0; attempt < maxRetries && Date.now() < deadline; attempt++) {
|
|
336
|
+
try {
|
|
337
|
+
// Every attempt starts from a confirmed roles list, so a swallowed nav
|
|
338
|
+
// click or a redirect back to the Admin root is recovered instead of
|
|
339
|
+
// being scanned again.
|
|
340
|
+
await this.openRolesList(budget(60000));
|
|
341
|
+
// Wait for the admin row itself before scanning: the scan gives each
|
|
342
|
+
// page only 5s, so against a table that is still fetching it reports
|
|
343
|
+
// the row missing and — with no next page to walk to — gives up in 5s.
|
|
344
|
+
// That is the shape of the run log's 52 "No more pages available after
|
|
345
|
+
// page 1" against 600 finds on page 1.
|
|
346
|
+
const onCurrentPage = await adminCell
|
|
347
|
+
.waitFor({ state: 'visible', timeout: budget(30000) })
|
|
348
|
+
.then(() => true)
|
|
349
|
+
.catch(() => false);
|
|
350
|
+
if (!onCurrentPage) {
|
|
351
|
+
// Scanned directly rather than through expectLocatorWithPagination:
|
|
352
|
+
// that wrapper hard-codes the scan to 30s (it forwards no options),
|
|
353
|
+
// which is ~5 pages, and the roles table reached 5 pages in the same
|
|
354
|
+
// run. Its retry also reloads, which is the recovery openRolesList
|
|
355
|
+
// exists to avoid.
|
|
356
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, adminCell, {
|
|
357
|
+
timeout: budget(90000),
|
|
358
|
+
});
|
|
359
|
+
if (!found) {
|
|
360
|
+
throw new Error('admin role row not found on any page of the roles table');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
await adminCell.click({ timeout: budget(30000) });
|
|
364
|
+
// The row click is the only thing that navigates to the role detail
|
|
365
|
+
// page, and nothing confirmed it landed — a click the table swallowed
|
|
366
|
+
// mid-render left the caller waiting out assignUserButton's whole
|
|
367
|
+
// budget on a page that never had one.
|
|
368
|
+
await (0, test_1.expect)(this.assignUserButton).toBeVisible({
|
|
369
|
+
timeout: budget(30000),
|
|
370
|
+
});
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
lastError = error;
|
|
375
|
+
if (attempt < maxRetries - 1) {
|
|
376
|
+
console.warn(`Attempt ${attempt + 1}: admin role detail page did not open, retrying...`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
throw new Error(`Admin role detail page did not open after ${maxRetries} attempts: ${String(lastError)}`);
|
|
381
|
+
}
|
|
382
|
+
async assignUserToRole(userId) {
|
|
383
|
+
await this.clickAssignUserButton();
|
|
384
|
+
await this.clickUsernameTextbox();
|
|
385
|
+
await this.fillUsernameTextbox(userId);
|
|
386
|
+
await this.clickAssignUserSubButton();
|
|
387
|
+
}
|
|
388
|
+
async clickAssignUserButton() {
|
|
389
|
+
// Rendered only once the role detail page has loaded its users tab, which
|
|
390
|
+
// outlasts the 10s actionTimeout a bare click inherits. Same budget the
|
|
391
|
+
// SaaS 8.10 doAssignUserToRole already gives this button.
|
|
392
|
+
await (0, test_1.expect)(this.assignUserButton).toBeVisible({ timeout: 60000 });
|
|
393
|
+
await this.assignUserButton.click({ timeout: 60000 });
|
|
394
|
+
}
|
|
395
|
+
async clickUsernameTextbox() {
|
|
396
|
+
// Waiting for the textbox is what confirms the modal finished opening.
|
|
397
|
+
await (0, test_1.expect)(this.usernameTextBox).toBeVisible({ timeout: 60000 });
|
|
398
|
+
await this.usernameTextBox.click({ timeout: 60000 });
|
|
399
|
+
}
|
|
400
|
+
async fillUsernameTextbox(userId) {
|
|
401
|
+
await this.usernameTextBox.fill(userId);
|
|
402
|
+
}
|
|
403
|
+
async clickAssignUserSubButton() {
|
|
404
|
+
// The modal's submit button stays disabled until the username field
|
|
405
|
+
// validates, so wait for it to be actionable rather than racing the 10s
|
|
406
|
+
// actionTimeout.
|
|
407
|
+
await (0, test_1.expect)(this.assignUserSubButton).toBeEnabled({ timeout: 60000 });
|
|
408
|
+
await this.assignUserSubButton.click({ timeout: 60000 });
|
|
409
|
+
}
|
|
410
|
+
async clickClientsTab() {
|
|
411
|
+
await this.clientsTab.click();
|
|
412
|
+
}
|
|
413
|
+
async clickAssignClientButton() {
|
|
414
|
+
await this.assignClientsButton.click();
|
|
415
|
+
}
|
|
416
|
+
async clickClientIdTextbox() {
|
|
417
|
+
await this.clientIdTextBox.click();
|
|
418
|
+
}
|
|
419
|
+
async fillClientIdTextbox(clientId) {
|
|
420
|
+
await this.clientIdTextBox.fill(clientId);
|
|
421
|
+
}
|
|
422
|
+
async clickAssignClientIdSubButton() {
|
|
423
|
+
await this.assignClientIdSubButton.click();
|
|
424
|
+
}
|
|
425
|
+
async addClientToRole(clientId) {
|
|
426
|
+
await this.clickClientsTab();
|
|
427
|
+
await this.clickAssignClientButton();
|
|
428
|
+
await this.clickClientIdTextbox();
|
|
429
|
+
await this.fillClientIdTextbox(clientId);
|
|
430
|
+
await this.clickAssignClientIdSubButton();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.OCIdentityRolesPage = OCIdentityRolesPage;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class OCTenantPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly createTenantMainButton: Locator;
|
|
5
|
+
readonly enterTenantNameInput: Locator;
|
|
6
|
+
readonly enterTenantIdInput: Locator;
|
|
7
|
+
readonly createTenantSubButton: Locator;
|
|
8
|
+
readonly usersTab: Locator;
|
|
9
|
+
readonly assignUsersButton: Locator;
|
|
10
|
+
readonly usernameInput: Locator;
|
|
11
|
+
readonly assignUserFinalButton: Locator;
|
|
12
|
+
readonly removeUserButton: Locator;
|
|
13
|
+
readonly removeButton: Locator;
|
|
14
|
+
readonly openSidebarButton: Locator;
|
|
15
|
+
readonly logoutButton: Locator;
|
|
16
|
+
readonly assignedApplicationsTab: Locator;
|
|
17
|
+
readonly searchApplicationsInput: Locator;
|
|
18
|
+
readonly assignApplicationsButtonFinal: Locator;
|
|
19
|
+
readonly assignApplicationsButton: Locator;
|
|
20
|
+
readonly tenantDescriptionInput: Locator;
|
|
21
|
+
readonly modalCloseButton: Locator;
|
|
22
|
+
readonly rolesTab: Locator;
|
|
23
|
+
readonly assignRoleButton: Locator;
|
|
24
|
+
readonly roleIdSearchBox: Locator;
|
|
25
|
+
readonly roleIdSearchTrigger: Locator;
|
|
26
|
+
readonly assignRoleSubButton: Locator;
|
|
27
|
+
readonly assignRoleModal: Locator;
|
|
28
|
+
readonly cancelButton: Locator;
|
|
29
|
+
readonly gotItButton: Locator;
|
|
30
|
+
readonly filterTableSearchbox: Locator;
|
|
31
|
+
readonly row: (name: string) => Locator;
|
|
32
|
+
constructor(page: Page);
|
|
33
|
+
clickCreateTenantMainButton(): Promise<void>;
|
|
34
|
+
clickCreateTenantSubButton(): Promise<void>;
|
|
35
|
+
clickEnterTenantNameInput(): Promise<void>;
|
|
36
|
+
fillEnterTenantNameInput(tenantName: string): Promise<void>;
|
|
37
|
+
clickEnterTenantIdInput(): Promise<void>;
|
|
38
|
+
fillEnterTenantIdInput(tenantId: string): Promise<void>;
|
|
39
|
+
clickUsersTab(): Promise<void>;
|
|
40
|
+
clickAssignUsersButton(): Promise<void>;
|
|
41
|
+
clickUsernameInput(): Promise<void>;
|
|
42
|
+
fillUsernameInput(user: string): Promise<void>;
|
|
43
|
+
clickAssignUserFinalButton(): Promise<void>;
|
|
44
|
+
clickRemoveUserButton(): Promise<void>;
|
|
45
|
+
clickRemoveButton(): Promise<void>;
|
|
46
|
+
clickTenantDescriptionInput(): Promise<void>;
|
|
47
|
+
fillTenantDescriptionInput(description: string): Promise<void>;
|
|
48
|
+
createTenant(tenantName: string): Promise<void>;
|
|
49
|
+
assertTenantCreated(): Promise<void>;
|
|
50
|
+
clickCancelButton(): Promise<void>;
|
|
51
|
+
clickGotItButton(): Promise<void>;
|
|
52
|
+
filterTable(tenantName: string): Promise<void>;
|
|
53
|
+
clickTenant(tenantName: string): Promise<void>;
|
|
54
|
+
assignUserToTenant(user: string): Promise<void>;
|
|
55
|
+
removeUserFromTenant(tenantName: string): Promise<void>;
|
|
56
|
+
clickOpenSidebarButton(): Promise<void>;
|
|
57
|
+
clickLogoutButton(): Promise<void>;
|
|
58
|
+
assignClientToTenant(clientName: string): Promise<void>;
|
|
59
|
+
clickAssignedApplicationsTab(): Promise<void>;
|
|
60
|
+
clickSearchApplicationsInput(): Promise<void>;
|
|
61
|
+
fillSearchApplicationsInput(clientName: string): Promise<void>;
|
|
62
|
+
clickAssignApplicationsFinalButton(): Promise<void>;
|
|
63
|
+
clickAssignApplicationsButton(): Promise<void>;
|
|
64
|
+
clickRolesTab(): Promise<void>;
|
|
65
|
+
clickAssignRoleButton(): Promise<void>;
|
|
66
|
+
clickRoleIdSearchBox(): Promise<void>;
|
|
67
|
+
private ensureRoleIdSearchBoxOpen;
|
|
68
|
+
fillRoleIdSearchBox(roleId: string, roleIdentifier: string): Promise<void>;
|
|
69
|
+
clickAssignRoleSubButton(): Promise<void>;
|
|
70
|
+
private isRoleAssigned;
|
|
71
|
+
assignRoleToTenant(roleId: string, roleIdentifier: string): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export { OCTenantPage };
|