@camunda/e2e-test-suite 0.0.1111 → 0.0.1112
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.js +17 -2
- package/dist/pages/8.10/Authorization.js +9 -1
- package/dist/pages/8.10/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.10/ClusterDetailsPage.js +23 -2
- package/dist/pages/8.10/ClusterPage.js +14 -6
- package/dist/pages/8.10/ModelerCreatePage.js +32 -4
- package/dist/pages/8.10/TaskProcessesPage.d.ts +2 -0
- package/dist/pages/8.10/TaskProcessesPage.js +26 -6
- package/dist/tests/8.10/rba-enabled-v2-user-flows.spec.js +9 -1
- package/package.json +1 -1
|
@@ -116,7 +116,12 @@ class AppsPage {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
async clickCluster(component, name) {
|
|
119
|
-
|
|
119
|
+
// Cluster names are not unique in an org, so this app's sub-menu can list
|
|
120
|
+
// the same name twice; pick the first entry (same row the Console cluster
|
|
121
|
+
// list resolves to) rather than strict-mode failing on the hand-off.
|
|
122
|
+
const cluster = component
|
|
123
|
+
.getByRole('link', { name: name, exact: true })
|
|
124
|
+
.first();
|
|
120
125
|
await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
|
|
121
126
|
await cluster.click();
|
|
122
127
|
}
|
|
@@ -555,7 +560,17 @@ class AppsPage {
|
|
|
555
560
|
}
|
|
556
561
|
catch (error) {
|
|
557
562
|
console.warn(`Click attempt ${retries + 1} failed: ${error}`);
|
|
558
|
-
|
|
563
|
+
// The other three app hand-offs (`clickTasklist`, `clickOperate`,
|
|
564
|
+
// `clickOptimize`) all reload here; this one only slept, and a slept
|
|
565
|
+
// retry re-opens the very same apps menu that just failed to list
|
|
566
|
+
// Console. When the shell is the thing that did not finish rendering
|
|
567
|
+
// -- the tab is still mid SSO hand-off, or `clickCamundaApps` found
|
|
568
|
+
// none of its four triggers and returned without opening anything --
|
|
569
|
+
// nothing changes between attempts and all three burn their 30s wait
|
|
570
|
+
// on an entry that was never going to appear. Re-render the shell
|
|
571
|
+
// instead, then let the loop re-open the menu.
|
|
572
|
+
await this.reloadForRecovery('opening Console');
|
|
573
|
+
await (0, sleep_1.sleep)(10000);
|
|
559
574
|
}
|
|
560
575
|
}
|
|
561
576
|
throw new Error(`Failed to click the console link after ${maxRetries} attempts.`);
|
|
@@ -103,8 +103,16 @@ class Authorization {
|
|
|
103
103
|
name: /delete authorization/i,
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
|
+
// The authorizations table paginates, and the RBA clusters these flows run
|
|
107
|
+
// against are long-lived: every night's run leaves more process-definition
|
|
108
|
+
// authorizations behind, so the row being asserted is regularly not on the
|
|
109
|
+
// page currently displayed. Scoping the assertion to the rendered rows then
|
|
110
|
+
// reports a row that exists as missing. Walk the pages -- the assertion is
|
|
111
|
+
// unchanged, it is only no longer limited to the first page, and the helper
|
|
112
|
+
// reloads between attempts so a row the product has not finished
|
|
113
|
+
// provisioning yet is still given time to appear.
|
|
106
114
|
async assertAuthorization(auth) {
|
|
107
|
-
await (0,
|
|
115
|
+
await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, this.authorizationsList.getByRole('row').filter({ hasText: auth }).first());
|
|
108
116
|
}
|
|
109
117
|
async createAuthorization(authorization) {
|
|
110
118
|
await this.createAuthorizationButton.click();
|
|
@@ -66,6 +66,7 @@ declare class ClusterDetailsPage {
|
|
|
66
66
|
assertAPIClientScopes(name: string): Promise<void>;
|
|
67
67
|
filterClientsList(name: string): Promise<void>;
|
|
68
68
|
clickSettingsTab(): Promise<void>;
|
|
69
|
+
private waitForAuthorizationsSection;
|
|
69
70
|
enableAuthorizations(): Promise<void>;
|
|
70
71
|
disableAuthorizations(): Promise<void>;
|
|
71
72
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
@@ -294,9 +294,30 @@ class ClusterDetailsPage {
|
|
|
294
294
|
});
|
|
295
295
|
}).toPass({ timeout: 120000, intervals: [1000, 2000, 5000] });
|
|
296
296
|
}
|
|
297
|
+
// The Settings panel body is fetched separately from the tab list, so a
|
|
298
|
+
// selected Settings tab does not guarantee the Authorizations section is
|
|
299
|
+
// there: when that fetch fails -- or Console lands on its hard-crash banner
|
|
300
|
+
// ("An error has occurred") on the way in -- the section never renders, and
|
|
301
|
+
// neither state recovers on its own. A flat 60s wait can then only time out.
|
|
302
|
+
// Reload and re-open Settings between attempts, which is the same recovery
|
|
303
|
+
// `clickSettingsTab` already applies to the tab itself.
|
|
304
|
+
async waitForAuthorizationsSection() {
|
|
305
|
+
await (0, test_1.expect)(async () => {
|
|
306
|
+
const rendered = await this.authorizationsHeading
|
|
307
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
308
|
+
.then(() => true)
|
|
309
|
+
.catch(() => false);
|
|
310
|
+
if (rendered) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
314
|
+
await this.clickSettingsTab();
|
|
315
|
+
await (0, test_1.expect)(this.authorizationsHeading).toBeVisible({ timeout: 60000 });
|
|
316
|
+
}).toPass({ timeout: 300000, intervals: [1000, 2000, 5000] });
|
|
317
|
+
}
|
|
297
318
|
async enableAuthorizations() {
|
|
298
319
|
await (0, sleep_1.sleep)(5000);
|
|
299
|
-
await
|
|
320
|
+
await this.waitForAuthorizationsSection();
|
|
300
321
|
// Locate all elements with class .cds--toggle__text
|
|
301
322
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
302
323
|
// Check if toggleTextElements has at least 2 elements
|
|
@@ -332,7 +353,7 @@ class ClusterDetailsPage {
|
|
|
332
353
|
}
|
|
333
354
|
}
|
|
334
355
|
async disableAuthorizations() {
|
|
335
|
-
await
|
|
356
|
+
await this.waitForAuthorizationsSection();
|
|
336
357
|
// Locate all elements with class .cds--toggle__text
|
|
337
358
|
const toggleTextElements = await this.page.$$('.cds--toggle__text');
|
|
338
359
|
// Check if toggleTextElements has at least 2 elements
|
|
@@ -107,6 +107,17 @@ class ClusterPage {
|
|
|
107
107
|
.filter({ hasNotText: 'Generation' }); //Filter out header row
|
|
108
108
|
this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
|
|
109
109
|
this.tasklistV2Api = page.getByText('Tasklist API v2');
|
|
110
|
+
// The Console does not enforce unique cluster names, so an org can end up
|
|
111
|
+
// holding several clusters under the same name (a create that was retried
|
|
112
|
+
// after the list-fetch race in clickClustersBreadcrumb, or two runs of the
|
|
113
|
+
// same spec overlapping on the long-lived RBA clusters). Every name-scoped
|
|
114
|
+
// locator below is derived from this one, so without .first() a single
|
|
115
|
+
// leftover duplicate turns each of them into a strict-mode violation and
|
|
116
|
+
// the spec dies in beforeEach instead of reusing the healthy cluster.
|
|
117
|
+
// Resolving to the first matching row keeps this accessor singular, which
|
|
118
|
+
// is how every caller already uses it (isVisible/waitFor/click). Callers
|
|
119
|
+
// that must act on *all* same-named rows (deleteCluster) re-query in a
|
|
120
|
+
// loop, so they still walk the duplicates one by one.
|
|
110
121
|
this.cluster = (clusterName) => this.clustersList.filter({
|
|
111
122
|
has: page.getByRole('link', { name: clusterName, exact: true }),
|
|
112
123
|
});
|
|
@@ -272,12 +283,9 @@ class ClusterPage {
|
|
|
272
283
|
// matches every ancestor whose combined text spans the name and a status
|
|
273
284
|
// (the whole app shell does, once a second row carries the same name),
|
|
274
285
|
// which turns this wait into a strict-mode violation.
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
const clusterRowVisible = this.clusterRow(clusterName).filter({
|
|
279
|
-
hasText: /Creating|Healthy/,
|
|
280
|
-
});
|
|
286
|
+
const clusterRow = this.clusterRow(clusterName);
|
|
287
|
+
const creatingRow = clusterRow.getByText('Creating', { exact: true });
|
|
288
|
+
const clusterRowVisible = creatingRow.or(clusterRow.getByText('Healthy', { exact: true }));
|
|
281
289
|
try {
|
|
282
290
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
283
291
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
@@ -628,11 +628,14 @@ class ModelerCreatePage {
|
|
|
628
628
|
await this.deployAndRunSubButton.click({ timeout: 30000 });
|
|
629
629
|
}
|
|
630
630
|
async runProcessInstance(clusterName, variables = '', formVariables = {}, textToVisible = '', hasDeploymentPermission = true, businessId = '') {
|
|
631
|
-
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
632
631
|
const maxRetries = 10;
|
|
633
632
|
const retryDelayMs = 20000;
|
|
634
633
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
635
634
|
try {
|
|
635
|
+
// Inside the loop, not before it: a no-op once bound, but selectCluster
|
|
636
|
+
// clears the binding when the dialog does not offer the cluster, so the
|
|
637
|
+
// next attempt rebinds rather than reopening an unchanged dialog.
|
|
638
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
636
639
|
await this.clickStartInstanceMainButton();
|
|
637
640
|
await this.selectCluster(clusterName);
|
|
638
641
|
if (variables.length > 0) {
|
|
@@ -714,7 +717,22 @@ class ModelerCreatePage {
|
|
|
714
717
|
.isVisible()
|
|
715
718
|
.catch(() => false);
|
|
716
719
|
if (!isLegacyDialog) {
|
|
717
|
-
await
|
|
720
|
+
const offered = await this.dialog
|
|
721
|
+
.getByText(clusterName, { exact: false })
|
|
722
|
+
.first()
|
|
723
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
724
|
+
.then(() => true)
|
|
725
|
+
.catch(() => false);
|
|
726
|
+
if (!offered) {
|
|
727
|
+
// Forget the binding before failing. The dialog lists exactly the
|
|
728
|
+
// clusters this application's stages point at, so a missing cluster
|
|
729
|
+
// means the binding did not take -- and no amount of reopening the
|
|
730
|
+
// dialog will make it appear. Clearing the record lets the caller's
|
|
731
|
+
// next attempt rebind instead of re-checking an unchanged dialog.
|
|
732
|
+
this.processApplicationPage.forgetStageCluster();
|
|
733
|
+
throw new Error(`The deploy dialog does not offer "${clusterName}": its stage ` +
|
|
734
|
+
'binding did not take. It will be rebound on the next attempt.');
|
|
735
|
+
}
|
|
718
736
|
return;
|
|
719
737
|
}
|
|
720
738
|
const healthyRegex = new RegExp(`${clusterName}.*?HealthydevManage`);
|
|
@@ -763,7 +781,11 @@ class ModelerCreatePage {
|
|
|
763
781
|
.isVisible()
|
|
764
782
|
.catch(() => false);
|
|
765
783
|
if (!menuOpen) {
|
|
766
|
-
|
|
784
|
+
// Bounded well below the loop budget rather than reusing
|
|
785
|
+
// `clickDiagramBreadcrumb`'s 60s: at 60s a single reopen is the whole
|
|
786
|
+
// retry window, so the attempt that would have recovered never got to
|
|
787
|
+
// run and toPass reported the previous attempt's error instead.
|
|
788
|
+
await this.diagramBreadcrumb.click({ timeout: 20000 });
|
|
767
789
|
}
|
|
768
790
|
// Park the pointer so the breadcrumb's own tooltip gets a chance to
|
|
769
791
|
// unmount -- best effort, because it is not reliably hover-driven, and
|
|
@@ -785,7 +807,13 @@ class ModelerCreatePage {
|
|
|
785
807
|
await this.renameDiagramNameButton.dispatchEvent('click');
|
|
786
808
|
}
|
|
787
809
|
await (0, test_1.expect)(this.diagramNameInput).toBeVisible({ timeout: 10000 });
|
|
788
|
-
|
|
810
|
+
// The menu also closes on its own: `modelDiagramFromFile` opens it right
|
|
811
|
+
// after the diagram is picked, and when bpmn-js finishes importing it
|
|
812
|
+
// takes focus, which dismisses the menu that `isVisible` had just seen.
|
|
813
|
+
// The click then fails with "Rename" gone from the DOM rather than
|
|
814
|
+
// intercepted. Reopening is what recovers, so the budget has to fit
|
|
815
|
+
// several whole attempts, not one.
|
|
816
|
+
}).toPass({ timeout: 180000, intervals: [1000, 2000, 5000] });
|
|
789
817
|
}
|
|
790
818
|
// The rename can silently no-op: camunda-hub's breadcrumb name editor
|
|
791
819
|
// (InlineEditable) resets its input back to the stored diagram name whenever
|
|
@@ -8,9 +8,11 @@ declare class TaskProcessesPage {
|
|
|
8
8
|
readonly waitingForTasksText: Locator;
|
|
9
9
|
readonly searchProcessesSearchbox: Locator;
|
|
10
10
|
readonly processesTab: Locator;
|
|
11
|
+
readonly tasksTab: Locator;
|
|
11
12
|
constructor(page: Page);
|
|
12
13
|
searchForProcess(name: string): Promise<void>;
|
|
13
14
|
clickpopupContinueButton(): Promise<void>;
|
|
15
|
+
private openTasksView;
|
|
14
16
|
startProcess(name: string): Promise<void>;
|
|
15
17
|
}
|
|
16
18
|
export { TaskProcessesPage };
|
|
@@ -13,6 +13,7 @@ class TaskProcessesPage {
|
|
|
13
13
|
waitingForTasksText;
|
|
14
14
|
searchProcessesSearchbox;
|
|
15
15
|
processesTab;
|
|
16
|
+
tasksTab;
|
|
16
17
|
constructor(page) {
|
|
17
18
|
this.page = page;
|
|
18
19
|
this.popupContinueButton = page.getByRole('button', { name: 'Continue' });
|
|
@@ -24,6 +25,7 @@ class TaskProcessesPage {
|
|
|
24
25
|
name: 'Search processes',
|
|
25
26
|
});
|
|
26
27
|
this.processesTab = page.getByRole('link', { name: 'Processes' });
|
|
28
|
+
this.tasksTab = page.getByRole('link', { name: 'Tasks' });
|
|
27
29
|
}
|
|
28
30
|
// Tasklist's process list is an infinite list that asks for only 12 process
|
|
29
31
|
// definitions at a time (`PROCESS_DEFINITIONS_PAGE_SIZE` in the
|
|
@@ -71,6 +73,28 @@ class TaskProcessesPage {
|
|
|
71
73
|
}
|
|
72
74
|
await (0, test_1.expect)(this.popup.first()).not.toBeVisible({ timeout: 10000 });
|
|
73
75
|
}
|
|
76
|
+
// Starting a process only lands on the Tasks view when Tasklist manages to
|
|
77
|
+
// resolve the new instance's user task in time. `startProcessMachine.ts` in
|
|
78
|
+
// camunda/camunda's orchestration-cluster webapp polls
|
|
79
|
+
// `queryUserTasks({processInstanceKey})` every second under a hard
|
|
80
|
+
// `TASKS_TIMEOUT: 15000`; when that elapses the machine goes straight to
|
|
81
|
+
// `Succeeded` and performs no navigation at all, leaving the browser on the
|
|
82
|
+
// Processes page -- which never renders the "Available tasks" panel. On
|
|
83
|
+
// these long-lived SaaS RBA clusters the exporter routinely lags past 15s,
|
|
84
|
+
// so waiting 90s for a redirect the product gave up on after 15 can only
|
|
85
|
+
// time out. Open the Tasks view ourselves when the redirect did not happen;
|
|
86
|
+
// the panel still has to appear, which is what this asserts, and the task
|
|
87
|
+
// count the caller then takes is what the test is actually about.
|
|
88
|
+
async openTasksView() {
|
|
89
|
+
const redirected = await this.availableTasks
|
|
90
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
91
|
+
.then(() => true)
|
|
92
|
+
.catch(() => false);
|
|
93
|
+
if (!redirected) {
|
|
94
|
+
await this.tasksTab.click({ timeout: 60000 });
|
|
95
|
+
}
|
|
96
|
+
await (0, test_1.expect)(this.availableTasks).toBeVisible({ timeout: 90000 });
|
|
97
|
+
}
|
|
74
98
|
async startProcess(name) {
|
|
75
99
|
if (process.env.TASKLIST_VERSION === 'v2') {
|
|
76
100
|
await this.searchForProcess(name);
|
|
@@ -88,9 +112,7 @@ class TaskProcessesPage {
|
|
|
88
112
|
await (0, test_1.expect)(this.waitingForTasksText).not.toBeVisible({
|
|
89
113
|
timeout: 60000,
|
|
90
114
|
});
|
|
91
|
-
await
|
|
92
|
-
timeout: 90000, //redirection to tasks tab
|
|
93
|
-
});
|
|
115
|
+
await this.openTasksView();
|
|
94
116
|
}
|
|
95
117
|
else if (process.env.TASKLIST_VERSION === 'v1') {
|
|
96
118
|
const processStartButton = this.processes
|
|
@@ -102,9 +124,7 @@ class TaskProcessesPage {
|
|
|
102
124
|
await (0, test_1.expect)(this.waitingForTasksText).not.toBeVisible({
|
|
103
125
|
timeout: 60000,
|
|
104
126
|
});
|
|
105
|
-
await
|
|
106
|
-
timeout: 90000,
|
|
107
|
-
});
|
|
127
|
+
await this.openTasksView();
|
|
108
128
|
}
|
|
109
129
|
else {
|
|
110
130
|
throw new Error(`Unknown TASKLIST_VERSION: ${process.env.TASKLIST_VERSION}`);
|
|
@@ -314,7 +314,15 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
|
|
|
314
314
|
await appsPage.openModelerWithRetry(modelerHomePage.modelerPageBanner);
|
|
315
315
|
});
|
|
316
316
|
await _8_10_1.test.step('Navigate to Cross Component Test Project', async () => {
|
|
317
|
-
|
|
317
|
+
// The project folder is per Modeler user, and this is the only test in
|
|
318
|
+
// the block that assumed it was already there: the other three call the
|
|
319
|
+
// create-if-missing helper. When the setup project did not manage to
|
|
320
|
+
// create it for this user, clickCrossComponentProjectFolder() burns its
|
|
321
|
+
// full 240s retry budget on a tile that will never render. Use the same
|
|
322
|
+
// helper as the sibling tests — it clicks straight into the folder when
|
|
323
|
+
// it exists (and guards against creating a duplicate), so nothing changes
|
|
324
|
+
// on the happy path.
|
|
325
|
+
await modelerHomePage.createCrossComponentProjectFolder();
|
|
318
326
|
});
|
|
319
327
|
await _8_10_1.test.step('Create Two BPMN Diagrams with User Task and Start Process Instance', async () => {
|
|
320
328
|
await (0, UtilitiesPage_1.runMultipleProcesses)(clusterName, page, modelerHomePage, modelerCreatePage, {
|