@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.
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,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskDetailsPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
6
+ function cardinalToOrdinal(numberValue) {
7
+ const realOrderIndex = numberValue.toString();
8
+ if (['11', '12', '13'].includes(realOrderIndex.slice(-2))) {
9
+ return `${realOrderIndex}th`;
10
+ }
11
+ switch (realOrderIndex.slice(-1)) {
12
+ case '1':
13
+ return `${realOrderIndex}st`;
14
+ case '2':
15
+ return `${realOrderIndex}nd`;
16
+ case '3':
17
+ return `${realOrderIndex}rd`;
18
+ default:
19
+ return `${realOrderIndex}th`;
20
+ }
21
+ }
22
+ class TaskDetailsPage {
23
+ page;
24
+ assignToMeButton;
25
+ completeButton;
26
+ unassignButton;
27
+ assignee;
28
+ completeTaskButton;
29
+ addVariableButton;
30
+ detailsPanel;
31
+ detailsHeader;
32
+ pendingTaskDescription;
33
+ pickATaskHeader;
34
+ emptyTaskMessage;
35
+ nameInput;
36
+ addressInput;
37
+ ageInput;
38
+ variablesTable;
39
+ nameColumnHeader;
40
+ valueColumnHeader;
41
+ form;
42
+ numberInput;
43
+ incrementButton;
44
+ decrementButton;
45
+ dateInput;
46
+ timeInput;
47
+ checkbox;
48
+ selectDropdown;
49
+ tagList;
50
+ detailsInfo;
51
+ textInput;
52
+ taskCompletedBanner;
53
+ assignedToMeText;
54
+ noPreviewContainer;
55
+ previewContainer;
56
+ constructor(page) {
57
+ this.page = page;
58
+ this.assignToMeButton = page.getByRole('button', { name: 'Assign to me' });
59
+ this.completeButton = page.getByRole('button', { name: 'Complete' });
60
+ this.unassignButton = page.getByRole('button', { name: 'Unassign' });
61
+ this.assignee = page.getByTestId('assignee');
62
+ this.completeTaskButton = page.getByRole('button', { name: 'Complete Task' });
63
+ this.addVariableButton = page.getByRole('button', { name: 'Add Variable' });
64
+ this.detailsPanel = this.page.getByRole('complementary', {
65
+ name: 'Task details right panel',
66
+ });
67
+ this.detailsHeader = page.getByTitle('Task details header');
68
+ this.pendingTaskDescription = page.getByText('Pending task');
69
+ this.pickATaskHeader = page.getByRole('heading', {
70
+ name: 'Pick a task to work on',
71
+ });
72
+ this.emptyTaskMessage = page.getByRole('heading', {
73
+ name: /task has no variables/i,
74
+ });
75
+ this.nameInput = page.getByLabel('Name*');
76
+ this.addressInput = page.getByLabel('Address*');
77
+ this.ageInput = page.getByLabel('Age');
78
+ this.variablesTable = page.getByTestId('variables-table');
79
+ this.nameColumnHeader = this.variablesTable.getByRole('columnheader', {
80
+ name: 'Name',
81
+ });
82
+ this.valueColumnHeader = this.variablesTable.getByRole('columnheader', {
83
+ name: 'Value',
84
+ });
85
+ this.form = page.getByTestId('embedded-form');
86
+ this.numberInput = this.form.getByLabel('Number');
87
+ this.incrementButton = page.getByRole('button', { name: 'Increment' });
88
+ this.decrementButton = page.getByRole('button', { name: 'Decrement' });
89
+ this.dateInput = page.getByPlaceholder('mm/dd/yyyy');
90
+ this.timeInput = page.getByPlaceholder('hh:mm ?m');
91
+ this.checkbox = this.form.getByLabel('Checkbox');
92
+ this.selectDropdown = this.form.getByText('Select').last();
93
+ this.tagList = page.getByPlaceholder('Search');
94
+ this.detailsInfo = page.getByTestId('details-info');
95
+ this.textInput = page.locator('[class="fjs-input"]');
96
+ this.textInput = page.locator('[class="fjs-input"]');
97
+ this.taskCompletedBanner = this.page.getByText('Task completed').first();
98
+ this.assignedToMeText = page
99
+ .getByTestId('assignee')
100
+ .getByText('Assigned to me');
101
+ this.noPreviewContainer = this.page.locator('[class = "fjs-documentPreview-non-preview-item fjs-documentPreview-single-document-container"]');
102
+ this.previewContainer = this.page.locator('[class = "fjs-documentPreview-single-document-container"]');
103
+ }
104
+ async clickAssignToMeButton() {
105
+ await (0, test_1.expect)(this.assignToMeButton).toBeVisible({ timeout: 60000 });
106
+ await this.assignToMeButton.click({ timeout: 60000 });
107
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.assignedToMeText, {
108
+ // Assignment is a round-trip through the Zeebe user-task backend, which
109
+ // on a loaded cluster outlasts the default 10s per attempt. Every failure
110
+ // here aborts the caller's whole task-completion attempt, so it retries
111
+ // the search, the click and the panel load as well — the run log shows
112
+ // that churn repeating up to attempt 7.
113
+ visibilityTimeout: 30000,
114
+ totalTimeout: 60000,
115
+ postAction: async () => {
116
+ await this.page.reload();
117
+ },
118
+ });
119
+ }
120
+ async clickUnassignButton() {
121
+ await this.unassignButton.click();
122
+ }
123
+ async clickCompleteTaskButton() {
124
+ await this.completeTaskButton.click({ timeout: 60000 });
125
+ await (0, test_1.expect)(this.taskCompletedBanner).toBeVisible({
126
+ timeout: 200000,
127
+ });
128
+ }
129
+ async clickAddVariableButton() {
130
+ await this.addVariableButton.click({ timeout: 60000 });
131
+ }
132
+ async replaceExistingVariableValue(values) {
133
+ const { name, value } = values;
134
+ await this.page.getByTitle(name).clear();
135
+ await this.page.getByTitle(name).fill(value);
136
+ }
137
+ getNthVariableNameInput(nth) {
138
+ return this.page.getByRole('textbox', {
139
+ name: `${cardinalToOrdinal(nth)} variable name`,
140
+ });
141
+ }
142
+ getNthVariableValueInput(nth) {
143
+ return this.page.getByRole('textbox', {
144
+ name: `${cardinalToOrdinal(nth)} variable value`,
145
+ });
146
+ }
147
+ async addVariable(payload) {
148
+ const { name, value } = payload;
149
+ this.clickAddVariableButton();
150
+ await this.getNthVariableNameInput(1).fill(name);
151
+ await this.getNthVariableValueInput(1).fill(value);
152
+ }
153
+ async fillNumber(number) {
154
+ await this.numberInput.fill(number);
155
+ }
156
+ async clickIncrementButton() {
157
+ await this.incrementButton.click();
158
+ }
159
+ async clickDecrementButton() {
160
+ await this.decrementButton.click();
161
+ }
162
+ async fillDate(date) {
163
+ await this.dateInput.click();
164
+ await this.dateInput.fill(date);
165
+ await this.dateInput.press('Enter');
166
+ }
167
+ async enterTime(time) {
168
+ await this.timeInput.click();
169
+ await this.page.getByText(time).click();
170
+ }
171
+ async checkCheckbox() {
172
+ await this.checkbox.check();
173
+ }
174
+ async selectDropdownValue(value) {
175
+ await this.selectDropdown.click();
176
+ await this.page.getByText(value).click();
177
+ }
178
+ async clickRadioButton(radioBtnLabel) {
179
+ await this.page.getByText(radioBtnLabel).click();
180
+ }
181
+ async checkChecklistBox(label) {
182
+ await this.page.getByLabel(label).check();
183
+ }
184
+ async enterTwoValuesInTagList(value1, value2) {
185
+ await this.tagList.click();
186
+ await this.page.getByText(value1).click();
187
+ await this.page.getByText(value2, { exact: true }).click();
188
+ }
189
+ async clickTextInput() {
190
+ await this.textInput.click();
191
+ }
192
+ async fillTextInput(value) {
193
+ await this.textInput.fill(value);
194
+ }
195
+ async priorityAssertion(priority) {
196
+ let retryCount = 0;
197
+ const maxRetries = 2;
198
+ while (retryCount < maxRetries) {
199
+ try {
200
+ await (0, test_1.expect)(this.detailsPanel.getByText(priority)).toBeVisible({
201
+ timeout: 45000,
202
+ });
203
+ return; // Exit the function if the expectation is met
204
+ }
205
+ catch (error) {
206
+ retryCount++;
207
+ console.log(`Attempt ${retryCount} failed. Retrying...`);
208
+ await this.page.reload();
209
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
210
+ await sleep(10000);
211
+ }
212
+ }
213
+ throw new Error(`Priority not visible after ${maxRetries} attempts.`);
214
+ }
215
+ async taskAssertion(name) {
216
+ let retryCount = 0;
217
+ const maxRetries = 2;
218
+ while (retryCount < maxRetries) {
219
+ try {
220
+ await (0, test_1.expect)(this.detailsInfo.getByText(name)).toBeVisible({
221
+ timeout: 45000,
222
+ });
223
+ return; // Exit the function if the expectation is met
224
+ }
225
+ catch (error) {
226
+ retryCount++;
227
+ console.log(`Attempt ${retryCount} failed. Retrying...`);
228
+ await this.page.reload();
229
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
230
+ await sleep(10000);
231
+ }
232
+ }
233
+ throw new Error(`Task not visible after ${maxRetries} attempts.`);
234
+ }
235
+ async assertLoadedImage(documentName) {
236
+ await (0, test_1.expect)(this.page.locator(`img[alt="${documentName}"]`)).toBeVisible({
237
+ timeout: 60000,
238
+ });
239
+ }
240
+ async assertDocumentDownloadButton(documentName) {
241
+ await (0, test_1.expect)(this.page.locator(`button[aria-label="Download ${documentName}"]`)).toBeVisible({ timeout: 60000 });
242
+ }
243
+ async assertPdfPreviewViewerExists() {
244
+ const containers = this.page.locator('[class="fjs-documentPreview-single-document-container"]');
245
+ const firstPdfViewer = containers
246
+ .first()
247
+ .locator('.fjs-documentPreview-pdf-viewer');
248
+ const lastPdfViewer = containers
249
+ .last()
250
+ .locator('.fjs-documentPreview-pdf-viewer');
251
+ await (0, test_1.expect)(firstPdfViewer.or(lastPdfViewer)).toBeVisible();
252
+ }
253
+ async assertTextIsPresent(expectedText) {
254
+ const maxAttempts = 3;
255
+ let attempts = 0;
256
+ while (attempts < maxAttempts) {
257
+ try {
258
+ await (0, test_1.expect)(this.page.getByText(expectedText).first()).toBeVisible({
259
+ timeout: 60000,
260
+ });
261
+ return;
262
+ }
263
+ catch (error) {
264
+ attempts++;
265
+ if (attempts >= maxAttempts) {
266
+ throw error;
267
+ }
268
+ await this.page.reload();
269
+ }
270
+ }
271
+ }
272
+ }
273
+ exports.TaskDetailsPage = TaskDetailsPage;
@@ -0,0 +1,29 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ declare class TaskPanelPage {
3
+ private page;
4
+ readonly availableTasks: Locator;
5
+ readonly filterPanelExpandButton: Locator;
6
+ readonly filterPanelCollapseButton: Locator;
7
+ readonly filterSelectButton: Locator;
8
+ readonly filterControls: Locator;
9
+ readonly tasklistBanner: Locator;
10
+ readonly processesPageTab: Locator;
11
+ readonly tasksTab: Locator;
12
+ readonly assignToMeButton: Locator;
13
+ readonly unassignButton: Locator;
14
+ readonly licenseKeyTagNonProduction: Locator;
15
+ readonly licenseKeyTagProduction: Locator;
16
+ constructor(page: Page);
17
+ openTask(name: string, assignButton?: boolean): Promise<void>;
18
+ filterBy(option: 'All open' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
19
+ private openFilterControls;
20
+ private static appliedFilterUrl;
21
+ activeFilter(option: string): Locator;
22
+ clickCollapseFilter(): Promise<void>;
23
+ scrollToLastTask(name: string): Promise<void>;
24
+ scrollToFirstTask(name: string): Promise<void>;
25
+ clickProcessesTab(): Promise<void>;
26
+ taskCount(name: string, waitForStable?: boolean): Promise<number>;
27
+ clickTasksTab(): Promise<void>;
28
+ }
29
+ export { TaskPanelPage };
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TaskPanelPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const constants_1 = require("../../utils/constants");
6
+ class TaskPanelPage {
7
+ page;
8
+ availableTasks;
9
+ filterPanelExpandButton;
10
+ filterPanelCollapseButton;
11
+ filterSelectButton;
12
+ filterControls;
13
+ tasklistBanner;
14
+ processesPageTab;
15
+ tasksTab;
16
+ assignToMeButton;
17
+ unassignButton;
18
+ licenseKeyTagNonProduction;
19
+ licenseKeyTagProduction;
20
+ constructor(page) {
21
+ this.page = page;
22
+ // The task-list wrapper's `title="Available tasks"` was removed on
23
+ // camunda/camunda `main` (8.11-SNAPSHOT) by #65074 ("truncate long task
24
+ // card labels instead of overflowing the panel"), so
25
+ // `getByTitle('Available tasks')` matches nothing on the shell the monorepo
26
+ // helm e2e deploys, and every openTask/completeTask scoped through it times
27
+ // out. The `<section aria-label="Tasks side panel">` that wraps the panel
28
+ // is present on both shells -- it is what the product's own
29
+ // qa/c8-orchestration-cluster-e2e-test-suite TaskPanelPage now anchors on
30
+ // -- so this also survives the pending backport of #65074 to stable/8.10,
31
+ // which will remove the same title from the shell the SM nightly deploys.
32
+ this.availableTasks = page.getByRole('region', { name: 'Tasks side panel' });
33
+ // SM-8.10 is tested against two different Tasklist shells, so every filter
34
+ // locator below has to match either one.
35
+ //
36
+ // `stable/8.10` -- which is what the published `8.10-SNAPSHOT` images this
37
+ // repo's own CI deploys are built from -- still renders the Carbon
38
+ // `CollapsiblePanel`: a collapsed rail carrying the expand control, an
39
+ // expanded `<nav id="task-nav-bar">` listing each filter as a `<Link>`,
40
+ // and an `<h1>` naming the active filter.
41
+ // camunda/camunda `main` is 8.11.0-SNAPSHOT and has replaced that with the
42
+ // Design System `FilterSelect`: one always-visible trigger (accessible name
43
+ // "Filters") opening a menu of `menuitem`s, no expand/collapse control and
44
+ // no `<h1>`. The monorepo's helm e2e deploys an image built from that
45
+ // commit, so it is the shell seen there.
46
+ // Matching only one shell is what made this pair of surfaces trade
47
+ // failures: targeting the Carbon rail found no expand control on the
48
+ // monorepo run, and targeting the dropdown found no "Filters" button at
49
+ // all here (`<element(s) not found>`).
50
+ this.filterPanelExpandButton = page.locator('button[aria-controls="task-nav-bar"][aria-expanded="false"]');
51
+ this.filterPanelCollapseButton = page.locator('button[aria-controls="task-nav-bar"][aria-expanded="true"]');
52
+ this.filterSelectButton = page.getByRole('button', {
53
+ name: 'Filters',
54
+ exact: true,
55
+ });
56
+ // "the task panel's filter control is on screen", whichever shell renders
57
+ // it: the Carbon rail shows the expand control when collapsed and the
58
+ // collapse control when expanded, never both.
59
+ this.filterControls = this.filterPanelExpandButton
60
+ .or(this.filterPanelCollapseButton)
61
+ .or(this.filterSelectButton)
62
+ .first();
63
+ // Same Design System header rollout NavigationPage.tasklistPageBanner
64
+ // covers: "Tasklist" is a breadcrumb link once the Carbon shell is gone,
65
+ // and "Camunda logo Tasklist" while it is still served.
66
+ this.tasklistBanner = page
67
+ .getByRole('navigation', { name: 'Camunda context' })
68
+ .getByRole('link', { name: 'Tasklist', exact: true })
69
+ .or(page.getByRole('link', { name: 'Camunda logo Tasklist' }))
70
+ .first();
71
+ this.processesPageTab = page.getByRole('link', { name: 'Processes' });
72
+ this.tasksTab = page.getByRole('link', { name: 'Tasks', exact: true });
73
+ this.assignToMeButton = page.getByRole('button', { name: 'Assign to me' });
74
+ this.unassignButton = page.getByRole('button', { name: 'Unassign' });
75
+ this.licenseKeyTagNonProduction = page.getByText('Non-production license', {
76
+ exact: true,
77
+ });
78
+ this.licenseKeyTagProduction = page.getByText('Production license', {
79
+ exact: true,
80
+ });
81
+ }
82
+ async openTask(name, assignButton = true) {
83
+ const maxAttempts = 10;
84
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
85
+ try {
86
+ // Removed waitForLoadState('networkidle') — Tasklist polls
87
+ // continuously, so networkidle never fires; the click below
88
+ // already has a 4s timeout that auto-waits for the locator.
89
+ await this.availableTasks
90
+ .getByText(name, { exact: true })
91
+ .nth(0)
92
+ .click({ timeout: constants_1._1_SECOND_IN_MS * 4 });
93
+ if (assignButton) {
94
+ await (0, test_1.expect)(this.assignToMeButton).toBeVisible({
95
+ timeout: constants_1._1_SECOND_IN_MS * 10,
96
+ });
97
+ }
98
+ return;
99
+ }
100
+ catch (error) {
101
+ if (attempt < maxAttempts) {
102
+ console.warn(`Attempt ${attempt} failed. Reloading page...`);
103
+ await this.page.reload();
104
+ await this.page.waitForLoadState('domcontentloaded');
105
+ }
106
+ else {
107
+ throw new Error(`Failed to open task "${name}" after ${maxAttempts} attempts: ${error}`);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ async filterBy(option) {
113
+ // Both shells list the options only once their control is open, so open it
114
+ // first and then pick from whichever of the two the deployed build renders:
115
+ // a `<Link>` in the Carbon panel's expanded nav, or a `role="menuitem"` in
116
+ // the Design System dropdown. The Carbon links are scoped to #task-nav-bar
117
+ // so "Completed" cannot pick up a custom filter or a task row carrying the
118
+ // same words. Both shells spell the default filter "All open tasks" while
119
+ // callers pass "All open".
120
+ //
121
+ // The task list refetches every 5s behind a suspense boundary, so a poll
122
+ // landing on the pick can tear the control down before the router
123
+ // navigates: it closes but the filter never changes, and the test fails
124
+ // much later on an assertion that reads the still-unfiltered list. Confirm
125
+ // the route actually carries the filter, and re-pick if it does not.
126
+ const optionName = option === 'All open' ? 'All open tasks' : option;
127
+ const filterOption = this.page
128
+ .locator('#task-nav-bar')
129
+ .getByRole('link', { name: optionName, exact: true })
130
+ .or(this.page.getByRole('menuitem', { name: optionName, exact: true }));
131
+ const maxAttempts = 3;
132
+ let lastError;
133
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
134
+ try {
135
+ // Only the first attempt waits out a slow first render; later
136
+ // attempts re-pick from a page that has already rendered once, so
137
+ // they stay short -- three attempts at the full budget would outlast
138
+ // the 12-minute test timeout and turn a dropped pick into a timeout
139
+ // with no retry spent.
140
+ const renderTimeout = attempt === 1 ? 120000 : 15000;
141
+ await this.openFilterControls();
142
+ await (0, test_1.expect)(filterOption).toBeVisible({ timeout: renderTimeout });
143
+ await filterOption.click({ timeout: renderTimeout });
144
+ const applied = await this.page
145
+ .waitForURL(TaskPanelPage.appliedFilterUrl(option), { timeout: 15000 })
146
+ .then(() => true)
147
+ .catch(() => false);
148
+ if (applied) {
149
+ return;
150
+ }
151
+ lastError = new Error(`Filter "${option}" was picked but the URL never carried it`);
152
+ }
153
+ catch (error) {
154
+ // Without this catch, a thrown assertion (the control never appearing,
155
+ // or the option never rendering) propagated straight out of filterBy()
156
+ // on attempt 1, so attempts 2 and 3 -- and their shorter,
157
+ // faster-failing budget -- could never run. maxAttempts was dead code.
158
+ lastError = error;
159
+ }
160
+ if (attempt < maxAttempts) {
161
+ console.warn(`Attempt ${attempt} did not apply the "${option}" filter. Retrying...`);
162
+ }
163
+ // This attempt did not land, which leaves the Design System menu in
164
+ // whichever state Radix last put it. Clicking that trigger again
165
+ // toggles, so on a still-open menu the next attempt would *close* it and
166
+ // then burn its whole budget waiting for an option that is gone. Escape
167
+ // resets it, and is a no-op on both an already-closed menu and the
168
+ // Carbon panel (whose expanded state openFilterControls() probes for).
169
+ // It sits outside the catch so a dead page surfaces as itself rather
170
+ // than replacing the real failure.
171
+ await this.page.keyboard.press('Escape');
172
+ }
173
+ throw new Error(`Filter "${option}" was not applied after ${maxAttempts} attempts: ${lastError}`);
174
+ }
175
+ // The Carbon panel renders either the collapsed rail (which carries the
176
+ // expand control) or the expanded nav -- never both -- and picking a filter
177
+ // leaves it expanded, so a retry must not click "expand" a second time. The
178
+ // Design System dropdown has the opposite need: it closes on select, so it
179
+ // has to be re-opened every attempt. Probing for the Carbon collapse control
180
+ // first tells the two apart without branching on a version flag.
181
+ async openFilterControls() {
182
+ const alreadyExpanded = await this.filterPanelCollapseButton
183
+ .waitFor({ state: 'visible', timeout: 2000 })
184
+ .then(() => true)
185
+ .catch(() => false);
186
+ if (alreadyExpanded) {
187
+ return;
188
+ }
189
+ // click() auto-waits for the control, so this budget doubles as the wait
190
+ // for a slow first render of whichever shell is served.
191
+ await this.filterPanelExpandButton
192
+ .or(this.filterSelectButton)
193
+ .first()
194
+ .click({ timeout: 60000 });
195
+ }
196
+ // Tasklist keeps the active filter in the `filter` search param. "All open"
197
+ // is the route's default and is stripped from the URL, so it is asserted as
198
+ // the absence of the param.
199
+ static appliedFilterUrl(option) {
200
+ const filterParam = {
201
+ 'All open': undefined,
202
+ Unassigned: 'unassigned',
203
+ 'Assigned to me': 'assigned-to-me',
204
+ Completed: 'completed',
205
+ };
206
+ const param = filterParam[option];
207
+ return param === undefined
208
+ ? /\/tasklist(?:\/[^?]*)?(?:\?(?!.*\bfilter=).*)?$/
209
+ : new RegExp(`[?&]filter=${param}(?:&|$)`);
210
+ }
211
+ // Where each shell shows which filter is currently applied: the Carbon
212
+ // panel renders an `<h1>` naming it, while the Design System dropdown has no
213
+ // heading and instead carries the label on the trigger itself.
214
+ activeFilter(option) {
215
+ return this.page
216
+ .getByRole('heading', { name: option })
217
+ .or(this.filterSelectButton.getByText(option, { exact: true }))
218
+ .first();
219
+ }
220
+ // Picking a filter leaves the Carbon panel expanded, and it floats over the
221
+ // task list -- callers that go straight to a task row would be clicking
222
+ // through the overlay. The Design System dropdown closes itself on select
223
+ // and has no collapse control at all, so this probes rather than clicking
224
+ // unconditionally: on that shell it is a no-op the callers can keep calling.
225
+ async clickCollapseFilter() {
226
+ const expanded = await this.filterPanelCollapseButton
227
+ .waitFor({ state: 'visible', timeout: 2000 })
228
+ .then(() => true)
229
+ .catch(() => false);
230
+ if (expanded) {
231
+ await this.filterPanelCollapseButton.click({ timeout: 45000 });
232
+ }
233
+ }
234
+ async scrollToLastTask(name) {
235
+ await this.page.getByText(name).last().scrollIntoViewIfNeeded();
236
+ }
237
+ async scrollToFirstTask(name) {
238
+ await this.page.getByText(name).first().scrollIntoViewIfNeeded();
239
+ }
240
+ async clickProcessesTab() {
241
+ await (0, test_1.expect)(this.processesPageTab).toBeVisible();
242
+ // Landing on Processes shows either the process search box or, on the
243
+ // first visit, the "Start your process on demand" onboarding dialog.
244
+ // `FirstTimeProcessWarning` renders the Processes content only after
245
+ // consent, so while that dialog is open the search box is not in the
246
+ // DOM at all -- confirming via the search box alone would never resolve
247
+ // on a first visit, and the retry would re-click Processes, which the
248
+ // dialog's modal overlay now covers. Treat either as proof the Processes
249
+ // view rendered; the caller dismisses the dialog.
250
+ const processesReady = this.page
251
+ .getByRole('searchbox', { name: 'Search processes' })
252
+ .or(this.page.getByRole('dialog', {
253
+ name: 'Start your process on demand',
254
+ }));
255
+ const maxAttempts = 3;
256
+ let lastError;
257
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
258
+ try {
259
+ await this.processesPageTab.click();
260
+ }
261
+ catch (error) {
262
+ // The click belongs inside the try for the same reason filterBy()'s
263
+ // assertions do: the onboarding dialog's modal overlay covers the
264
+ // nav, so a click that lands on it throws once the repo's 10s action
265
+ // timeout expires. Letting that escape made maxAttempts dead code on
266
+ // exactly the path that needs it. Fall through to the landing probe
267
+ // below -- a click that threw may still have navigated.
268
+ lastError = error;
269
+ }
270
+ // A newly-arrived task can bounce the app back to the Tasks tab right
271
+ // as this click lands (nightly SM-8.10 run 31782502787: the click
272
+ // itself succeeded but the app ended up back on Tasks with the new
273
+ // task selected), so confirm the Processes view actually rendered
274
+ // before returning instead of trusting the click alone.
275
+ const landedOnProcesses = await processesReady
276
+ .first()
277
+ .waitFor({ state: 'visible', timeout: 5000 })
278
+ .then(() => true)
279
+ .catch(() => false);
280
+ if (landedOnProcesses) {
281
+ return;
282
+ }
283
+ if (attempt < maxAttempts) {
284
+ console.warn(`Attempt ${attempt} landed back on Tasks instead of Processes. Retrying...`);
285
+ }
286
+ }
287
+ throw new Error(`Processes tab did not open after ${maxAttempts} attempts: ${lastError}`);
288
+ }
289
+ async taskCount(name, waitForStable = true) {
290
+ // Removed waitForLoadState('networkidle') — Tasklist polls;
291
+ // the stability poll below already guarantees readiness.
292
+ if (waitForStable) {
293
+ // Wait for the task list to stabilize: poll until count is unchanged for 2s
294
+ let previousCount = -1;
295
+ let stableIterations = 0;
296
+ const maxAttempts = 15; // 15 * 1s = 15s max wait
297
+ for (let i = 0; i < maxAttempts; i++) {
298
+ const currentCount = await this.availableTasks
299
+ .getByText(name, { exact: true })
300
+ .count();
301
+ if (currentCount === previousCount) {
302
+ stableIterations++;
303
+ if (stableIterations >= 2) {
304
+ return currentCount;
305
+ }
306
+ }
307
+ else {
308
+ stableIterations = 0;
309
+ previousCount = currentCount;
310
+ }
311
+ await this.page.waitForTimeout(1000);
312
+ }
313
+ }
314
+ return this.availableTasks.getByText(name, { exact: true }).count();
315
+ }
316
+ async clickTasksTab() {
317
+ await this.tasksTab.click();
318
+ }
319
+ }
320
+ exports.TaskPanelPage = TaskPanelPage;
@@ -0,0 +1,15 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ declare class TaskProcessesPage {
3
+ private page;
4
+ readonly popupContinueButton: Locator;
5
+ readonly popup: Locator;
6
+ readonly processes: Locator;
7
+ readonly searchProcessesSearchbox: Locator;
8
+ constructor(page: Page);
9
+ clickpopupContinueButton(): Promise<void>;
10
+ private processStartButton;
11
+ startProcess(name: string): Promise<void>;
12
+ clickSearchProcessesSearchbox(): Promise<void>;
13
+ fillSearchProcessesSearchbox(process: string): Promise<void>;
14
+ }
15
+ export { TaskProcessesPage };