@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.
- 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,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 };
|