@camunda/e2e-test-suite 0.0.1271 → 0.0.1273
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/ClusterSecretsPage.js +17 -1
- package/dist/pages/8.9/AppsPage.js +10 -1
- package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
- package/dist/pages/8.9/ClusterDetailsPage.js +66 -1
- package/dist/pages/8.9/ClusterPage.js +11 -2
- package/dist/pages/8.9/ClusterSecretsPage.js +19 -4
- package/dist/pages/8.9/ConsoleOrganizationPage.js +50 -17
- package/dist/pages/8.9/FormJsPage.js +23 -0
- package/dist/pages/8.9/ModelerCreatePage.d.ts +3 -0
- package/dist/pages/8.9/ModelerCreatePage.js +108 -7
- package/dist/pages/8.9/ModelerHomePage.d.ts +8 -0
- package/dist/pages/8.9/ModelerHomePage.js +116 -7
- package/dist/pages/8.9/ProcessApplicationPage.d.ts +1 -0
- package/dist/pages/8.9/ProcessApplicationPage.js +96 -0
- package/dist/pages/8.9/SettingsPage.js +12 -2
- package/dist/pages/BrowseAllConnectorMarketplacePage.d.ts +1 -0
- package/dist/pages/BrowseAllConnectorMarketplacePage.js +26 -14
- package/dist/pages/SM-8.9/ClusterPage.js +5 -1
- package/dist/resources/Basic_Auth_REST_Connector.bpmn +2 -2
- package/dist/resources/ConnectorsBasicTest.bpmn +6 -6
- package/dist/resources/REST_Connector_Bearer_Auth_Process.bpmn +2 -2
- package/dist/resources/REST_Connector_No_Auth_Process.bpmn +1 -1
- package/dist/test-setup.d.ts +0 -1
- package/dist/test-setup.js +7 -29
- package/dist/tests/8.10/connectors-user-flows.spec.js +4 -7
- package/dist/tests/8.10/saas-migration-path-user-flows.spec.js +1 -2
- package/dist/tests/8.10/smoke-tests.spec.js +2 -3
- package/dist/tests/8.7/connectors-user-flows.spec.js +10 -14
- package/dist/tests/8.7/saas-migration-path-user-flows.spec.js +1 -2
- package/dist/tests/8.7/smoke-tests.spec.js +3 -3
- package/dist/tests/8.8/connectors-user-flows.spec.js +5 -7
- package/dist/tests/8.8/saas-migration-path-user-flows.spec.js +1 -2
- package/dist/tests/8.8/smoke-tests.spec.js +3 -3
- package/dist/tests/8.9/connectors-user-flows.spec.js +5 -7
- package/dist/tests/8.9/console-user-flows.spec.js +3 -0
- package/dist/tests/8.9/saas-migration-path-user-flows.spec.js +1 -2
- package/dist/tests/8.9/smoke-tests.spec.js +3 -3
- package/dist/tests/SM-8.10/connectors-user-flows.spec.js +6 -9
- package/dist/tests/SM-8.10/mt-enabled-user-flows.spec.js +6 -6
- package/dist/tests/SM-8.10/smoke-tests.spec.js +2 -1
- package/dist/tests/SM-8.7/connectors-user-flows.spec.js +4 -4
- package/dist/tests/SM-8.7/mt-enabled-user-flows.spec.js +5 -4
- package/dist/tests/SM-8.7/smoke-tests.spec.js +3 -2
- package/dist/tests/SM-8.8/connectors-user-flows.spec.js +4 -4
- package/dist/tests/SM-8.8/mt-enabled-user-flows.spec.js +5 -4
- package/dist/tests/SM-8.8/smoke-tests.spec.js +2 -1
- package/dist/tests/SM-8.9/connectors-user-flows.spec.js +6 -6
- package/dist/tests/SM-8.9/mt-enabled-user-flows.spec.js +5 -4
- package/dist/tests/SM-8.9/smoke-tests.spec.js +2 -1
- package/dist/tests/c8Run-8.10/connectors-user-flows.spec.js +2 -2
- package/dist/tests/c8Run-8.7/connectors-user-flows.spec.js +5 -8
- package/dist/tests/c8Run-8.8/connectors-user-flows.spec.js +5 -8
- package/dist/tests/c8Run-8.9/connectors-user-flows.spec.js +5 -8
- package/dist/utils/connectorSecrets.js +2 -1
- package/dist/utils/constants.d.ts +2 -0
- package/dist/utils/constants.js +20 -1
- package/package.json +1 -1
- package/resources/Basic_Auth_REST_Connector.bpmn +2 -2
- package/resources/ConnectorsBasicTest.bpmn +6 -6
- package/resources/REST_Connector_Bearer_Auth_Process.bpmn +2 -2
- package/resources/REST_Connector_No_Auth_Process.bpmn +1 -1
|
@@ -88,7 +88,23 @@ class ClusterSecretsPage {
|
|
|
88
88
|
.or(this.page
|
|
89
89
|
.getByRole('navigation', { name: 'Main navigation' })
|
|
90
90
|
.getByRole('link', { name: 'Environments', exact: true }));
|
|
91
|
-
|
|
91
|
+
// Scope the cluster link to its row in the clusters table, the way
|
|
92
|
+
// ClusterPage.clusterLink already does (see the note on
|
|
93
|
+
// ClusterPage.clusterRow). A page-wide getByRole('link', {name}) matches
|
|
94
|
+
// two more things and turns an ordinary click into a strict-mode
|
|
95
|
+
// violation: Hub's sidebar entry for the same environment, and every
|
|
96
|
+
// extra row when the org holds several clusters under one name -- which
|
|
97
|
+
// it does as soon as a failed create is retried, because the Console
|
|
98
|
+
// enforces name uniqueness only per region. The comment on
|
|
99
|
+
// clickClusterBanner() below already describes this locator as
|
|
100
|
+
// row-based; now it is.
|
|
101
|
+
const clustersList = page
|
|
102
|
+
.getByRole('row')
|
|
103
|
+
.filter({ hasNotText: 'Generation' }); // Filter out header row
|
|
104
|
+
this.clusterLink = (name) => clustersList
|
|
105
|
+
.filter({ has: page.getByRole('link', { name: name, exact: true }) })
|
|
106
|
+
.first()
|
|
107
|
+
.getByRole('link', { name: name, exact: true });
|
|
92
108
|
// Some environments render this tab as "Cluster secrets" rather than
|
|
93
109
|
// "Connector Secrets" -- match either (see ClusterPage's identical
|
|
94
110
|
// connectorSecretsTab fallback; this page object has its own separate
|
|
@@ -103,10 +103,19 @@ class AppsPage {
|
|
|
103
103
|
// for the expanded-sidebar layout, where the links render inline.
|
|
104
104
|
// Not exact: the rendered accessible name carries more than the bare app
|
|
105
105
|
// name (e.g. "Operate in <cluster> (opens in a new tab)").
|
|
106
|
+
//
|
|
107
|
+
// The sidebar-scoped fallback must also carry clusterName in its name: the
|
|
108
|
+
// sidebar lists every Environment as a link named after its cluster, so a
|
|
109
|
+
// bare /Admin|Identity/ match resolved to the "Admin Role Cluster"
|
|
110
|
+
// environment link -- which precedes the portalled popover in DOM order --
|
|
111
|
+
// and navigated to that environment's page instead of Admin (nightly
|
|
112
|
+
// trace, UTR Candidate Group).
|
|
106
113
|
const appLink = this.page
|
|
107
114
|
.getByRole('dialog', { name: clusterName, exact: true })
|
|
108
115
|
.getByRole('link', { name: appName })
|
|
109
|
-
.or(this.hubSidebar
|
|
116
|
+
.or(this.hubSidebar
|
|
117
|
+
.getByRole('link', { name: appName })
|
|
118
|
+
.and(this.hubSidebar.getByRole('link', { name: ` in ${clusterName}` })))
|
|
110
119
|
.first();
|
|
111
120
|
// Decide whether to expand from clusterName's OWN toggle state, not from
|
|
112
121
|
// whether "some" appLink is already visible sidebar-wide: in a
|
|
@@ -25,6 +25,10 @@ declare class ClusterDetailsPage {
|
|
|
25
25
|
readonly clustersLink: Locator;
|
|
26
26
|
readonly alertsTab: Locator;
|
|
27
27
|
readonly alertsList: Locator;
|
|
28
|
+
readonly alertExpandRowButtons: Locator;
|
|
29
|
+
readonly alertRowActionsButtons: Locator;
|
|
30
|
+
readonly deleteAlertMenuItem: Locator;
|
|
31
|
+
readonly deleteAlertConfirmButton: Locator;
|
|
28
32
|
readonly createFirstAlertButton: Locator;
|
|
29
33
|
readonly createNewAlertButton: Locator;
|
|
30
34
|
readonly alertsPanel: Locator;
|
|
@@ -82,6 +86,7 @@ declare class ClusterDetailsPage {
|
|
|
82
86
|
selectEmailRadioButton(): Promise<void>;
|
|
83
87
|
clickCreateAlertButton(): Promise<void>;
|
|
84
88
|
deleteAlerts(): Promise<void>;
|
|
89
|
+
expandAlerts(): Promise<void>;
|
|
85
90
|
private doDelete;
|
|
86
91
|
assertComponentsHealth(components?: string[]): Promise<void>;
|
|
87
92
|
createAPIClientAndReturnVariables(name: string, setEnvVariables?: boolean, clusterType?: string): Promise<{
|
|
@@ -32,6 +32,10 @@ class ClusterDetailsPage {
|
|
|
32
32
|
clustersLink;
|
|
33
33
|
alertsTab;
|
|
34
34
|
alertsList;
|
|
35
|
+
alertExpandRowButtons;
|
|
36
|
+
alertRowActionsButtons;
|
|
37
|
+
deleteAlertMenuItem;
|
|
38
|
+
deleteAlertConfirmButton;
|
|
35
39
|
createFirstAlertButton;
|
|
36
40
|
createNewAlertButton;
|
|
37
41
|
alertsPanel;
|
|
@@ -133,6 +137,25 @@ class ClusterDetailsPage {
|
|
|
133
137
|
this.alertsList = page
|
|
134
138
|
.getByRole('row')
|
|
135
139
|
.filter({ hasNotText: 'Delivery method' }); //Filter out header row
|
|
140
|
+
// Hub's Alerts tab is a design-system data table: one summary row per
|
|
141
|
+
// alert (Delivery method / Created at / Created by), with the detail
|
|
142
|
+
// fields in a sibling expansion row that only exists once the row's
|
|
143
|
+
// "Expand row" toggle is clicked, and Delete behind the row's "Row
|
|
144
|
+
// actions" kebab -> "Delete" -> "Delete alert" dialog (camunda-hub e2e
|
|
145
|
+
// ConsoleClusterDetailsPage). Scoped to the visible tab panel.
|
|
146
|
+
this.alertExpandRowButtons = page
|
|
147
|
+
.getByRole('tabpanel')
|
|
148
|
+
.getByRole('button', { name: 'Expand row', exact: true });
|
|
149
|
+
this.alertRowActionsButtons = page
|
|
150
|
+
.getByRole('tabpanel')
|
|
151
|
+
.getByRole('button', { name: 'Row actions', exact: true });
|
|
152
|
+
this.deleteAlertMenuItem = page.getByRole('menuitem', {
|
|
153
|
+
name: 'Delete',
|
|
154
|
+
exact: true,
|
|
155
|
+
});
|
|
156
|
+
this.deleteAlertConfirmButton = page
|
|
157
|
+
.getByRole('dialog', { name: 'Delete alert' })
|
|
158
|
+
.getByRole('button', { name: 'Delete', exact: true });
|
|
136
159
|
this.createFirstAlertButton = page.getByRole('button', {
|
|
137
160
|
name: 'Create an alert',
|
|
138
161
|
});
|
|
@@ -524,7 +547,47 @@ class ClusterDetailsPage {
|
|
|
524
547
|
});
|
|
525
548
|
}
|
|
526
549
|
async deleteAlerts() {
|
|
527
|
-
await this.
|
|
550
|
+
const hasRowActions = await this.alertRowActionsButtons
|
|
551
|
+
.first()
|
|
552
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
553
|
+
.then(() => true)
|
|
554
|
+
.catch(() => false);
|
|
555
|
+
if (!hasRowActions) {
|
|
556
|
+
await this.doDelete(this.dialog, 'Alerts');
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
let remaining = await this.alertRowActionsButtons.count();
|
|
560
|
+
while (remaining > 0) {
|
|
561
|
+
// A re-render can detach the menu between opening it and the click, so
|
|
562
|
+
// open-menu-and-choose-Delete is retried as one unit.
|
|
563
|
+
await (0, test_1.expect)(async () => {
|
|
564
|
+
await this.alertRowActionsButtons.first().click({ timeout: 10000 });
|
|
565
|
+
await this.deleteAlertMenuItem.click({ timeout: 10000 });
|
|
566
|
+
await (0, test_1.expect)(this.deleteAlertConfirmButton).toBeVisible({
|
|
567
|
+
timeout: 10000,
|
|
568
|
+
});
|
|
569
|
+
}).toPass({ timeout: 60000 });
|
|
570
|
+
await this.deleteAlertConfirmButton.click({ timeout: 30000 });
|
|
571
|
+
await (0, test_1.expect)(this.alertRowActionsButtons).toHaveCount(remaining - 1, {
|
|
572
|
+
timeout: 60000,
|
|
573
|
+
});
|
|
574
|
+
remaining = await this.alertRowActionsButtons.count();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Reveals each alert's detail row. A no-op on the legacy table, whose
|
|
578
|
+
// detail rows are always rendered.
|
|
579
|
+
async expandAlerts() {
|
|
580
|
+
for (let i = 0; i < 10; i++) {
|
|
581
|
+
const toggle = this.alertExpandRowButtons.first();
|
|
582
|
+
if (!(await toggle.isVisible().catch(() => false))) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const before = await this.alertExpandRowButtons.count();
|
|
586
|
+
await toggle.click({ timeout: 10000 });
|
|
587
|
+
await (0, test_1.expect)(this.alertExpandRowButtons).toHaveCount(before - 1, {
|
|
588
|
+
timeout: 10000,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
528
591
|
}
|
|
529
592
|
async doDelete(dialog, text) {
|
|
530
593
|
try {
|
|
@@ -758,6 +821,8 @@ class ClusterDetailsPage {
|
|
|
758
821
|
async assertAlertText(text, timeout = 60000, maxRetries = 10) {
|
|
759
822
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
760
823
|
try {
|
|
824
|
+
await (0, test_1.expect)(this.alertsList.first()).toBeVisible({ timeout });
|
|
825
|
+
await this.expandAlerts();
|
|
761
826
|
await (0, test_1.expect)(this.alertsList.nth(1)).toContainText(text, { timeout });
|
|
762
827
|
return;
|
|
763
828
|
}
|
|
@@ -73,9 +73,18 @@ class ClusterPage {
|
|
|
73
73
|
.getByRole('dialog')
|
|
74
74
|
.getByRole('button', { name: 'Delete' });
|
|
75
75
|
this.testClusterLink = page.getByRole('link', { name: 'Test Cluster' });
|
|
76
|
-
this.
|
|
76
|
+
// Camunda Hub renames this tab to "Cluster secrets". Accept either name,
|
|
77
|
+
// the way 8.10's ClusterPage already does: the 8.9 SaaS org is on Hub
|
|
78
|
+
// (see the create-cluster note below, which adapts to the same shell),
|
|
79
|
+
// so the Carbon-era name alone never matches there and every setup step
|
|
80
|
+
// that opens this tab fails on all three clusters.
|
|
81
|
+
this.connectorSecretsTab = page
|
|
82
|
+
.getByRole('tab', {
|
|
83
|
+
name: 'Cluster secrets',
|
|
84
|
+
})
|
|
85
|
+
.or(page.getByRole('tab', {
|
|
77
86
|
name: 'Connector Secrets',
|
|
78
|
-
});
|
|
87
|
+
}));
|
|
79
88
|
// Camunda Hub's create-cluster form replaced the Carbon tiles (a
|
|
80
89
|
// <label> per option) and tabs with design-system choice cards and toggle
|
|
81
90
|
// groups, all exposed as role="radio" named by their title. Each locator
|
|
@@ -77,10 +77,25 @@ class ClusterSecretsPage {
|
|
|
77
77
|
.or(this.page
|
|
78
78
|
.getByRole('navigation', { name: 'Main navigation' })
|
|
79
79
|
.getByRole('link', { name: 'Environments', exact: true }));
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
// Scope the cluster link to its row in the clusters table, as 8.10's
|
|
81
|
+
// ClusterSecretsPage does. The 8.9 org runs the Hub shell -- the tab
|
|
82
|
+
// rename handled above proves it -- and Hub's sidebar carries a link
|
|
83
|
+
// with the same name as the environment, so a page-wide
|
|
84
|
+
// getByRole('link', {name}) matches the sidebar entry and the table row
|
|
85
|
+
// together. Extra rows from a retried create collide the same way,
|
|
86
|
+
// because the Console enforces cluster-name uniqueness only per region.
|
|
87
|
+
const clustersList = page
|
|
88
|
+
.getByRole('row')
|
|
89
|
+
.filter({ hasNotText: 'Generation' }); // Filter out header row
|
|
90
|
+
this.clusterLink = (name) => clustersList
|
|
91
|
+
.filter({ has: page.getByRole('link', { name: name, exact: true }) })
|
|
92
|
+
.first()
|
|
93
|
+
.getByRole('link', { name: name, exact: true });
|
|
94
|
+
// Same Hub tab rename as ClusterPage.connectorSecretsTab above -- accept
|
|
95
|
+
// either name, as 8.10's ClusterSecretsPage does.
|
|
96
|
+
this.clusterSecretTab = this.page
|
|
97
|
+
.getByRole('tab', { name: 'Cluster secrets' })
|
|
98
|
+
.or(this.page.getByRole('tab', { name: 'Connector Secrets' }));
|
|
84
99
|
this.confirmDeleteInput = page.getByRole('textbox', {
|
|
85
100
|
name: 'Type the word DELETE to confirm',
|
|
86
101
|
});
|
|
@@ -43,20 +43,39 @@ class ConsoleOrganizationPage {
|
|
|
43
43
|
constructor(page) {
|
|
44
44
|
this.page = page;
|
|
45
45
|
this.usersTab = page.getByRole('tab', { name: 'Users' });
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
// Hub's org-management Users table is a design-system data table whose
|
|
47
|
+
// per-row kebab is named "Row actions" (camunda-hub e2e
|
|
48
|
+
// ConsoleOrgManagementPage), not Carbon's "Options". Scoped to the visible
|
|
49
|
+
// tab panel so another tab's rows cannot match.
|
|
50
|
+
this.optionsButton = page
|
|
51
|
+
.getByRole('button', { name: 'Options' })
|
|
52
|
+
.or(page.getByRole('tabpanel').getByRole('button', { name: 'Row actions' }))
|
|
53
|
+
.first();
|
|
54
|
+
this.editUserMenuItem = page.getByRole('menuitem', {
|
|
55
|
+
name: /^Edit user$/i,
|
|
56
|
+
});
|
|
57
|
+
// The "Edit user" dialog's role toggles are plain Carbon Checkboxes
|
|
58
|
+
// (@camunda/ccma-saas-frontend c4-user-details-page.tsx), each its own
|
|
59
|
+
// labelText-bound <input type="checkbox">, not a role="group" of
|
|
60
|
+
// <label>s -- there is no group wrapper around them at all. Scoped to
|
|
61
|
+
// whichever dialog is open rather than one named "Edit user": a modal
|
|
62
|
+
// whose heading isn't wired via aria-labelledby carries no accessible
|
|
63
|
+
// name at all, which silently zeroes out a name-matched dialog locator
|
|
64
|
+
// (confirmed live: the name-scoped version timed out the same as the
|
|
65
|
+
// original role="group" one).
|
|
66
|
+
const openDialog = page.getByRole('dialog');
|
|
67
|
+
this.modelerCheckbox = openDialog.getByRole('checkbox', {
|
|
68
|
+
name: 'Modeler',
|
|
69
|
+
exact: true,
|
|
70
|
+
});
|
|
71
|
+
this.adminCheckbox = openDialog.getByRole('checkbox', {
|
|
72
|
+
name: 'Admin',
|
|
73
|
+
exact: true,
|
|
74
|
+
});
|
|
75
|
+
this.analystCheckbox = openDialog.getByRole('checkbox', {
|
|
76
|
+
name: 'Analyst',
|
|
77
|
+
exact: true,
|
|
78
|
+
});
|
|
60
79
|
this.confirmButton = page.getByRole('button', { name: 'Confirm' });
|
|
61
80
|
this.settingsTab = page.getByRole('tab', { name: 'Settings' });
|
|
62
81
|
this.optInButton = page.getByRole('button', { name: 'Opt-in', exact: true });
|
|
@@ -118,14 +137,28 @@ class ConsoleOrganizationPage {
|
|
|
118
137
|
name: 'danger',
|
|
119
138
|
})
|
|
120
139
|
.last();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
// Hub's org-management "Users" tab renders its table filter as a
|
|
141
|
+
// design-system <input type="search" aria-label="Search">, not Carbon's
|
|
142
|
+
// "Filter table" (nightly trace DOM; camunda-hub e2e
|
|
143
|
+
// ConsoleOrgManagementPage.searchbox). Keep the legacy name as a fallback.
|
|
144
|
+
this.filterTableSearchbox = page
|
|
145
|
+
.getByRole('searchbox', { name: 'Search', exact: true })
|
|
146
|
+
.or(page.getByRole('searchbox', { name: 'Filter table' }))
|
|
147
|
+
.first();
|
|
124
148
|
}
|
|
125
149
|
async filterTable(names) {
|
|
126
150
|
for (const name of names) {
|
|
127
151
|
await this.filterTableSearchbox.click({ timeout: 60000 });
|
|
128
152
|
await this.filterTableSearchbox.fill(name);
|
|
153
|
+
// The design-system filter applies after a debounce; callers act on the
|
|
154
|
+
// first row next, so wait for a data row containing this name. A
|
|
155
|
+
// "does every row match" assertion never resolves: one row (header or
|
|
156
|
+
// toolbar) always carries a role="cell" descendant without the name.
|
|
157
|
+
await (0, test_1.expect)(this.page
|
|
158
|
+
.getByRole('row')
|
|
159
|
+
.filter({ has: this.page.getByRole('cell') })
|
|
160
|
+
.filter({ hasText: name })
|
|
161
|
+
.first()).toBeVisible({ timeout: 30000 });
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
164
|
async clickProcessIdInput() {
|
|
@@ -4,6 +4,7 @@ exports.FormJsPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
7
|
+
const modelerCluster_1 = require("../../utils/modelerCluster");
|
|
7
8
|
class FormJsPage {
|
|
8
9
|
page;
|
|
9
10
|
processApplicationPage;
|
|
@@ -144,6 +145,28 @@ class FormJsPage {
|
|
|
144
145
|
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
145
146
|
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
146
147
|
}
|
|
148
|
+
// Hub's environment-based dialog (camunda-hub#29210) lists every
|
|
149
|
+
// Environment assigned to the workspace, preselects the first -- which
|
|
150
|
+
// need not be this one -- and renders a <label> per row that the legacy
|
|
151
|
+
// toggle detection below would mistake for its own. Same handling as
|
|
152
|
+
// ModelerCreatePage.selectCluster, so a form deploy targets the requested
|
|
153
|
+
// cluster instead of whichever environment the dialog preselected.
|
|
154
|
+
const hasEnvironmentRow = await this.dialog
|
|
155
|
+
.locator('[data-test^="environment-row-"]')
|
|
156
|
+
.first()
|
|
157
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
158
|
+
.then(() => true)
|
|
159
|
+
.catch(() => false);
|
|
160
|
+
if (hasEnvironmentRow) {
|
|
161
|
+
try {
|
|
162
|
+
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.processApplicationPage.forgetStageCluster();
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
147
170
|
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
148
171
|
hasText: new RegExp(`^${clusterName}`),
|
|
149
172
|
});
|
|
@@ -14,8 +14,11 @@ declare class ModelerCreatePage {
|
|
|
14
14
|
readonly viewProcessInstanceLink: Locator;
|
|
15
15
|
readonly nameInput: Locator;
|
|
16
16
|
readonly diagramBreadcrumb: Locator;
|
|
17
|
+
readonly diagramActionsButton: Locator;
|
|
17
18
|
readonly renameDiagramNameButton: Locator;
|
|
18
19
|
readonly diagramNameInput: Locator;
|
|
20
|
+
readonly renameInput: Locator;
|
|
21
|
+
readonly renameSubmitButton: Locator;
|
|
19
22
|
readonly variableInput: Locator;
|
|
20
23
|
readonly embedFormButton: Locator;
|
|
21
24
|
readonly embedButton: Locator;
|
|
@@ -6,6 +6,7 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
6
6
|
const ConnectorMarketplacePage_1 = require("./ConnectorMarketplacePage");
|
|
7
7
|
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
8
8
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
9
|
+
const modelerCluster_1 = require("../../utils/modelerCluster");
|
|
9
10
|
class ModelerCreatePage {
|
|
10
11
|
page;
|
|
11
12
|
processApplicationPage;
|
|
@@ -21,8 +22,11 @@ class ModelerCreatePage {
|
|
|
21
22
|
viewProcessInstanceLink;
|
|
22
23
|
nameInput;
|
|
23
24
|
diagramBreadcrumb;
|
|
25
|
+
diagramActionsButton;
|
|
24
26
|
renameDiagramNameButton;
|
|
25
27
|
diagramNameInput;
|
|
28
|
+
renameInput;
|
|
29
|
+
renameSubmitButton;
|
|
26
30
|
variableInput;
|
|
27
31
|
embedFormButton;
|
|
28
32
|
embedButton;
|
|
@@ -165,8 +169,25 @@ class ModelerCreatePage {
|
|
|
165
169
|
});
|
|
166
170
|
this.nameInput = page.getByLabel('Name', { exact: true });
|
|
167
171
|
this.diagramBreadcrumb = page.locator('[data-test="breadcrumb-diagram"]');
|
|
168
|
-
|
|
172
|
+
// Hub's newer nav (isNavV2, camunda-hub use-file-page-actions.ts) renders
|
|
173
|
+
// no breadcrumb-diagram dropdown: Rename/Duplicate/Delete sit behind a
|
|
174
|
+
// kebab next to the current (last) crumb, named "<entity name> actions".
|
|
175
|
+
// Same locator as camunda-hub's own e2e BasePage.breadcrumbActionsButton.
|
|
176
|
+
this.diagramActionsButton = page
|
|
177
|
+
.getByRole('navigation', { name: 'Context navigation' })
|
|
178
|
+
.or(page.getByRole('navigation', { name: 'Breadcrumb', exact: true }))
|
|
179
|
+
.getByRole('button', { name: / actions$/ })
|
|
180
|
+
.last();
|
|
181
|
+
this.renameDiagramNameButton = page
|
|
182
|
+
.getByRole('menuitem', { name: 'Rename', exact: true })
|
|
183
|
+
.or(page.locator('[data-test="diagram-menu-item-rename-diagram"]'))
|
|
184
|
+
.first();
|
|
169
185
|
this.diagramNameInput = page.locator('[data-test="editable-input"]');
|
|
186
|
+
// The nav-v2 Rename opens a modal dialog (camunda-hub RenameDialogDs.tsx)
|
|
187
|
+
// instead of the legacy inline breadcrumb editor, and a fresh diagram no
|
|
188
|
+
// longer opens with its name in edit mode.
|
|
189
|
+
this.renameInput = page.locator('[data-test="rename-input"]');
|
|
190
|
+
this.renameSubmitButton = page.locator('[data-test="rename-submit"]');
|
|
170
191
|
this.variableInput = page.locator('[id="variables-json"]');
|
|
171
192
|
this.embedFormButton = page.getByRole('button', { name: 'Link form' });
|
|
172
193
|
this.embedButton = page.locator('[data-test="confirm-move"]');
|
|
@@ -337,9 +358,14 @@ class ModelerCreatePage {
|
|
|
337
358
|
this.formLinkedSuccessMessage = page
|
|
338
359
|
.locator('.cds--toast-notification__subtitle')
|
|
339
360
|
.getByText('Form has been successfully linked.');
|
|
340
|
-
|
|
361
|
+
// The legacy breadcrumb shows the name directly; the nav-v2 kebab embeds
|
|
362
|
+
// it in its own accessible name ("<name> actions").
|
|
363
|
+
this.diagramNameBreadcrumb = (diagramName) => page
|
|
364
|
+
.locator('button[data-test="breadcrumb-diagram"]', {
|
|
341
365
|
hasText: diagramName,
|
|
342
|
-
})
|
|
366
|
+
})
|
|
367
|
+
.or(page.getByRole('button', { name: `${diagramName} actions` }))
|
|
368
|
+
.first();
|
|
343
369
|
this.webhookActiveMessage = page.getByText('Webhook is active');
|
|
344
370
|
}
|
|
345
371
|
async clickSecondDeployButton() {
|
|
@@ -699,6 +725,27 @@ class ModelerCreatePage {
|
|
|
699
725
|
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
700
726
|
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
701
727
|
}
|
|
728
|
+
// Hub's environment-based dialog (camunda-hub#29210) lists every
|
|
729
|
+
// Environment assigned to the workspace, preselects the first -- which
|
|
730
|
+
// need not be this one -- and renders a <label> per row that the legacy
|
|
731
|
+
// toggle detection below would mistake for its own. The dialog is visible
|
|
732
|
+
// a beat before its rows, so wait briefly rather than sampling once.
|
|
733
|
+
const hasEnvironmentRow = await this.dialog
|
|
734
|
+
.locator('[data-test^="environment-row-"]')
|
|
735
|
+
.first()
|
|
736
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
737
|
+
.then(() => true)
|
|
738
|
+
.catch(() => false);
|
|
739
|
+
if (hasEnvironmentRow) {
|
|
740
|
+
try {
|
|
741
|
+
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
this.processApplicationPage.forgetStageCluster();
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
702
749
|
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
703
750
|
hasText: new RegExp(`^${clusterName}`),
|
|
704
751
|
});
|
|
@@ -747,8 +794,23 @@ class ModelerCreatePage {
|
|
|
747
794
|
async fillNamedInput(name) {
|
|
748
795
|
await this.nameInput.fill(name, { timeout: 60000 });
|
|
749
796
|
}
|
|
797
|
+
// Opens the current diagram's crumb menu: the legacy breadcrumb dropdown, or
|
|
798
|
+
// the nav-v2 "<name> actions" kebab. Waits for the properties panel first:
|
|
799
|
+
// until the diagram has loaded, nav-v2 keeps showing the previous page's
|
|
800
|
+
// crumbs (camunda-hub NavProvider), so the last kebab would belong to the
|
|
801
|
+
// parent container and Rename would rename that instead.
|
|
750
802
|
async clickDiagramBreadcrumb() {
|
|
751
|
-
await this.
|
|
803
|
+
await (0, test_1.expect)(this.generalPanel).toBeVisible({ timeout: 120000 });
|
|
804
|
+
const crumbMenuTrigger = this.diagramBreadcrumb
|
|
805
|
+
.or(this.diagramActionsButton)
|
|
806
|
+
.first();
|
|
807
|
+
await (0, test_1.expect)(crumbMenuTrigger).toBeVisible({ timeout: 60000 });
|
|
808
|
+
if (await this.diagramActionsButton.isVisible().catch(() => false)) {
|
|
809
|
+
await this.diagramActionsButton.click({ timeout: 10000 });
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
await this.diagramBreadcrumb.click({ timeout: 10000 });
|
|
813
|
+
}
|
|
752
814
|
}
|
|
753
815
|
// Opening the breadcrumb menu leaves the pointer on the breadcrumb button,
|
|
754
816
|
// and hub renders that button's truncated-name tooltip on top of the menu,
|
|
@@ -787,12 +849,51 @@ class ModelerCreatePage {
|
|
|
787
849
|
// failure: the assertion below is what decides whether this worked.
|
|
788
850
|
await this.renameDiagramNameButton.dispatchEvent('click');
|
|
789
851
|
}
|
|
790
|
-
await (0, test_1.expect)(this.diagramNameInput).toBeVisible({ timeout: 10000 });
|
|
852
|
+
await (0, test_1.expect)(this.diagramNameInput.or(this.renameInput).first()).toBeVisible({ timeout: 10000 });
|
|
791
853
|
}).toPass({ timeout: 60000, intervals: [1000, 2000, 5000] });
|
|
792
854
|
}
|
|
855
|
+
// Callers either just opened the rename editor or just created the diagram,
|
|
856
|
+
// which the legacy shell opens with its name already in inline-edit mode.
|
|
857
|
+
// Nav-v2 does neither for a fresh diagram, so open the rename action when no
|
|
858
|
+
// editor is showing, fill whichever editor it is, and confirm the crumb
|
|
859
|
+
// actually took the name rather than trusting the fill. Retried as a unit:
|
|
860
|
+
// a re-render (autosave, post-create refetch) can drop the editor mid-fill.
|
|
793
861
|
async enterDiagramName(name) {
|
|
794
|
-
|
|
795
|
-
|
|
862
|
+
const maxAttempts = 3;
|
|
863
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
864
|
+
try {
|
|
865
|
+
const editorOpen = await this.diagramNameInput
|
|
866
|
+
.or(this.renameInput)
|
|
867
|
+
.first()
|
|
868
|
+
.waitFor({ state: 'visible', timeout: 5000 })
|
|
869
|
+
.then(() => true)
|
|
870
|
+
.catch(() => false);
|
|
871
|
+
if (!editorOpen) {
|
|
872
|
+
await this.clickEditDiagramNameButton();
|
|
873
|
+
}
|
|
874
|
+
if (await this.renameInput.isVisible().catch(() => false)) {
|
|
875
|
+
await this.renameInput.fill(name, { timeout: 30000 });
|
|
876
|
+
await (0, test_1.expect)(this.renameInput).toHaveValue(name, { timeout: 5000 });
|
|
877
|
+
await this.renameSubmitButton.click({ timeout: 10000 });
|
|
878
|
+
await (0, test_1.expect)(this.renameInput).toBeHidden({ timeout: 30000 });
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
await this.diagramNameInput.fill(name, { timeout: 45000 });
|
|
882
|
+
await this.diagramNameInput.press('Enter', { timeout: 45000 });
|
|
883
|
+
}
|
|
884
|
+
await (0, test_1.expect)(this.diagramNameBreadcrumb(name)).toBeVisible({
|
|
885
|
+
timeout: 30000,
|
|
886
|
+
});
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
if (attempt === maxAttempts) {
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
console.warn(`Diagram rename to "${name}" did not commit ` +
|
|
894
|
+
`(attempt ${attempt}/${maxAttempts}): ${error}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
796
897
|
}
|
|
797
898
|
async clickVariableInput() {
|
|
798
899
|
await this.variableInput.click({ timeout: 60000 });
|
|
@@ -30,6 +30,8 @@ declare class ModelerHomePage {
|
|
|
30
30
|
readonly messageBanner: Locator;
|
|
31
31
|
readonly idpTemplateOption: Locator;
|
|
32
32
|
readonly idpEmptyStateButton: Locator;
|
|
33
|
+
readonly idpProjectsTab: Locator;
|
|
34
|
+
readonly createIdpProjectButton: Locator;
|
|
33
35
|
readonly idpApplicationNameInput: Locator;
|
|
34
36
|
readonly createIdpApplicationButton: Locator;
|
|
35
37
|
readonly dialog: Locator;
|
|
@@ -38,6 +40,11 @@ declare class ModelerHomePage {
|
|
|
38
40
|
readonly blockingDialogDismiss: Locator;
|
|
39
41
|
readonly optionsButton: Locator;
|
|
40
42
|
readonly formNameBreadcrumb: (formName: string) => Locator;
|
|
43
|
+
readonly formBreadcrumbButton: Locator;
|
|
44
|
+
readonly formActionsButton: Locator;
|
|
45
|
+
readonly renameFormMenuItem: Locator;
|
|
46
|
+
readonly renameInput: Locator;
|
|
47
|
+
readonly renameSubmitButton: Locator;
|
|
41
48
|
constructor(page: Page);
|
|
42
49
|
dismissBlockingDialog(): Promise<void>;
|
|
43
50
|
private dismissUncataloguedDialog;
|
|
@@ -55,6 +62,7 @@ declare class ModelerHomePage {
|
|
|
55
62
|
clickBpmnTemplateOption(): Promise<void>;
|
|
56
63
|
clickFormOption(): Promise<void>;
|
|
57
64
|
enterFormName(name: string): Promise<void>;
|
|
65
|
+
openFormRenameEditor(): Promise<void>;
|
|
58
66
|
clickProjectBreadcrumb(): Promise<void>;
|
|
59
67
|
clickWorkspaceBreadcrumb(): Promise<void>;
|
|
60
68
|
enterResourceContainer(name?: string): Promise<void>;
|