@camunda/e2e-test-suite 0.0.943 → 0.0.945
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/8.10/AppsPage.d.ts +5 -0
- package/dist/pages/8.10/AppsPage.js +152 -0
- package/dist/pages/8.10/ModelerHomePage.d.ts +1 -0
- package/dist/pages/8.10/ModelerHomePage.js +52 -5
- package/dist/pages/8.10/OperateHomePage.js +23 -2
- package/dist/pages/8.10/OperateProcessInstancePage.d.ts +17 -3
- package/dist/pages/8.10/OperateProcessInstancePage.js +718 -16
- package/dist/pages/8.10/OperateProcessesPage.d.ts +1 -0
- package/dist/pages/8.10/OperateProcessesPage.js +38 -2
- package/dist/pages/8.10/OptimizeHomePage.d.ts +5 -0
- package/dist/pages/8.10/OptimizeHomePage.js +138 -0
- package/dist/pages/8.10/OptimizeReportPage.d.ts +1 -0
- package/dist/pages/8.10/OptimizeReportPage.js +25 -0
- package/dist/pages/8.10/TaskPanelPage.d.ts +2 -1
- package/dist/pages/8.10/TaskPanelPage.js +37 -9
- package/dist/pages/8.10/UtilitiesPage.d.ts +2 -1
- package/dist/pages/8.10/UtilitiesPage.js +155 -23
- package/dist/pages/SM-8.10/KeycloakAdminPage.js +1 -1
- package/dist/pages/SM-8.10/KeycloakLoginPage.d.ts +1 -0
- package/dist/pages/SM-8.10/KeycloakLoginPage.js +8 -1
- package/dist/pages/SM-8.10/LoginPage.d.ts +1 -1
- package/dist/pages/SM-8.10/LoginPage.js +11 -2
- package/dist/pages/SM-8.10/NavigationPage.d.ts +1 -0
- package/dist/pages/SM-8.10/NavigationPage.js +86 -8
- package/dist/pages/SM-8.10/OCIdentityHomePage.js +12 -2
- package/dist/pages/SM-8.10/OCIdentityRolesPage.d.ts +3 -0
- package/dist/pages/SM-8.10/OCIdentityRolesPage.js +110 -4
- package/dist/pages/SM-8.10/OperateHomePage.js +13 -1
- package/dist/pages/SM-8.10/OperateProcessesPage.d.ts +1 -0
- package/dist/pages/SM-8.10/OperateProcessesPage.js +37 -18
- package/dist/pages/SM-8.10/OptimizeReportPage.js +21 -1
- package/dist/pages/SM-8.10/TaskDetailsPage.js +7 -0
- package/dist/pages/SM-8.10/TaskPanelPage.js +21 -1
- package/dist/pages/SM-8.10/TaskProcessesPage.d.ts +1 -0
- package/dist/pages/SM-8.10/TaskProcessesPage.js +30 -6
- package/dist/pages/SM-8.10/UtilitiesPage.js +15 -4
- package/dist/pages/SM-8.10/optimizeReportUtils.js +30 -4
- package/dist/tests/8.10/smoke-tests.spec.js +182 -44
- package/dist/tests/SM-8.10/identity-user-flows.spec.js +2 -1
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.js +4 -0
- package/package.json +1 -1
|
@@ -28,13 +28,18 @@ class TaskProcessesPage {
|
|
|
28
28
|
console.log('Popup not present');
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
// Scopes to the tile by walking up from its heading to the closest ancestor
|
|
32
|
+
// that owns a button: the unified webapp tile carries no test id, and the
|
|
33
|
+
// wrapper class names are build-hashed CSS modules.
|
|
34
|
+
processStartButton(name) {
|
|
35
|
+
return this.page
|
|
36
|
+
.getByRole('heading', { name, exact: true })
|
|
37
|
+
.locator('xpath=ancestor::div[.//button][1]')
|
|
38
|
+
.getByRole('button', { name: 'Start process' });
|
|
39
|
+
}
|
|
31
40
|
async startProcess(name) {
|
|
32
41
|
const found = await (0, UtilitiesPage_1.findTextWithPagination)(this.page, name, 60000, async () => {
|
|
33
|
-
await this.
|
|
34
|
-
.locator('[data-testid="process-tile"]')
|
|
35
|
-
.filter({ hasText: name })
|
|
36
|
-
.getByRole('button', { name: 'Start process' })
|
|
37
|
-
.click({ timeout: 60000 });
|
|
42
|
+
await this.processStartButton(name).click({ timeout: 60000 });
|
|
38
43
|
});
|
|
39
44
|
if (!found) {
|
|
40
45
|
throw new Error(`Process "${name}" not found in pagination`);
|
|
@@ -44,7 +49,26 @@ class TaskProcessesPage {
|
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
async clickSearchProcessesSearchbox() {
|
|
47
|
-
|
|
52
|
+
const maxAttempts = 3;
|
|
53
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
await this.searchProcessesSearchbox.click({ timeout: 5000 });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (attempt === maxAttempts) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
// The app can bounce back to the Tasks tab at any point after a new
|
|
63
|
+
// task arrives (not just synchronously with the Processes tab click
|
|
64
|
+
// -- run 31785205047 saw it happen mid-way through the unrelated
|
|
65
|
+
// 15s wait for a "Continue" popup that wasn't present), leaving
|
|
66
|
+
// Processes' own searchbox gone by the time this runs. Re-open
|
|
67
|
+
// Processes and retry rather than trusting we're still on it.
|
|
68
|
+
console.warn(`Attempt ${attempt} failed to click the Processes searchbox, probably bounced back to Tasks. Re-opening Processes and retrying...`);
|
|
69
|
+
await this.page.getByRole('link', { name: 'Processes' }).click();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
}
|
|
49
73
|
async fillSearchProcessesSearchbox(process) {
|
|
50
74
|
await this.searchProcessesSearchbox.fill(process);
|
|
@@ -82,7 +82,12 @@ async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskN
|
|
|
82
82
|
}
|
|
83
83
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
// Tasklist keeps polling for task updates, so 'networkidle' never
|
|
86
|
+
// settles here. This wait carried no timeout, so it consumed whatever
|
|
87
|
+
// was left of the 12-minute test budget and the run died inside
|
|
88
|
+
// waitForLoadState. The task panel rendering is the precondition this
|
|
89
|
+
// step actually needs, so wait for that instead.
|
|
90
|
+
await (0, test_1.expect)(taskPanelPage.availableTasks).toBeVisible({ timeout: 60000 });
|
|
86
91
|
const taskLocator = taskPanelPage.availableTasks
|
|
87
92
|
.getByText(taskName, { exact: true })
|
|
88
93
|
.first();
|
|
@@ -150,7 +155,8 @@ async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskN
|
|
|
150
155
|
catch {
|
|
151
156
|
await page.reload();
|
|
152
157
|
}
|
|
153
|
-
|
|
158
|
+
// No settle wait here either — the goto already waited for
|
|
159
|
+
// domcontentloaded and the next attempt waits for the task panel.
|
|
154
160
|
}
|
|
155
161
|
else {
|
|
156
162
|
console.error(`All ${maxRetries} attempts failed for task ${taskName}`);
|
|
@@ -343,7 +349,12 @@ async function assertLocatorVisibleWithRetry(page, locator, text, timeout = 3000
|
|
|
343
349
|
console.warn(`Attempt ${attempt + 1} failed for asserting ${text}. Retrying...`);
|
|
344
350
|
}
|
|
345
351
|
else {
|
|
346
|
-
|
|
352
|
+
// Keep the cause. Callers branch on the original error to tell a real
|
|
353
|
+
// failure apart from the test timeout aborting an in-flight wait (see
|
|
354
|
+
// assertReportWithRefreshes), and a bare attempt count told them
|
|
355
|
+
// nothing — so they retried into an already-dead test budget and
|
|
356
|
+
// reported their own generic "after N attempts" instead.
|
|
357
|
+
throw new Error(`Assertion failed after ${maxRetries} attempts: ${String(error)}`);
|
|
347
358
|
}
|
|
348
359
|
}
|
|
349
360
|
}
|
|
@@ -371,7 +382,7 @@ async function assertPageTextWithRetry(page, text, notVisible, timeout = 30000,
|
|
|
371
382
|
console.warn(`Attempt ${attempt + 1} failed for asserting ${text}. Retrying...`);
|
|
372
383
|
}
|
|
373
384
|
else {
|
|
374
|
-
throw new Error(`Assertion failed after ${maxRetries} attempts`);
|
|
385
|
+
throw new Error(`Assertion failed after ${maxRetries} attempts: ${String(error)}`);
|
|
375
386
|
}
|
|
376
387
|
}
|
|
377
388
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.assertReportWithRefreshes = exports.createReportForProcess = void 0;
|
|
4
4
|
const UtilitiesPage_1 = require("../SM-8.10/UtilitiesPage");
|
|
5
5
|
const UtilitiesPage_2 = require("../SM-8.10/UtilitiesPage");
|
|
6
|
+
const constants_1 = require("../../utils/constants");
|
|
6
7
|
const createReportForProcess = async (optimizeCollectionsPage, optimizeReportPage, processName) => {
|
|
7
8
|
const maxRetries = 30;
|
|
8
9
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
@@ -34,18 +35,43 @@ const assertReportWithRefreshes = async (page, optimizeHomePage, optimizeCollect
|
|
|
34
35
|
// refresh cycles for Optimize to catch up has a bigger underlying
|
|
35
36
|
// problem worth surfacing.
|
|
36
37
|
const maxRetries = 10;
|
|
37
|
-
|
|
38
|
+
// Playwright treats a 0 timeout as "no timeout", so passing 0 here made the
|
|
39
|
+
// visibility wait poll one already-rendered report view until the 720s test
|
|
40
|
+
// timeout killed the test. Optimize report views do not live-refresh, so that
|
|
41
|
+
// single wait could never observe the batch import landing, and it consumed
|
|
42
|
+
// every cycle this loop exists to spread out — the reason the failure surfaces
|
|
43
|
+
// as a bare "Test timeout of 720000ms exceeded". Bound each cycle instead, so
|
|
44
|
+
// the reload between cycles actually refetches. 60s (rather than the 90s the
|
|
45
|
+
// identical assertion gets in optimize-user-flows.spec.ts) keeps several
|
|
46
|
+
// cycles inside the smoke test's shared budget.
|
|
47
|
+
const attemptTimeout = constants_1._1_SECOND_IN_MS * 60;
|
|
48
|
+
// Stop starting cycles in time to throw the error below, instead of being
|
|
49
|
+
// killed mid-cycle by the test timeout, which reports no cause at all.
|
|
50
|
+
const deadline = Date.now() + constants_1._1_MINUTE_IN_MS * 6;
|
|
51
|
+
let attempts = 0;
|
|
52
|
+
let lastError;
|
|
53
|
+
while (attempts < maxRetries && Date.now() < deadline) {
|
|
54
|
+
attempts++;
|
|
38
55
|
try {
|
|
39
56
|
await optimizeHomePage.clickCollectionsLink();
|
|
40
57
|
await (0, UtilitiesPage_1.assertPageTextWithRetry)(page, reportName, false);
|
|
41
58
|
await optimizeCollectionsPage.clickMostRecentProcessReport(reportName);
|
|
42
|
-
await (0, UtilitiesPage_2.assertLocatorVisibleWithRetry)(page, locator, text,
|
|
59
|
+
await (0, UtilitiesPage_2.assertLocatorVisibleWithRetry)(page, locator, text, attemptTimeout);
|
|
43
60
|
return;
|
|
44
61
|
}
|
|
45
62
|
catch (error) {
|
|
46
|
-
|
|
63
|
+
lastError = error;
|
|
64
|
+
// Once the test budget is gone (or the page is closed) every remaining
|
|
65
|
+
// attempt rejects instantly, so this loop used to spin out and report
|
|
66
|
+
// "after 10 attempts" for a failure that happened somewhere else
|
|
67
|
+
// entirely. Surface the real error instead of retrying into it.
|
|
68
|
+
if (page.isClosed() || String(error).includes('Test timeout')) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
// otherwise nothing to do. Just loop back to the collections
|
|
47
72
|
}
|
|
48
73
|
}
|
|
49
|
-
throw new Error(`Failed to assert report ${reportName} after ${
|
|
74
|
+
throw new Error(`Failed to assert report ${reportName} after ${attempts} attempts: ` +
|
|
75
|
+
String(lastError));
|
|
50
76
|
};
|
|
51
77
|
exports.assertReportWithRefreshes = assertReportWithRefreshes;
|
|
@@ -35,21 +35,35 @@ _8_10_1.test.describe('Smoke Tests', () => {
|
|
|
35
35
|
const userTaskName = 'zeebeUserTaskWithForm' + randomString;
|
|
36
36
|
const reportName = await (0, _setup_1.generateRandomStringAsync)(5);
|
|
37
37
|
let appTab;
|
|
38
|
+
// One wall-clock deadline for the whole flow. The per-hop budgets below
|
|
39
|
+
// (2 x 8 min Operate import, 10 min Tasklist, 10 x 2 min Optimize import)
|
|
40
|
+
// sum to well over this test's own 36 min timeout, so a run where several
|
|
41
|
+
// hops were merely slow could be killed by the harness mid-wait and
|
|
42
|
+
// reported as a bare "Test timeout of 2160000ms exceeded" — no failing
|
|
43
|
+
// assertion, no named hop, and no screenshot. Clamping each hop to the time
|
|
44
|
+
// actually left makes the slow hop fail with its own diagnostic instead.
|
|
45
|
+
// The reserve covers the login already spent in `beforeEach` plus the
|
|
46
|
+
// afterEach screenshot/video capture, which is what the on-call reads.
|
|
47
|
+
// In a healthy run nothing is clamped: each wait resolves long before the
|
|
48
|
+
// deadline, so `flowBudget` returns the full budget it was asked for.
|
|
49
|
+
const flowDeadline = Date.now() + _8_10_1.test.info().timeout - 120000;
|
|
50
|
+
const flowBudget = (wanted) => Math.max(30000, Math.min(wanted, flowDeadline - Date.now()));
|
|
38
51
|
await _8_10_1.test.step('Navigate to Web Modeler', async () => {
|
|
39
52
|
await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
|
|
40
53
|
timeout: constants_1.TIMEOUT.medium,
|
|
41
54
|
});
|
|
42
|
-
await appsPage.
|
|
43
|
-
await appsPage.clickModeler();
|
|
44
|
-
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
45
|
-
timeout: constants_1.TIMEOUT.navigation,
|
|
46
|
-
});
|
|
55
|
+
await appsPage.openModelerWithRetry(modelerHomePage.modelerPageBanner, constants_1.TIMEOUT.navigation);
|
|
47
56
|
});
|
|
48
57
|
await _8_10_1.test.step('Create form to be linked for Zeebe user task', async () => {
|
|
49
|
-
|
|
58
|
+
// Ensure the per-user project exists before using it: the shared
|
|
59
|
+
// test-setup project creates it, but if that per-user setup step is
|
|
60
|
+
// absent/flaky the folder is missing and clicking it hangs forever.
|
|
61
|
+
// createCrossComponentProjectFolder clicks into the folder when present
|
|
62
|
+
// (the normal path) and creates it only when it is genuinely missing.
|
|
63
|
+
await modelerHomePage.createCrossComponentProjectFolder();
|
|
50
64
|
await modelerHomePage.clickDiagramTypeDropdown();
|
|
51
65
|
await modelerHomePage.clickFormOption();
|
|
52
|
-
await modelerHomePage.
|
|
66
|
+
await modelerHomePage.enterFormNameWithRetry(formName);
|
|
53
67
|
await modelerHomePage.assertFormBreadcrumbVisible(formName);
|
|
54
68
|
await formJsPage.deployForm(CLUSTER_NAME);
|
|
55
69
|
// Wait for the form to propagate to the cluster before it can be embedded
|
|
@@ -91,52 +105,152 @@ _8_10_1.test.describe('Smoke Tests', () => {
|
|
|
91
105
|
});
|
|
92
106
|
});
|
|
93
107
|
await _8_10_1.test.step('Open process instance in Operate and verify it is active', async () => {
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
// Start latching the Operate deep link on the CONTEXT before clicking.
|
|
109
|
+
// Playwright raises `popup` only once the popup's initial navigation
|
|
110
|
+
// response has started loading, so a listener attached to the popup page
|
|
111
|
+
// afterwards can never see the deep-link request -- and the auth redirect
|
|
112
|
+
// chain of a session-less tab drops that path, so nothing else observes it
|
|
113
|
+
// either. Missing it leaves the retry loop below polling the Operate app
|
|
114
|
+
// root for the whole budget (see OperateProcessInstancePage).
|
|
115
|
+
const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
|
|
116
|
+
// Open the Operate tab through the retrying helper: it registers the
|
|
117
|
+
// popup wait before each click (`waitForEvent` only sees events raised
|
|
118
|
+
// after it is called, and the tab opens while the click is still
|
|
119
|
+
// resolving) and re-clicks when a click resolved without opening a tab,
|
|
120
|
+
// which is how this step used to sit out its whole 60s budget.
|
|
121
|
+
appTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink, constants_1.TIMEOUT.navigation);
|
|
96
122
|
const appTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(appTab);
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
await appTabProcessInstancePage.
|
|
123
|
+
// Capture the deep link first: when the instance is not imported yet
|
|
124
|
+
// Operate 404s and replaces the URL with the processes list, so the
|
|
125
|
+
// retry loop has to re-navigate to this URL instead of reloading the
|
|
126
|
+
// list page (see OperateProcessInstancePage).
|
|
127
|
+
await appTabProcessInstancePage.rememberInstanceUrl(constants_1.TIMEOUT.navigation, watchedInstanceUrl);
|
|
128
|
+
// Poll the instance deep link rather than the page Operate bounced us
|
|
129
|
+
// to: while the Zeebe -> Operate import lags, the deep link 404s and
|
|
130
|
+
// Operate replaces the URL with the processes list, where the instance
|
|
131
|
+
// header can never render. The previous loop spent most of its ~11 min
|
|
132
|
+
// idling on that list page; this re-asks Operate instead.
|
|
133
|
+
//
|
|
134
|
+
// `processImport`, the same budget the COMPLETED-icon gate below uses:
|
|
135
|
+
// both wait on the identical Zeebe -> Operate import, so the shorter
|
|
136
|
+
// `processActive` (300s) made this the first place a lagging import ran
|
|
137
|
+
// out of road ("active icon in Operate timed out after 300000ms" with the
|
|
138
|
+
// instance header never rendering). This does not lengthen the test in
|
|
139
|
+
// practice -- the import lag is paid once, so time spent here is time the
|
|
140
|
+
// COMPLETED-icon gate no longer needs.
|
|
141
|
+
await appTabProcessInstancePage.waitForInstanceLocator(appTabProcessInstancePage.activeIcon, 'active icon', flowBudget(constants_1.TIMEOUT.processImport));
|
|
142
|
+
// The active-icon wait above already proves the instance page rendered,
|
|
143
|
+
// so only the "what's new" popup is left to dismiss. Re-polling for the
|
|
144
|
+
// "Instance History" panel here would spend a second 8 min budget on a
|
|
145
|
+
// weaker version of the same gate.
|
|
146
|
+
await appTabProcessInstancePage.dismissWhatsNewIfVisible();
|
|
102
147
|
});
|
|
103
148
|
await _8_10_1.test.step('Navigate to Tasklist and complete both user tasks', async () => {
|
|
104
149
|
const appTabAppsPage = new AppsPage_1.AppsPage(appTab);
|
|
105
150
|
const appTabTaskPanelPage = new TaskPanelPage_1.TaskPanelPage(appTab);
|
|
106
151
|
const appTabTaskDetailsPage = new TaskDetailsPage_1.TaskDetailsPage(appTab);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
// Gate the hop on Tasklist's own banner, for the same reason the Operate
|
|
153
|
+
// hop below is gated: `clickTasklist` returns as soon as its click
|
|
154
|
+
// resolves, so a hand-off that never landed left this tab on Operate —
|
|
155
|
+
// and the task poll below then spent its whole budget reloading Operate
|
|
156
|
+
// looking for a row that can only exist in Tasklist (see
|
|
157
|
+
// `openTasklistWithRetry`).
|
|
158
|
+
await appTabAppsPage.openTasklistWithRetry(appTabTaskPanelPage.taskListPageBanner, CLUSTER_NAME, constants_1.TIMEOUT.navigation);
|
|
159
|
+
// One budget for the whole Tasklist hop. The two tasks are parallel
|
|
160
|
+
// branches of the same instance, so they are imported together: the
|
|
161
|
+
// first wait absorbs the importer lag and the rest are already present.
|
|
162
|
+
// Per-call budgets are what killed this test -- 10 x 120s inside
|
|
163
|
+
// completeTaskWithRetry's 3 attempts is a 60 min worst case for ONE
|
|
164
|
+
// task, more than the 36 min test timeout, so a lagging importer ran the
|
|
165
|
+
// clock out mid-wait and the run reported "Test timeout exceeded" with no
|
|
166
|
+
// failing assertion instead of naming the task that never arrived.
|
|
167
|
+
const tasklistDeadline = Date.now() + flowBudget(10 * 60 * 1000);
|
|
168
|
+
const tasklistBudget = () => Math.max(30000, tasklistDeadline - Date.now());
|
|
169
|
+
await (0, UtilitiesPage_1.completeTaskWithRetry)(appTabTaskPanelPage, appTabTaskDetailsPage, `${userTaskName}1`, 'Medium', 3, tasklistBudget());
|
|
170
|
+
await (0, UtilitiesPage_1.completeTaskWithRetry)(appTabTaskPanelPage, appTabTaskDetailsPage, `${userTaskName}2`, 'Medium', 3, tasklistBudget());
|
|
171
|
+
// Re-select the row between attempts instead of asserting once: the click
|
|
172
|
+
// in `openTask` can land while the Completed list is still re-rendering
|
|
173
|
+
// after the filter change, which leaves the details panel on the
|
|
174
|
+
// previously selected task (or on its empty state) with nothing left to
|
|
175
|
+
// move it. A single 30s wait then reported
|
|
176
|
+
// "getByTestId('details-info').getByText('<task>') ... element(s) not
|
|
177
|
+
// found". Re-clicking the row is what recovers it, so the assertion
|
|
178
|
+
// itself is unchanged — the details panel must still name the task.
|
|
179
|
+
const assertTaskDetails = async (taskName) => {
|
|
180
|
+
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(appTab, appTabTaskDetailsPage.detailsInfo.getByText(taskName), `${taskName} in the task details panel`, constants_1.TIMEOUT.medium, false, 3, appTabTaskPanelPage.taskInList(taskName));
|
|
181
|
+
};
|
|
111
182
|
await appTabTaskPanelPage.filterBy('Completed');
|
|
112
|
-
await appTabTaskPanelPage.openTask(`${userTaskName}1
|
|
113
|
-
await (
|
|
114
|
-
await appTabTaskPanelPage.openTask(`${userTaskName}2
|
|
115
|
-
await (
|
|
183
|
+
await appTabTaskPanelPage.openTask(`${userTaskName}1`, tasklistBudget());
|
|
184
|
+
await assertTaskDetails(`${userTaskName}1`);
|
|
185
|
+
await appTabTaskPanelPage.openTask(`${userTaskName}2`, tasklistBudget());
|
|
186
|
+
await assertTaskDetails(`${userTaskName}2`);
|
|
116
187
|
});
|
|
117
188
|
await _8_10_1.test.step('Assert process completion in Operate', async () => {
|
|
118
189
|
const appTabAppsPage = new AppsPage_1.AppsPage(appTab);
|
|
119
190
|
const appTabHomePage = new OperateHomePage_1.OperateHomePage(appTab);
|
|
120
191
|
const appTabProcessesPage = new OperateProcessesPage_1.OperateProcessesPage(appTab);
|
|
121
192
|
const appTabProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(appTab);
|
|
122
|
-
|
|
123
|
-
|
|
193
|
+
// Gate the hop on Operate's own banner: `clickOperate` returns as soon as
|
|
194
|
+
// its click resolves, so a hand-off that never landed left the tab on
|
|
195
|
+
// Tasklist — whose nav also has a "Processes" link, so the next step
|
|
196
|
+
// clicked THAT and then waited out its budget for Operate's Processes
|
|
197
|
+
// view (see `openOperateWithRetry`).
|
|
198
|
+
await appTabAppsPage.openOperateWithRetry(appTabHomePage.operateBanner, CLUSTER_NAME, constants_1.TIMEOUT.navigation);
|
|
124
199
|
await appTabHomePage.clickProcessesTab();
|
|
125
200
|
await (0, test_1.expect)(appTabProcessesPage.processCompletedCheckbox).toBeVisible({
|
|
126
201
|
timeout: constants_1.TIMEOUT.short,
|
|
127
202
|
});
|
|
128
203
|
await appTabProcessesPage.clickProcessCompletedCheckbox();
|
|
129
204
|
await appTabProcessesPage.clickProcessInstanceLink(processName);
|
|
130
|
-
|
|
205
|
+
// Poll the instance deep link for the COMPLETED icon, the same way the
|
|
206
|
+
// active-icon gate above does. `assertLocatorVisibleWithRetry` is blind
|
|
207
|
+
// to Operate's 404 bounce: the completed filter also lists still-ACTIVE
|
|
208
|
+
// instances, so this link is often opened before the import has caught
|
|
209
|
+
// up, and each of its 6 attempts then idled a full 120s on a page that
|
|
210
|
+
// could not render the header -- 13 min for only 6 re-asks. This budget
|
|
211
|
+
// re-asks Operate ~16 times and leaves more of the test's 36 min to the
|
|
212
|
+
// Optimize import later on.
|
|
213
|
+
await appTabProcessInstancePage.rememberInstanceUrl(constants_1.TIMEOUT.navigation);
|
|
214
|
+
await appTabProcessInstancePage.waitForInstanceLocator(appTabProcessInstancePage.completedIcon, 'completed icon', flowBudget(constants_1.TIMEOUT.processImport));
|
|
131
215
|
});
|
|
132
216
|
await _8_10_1.test.step('Assert process imported in Optimize and create user task report', async () => {
|
|
133
217
|
const appTabAppsPage = new AppsPage_1.AppsPage(appTab);
|
|
134
218
|
const optimizeHomePage = new OptimizeHomePage_1.OptimizeHomePage(appTab);
|
|
135
219
|
const optimizeCollectionsPage = new OptimizeCollectionsPage_1.OptimizeCollectionsPage(appTab);
|
|
136
220
|
const optimizeReportPage = new OptimizeReportPage_1.OptimizeReportPage(appTab);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
221
|
+
// Gate the hop on Optimize's own banner, for the same reason the Operate
|
|
222
|
+
// and Tasklist hops above are gated: `clickOptimize` returns as soon as
|
|
223
|
+
// its click resolves, so a hand-off that never landed left this tab off
|
|
224
|
+
// Optimize — and the import poll below then spent its whole 20 min
|
|
225
|
+
// budget reloading a page that can never render a process definition
|
|
226
|
+
// (see `openOptimizeWithRetry`).
|
|
227
|
+
await appTabAppsPage.openOptimizeWithRetry(optimizeHomePage.optimizeBanner, CLUSTER_NAME, constants_1.TIMEOUT.navigation);
|
|
228
|
+
// Reloads for ~10 rounds of the 2 min import window, dismissing the
|
|
229
|
+
// onboarding modal that reappears on every load and covers the list.
|
|
230
|
+
// Two extra rounds over the previous 8: the Optimize import is the
|
|
231
|
+
// slowest hop in this flow and the time comes out of the Operate wait
|
|
232
|
+
// above, so the test's overall worst case still shrinks.
|
|
233
|
+
// Rounds are derived from the time the flow has left rather than fixed at
|
|
234
|
+
// 10, for the same reason the waits above are clamped: 10 x 2 min is 20
|
|
235
|
+
// min of the test's 36, so on a run that already spent its budget on the
|
|
236
|
+
// Operate imports these rounds were what pushed it past the harness
|
|
237
|
+
// timeout. At least 2 rounds are always attempted, and a healthy run
|
|
238
|
+
// still gets all 10.
|
|
239
|
+
// Deriving only the ROUND COUNT from `flowBudget` left the wait itself
|
|
240
|
+
// unclamped: the helper's budget is `timeout * maxRetries`, computed from
|
|
241
|
+
// the per-round timeout it is handed, so the `max(2, ...)` floor kept at
|
|
242
|
+
// least 2 x 120s of polling even on a flow with seconds left -- and the
|
|
243
|
+
// report-creation steps below then ran past the test's own 36 min
|
|
244
|
+
// timeout. That is the bare "Test timeout of 2160000ms exceeded" with no
|
|
245
|
+
// failing assertion and no named hop. Splitting the budget across the
|
|
246
|
+
// rounds makes `timeout * maxRetries` equal the time the flow actually
|
|
247
|
+
// has, so a lagging Optimize import fails with its own diagnostic
|
|
248
|
+
// ("process <name> in Optimize failed after ...ms of polling") instead of
|
|
249
|
+
// being killed by the harness. A healthy run is unchanged: the full
|
|
250
|
+
// 10 x 120s is still granted whenever the flow has it to give.
|
|
251
|
+
const optimizeBudget = flowBudget(10 * constants_1.TIMEOUT.optimizeImport);
|
|
252
|
+
const optimizeRounds = Math.max(2, Math.floor(optimizeBudget / constants_1.TIMEOUT.optimizeImport));
|
|
253
|
+
await optimizeHomePage.assertProcessImportedWithRetry(processName, Math.floor(optimizeBudget / optimizeRounds), optimizeRounds);
|
|
140
254
|
await optimizeHomePage.clickCollectionsLink();
|
|
141
255
|
await optimizeCollectionsPage.clickCreateNewButtonWithRetry();
|
|
142
256
|
await optimizeCollectionsPage.clickReportOption();
|
|
@@ -165,10 +279,17 @@ _8_10_1.test.describe('Smoke Tests', () => {
|
|
|
165
279
|
timeout: constants_1.TIMEOUT.medium,
|
|
166
280
|
});
|
|
167
281
|
await optimizeCollectionsPage.clickMostRecentProcessReport(reportName);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
282
|
+
// Poll the row count itself across reloads: the user-task instance data
|
|
283
|
+
// arrives on a later Optimize import cycle than the definition, so
|
|
284
|
+
// probing visibility first and then asserting the count once reported
|
|
285
|
+
// "expected 2, received 0" while the import was still in flight.
|
|
286
|
+
// Clamped for the same reason as the import wait above: this is the last
|
|
287
|
+
// hop of the flow and its default 4 x 60s is spent after the slowest
|
|
288
|
+
// wait in the test, so leaving it unclamped re-opens the same overrun
|
|
289
|
+
// the budget above closes. The retry count is unchanged -- only the time
|
|
290
|
+
// each attempt may idle is bounded by what the flow has left.
|
|
291
|
+
const countRetries = 4;
|
|
292
|
+
await optimizeReportPage.assertUserTaskInstanceCountWithRetry(2, appTab, countRetries, Math.floor(flowBudget(countRetries * constants_1.TIMEOUT.navigation) / countRetries));
|
|
172
293
|
});
|
|
173
294
|
});
|
|
174
295
|
(0, _8_10_1.test)('Most Common REST Connector User Flow', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }) => {
|
|
@@ -179,14 +300,12 @@ _8_10_1.test.describe('Smoke Tests', () => {
|
|
|
179
300
|
await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
|
|
180
301
|
timeout: constants_1.TIMEOUT.medium,
|
|
181
302
|
});
|
|
182
|
-
await appsPage.
|
|
183
|
-
await appsPage.clickModeler();
|
|
184
|
-
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
185
|
-
timeout: constants_1.TIMEOUT.navigation,
|
|
186
|
-
});
|
|
303
|
+
await appsPage.openModelerWithRetry(modelerHomePage.modelerPageBanner, constants_1.TIMEOUT.navigation);
|
|
187
304
|
});
|
|
188
305
|
await _8_10_1.test.step('Open Cross Component Test Project and create a BPMN diagram template', async () => {
|
|
189
|
-
|
|
306
|
+
// See note in the flow above: ensure the per-user project exists rather
|
|
307
|
+
// than hanging on a click when the shared setup did not create it.
|
|
308
|
+
await modelerHomePage.createCrossComponentProjectFolder();
|
|
190
309
|
await modelerHomePage.clickDiagramTypeDropdown();
|
|
191
310
|
await modelerHomePage.clickBpmnTemplateOption();
|
|
192
311
|
});
|
|
@@ -200,13 +319,32 @@ _8_10_1.test.describe('Smoke Tests', () => {
|
|
|
200
319
|
});
|
|
201
320
|
});
|
|
202
321
|
await _8_10_1.test.step('Assert process completes in Operate and verify connector result', async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
322
|
+
// Context-level deep-link watcher before the click — same reason as in
|
|
323
|
+
// the flow above: the popup event fires too late to observe the
|
|
324
|
+
// deep-link request, so without this the retry loop below can only poll
|
|
325
|
+
// the Operate app root (see OperateProcessInstancePage).
|
|
326
|
+
const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
|
|
327
|
+
// Same retrying open as in the flow above: the listener goes up before
|
|
328
|
+
// the click, and a click that resolved without opening a tab is retried
|
|
329
|
+
// instead of being waited out.
|
|
330
|
+
const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink, constants_1.TIMEOUT.navigation);
|
|
207
331
|
const operateProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
|
|
208
|
-
|
|
209
|
-
|
|
332
|
+
// Capture the deep link before Operate can bounce the tab to the
|
|
333
|
+
// processes list on a not-yet-imported instance (see
|
|
334
|
+
// OperateProcessInstancePage) — otherwise every retry below reloads
|
|
335
|
+
// the list page, where the instance header can never render.
|
|
336
|
+
await operateProcessInstancePage.rememberInstanceUrl(constants_1.TIMEOUT.navigation, watchedInstanceUrl);
|
|
337
|
+
// Wait for the COMPLETED icon as the single import gate, polling the deep
|
|
338
|
+
// link so a bounce to the processes list is re-navigated instead of
|
|
339
|
+
// waited out: this tab is opened seconds after the instance starts, so
|
|
340
|
+
// the first query almost always 404s. `assertLocatorVisibleWithRetry`
|
|
341
|
+
// does not know about the bounce and burned 120s per attempt on the list
|
|
342
|
+
// page, which is what reported "completed icon in Operate failed after 6
|
|
343
|
+
// attempts" whenever the import lagged. The popup is dismissed
|
|
344
|
+
// afterwards, once the last re-navigation has settled, so it cannot cover
|
|
345
|
+
// the variables assertions.
|
|
346
|
+
await operateProcessInstancePage.waitForInstanceLocator(operateProcessInstancePage.completedIcon, 'completed icon', constants_1.TIMEOUT.processImport);
|
|
347
|
+
await operateProcessInstancePage.dismissWhatsNewIfVisible();
|
|
210
348
|
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTab, operateProcessInstancePage.variablesList, 'variable list in Operate', constants_1.TIMEOUT.navigation);
|
|
211
349
|
await (0, test_1.expect)(operateProcessInstancePage.connectorResultVariableName('message')).toBeVisible({ timeout: constants_1.TIMEOUT.navigation });
|
|
212
350
|
await (0, test_1.expect)(operateProcessInstancePage
|
|
@@ -105,7 +105,8 @@ SM_8_10_1.test.describe('Identity User Flow Tests', () => {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
|
-
|
|
108
|
+
// Skipped due to bug #60139: https://github.com/camunda/camunda/issues/60139
|
|
109
|
+
SM_8_10_1.test.skip('User Roles User Flow on Orchestration Cluster', async ({ page, navigationPage, ocIdentityMappingRulesPage, ocIdentityHomePage, ocIdentityRolesPage, taskPanelPage, operateHomePage, settingsPage, browser, keycloakLoginPage, keycloakAdminPage, managementIdentityPage, }) => {
|
|
109
110
|
if (node_process_1.default.env.IS_RBA === 'true' ||
|
|
110
111
|
node_process_1.default.env.IS_MT === 'true' ||
|
|
111
112
|
node_process_1.default.env.IS_LICENSE_KEY === 'true') {
|
package/dist/utils/constants.js
CHANGED
|
@@ -28,5 +28,9 @@ exports.TIMEOUT = {
|
|
|
28
28
|
navigation: 60000,
|
|
29
29
|
processActive: 300000,
|
|
30
30
|
processComplete: 120000,
|
|
31
|
+
// Total budget for polling an Operate instance deep link until the
|
|
32
|
+
// Zeebe -> Operate import has caught up (see `waitForInstanceLocator`).
|
|
33
|
+
// Spent as ~30s polls that each re-ask Operate, not as one long idle wait.
|
|
34
|
+
processImport: 480000,
|
|
31
35
|
optimizeImport: 120000,
|
|
32
36
|
};
|