@camunda/e2e-test-suite 0.0.1170 → 0.0.1172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pages/SM-8.10/TaskPanelPage.d.ts +2 -0
- package/dist/pages/SM-8.10/TaskPanelPage.js +60 -5
- package/dist/pages/c8Run-8.10/OperateProcessesPage.d.ts +1 -1
- package/dist/pages/c8Run-8.10/OperateProcessesPage.js +21 -8
- package/dist/pages/c8Run-8.10/TaskDetailsPage.js +21 -1
- package/dist/pages/c8Run-8.10/UtilitiesPage.d.ts +2 -1
- package/dist/pages/c8Run-8.10/UtilitiesPage.js +30 -1
- package/dist/tests/c8Run-8.10/hto-user-flows.spec.js +11 -4
- package/package.json +1 -1
|
@@ -14,6 +14,8 @@ declare class TaskPanelPage {
|
|
|
14
14
|
constructor(page: Page);
|
|
15
15
|
openTask(name: string, assignButton?: boolean): Promise<void>;
|
|
16
16
|
filterBy(option: 'All open' | 'Unassigned' | 'Assigned to me' | 'Completed'): Promise<void>;
|
|
17
|
+
private expandFilterPanel;
|
|
18
|
+
private static appliedFilterUrl;
|
|
17
19
|
scrollToLastTask(name: string): Promise<void>;
|
|
18
20
|
scrollToFirstTask(name: string): Promise<void>;
|
|
19
21
|
clickCollapseFilter(): Promise<void>;
|
|
@@ -72,11 +72,66 @@ class TaskPanelPage {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
async filterBy(option) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
// The task list refetches every 5s behind a suspense boundary, so a poll
|
|
76
|
+
// landing on the filter click tears the floating filter panel down before
|
|
77
|
+
// the router navigates: the panel closes but the filter never changes.
|
|
78
|
+
// Nothing downstream notices, so the test fails much later on an assertion
|
|
79
|
+
// that reads the still-unfiltered list -- which is how a dropped click on
|
|
80
|
+
// "Completed" surfaced as the completed-filter heading never appearing.
|
|
81
|
+
// Confirm the route actually carries the filter, and re-pick if it does not.
|
|
82
|
+
const maxAttempts = 3;
|
|
83
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
84
|
+
await this.expandFilterPanel();
|
|
85
|
+
// Deliberately not `exact`: the panel spells the default filter
|
|
86
|
+
// "All open tasks" while callers pass "All open".
|
|
87
|
+
const filterLink = this.page.getByRole('link', { name: option });
|
|
88
|
+
// Only the first attempt waits out a slow first render. Later attempts
|
|
89
|
+
// re-pick from a panel that has already rendered once, so they stay
|
|
90
|
+
// short -- three attempts at the full budget would outlast the
|
|
91
|
+
// 12-minute test timeout and turn a dropped click into a timeout with
|
|
92
|
+
// no retry spent.
|
|
93
|
+
const renderTimeout = attempt === 1 ? 120000 : 15000;
|
|
94
|
+
await (0, test_1.expect)(filterLink).toBeVisible({ timeout: renderTimeout });
|
|
95
|
+
await filterLink.click({ timeout: renderTimeout });
|
|
96
|
+
const applied = await this.page
|
|
97
|
+
.waitForURL(TaskPanelPage.appliedFilterUrl(option), { timeout: 15000 })
|
|
98
|
+
.then(() => true)
|
|
99
|
+
.catch(() => false);
|
|
100
|
+
if (applied) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (attempt < maxAttempts) {
|
|
104
|
+
console.warn(`Attempt ${attempt} did not apply the "${option}" filter. Retrying...`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`Filter "${option}" was not applied after ${maxAttempts} attempts`);
|
|
108
|
+
}
|
|
109
|
+
// The filter panel renders either the collapsed rail (which carries the
|
|
110
|
+
// expand control) or the expanded nav -- never both -- and picking a filter
|
|
111
|
+
// leaves it expanded, so a retry must not click "expand" a second time.
|
|
112
|
+
async expandFilterPanel() {
|
|
113
|
+
const alreadyExpanded = await this.collapseFilter
|
|
114
|
+
.waitFor({ state: 'visible', timeout: 2000 })
|
|
115
|
+
.then(() => true)
|
|
116
|
+
.catch(() => false);
|
|
117
|
+
if (!alreadyExpanded) {
|
|
118
|
+
await this.filterOptions.click({ timeout: 45000 });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Tasklist keeps the active filter in the `filter` search param. "All open"
|
|
122
|
+
// is the route's default and is stripped from the URL, so it is asserted as
|
|
123
|
+
// the absence of the param.
|
|
124
|
+
static appliedFilterUrl(option) {
|
|
125
|
+
const filterParam = {
|
|
126
|
+
'All open': undefined,
|
|
127
|
+
Unassigned: 'unassigned',
|
|
128
|
+
'Assigned to me': 'assigned-to-me',
|
|
129
|
+
Completed: 'completed',
|
|
130
|
+
};
|
|
131
|
+
const param = filterParam[option];
|
|
132
|
+
return param === undefined
|
|
133
|
+
? /\/tasklist(?:\/[^?]*)?(?:\?(?!.*\bfilter=).*)?$/
|
|
134
|
+
: new RegExp(`[?&]filter=${param}(?:&|$)`);
|
|
80
135
|
}
|
|
81
136
|
async scrollToLastTask(name) {
|
|
82
137
|
await this.page.getByText(name).last().scrollIntoViewIfNeeded();
|
|
@@ -17,6 +17,6 @@ declare class OperateProcessesPage {
|
|
|
17
17
|
clickProcessIncidentsCheckbox(): Promise<void>;
|
|
18
18
|
clickRunningProcessInstancesCheckbox(): Promise<void>;
|
|
19
19
|
clickFinishedProcessInstancesCheckbox(): Promise<void>;
|
|
20
|
-
clickProcessInstanceLink(processName: string): Promise<void>;
|
|
20
|
+
clickProcessInstanceLink(processName: string, processInstanceKey?: string): Promise<void>;
|
|
21
21
|
}
|
|
22
22
|
export { OperateProcessesPage };
|
|
@@ -61,20 +61,33 @@ class OperateProcessesPage {
|
|
|
61
61
|
async clickFinishedProcessInstancesCheckbox() {
|
|
62
62
|
await this.processFinishedInstancesCheckbox.click({ timeout: 90000 });
|
|
63
63
|
}
|
|
64
|
-
async clickProcessInstanceLink(processName) {
|
|
64
|
+
async clickProcessInstanceLink(processName, processInstanceKey) {
|
|
65
|
+
// Both Operate frontends label the instance-key cell link
|
|
66
|
+
// "View instance <key>" -- the aria-label in operate/client's
|
|
67
|
+
// InstancesTable, the operate.processes.instancesTable.viewInstance string
|
|
68
|
+
// in the orchestration-cluster webapp -- so passing a key pins the click
|
|
69
|
+
// to one instance. Without a key every row carrying the process name is a
|
|
70
|
+
// candidate and `.first()` opens an arbitrary instance of that process.
|
|
71
|
+
// Both frontends sort the list by start date descending, so a
|
|
72
|
+
// just-created instance is on the first page.
|
|
73
|
+
const instanceLink = processInstanceKey === undefined
|
|
74
|
+
? this.page
|
|
75
|
+
.getByRole('row')
|
|
76
|
+
.filter({ hasText: new RegExp(`^(?:Select row)?${processName}\\d+`) })
|
|
77
|
+
.first()
|
|
78
|
+
.getByRole('link')
|
|
79
|
+
: this.page.getByRole('link', {
|
|
80
|
+
name: `View instance ${processInstanceKey}`,
|
|
81
|
+
exact: true,
|
|
82
|
+
});
|
|
65
83
|
let retryCount = 0;
|
|
66
|
-
const maxRetries =
|
|
84
|
+
const maxRetries = 4;
|
|
67
85
|
while (retryCount < maxRetries) {
|
|
68
86
|
try {
|
|
69
87
|
await (0, sleep_1.sleep)(1000);
|
|
70
88
|
await this.page.reload();
|
|
71
89
|
await (0, sleep_1.sleep)(1000);
|
|
72
|
-
await
|
|
73
|
-
.getByRole('row')
|
|
74
|
-
.filter({ hasText: new RegExp(`^(?:Select row)?${processName}\\d+`) })
|
|
75
|
-
.first()
|
|
76
|
-
.getByRole('link')
|
|
77
|
-
.click({ timeout: 30000 });
|
|
90
|
+
await instanceLink.click({ timeout: 30000 });
|
|
78
91
|
await (0, test_1.expect)(this.page.getByText('Instance History').first()).toBeVisible({
|
|
79
92
|
timeout: 10000,
|
|
80
93
|
});
|
|
@@ -220,7 +220,27 @@ class TaskDetailsPage {
|
|
|
220
220
|
// taskVariablesValueLabel) associated by htmlFor -- the old Carbon input
|
|
221
221
|
// exposed the same string as an HTML title attribute, which getByTitle
|
|
222
222
|
// matched directly; getByLabel is the equivalent for a real <label>.
|
|
223
|
-
|
|
223
|
+
//
|
|
224
|
+
// The panel fetches the task's variables once, when the task is opened, so
|
|
225
|
+
// polling the DOM alone can never observe a value that was still being
|
|
226
|
+
// exported at that moment. Reload between attempts to force a re-fetch --
|
|
227
|
+
// same retry shape as taskAssertion/priorityAssertion above.
|
|
228
|
+
const valueInput = this.page.getByLabel(variableName + ' Value');
|
|
229
|
+
const maxRetries = 3;
|
|
230
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
231
|
+
try {
|
|
232
|
+
await (0, test_1.expect)(valueInput).toHaveValue(variableValue, { timeout: 30000 });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (attempt === maxRetries) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
console.log(`Attempt ${attempt} failed. Retrying...`);
|
|
240
|
+
await this.page.reload();
|
|
241
|
+
await (0, sleep_1.sleep)(5000);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
224
244
|
}
|
|
225
245
|
}
|
|
226
246
|
exports.TaskDetailsPage = TaskDetailsPage;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Page, Locator } from '@playwright/test';
|
|
1
|
+
import { APIRequestContext, Page, Locator } from '@playwright/test';
|
|
2
2
|
import { TaskPanelPage } from '../c8Run-8.10/TaskPanelPage';
|
|
3
3
|
import { TaskDetailsPage } from '../c8Run-8.10/TaskDetailsPage';
|
|
4
4
|
export declare function navigateToApp(page: Page, appName: string): Promise<void>;
|
|
5
|
+
export declare function openUserTaskOfProcessInstance(page: Page, request: APIRequestContext, processInstanceKey: string, maxRetries?: number, retryDelayMs?: number): Promise<string>;
|
|
5
6
|
export declare function assertLocatorVisibleWithRetry(page: Page, locator: Locator, text: string, timeout?: number, maxRetries?: number): Promise<void>;
|
|
6
7
|
export declare function validateURL(page: Page, URL: RegExp): Promise<void>;
|
|
7
8
|
export declare function completeTaskWithRetry(taskPanelPage: TaskPanelPage, taskDetailsPage: TaskDetailsPage, taskName: string, taskPriority: string, userTaskName?: string, textInput?: string, maxRetries?: number): Promise<void>;
|
|
@@ -7,13 +7,42 @@
|
|
|
7
7
|
* except in compliance with the Camunda License 1.0.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.completeTaskWithRetry = exports.validateURL = exports.assertLocatorVisibleWithRetry = exports.navigateToApp = void 0;
|
|
10
|
+
exports.completeTaskWithRetry = exports.validateURL = exports.assertLocatorVisibleWithRetry = exports.openUserTaskOfProcessInstance = exports.navigateToApp = void 0;
|
|
11
11
|
const test_1 = require("@playwright/test");
|
|
12
12
|
const sleep_1 = require("../../utils/sleep");
|
|
13
13
|
async function navigateToApp(page, appName) {
|
|
14
14
|
page.goto('/' + appName.toLowerCase() + '/login');
|
|
15
15
|
}
|
|
16
16
|
exports.navigateToApp = navigateToApp;
|
|
17
|
+
async function openUserTaskOfProcessInstance(page, request, processInstanceKey, maxRetries = 30, retryDelayMs = 2000) {
|
|
18
|
+
// A task card shows only the task and process display names (Task.tsx in
|
|
19
|
+
// camunda/camunda), so nothing in the Tasklist UI tells two instances of the
|
|
20
|
+
// same process apart -- opening "the first card named X" lands on whichever
|
|
21
|
+
// instance happens to sort first. Resolve the user task of THIS instance
|
|
22
|
+
// over the v2 API instead (c8Run serves it unauthenticated:
|
|
23
|
+
// camunda.security.authentication.unprotected-api is true in
|
|
24
|
+
// c8run/configuration/application.yaml, and api-tests-v2.spec.ts calls
|
|
25
|
+
// /v2/user-tasks/search the same way) and open it at its own route,
|
|
26
|
+
// /tasklist/{userTaskKey}. Retrying covers the export lag between creating
|
|
27
|
+
// the instance and its user task becoming searchable.
|
|
28
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
29
|
+
const response = await request.post('/v2/user-tasks/search', {
|
|
30
|
+
data: { filter: { processInstanceKey } },
|
|
31
|
+
});
|
|
32
|
+
if (response.ok()) {
|
|
33
|
+
const body = await response.json();
|
|
34
|
+
const userTaskKey = body?.items?.[0]?.userTaskKey;
|
|
35
|
+
if (userTaskKey != null) {
|
|
36
|
+
await page.goto('/tasklist/' + userTaskKey);
|
|
37
|
+
return String(userTaskKey);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await (0, sleep_1.sleep)(retryDelayMs);
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`No user task found for process instance ${processInstanceKey} after ` +
|
|
43
|
+
`${maxRetries} attempts.`);
|
|
44
|
+
}
|
|
45
|
+
exports.openUserTaskOfProcessInstance = openUserTaskOfProcessInstance;
|
|
17
46
|
async function assertLocatorVisibleWithRetry(page, locator, text, timeout = 20000, maxRetries = 3) {
|
|
18
47
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
19
48
|
try {
|
|
@@ -6,6 +6,11 @@ const zeebeClient_1 = require("../../utils/zeebeClient");
|
|
|
6
6
|
const UtilitiesPage_1 = require("../../pages/c8Run-8.10/UtilitiesPage");
|
|
7
7
|
const sleep_1 = require("../../utils/sleep");
|
|
8
8
|
const _setup_1 = require("../../test-setup.js");
|
|
9
|
+
// The key of the User_Task_Editing_Variables instance this run owns. Operate
|
|
10
|
+
// and Tasklist both resolve that instance by key, so a second live instance of
|
|
11
|
+
// the same process -- another worker, a --repeat-each copy, a leftover from an
|
|
12
|
+
// earlier run -- can no longer be picked up by mistake.
|
|
13
|
+
let variableEditingInstanceKey;
|
|
9
14
|
c8Run_8_10_1.test.beforeAll(async () => {
|
|
10
15
|
await Promise.all([
|
|
11
16
|
(0, zeebeClient_1.deploy)('./resources/User_Task_Editing_Variables.bpmn'),
|
|
@@ -15,7 +20,7 @@ c8Run_8_10_1.test.beforeAll(async () => {
|
|
|
15
20
|
(0, zeebeClient_1.deploy)('./resources/Start_Form_Process.bpmn'),
|
|
16
21
|
(0, zeebeClient_1.deploy)('./resources/Zeebe_User_Task_Process_With_Priority.bpmn'),
|
|
17
22
|
]);
|
|
18
|
-
await Promise.all([
|
|
23
|
+
const [, variableEditingInstances] = await Promise.all([
|
|
19
24
|
(0, zeebeClient_1.createInstances)('Zeebe_User_Task', 1, 1),
|
|
20
25
|
(0, zeebeClient_1.createInstances)('User_Task_Editing_Variables', 1, 1, {
|
|
21
26
|
testVariable: 'testValue',
|
|
@@ -24,6 +29,7 @@ c8Run_8_10_1.test.beforeAll(async () => {
|
|
|
24
29
|
(0, zeebeClient_1.createInstances)('Start_Form_Process', 1, 1),
|
|
25
30
|
(0, zeebeClient_1.createInstances)('Zeebe_User_Task_Process_With_Priority', 1, 1),
|
|
26
31
|
]);
|
|
32
|
+
variableEditingInstanceKey = String(variableEditingInstances[0].processInstanceKey);
|
|
27
33
|
await (0, sleep_1.sleep)(5000);
|
|
28
34
|
});
|
|
29
35
|
c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
|
|
@@ -36,13 +42,13 @@ c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
|
|
|
36
42
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
37
43
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
38
44
|
});
|
|
39
|
-
(0, c8Run_8_10_1.test)('User Task Editing Variables Flow', async ({ page, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskPanelPage, taskListLoginPage, operateLoginPage, taskDetailsPage, }) => {
|
|
45
|
+
(0, c8Run_8_10_1.test)('User Task Editing Variables Flow', async ({ page, request, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskPanelPage, taskListLoginPage, operateLoginPage, taskDetailsPage, }) => {
|
|
40
46
|
await c8Run_8_10_1.test.step('View Process Instance in Operate, Edit the Variable & Assert the Variable is Updated in Tasklist', async () => {
|
|
41
47
|
await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
|
|
42
48
|
await operateLoginPage.login('demo', 'demo');
|
|
43
49
|
await (0, test_1.expect)(operateHomePage.processesTab).toBeVisible({ timeout: 180000 });
|
|
44
50
|
await operateHomePage.clickProcessesTab();
|
|
45
|
-
await operateProcessesPage.clickProcessInstanceLink('User_Task_Editing_Variables');
|
|
51
|
+
await operateProcessesPage.clickProcessInstanceLink('User_Task_Editing_Variables', variableEditingInstanceKey);
|
|
46
52
|
await operateProcessInstancePage.activeIconAssertion();
|
|
47
53
|
await (0, test_1.expect)(page.getByText('"testValue"')).toBeVisible({
|
|
48
54
|
timeout: 120000,
|
|
@@ -63,7 +69,8 @@ c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
|
|
|
63
69
|
await (0, sleep_1.sleep)(10000);
|
|
64
70
|
await (0, UtilitiesPage_1.navigateToApp)(page, 'tasklist');
|
|
65
71
|
await taskListLoginPage.login('demo', 'demo');
|
|
66
|
-
await taskPanelPage.
|
|
72
|
+
await taskPanelPage.taskListBannerIsVisible();
|
|
73
|
+
await (0, UtilitiesPage_1.openUserTaskOfProcessInstance)(page, request, variableEditingInstanceKey);
|
|
67
74
|
await taskDetailsPage.clickAssignToMeButton();
|
|
68
75
|
await (0, test_1.expect)(page.getByText('Assigning...')).not.toBeVisible({
|
|
69
76
|
timeout: 90000,
|