@camunda/e2e-test-suite 0.0.1443 → 0.0.1444

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.
Files changed (122) hide show
  1. package/.github/c8_versions.json +1 -0
  2. package/README.md +2 -0
  3. package/dist/fixtures/SM-8.11.d.ts +77 -0
  4. package/dist/fixtures/SM-8.11.js +176 -0
  5. package/dist/pages/SM-8.11/Authorization.d.ts +39 -0
  6. package/dist/pages/SM-8.11/Authorization.js +349 -0
  7. package/dist/pages/SM-8.11/ClusterDetailsPage.d.ts +8 -0
  8. package/dist/pages/SM-8.11/ClusterDetailsPage.js +65 -0
  9. package/dist/pages/SM-8.11/ClusterPage.d.ts +11 -0
  10. package/dist/pages/SM-8.11/ClusterPage.js +37 -0
  11. package/dist/pages/SM-8.11/ConnectorMarketplacePage.d.ts +27 -0
  12. package/dist/pages/SM-8.11/ConnectorMarketplacePage.js +187 -0
  13. package/dist/pages/SM-8.11/ConnectorSettingsPage.d.ts +41 -0
  14. package/dist/pages/SM-8.11/ConnectorSettingsPage.js +153 -0
  15. package/dist/pages/SM-8.11/ConnectorTemplatePage.d.ts +11 -0
  16. package/dist/pages/SM-8.11/ConnectorTemplatePage.js +34 -0
  17. package/dist/pages/SM-8.11/FormJsPage.d.ts +33 -0
  18. package/dist/pages/SM-8.11/FormJsPage.js +109 -0
  19. package/dist/pages/SM-8.11/HomePage.d.ts +20 -0
  20. package/dist/pages/SM-8.11/HomePage.js +49 -0
  21. package/dist/pages/SM-8.11/IdentityTenantPage.d.ts +53 -0
  22. package/dist/pages/SM-8.11/IdentityTenantPage.js +264 -0
  23. package/dist/pages/SM-8.11/IdentityUsersPage.d.ts +92 -0
  24. package/dist/pages/SM-8.11/IdentityUsersPage.js +485 -0
  25. package/dist/pages/SM-8.11/KeycloakAdminPage.d.ts +17 -0
  26. package/dist/pages/SM-8.11/KeycloakAdminPage.js +78 -0
  27. package/dist/pages/SM-8.11/KeycloakLoginPage.d.ts +12 -0
  28. package/dist/pages/SM-8.11/KeycloakLoginPage.js +32 -0
  29. package/dist/pages/SM-8.11/KeycloakUtils.d.ts +17 -0
  30. package/dist/pages/SM-8.11/KeycloakUtils.js +94 -0
  31. package/dist/pages/SM-8.11/LoginPage.d.ts +21 -0
  32. package/dist/pages/SM-8.11/LoginPage.js +112 -0
  33. package/dist/pages/SM-8.11/ManagementIdentityPage.d.ts +44 -0
  34. package/dist/pages/SM-8.11/ManagementIdentityPage.js +176 -0
  35. package/dist/pages/SM-8.11/ModelerCreatePage.d.ts +238 -0
  36. package/dist/pages/SM-8.11/ModelerCreatePage.js +1950 -0
  37. package/dist/pages/SM-8.11/ModelerHomePage.d.ts +97 -0
  38. package/dist/pages/SM-8.11/ModelerHomePage.js +956 -0
  39. package/dist/pages/SM-8.11/NavigationPage.d.ts +58 -0
  40. package/dist/pages/SM-8.11/NavigationPage.js +389 -0
  41. package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.d.ts +10 -0
  42. package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.js +22 -0
  43. package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.d.ts +22 -0
  44. package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.js +120 -0
  45. package/dist/pages/SM-8.11/OCIdentityGroupsPage.d.ts +39 -0
  46. package/dist/pages/SM-8.11/OCIdentityGroupsPage.js +154 -0
  47. package/dist/pages/SM-8.11/OCIdentityHomePage.d.ts +23 -0
  48. package/dist/pages/SM-8.11/OCIdentityHomePage.js +115 -0
  49. package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.d.ts +23 -0
  50. package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.js +116 -0
  51. package/dist/pages/SM-8.11/OCIdentityRolesPage.d.ts +61 -0
  52. package/dist/pages/SM-8.11/OCIdentityRolesPage.js +433 -0
  53. package/dist/pages/SM-8.11/OCTenantPage.d.ts +73 -0
  54. package/dist/pages/SM-8.11/OCTenantPage.js +399 -0
  55. package/dist/pages/SM-8.11/OperateHomePage.d.ts +26 -0
  56. package/dist/pages/SM-8.11/OperateHomePage.js +139 -0
  57. package/dist/pages/SM-8.11/OperateProcessInstancePage.d.ts +30 -0
  58. package/dist/pages/SM-8.11/OperateProcessInstancePage.js +276 -0
  59. package/dist/pages/SM-8.11/OperateProcessesPage.d.ts +38 -0
  60. package/dist/pages/SM-8.11/OperateProcessesPage.js +383 -0
  61. package/dist/pages/SM-8.11/OptimizeCollectionsPage.d.ts +17 -0
  62. package/dist/pages/SM-8.11/OptimizeCollectionsPage.js +62 -0
  63. package/dist/pages/SM-8.11/OptimizeDashboardPage.d.ts +14 -0
  64. package/dist/pages/SM-8.11/OptimizeDashboardPage.js +63 -0
  65. package/dist/pages/SM-8.11/OptimizeHomePage.d.ts +19 -0
  66. package/dist/pages/SM-8.11/OptimizeHomePage.js +76 -0
  67. package/dist/pages/SM-8.11/OptimizeReportPage.d.ts +50 -0
  68. package/dist/pages/SM-8.11/OptimizeReportPage.js +209 -0
  69. package/dist/pages/SM-8.11/PlayPage.d.ts +47 -0
  70. package/dist/pages/SM-8.11/PlayPage.js +389 -0
  71. package/dist/pages/SM-8.11/ProcessApplicationPage.d.ts +35 -0
  72. package/dist/pages/SM-8.11/ProcessApplicationPage.js +340 -0
  73. package/dist/pages/SM-8.11/SettingsPage.d.ts +12 -0
  74. package/dist/pages/SM-8.11/SettingsPage.js +34 -0
  75. package/dist/pages/SM-8.11/TaskDetailsPage.d.ts +70 -0
  76. package/dist/pages/SM-8.11/TaskDetailsPage.js +273 -0
  77. package/dist/pages/SM-8.11/TaskPanelPage.d.ts +29 -0
  78. package/dist/pages/SM-8.11/TaskPanelPage.js +320 -0
  79. package/dist/pages/SM-8.11/TaskProcessesPage.d.ts +15 -0
  80. package/dist/pages/SM-8.11/TaskProcessesPage.js +77 -0
  81. package/dist/pages/SM-8.11/UtilitiesPage.d.ts +38 -0
  82. package/dist/pages/SM-8.11/UtilitiesPage.js +573 -0
  83. package/dist/pages/SM-8.11/optimizeReportUtils.d.ts +6 -0
  84. package/dist/pages/SM-8.11/optimizeReportUtils.js +77 -0
  85. package/dist/tests/SM-8.11/cluster-management-user-flows.spec.d.ts +1 -0
  86. package/dist/tests/SM-8.11/cluster-management-user-flows.spec.js +48 -0
  87. package/dist/tests/SM-8.11/cluster-variables.spec.d.ts +1 -0
  88. package/dist/tests/SM-8.11/cluster-variables.spec.js +226 -0
  89. package/dist/tests/SM-8.11/connectors-user-flows.spec.d.ts +1 -0
  90. package/dist/tests/SM-8.11/connectors-user-flows.spec.js +307 -0
  91. package/dist/tests/SM-8.11/document-handling-user-flows.spec.d.ts +1 -0
  92. package/dist/tests/SM-8.11/document-handling-user-flows.spec.js +174 -0
  93. package/dist/tests/SM-8.11/hto-user-flows.spec.d.ts +1 -0
  94. package/dist/tests/SM-8.11/hto-user-flows.spec.js +410 -0
  95. package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.d.ts +1 -0
  96. package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.js +24 -0
  97. package/dist/tests/SM-8.11/identity-user-flows.spec.d.ts +1 -0
  98. package/dist/tests/SM-8.11/identity-user-flows.spec.js +220 -0
  99. package/dist/tests/SM-8.11/license-key-user-flows.spec.d.ts +1 -0
  100. package/dist/tests/SM-8.11/license-key-user-flows.spec.js +461 -0
  101. package/dist/tests/SM-8.11/login.spec.d.ts +1 -0
  102. package/dist/tests/SM-8.11/login.spec.js +24 -0
  103. package/dist/tests/SM-8.11/migration-path-user-flows.spec.d.ts +1 -0
  104. package/dist/tests/SM-8.11/migration-path-user-flows.spec.js +263 -0
  105. package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.d.ts +1 -0
  106. package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.js +607 -0
  107. package/dist/tests/SM-8.11/optimize-user-flows.spec.d.ts +1 -0
  108. package/dist/tests/SM-8.11/optimize-user-flows.spec.js +505 -0
  109. package/dist/tests/SM-8.11/play.spec.d.ts +1 -0
  110. package/dist/tests/SM-8.11/play.spec.js +144 -0
  111. package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.d.ts +1 -0
  112. package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.js +712 -0
  113. package/dist/tests/SM-8.11/smoke-tests.spec.d.ts +1 -0
  114. package/dist/tests/SM-8.11/smoke-tests.spec.js +167 -0
  115. package/dist/tests/SM-8.11/test-setup.spec.d.ts +1 -0
  116. package/dist/tests/SM-8.11/test-setup.spec.js +24 -0
  117. package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.d.ts +1 -0
  118. package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.js +58 -0
  119. package/dist/tests/SM-8.11/web-modeler-user-flows.spec.d.ts +1 -0
  120. package/dist/tests/SM-8.11/web-modeler-user-flows.spec.js +223 -0
  121. package/package.json +1 -1
  122. package/playwright.config.ts +21 -0
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OCTenantPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const expectTextWithPagination_1 = require("../../utils/assertionHelpers/expectTextWithPagination");
6
+ const ROLE_ID_SEARCH_LABEL = /^(Search by role ID|searchByRoleId)$/i;
7
+ class OCTenantPage {
8
+ page;
9
+ createTenantMainButton;
10
+ enterTenantNameInput;
11
+ enterTenantIdInput;
12
+ createTenantSubButton;
13
+ usersTab;
14
+ assignUsersButton;
15
+ usernameInput;
16
+ assignUserFinalButton;
17
+ removeUserButton;
18
+ removeButton;
19
+ openSidebarButton;
20
+ logoutButton;
21
+ assignedApplicationsTab;
22
+ searchApplicationsInput;
23
+ assignApplicationsButtonFinal;
24
+ assignApplicationsButton;
25
+ tenantDescriptionInput;
26
+ modalCloseButton;
27
+ rolesTab;
28
+ assignRoleButton;
29
+ roleIdSearchBox;
30
+ roleIdSearchTrigger;
31
+ assignRoleSubButton;
32
+ assignRoleModal;
33
+ cancelButton;
34
+ gotItButton;
35
+ filterTableSearchbox;
36
+ row;
37
+ constructor(page) {
38
+ this.page = page;
39
+ this.createTenantMainButton = page.getByRole('button', {
40
+ name: 'Create Tenant',
41
+ });
42
+ this.enterTenantNameInput = page.getByPlaceholder('Enter tenant name');
43
+ this.enterTenantIdInput = page.getByPlaceholder('Enter tenant ID');
44
+ this.createTenantSubButton = page
45
+ .getByLabel('Create new tenant')
46
+ .getByRole('button', { name: 'Create tenant' });
47
+ this.usersTab = page.getByRole('tab', { name: 'Users' });
48
+ this.assignUsersButton = page.getByRole('button', { name: 'Assign user' });
49
+ this.usernameInput = page.getByTitle('Username');
50
+ this.assignUserFinalButton = page
51
+ .getByLabel('Assign user')
52
+ .getByRole('button', { name: 'Assign user' });
53
+ this.removeUserButton = page
54
+ .getByRole('dialog', { name: 'Remove user' })
55
+ .getByRole('button', { name: 'Remove user' });
56
+ this.removeButton = page.getByRole('button', { name: 'Remove', exact: true });
57
+ this.openSidebarButton = page.getByLabel('Open User Sidebar');
58
+ this.logoutButton = page.getByRole('button', { name: 'Log out' });
59
+ this.assignedApplicationsTab = page.getByRole('tab', {
60
+ name: 'Assigned applications',
61
+ });
62
+ this.searchApplicationsInput = page.getByPlaceholder('Search applications');
63
+ this.assignApplicationsButtonFinal = page
64
+ .getByLabel('Assign applications')
65
+ .getByRole('button', { name: 'Assign applications' });
66
+ this.assignApplicationsButton = page.getByRole('button', {
67
+ name: 'Assign applications',
68
+ });
69
+ this.tenantDescriptionInput = page.getByTitle('Description');
70
+ this.modalCloseButton = page
71
+ .getByRole('dialog')
72
+ .getByRole('button', { name: 'Close' });
73
+ this.rolesTab = page.getByRole('tab', { name: 'Roles' });
74
+ this.assignRoleButton = page.getByRole('button', { name: 'Assign role' });
75
+ // The design-system Admin shell renders the Assign role modal's search
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.
92
+ this.roleIdSearchBox = page
93
+ .locator('[data-slot="multi-select-content"] input[cmdk-input]')
94
+ .or(page.getByPlaceholder('Search by role ID'))
95
+ .or(page.getByRole('searchbox', { name: 'Search by Role ID' }))
96
+ .first();
97
+ this.assignRoleModal = page.getByLabel('Assign role');
98
+ this.assignRoleSubButton = this.assignRoleModal.getByRole('button', {
99
+ name: 'Assign role',
100
+ });
101
+ this.cancelButton = page
102
+ .getByRole('dialog')
103
+ .getByRole('button', { name: 'Cancel' });
104
+ this.gotItButton = page.getByRole('button', { name: 'Got it' });
105
+ // The design-system Admin shell replaced Carbon's table toolbar search
106
+ // (labelled "Filter table") with an input whose aria-label is its
107
+ // placeholder, so match the new shell first and fall back to the legacy
108
+ // one - the same dual-shell pattern the Admin nav locators use.
109
+ this.filterTableSearchbox = page
110
+ .getByRole('searchbox', { name: 'Search by tenant ID' })
111
+ .or(page.getByRole('searchbox', { name: 'Filter table' }))
112
+ .first();
113
+ this.row = (name) => this.page.getByRole('row').filter({ hasText: name }).first();
114
+ }
115
+ async clickCreateTenantMainButton() {
116
+ await this.createTenantMainButton.click({ timeout: 60000 });
117
+ }
118
+ async clickCreateTenantSubButton() {
119
+ await this.createTenantSubButton.click({ timeout: 60000 });
120
+ }
121
+ async clickEnterTenantNameInput() {
122
+ await this.enterTenantNameInput.click();
123
+ }
124
+ async fillEnterTenantNameInput(tenantName) {
125
+ await this.enterTenantNameInput.fill(tenantName);
126
+ }
127
+ async clickEnterTenantIdInput() {
128
+ await this.enterTenantIdInput.click();
129
+ }
130
+ async fillEnterTenantIdInput(tenantId) {
131
+ await this.enterTenantIdInput.fill(tenantId);
132
+ }
133
+ async clickUsersTab() {
134
+ await this.usersTab.click({ timeout: 60000 });
135
+ }
136
+ async clickAssignUsersButton() {
137
+ await this.assignUsersButton.click();
138
+ }
139
+ async clickUsernameInput() {
140
+ await this.usernameInput.click();
141
+ }
142
+ async fillUsernameInput(user) {
143
+ await this.usernameInput.fill(user);
144
+ }
145
+ async clickAssignUserFinalButton() {
146
+ try {
147
+ await this.assignUserFinalButton.click();
148
+ await (0, test_1.expect)(this.page.getByRole('dialog')).not.toBeVisible();
149
+ }
150
+ catch (e) {
151
+ if (await this.page.getByText('ALREADY_EXISTS').first().isVisible()) {
152
+ console.log('User is already assigned to tenant');
153
+ await this.clickCancelButton();
154
+ }
155
+ else {
156
+ throw new Error('Assigning user to tenant failed' + e);
157
+ }
158
+ }
159
+ }
160
+ async clickRemoveUserButton() {
161
+ await this.removeUserButton.click();
162
+ }
163
+ async clickRemoveButton() {
164
+ await this.removeButton.click();
165
+ }
166
+ async clickTenantDescriptionInput() {
167
+ await this.tenantDescriptionInput.click();
168
+ }
169
+ async fillTenantDescriptionInput(description) {
170
+ await this.tenantDescriptionInput.fill(description);
171
+ }
172
+ async createTenant(tenantName) {
173
+ await this.clickCreateTenantMainButton();
174
+ await this.clickEnterTenantNameInput();
175
+ await this.fillEnterTenantNameInput(tenantName);
176
+ await this.clickEnterTenantIdInput();
177
+ await this.fillEnterTenantIdInput(tenantName);
178
+ await this.clickTenantDescriptionInput();
179
+ await this.fillTenantDescriptionInput('This is a test');
180
+ await this.clickCreateTenantSubButton();
181
+ await this.assertTenantCreated();
182
+ }
183
+ async assertTenantCreated() {
184
+ try {
185
+ await (0, test_1.expect)(this.page.getByText('successfully created')).toBeVisible({
186
+ timeout: 8000,
187
+ });
188
+ await this.clickGotItButton();
189
+ }
190
+ catch (error) {
191
+ if (await this.page.getByText('ALREADY_EXISTS').first().isVisible()) {
192
+ await this.clickCancelButton();
193
+ }
194
+ else {
195
+ throw new Error('Creating tenant failed' + error);
196
+ }
197
+ }
198
+ }
199
+ async clickCancelButton() {
200
+ await this.cancelButton.click({ timeout: 30000 });
201
+ }
202
+ async clickGotItButton() {
203
+ await this.gotItButton.click({ timeout: 30000 });
204
+ }
205
+ async filterTable(tenantName) {
206
+ await this.filterTableSearchbox.click({ timeout: 60000 });
207
+ await this.filterTableSearchbox.fill(tenantName);
208
+ await this.filterTableSearchbox.press('Enter');
209
+ }
210
+ async clickTenant(tenantName) {
211
+ const maxRetries = 10;
212
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
213
+ try {
214
+ await this.page.reload();
215
+ await this.page.waitForLoadState('networkidle');
216
+ await this.filterTable(tenantName);
217
+ const tenant = this.page.getByText(tenantName).first();
218
+ await (0, test_1.expect)(tenant).toBeVisible({ timeout: 30000 });
219
+ await tenant.click({ timeout: 30000 });
220
+ return;
221
+ }
222
+ catch (error) {
223
+ console.warn(`Attempt ${attempt} to click tenant '${tenantName}' failed. Retrying...`, error);
224
+ if (attempt === maxRetries) {
225
+ throw new Error(`Failed to click tenant '${tenantName}' after ${maxRetries} attempts. Last error: ${error}`);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ async assignUserToTenant(user) {
231
+ await this.clickUsersTab();
232
+ await this.clickAssignUsersButton();
233
+ await this.clickUsernameInput();
234
+ await this.fillUsernameInput(user);
235
+ await this.clickAssignUserFinalButton();
236
+ }
237
+ async removeUserFromTenant(tenantName) {
238
+ await (0, expectTextWithPagination_1.expectTextWithPagination)(this.page, tenantName);
239
+ await this.page.getByText(tenantName).first().click();
240
+ await this.clickUsersTab();
241
+ await this.clickRemoveButton();
242
+ await this.clickRemoveUserButton();
243
+ await (0, test_1.expect)(this.page.getByText('Tenant member has been removed.')).toBeVisible({
244
+ timeout: 60000,
245
+ });
246
+ }
247
+ async clickOpenSidebarButton() {
248
+ await this.openSidebarButton.click({ timeout: 60000 });
249
+ }
250
+ async clickLogoutButton() {
251
+ await this.logoutButton.click({ timeout: 60000 });
252
+ }
253
+ async assignClientToTenant(clientName) {
254
+ await this.clickAssignedApplicationsTab();
255
+ if (await this.page.getByText(clientName, { exact: true }).isVisible()) {
256
+ return;
257
+ }
258
+ else {
259
+ await this.clickAssignApplicationsButton();
260
+ await this.clickSearchApplicationsInput();
261
+ await this.fillSearchApplicationsInput(clientName);
262
+ await this.page.getByText(clientName, { exact: true }).click();
263
+ await this.clickAssignApplicationsFinalButton();
264
+ await (0, test_1.expect)(this.page.getByText('Applications assigned')).toBeVisible({
265
+ timeout: 60000,
266
+ });
267
+ }
268
+ }
269
+ async clickAssignedApplicationsTab() {
270
+ await this.assignedApplicationsTab.click({ timeout: 60000 });
271
+ }
272
+ async clickSearchApplicationsInput() {
273
+ await this.searchApplicationsInput.click({ timeout: 60000 });
274
+ }
275
+ async fillSearchApplicationsInput(clientName) {
276
+ await this.searchApplicationsInput.fill(clientName);
277
+ }
278
+ async clickAssignApplicationsFinalButton() {
279
+ await this.assignApplicationsButtonFinal.click({ timeout: 60000 });
280
+ }
281
+ async clickAssignApplicationsButton() {
282
+ await this.assignApplicationsButton.click({ timeout: 60000 });
283
+ }
284
+ async clickRolesTab() {
285
+ await this.rolesTab.click();
286
+ }
287
+ async clickAssignRoleButton() {
288
+ await this.assignRoleButton.click();
289
+ }
290
+ async clickRoleIdSearchBox() {
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
+ }
304
+ }
305
+ async fillRoleIdSearchBox(roleId, roleIdentifier) {
306
+ // The option row renders the ID followed by the name, so the name is the
307
+ // tail of `roleIdentifier`.
308
+ const roleName = roleIdentifier.startsWith(roleId)
309
+ ? roleIdentifier.slice(roleId.length)
310
+ : '';
311
+ // The design-system row separates the two parts with an em dash
312
+ // ('connectors — Connectors') while the legacy Carbon row concatenates
313
+ // them ('connectorsConnectors'), so match both with an optional
314
+ // dash/whitespace run between the ID and the name.
315
+ const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
316
+ const rolePattern = new RegExp(`${escapeForRegExp(roleId)}[-\\s\\u2010-\\u2015]*${escapeForRegExp(roleName)}`);
317
+ // The design-system shell portals the matches into a listbox rendered as
318
+ // a sibling of the dialog, so they cannot be scoped to the modal. The
319
+ // legacy Carbon list stays inside it and must remain scoped: the tenant's
320
+ // own roles table sits behind the modal and carries the same role text, so
321
+ // an unscoped match resolves to that row and its click is intercepted by
322
+ // the modal overlay.
323
+ const result = this.page
324
+ .getByRole('option')
325
+ .filter({ hasText: rolePattern })
326
+ .or(this.assignRoleModal.getByText(rolePattern))
327
+ .first();
328
+ // The dropdown no longer filters client-side: it queries
329
+ // `/v2/roles/search` with `name: {$like: '*<typed>*'}`, which is
330
+ // case-sensitive, so the role ID ('connectors') never matches the default
331
+ // role's name ('Connectors'): search the ID first for the shells that
332
+ // still match on it, then fall back to the name.
333
+ const searchTerms = [roleId];
334
+ if (roleName !== '' && roleName !== roleId) {
335
+ searchTerms.push(roleName);
336
+ }
337
+ // The dropdown only queries for a non-empty value, so clearing the box
338
+ // between terms resets it before the next one is typed.
339
+ await (0, test_1.expect)(async () => {
340
+ for (const searchTerm of searchTerms) {
341
+ await this.ensureRoleIdSearchBoxOpen();
342
+ await this.roleIdSearchBox.fill('');
343
+ await this.page.waitForLoadState('networkidle');
344
+ // The cmdk input only issues its server-side query on real
345
+ // keystrokes, so type the term in rather than setting it in one shot.
346
+ await this.roleIdSearchBox.pressSequentially(searchTerm);
347
+ const offered = await result
348
+ .waitFor({ state: 'visible', timeout: 15000 })
349
+ .then(() => true)
350
+ .catch(() => false);
351
+ if (offered) {
352
+ return;
353
+ }
354
+ }
355
+ throw new Error(`Role ${roleIdentifier} was not offered by the assign role search for` +
356
+ ` any of: ${searchTerms.join(', ')}`);
357
+ }).toPass({ timeout: 120000, intervals: [2000] });
358
+ await result.click();
359
+ }
360
+ async clickAssignRoleSubButton() {
361
+ await this.assignRoleSubButton.click();
362
+ }
363
+ async isRoleAssigned(roleId) {
364
+ return this.row(roleId)
365
+ .waitFor({ state: 'visible', timeout: 15000 })
366
+ .then(() => true)
367
+ .catch(() => false);
368
+ }
369
+ async assignRoleToTenant(roleId, roleIdentifier) {
370
+ await this.clickRolesTab();
371
+ await this.page.waitForLoadState('networkidle');
372
+ // The roles table renders from its own fetch, which `networkidle` can
373
+ // resolve ahead of, so an immediate `isVisible()` reads the table as empty
374
+ // and reopens the modal for a role that is already assigned. The assign
375
+ // dropdown never offers an already-assigned role, so that attempt can
376
+ // never converge.
377
+ if (await this.isRoleAssigned(roleId)) {
378
+ console.log(`Role ${roleId} is already assigned to tenant`);
379
+ return;
380
+ }
381
+ await this.clickAssignRoleButton();
382
+ await this.page.waitForLoadState('networkidle');
383
+ await this.clickRoleIdSearchBox();
384
+ await this.fillRoleIdSearchBox(roleId, roleIdentifier);
385
+ await this.clickAssignRoleSubButton();
386
+ await (0, test_1.expect)(this.page.getByLabel('Assign role')).not.toBeVisible({
387
+ timeout: 30000,
388
+ });
389
+ // Use retry-with-reload pattern for role visibility (MT operations need time for data replication)
390
+ await (0, test_1.expect)(async () => {
391
+ if (!(await this.row(roleId).isVisible())) {
392
+ await this.page.reload();
393
+ await this.page.waitForLoadState('networkidle');
394
+ }
395
+ await (0, test_1.expect)(this.row(roleId)).toBeVisible({ timeout: 10000 });
396
+ }).toPass({ timeout: 60000, intervals: [5000] });
397
+ }
398
+ }
399
+ exports.OCTenantPage = OCTenantPage;
@@ -0,0 +1,26 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ declare class OperateHomePage {
3
+ private page;
4
+ readonly operateBanner: Locator;
5
+ readonly processesTab: Locator;
6
+ readonly informationDialog: Locator;
7
+ readonly editVariableButton: Locator;
8
+ readonly variableValueInput: Locator;
9
+ readonly saveVariableButton: Locator;
10
+ readonly editVariableSpinner: Locator;
11
+ readonly licenseKeyTagNonProduction: Locator;
12
+ readonly licenseKeyTagProduction: Locator;
13
+ readonly processPageHeading: Locator;
14
+ readonly applyButton: Locator;
15
+ readonly openButton: Locator;
16
+ constructor(page: Page);
17
+ clickProcessesTab(): Promise<void>;
18
+ closeInformationDialog(): Promise<void>;
19
+ clickEditVariableButton(variableName: string): Promise<void>;
20
+ clickVariableValueInput(): Promise<void>;
21
+ clearVariableValueInput(): Promise<void>;
22
+ fillVariableValueInput(newValue: string): Promise<void>;
23
+ clickSaveVariableButton(): Promise<void>;
24
+ openProcess(processName: string): Promise<void>;
25
+ }
26
+ export { OperateHomePage };
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OperateHomePage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
6
+ const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
7
+ const constants_1 = require("../../utils/constants");
8
+ class OperateHomePage {
9
+ page;
10
+ operateBanner;
11
+ processesTab;
12
+ informationDialog;
13
+ editVariableButton;
14
+ variableValueInput;
15
+ saveVariableButton;
16
+ editVariableSpinner;
17
+ licenseKeyTagNonProduction;
18
+ licenseKeyTagProduction;
19
+ processPageHeading;
20
+ applyButton;
21
+ openButton;
22
+ constructor(page) {
23
+ this.page = page;
24
+ // Same Design System header rollout NavigationPage.operatePageBanner
25
+ // covers: the home link is aria-label="Camunda Operate" once
26
+ // `navV2Enabled` is on, and "Camunda logo Operate" while it is off.
27
+ this.operateBanner = page
28
+ .getByRole('link', { name: 'Camunda logo Operate' })
29
+ .or(page.getByRole('link', { name: 'Camunda Operate', exact: true }))
30
+ .first();
31
+ this.processesTab = page.getByRole('link', {
32
+ name: 'Processes',
33
+ exact: true,
34
+ });
35
+ this.informationDialog = page.getByRole('button', {
36
+ name: 'Close this dialog',
37
+ });
38
+ this.editVariableButton = page.getByTestId('edit-variable-button');
39
+ this.variableValueInput = page.getByTestId('edit-variable-value');
40
+ this.saveVariableButton = page.getByLabel('Save variable');
41
+ this.editVariableSpinner = page
42
+ .getByTestId('variable-operation-spinner')
43
+ .locator('circle')
44
+ .nth(1);
45
+ this.licenseKeyTagNonProduction = page
46
+ .getByText('Non-production license')
47
+ .first();
48
+ this.licenseKeyTagProduction = page.getByText('Production license').first();
49
+ this.processPageHeading = page
50
+ .getByTestId('expanded-panel')
51
+ .getByRole('heading', { name: 'Process' });
52
+ this.applyButton = page.getByRole('button', { name: 'Apply' });
53
+ this.openButton = page
54
+ .getByTestId('variable-testVariable')
55
+ .getByLabel('Open');
56
+ }
57
+ async clickProcessesTab() {
58
+ // Called right after navigating into Operate, a Vite SPA whose nav bar can
59
+ // take longer than clickLocatorWithRetry's default 10s visibilityTimeout to
60
+ // render on a loaded cluster — so all 3 attempts could burn inside 30s and
61
+ // throw while the link was still on its way. Give each attempt the 30s the
62
+ // callers that pre-wait this same link already assume.
63
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.processesTab, {
64
+ totalTimeout: 120000,
65
+ visibilityTimeout: 30000,
66
+ });
67
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.processPageHeading, {
68
+ // Defaults (10s per attempt, 60s total) gave the Processes panel less
69
+ // budget than the tab click above already assumes it needs on a loaded
70
+ // cluster, so all three attempts could expire while the view was still
71
+ // rendering. Match the 30s/120s the click uses.
72
+ visibilityTimeout: 30000,
73
+ totalTimeout: 120000,
74
+ preAction: async () => {
75
+ await (0, test_1.expect)(this.processesTab).toBeVisible({
76
+ timeout: constants_1._1_SECOND_IN_MS * 4,
77
+ });
78
+ await this.processesTab.click({ timeout: constants_1._1_SECOND_IN_MS * 4 });
79
+ },
80
+ postAction: async () => {
81
+ // Operate is a Vite SPA: a transient module/CSS preload failure
82
+ // renders a "Something went wrong - Please reload the page" screen
83
+ // on which the Processes tab click is a no-op. Reload to fetch fresh
84
+ // assets so the next attempt can render the Processes view.
85
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
86
+ },
87
+ });
88
+ }
89
+ async closeInformationDialog() {
90
+ await this.informationDialog.click();
91
+ }
92
+ async clickEditVariableButton(variableName) {
93
+ const editVariableButton = 'variable-' + variableName;
94
+ const button = this.page.getByTestId(editVariableButton).getByLabel('Edit');
95
+ await (0, test_1.expect)(button).toBeVisible({ timeout: 30000 });
96
+ await button.click();
97
+ }
98
+ async clickVariableValueInput() {
99
+ await this.variableValueInput.click();
100
+ }
101
+ async clearVariableValueInput() {
102
+ await this.variableValueInput.clear();
103
+ }
104
+ async fillVariableValueInput(newValue) {
105
+ await this.openButton.click();
106
+ await this.page
107
+ .getByLabel('Edit Variable "testVariable"')
108
+ .getByText('"testValue"')
109
+ .click();
110
+ // Replace the whole JSON document in one shot. Typing it per-character
111
+ // raced the editor's re-render: after a couple of keystrokes the caret
112
+ // jumped outside the closing quote, leaving invalid JSON such as
113
+ // `"up"datedValue`. Apply then rejects it silently, the modal stays open
114
+ // and the old value is still on screen, so the caller's
115
+ // `not.toBeVisible('"testValue"')` assertion times out. insertText emits a
116
+ // single input event, so there is no intermediate state to race.
117
+ //
118
+ // Pass only the OPENING quote: Monaco auto-closes it. Supplying both ends
119
+ // yields `"updatedValue""` -- invalid JSON, and Apply rejects it exactly
120
+ // like the per-character version did. Same recipe as
121
+ // pages/c8Run-8.10/OperateHomePage.ts.
122
+ //
123
+ // Select with Home + Shift+End rather than ControlOrMeta+A: headless
124
+ // Chromium on macOS does not apply Meta+A as select-all inside Monaco's
125
+ // native edit context, so the insert landed mid-string. The value is a
126
+ // single-line JSON string, so selecting the line selects the document on
127
+ // every platform.
128
+ await this.page.keyboard.press('Home');
129
+ await this.page.keyboard.press('Shift+End');
130
+ await this.page.keyboard.insertText(`"${newValue}`);
131
+ }
132
+ async clickSaveVariableButton() {
133
+ await this.applyButton.click();
134
+ }
135
+ async openProcess(processName) {
136
+ await this.page.getByText(processName).click();
137
+ }
138
+ }
139
+ exports.OperateHomePage = OperateHomePage;
@@ -0,0 +1,30 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ declare class OperateProcessInstancePage {
3
+ private page;
4
+ readonly diagram: Locator;
5
+ readonly completedIcon: Locator;
6
+ readonly diagramSpinner: Locator;
7
+ readonly activeIcon: Locator;
8
+ readonly incidentIcon: Locator;
9
+ readonly variablesList: Locator;
10
+ readonly messageVariable: Locator;
11
+ constructor(page: Page);
12
+ connectorResultVariableName(name: string): Promise<Locator>;
13
+ connectorResultVariableValue(variableName: string): Promise<Locator>;
14
+ completedIconAssertion(): Promise<void>;
15
+ activeIconAssertion(): Promise<void>;
16
+ activeUserTaskIconVisibleAssertion(taskName: string): Promise<void>;
17
+ activeUserTaskIconNotVisibleAssertion(taskName: string): Promise<void>;
18
+ tenantNameAssertion(name: string): Promise<void>;
19
+ incidentIconAssertion(): Promise<void>;
20
+ assertEitherIncidentOrActiveIconVisible(): Promise<string>;
21
+ assertEitherIncidentOrCompletedIconVisible(): Promise<string>;
22
+ assertProcessCompleteStatusWithRetry(timeout?: number, maxRetries?: number): Promise<void>;
23
+ assertProcessVariableContainsText(variableName: string, text: string): Promise<void>;
24
+ private captureVariableJsonValue;
25
+ assertResultVariableVisibleWithRetry(variableName: string): Promise<void>;
26
+ assertActiveTokenIsPresent(): Promise<void>;
27
+ assertBusinessIdVisible(businessId: string): Promise<void>;
28
+ assertVariablesListVisible(timeout?: number): Promise<void>;
29
+ }
30
+ export { OperateProcessInstancePage };