@camunda/e2e-test-suite 0.0.1275 → 0.0.1277
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/ClusterPage.js +8 -1
- package/dist/pages/8.10/ConsoleOrganizationPage.js +10 -3
- package/dist/pages/8.10/ProcessApplicationPage.js +36 -8
- package/dist/pages/8.10/SettingsPage.js +12 -2
- package/dist/pages/8.8/AppsPage.d.ts +5 -0
- package/dist/pages/8.8/AppsPage.js +231 -22
- package/dist/pages/8.8/ClusterDetailsPage.d.ts +9 -0
- package/dist/pages/8.8/ClusterDetailsPage.js +154 -3
- package/dist/pages/8.8/ClusterPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterPage.js +49 -20
- package/dist/pages/8.8/ConnectorMarketplacePage.d.ts +2 -0
- package/dist/pages/8.8/ConnectorMarketplacePage.js +44 -2
- package/dist/pages/8.8/ConsoleOrganizationPage.js +63 -18
- package/dist/pages/8.8/FormJsPage.d.ts +4 -0
- package/dist/pages/8.8/FormJsPage.js +31 -1
- package/dist/pages/8.8/ModelerCreatePage.d.ts +3 -0
- package/dist/pages/8.8/ModelerCreatePage.js +167 -18
- package/dist/pages/8.8/PlayPage.js +14 -3
- package/dist/pages/8.8/SettingsPage.js +19 -2
- package/dist/pages/8.8/UtilitiesPage.js +43 -3
- package/dist/tests/8.8/console-user-flows.spec.js +6 -0
- package/dist/tests/8.8/web-modeler-user-flows.spec.js +6 -2
- package/dist/utils/consoleApiHelpers.js +38 -11
- package/package.json +1 -1
|
@@ -280,7 +280,14 @@ class ClusterPage {
|
|
|
280
280
|
await this.clickClusterNameInput();
|
|
281
281
|
await this.fillClusterNameInput(name);
|
|
282
282
|
await this.clickClusterType();
|
|
283
|
-
|
|
283
|
+
// Same design-system role="radio" fallback as the choice()-wrapped
|
|
284
|
+
// locators above -- `region` is a runtime parameter ('GCP'/'AWS') so
|
|
285
|
+
// this can't reuse a named locator directly. Ported from
|
|
286
|
+
// pages/8.9/ClusterPage.ts.
|
|
287
|
+
await this.page
|
|
288
|
+
.getByRole('tab', { name: region })
|
|
289
|
+
.or(this.page.getByRole('radio', { name: region, exact: true }))
|
|
290
|
+
.click();
|
|
284
291
|
await this.selectSubRegion(region);
|
|
285
292
|
await this.clickClusterOption();
|
|
286
293
|
await this.determineTasklistAPI(tasklistV1Api);
|
|
@@ -118,9 +118,16 @@ class ConsoleOrganizationPage {
|
|
|
118
118
|
name: 'danger',
|
|
119
119
|
})
|
|
120
120
|
.last();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
// Camunda Hub's design-system Organization Management page renamed the
|
|
122
|
+
// Carbon TableToolbarSearch's default "Filter table" label to plain
|
|
123
|
+
// "Search" (same rename already ported elsewhere for the secrets
|
|
124
|
+
// panels). Confirmed against camunda-hub's own
|
|
125
|
+
// e2e-playwright/pages/Console/ConsoleOrgManagementPage.ts, which already
|
|
126
|
+
// uses `getByRole('searchbox', {name: 'Search'})` for this exact users
|
|
127
|
+
// list. Match either.
|
|
128
|
+
this.filterTableSearchbox = page
|
|
129
|
+
.getByRole('searchbox', { name: 'Filter table' })
|
|
130
|
+
.or(page.getByRole('searchbox', { name: 'Search' }));
|
|
124
131
|
}
|
|
125
132
|
async filterTable(names) {
|
|
126
133
|
for (const name of names) {
|
|
@@ -48,21 +48,49 @@ class ProcessApplicationPage {
|
|
|
48
48
|
// .first() defensively only: hub renders exactly one of these today, in the
|
|
49
49
|
// workspace-root empty state. A future second placement should not turn
|
|
50
50
|
// every PA-creating test into a strict-mode violation.
|
|
51
|
+
//
|
|
52
|
+
// hubTerm('project') (utils/hub-terminology.ts) renames "process
|
|
53
|
+
// application" to "project" in copy once an org has workspacesEnabled --
|
|
54
|
+
// confirmed from a nightly trace screenshot on hub.ultrawombat.com: the
|
|
55
|
+
// workspace-root empty state renders "Create project" (not "Create
|
|
56
|
+
// process application"), and EmptyStateActionButtons.tsx's source
|
|
57
|
+
// confirms the button text is `Create {hubTerm('project')}`. Accept both
|
|
58
|
+
// so this keeps working on orgs at either rollout stage.
|
|
59
|
+
// exact:true on both -- without it, "Create project" substring-matches
|
|
60
|
+
// the split button's own "Create project options" chevron too, and
|
|
61
|
+
// .first() then picks whichever the DOM happens to order first. Same
|
|
62
|
+
// pattern already fixed for the same chevron in
|
|
63
|
+
// ModelerHomePage.ts's createNewProjectButton/createWorkspaceButton.
|
|
51
64
|
this.createProcessApplicationButton = page
|
|
52
|
-
.getByRole('button', { name: 'Create process application' })
|
|
65
|
+
.getByRole('button', { name: 'Create process application', exact: true })
|
|
66
|
+
.or(page.getByRole('button', { name: 'Create project', exact: true }))
|
|
53
67
|
.first();
|
|
54
68
|
this.createProcessApplicationOption = page.locator('[data-test="create-process-application"]');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
69
|
+
// Same hubTerm('project') rename as the button above --
|
|
70
|
+
// CreationModalDs.common.tsx's dialog title is `Create a
|
|
71
|
+
// {hubTerm('project')}`. A confirmed nightly trace showed a THIRD shape,
|
|
72
|
+
// live on this SaaS org: `app/pages/workspace-projects/features/
|
|
73
|
+
// create-project/CreateProjectModal.tsx` -- a newer, unrelated component
|
|
74
|
+
// (built on `api/projects`' useCreateProject, not processApplicationStore)
|
|
75
|
+
// titled plainly "Create project" (no "a"), with its field labelled
|
|
76
|
+
// "Project name" (not "Name") and its submit button reading "Create
|
|
77
|
+
// project" (not "Create"). Accept all three shapes.
|
|
78
|
+
this.creationDialog = page
|
|
79
|
+
.getByRole('dialog', { name: 'Create a process application' })
|
|
80
|
+
.or(page.getByRole('dialog', { name: 'Create a project' }))
|
|
81
|
+
.or(page.getByRole('dialog', { name: 'Create project', exact: true }));
|
|
58
82
|
// Deliberately scoped to the dialog: ModelerCreatePage owns an unscoped
|
|
59
83
|
// getByLabel('Name', {exact: true}) for the properties panel, which would
|
|
60
84
|
// resolve to two elements while the creation modal is open.
|
|
61
|
-
this.nameInput = this.creationDialog
|
|
62
|
-
|
|
63
|
-
name:
|
|
85
|
+
this.nameInput = this.creationDialog
|
|
86
|
+
.getByLabel('Name', { exact: true })
|
|
87
|
+
.or(this.creationDialog.getByLabel('Project name', { exact: true }));
|
|
88
|
+
this.submitButton = this.creationDialog
|
|
89
|
+
.getByRole('button', { name: 'Create', exact: true })
|
|
90
|
+
.or(this.creationDialog.getByRole('button', {
|
|
91
|
+
name: 'Create project',
|
|
64
92
|
exact: true,
|
|
65
|
-
});
|
|
93
|
+
}));
|
|
66
94
|
this.cancelButton = this.creationDialog.getByRole('button', {
|
|
67
95
|
name: 'Cancel',
|
|
68
96
|
exact: true,
|
|
@@ -7,8 +7,18 @@ class SettingsPage {
|
|
|
7
7
|
logoutButton;
|
|
8
8
|
constructor(page) {
|
|
9
9
|
this.page = page;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// Camunda Hub's design-system shell renders a `UserMenu` button named
|
|
11
|
+
// "User menu, <full name>" in place of the legacy Carbon "Open Settings"
|
|
12
|
+
// icon button, and its logout control is a menuitem inside that menu
|
|
13
|
+
// rather than a plain button. Both shells still ship -- match either.
|
|
14
|
+
// Ported from camunda-hub's own e2e-playwright/pages/Console/ConsoleBasePage.ts
|
|
15
|
+
// (settingsButton / dsLogoutMenuItem / carbonLogoutButton).
|
|
16
|
+
this.openSettingsButton = page
|
|
17
|
+
.getByRole('button', { name: /^User menu,/ })
|
|
18
|
+
.or(page.getByLabel('Open Settings'));
|
|
19
|
+
this.logoutButton = page
|
|
20
|
+
.getByRole('menuitem', { name: 'Log out', exact: true })
|
|
21
|
+
.or(page.getByRole('button', { name: 'Log out' }));
|
|
12
22
|
}
|
|
13
23
|
async clickOpenSettingsButton(retries = 3) {
|
|
14
24
|
for (let i = 0; i < retries; i++) {
|
|
@@ -21,8 +21,13 @@ declare class AppsPage {
|
|
|
21
21
|
readonly hubSidebar: Locator;
|
|
22
22
|
constructor(page: Page);
|
|
23
23
|
clickCluster(component: Locator, name: string): Promise<void>;
|
|
24
|
+
private expandHubClusterGroup;
|
|
25
|
+
private expandHubSidebar;
|
|
26
|
+
private showAllWorkspaces;
|
|
24
27
|
private clickHubClusterApp;
|
|
28
|
+
private isOnHub;
|
|
25
29
|
clickModeler(): Promise<void>;
|
|
30
|
+
private clickHubModelerHome;
|
|
26
31
|
/**
|
|
27
32
|
* Waits for the Modeler app to actually render, and recovers the one landing
|
|
28
33
|
* state a hostname check cannot see: Modeler's OAuth `/login-callback`
|
|
@@ -4,6 +4,9 @@ exports.AppsPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const UtilitiesPage_1 = require("../8.8/UtilitiesPage");
|
|
7
|
+
function escapeRegExp(value) {
|
|
8
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
9
|
+
}
|
|
7
10
|
class AppsPage {
|
|
8
11
|
page;
|
|
9
12
|
modelerLink;
|
|
@@ -85,6 +88,187 @@ class AppsPage {
|
|
|
85
88
|
// up the app link sidebar-wide rather than scoped to a "group" container
|
|
86
89
|
// that doesn't reliably exist. Ported from pages/8.9/AppsPage.ts, where
|
|
87
90
|
// this shape is already nightly-proven.
|
|
91
|
+
async expandHubClusterGroup(clusterName) {
|
|
92
|
+
const toggle = this.hubSidebar
|
|
93
|
+
.getByRole('button', { name: clusterName })
|
|
94
|
+
.first();
|
|
95
|
+
// The diagram editor (and other full-screen views reached from it, e.g.
|
|
96
|
+
// right after runProcessInstance/deployDiagram) has no "Main navigation"
|
|
97
|
+
// sidebar at all -- confirmed from a nightly trace's DOM snapshot: the
|
|
98
|
+
// page's only <nav> is aria-label="Context navigation" (the breadcrumb),
|
|
99
|
+
// and neither "Operate"/"Tasklist"/"Optimize" nor any app-switcher text
|
|
100
|
+
// appears anywhere on it. clickCamundaApps() is a no-op on Hub (there is
|
|
101
|
+
// no switcher panel to open), so a caller reaching clickOperate/
|
|
102
|
+
// clickTasklist/clickOptimize straight from a diagram was left on a page
|
|
103
|
+
// this sidebar could never resolve on, no matter how the toggle itself
|
|
104
|
+
// is matched. Navigate home first, the same way clickModeler() does,
|
|
105
|
+
// whenever the sidebar isn't already there.
|
|
106
|
+
//
|
|
107
|
+
// A freshly re-authenticated page can also land on a workspace-agnostic
|
|
108
|
+
// route (e.g. bare /workspaces right after loginWithTestUser(), which
|
|
109
|
+
// clickCamundaApps() does nothing to fix -- it is a no-op on Hub) whose
|
|
110
|
+
// sidebar renders but has not resolved this cluster's own group yet.
|
|
111
|
+
// Confirmed from a nightly trace: both the getAttribute and click below
|
|
112
|
+
// timed out (10s each) against this exact toggle while the URL sat on
|
|
113
|
+
// /workspaces, right after a mid-test re-login. Route through the same
|
|
114
|
+
// clickModeler() recovery whenever the toggle itself hasn't shown up
|
|
115
|
+
// either, not only when the whole sidebar is missing.
|
|
116
|
+
// Before deciding whether the group is reachable: a collapsed rail hides
|
|
117
|
+
// every cluster trigger, so the probe below would report "not ready" and
|
|
118
|
+
// the recovery would then wait out its own timeout on the same hidden
|
|
119
|
+
// element.
|
|
120
|
+
await this.expandHubSidebar();
|
|
121
|
+
const sidebarReady = (await this.hubSidebar.isVisible({ timeout: 5000 }).catch(() => false)) &&
|
|
122
|
+
(await toggle
|
|
123
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
124
|
+
.then(() => true)
|
|
125
|
+
.catch(() => false));
|
|
126
|
+
if (!sidebarReady) {
|
|
127
|
+
await this.clickModeler();
|
|
128
|
+
await (0, test_1.expect)(this.hubSidebar).toBeVisible({ timeout: 30000 });
|
|
129
|
+
// Since camunda-hub #28956 the Workspaces overview defaults to "My
|
|
130
|
+
// workspaces" -- only the ones the current user is an EXPLICIT member
|
|
131
|
+
// of -- and resets to it on every load. The shared workspace is created
|
|
132
|
+
// by whichever setup user won the race, so a role-flow user lands on
|
|
133
|
+
// "No workspaces match this filter / You don't have explicit membership
|
|
134
|
+
// in any workspace in this organization" (confirmed live) and the
|
|
135
|
+
// sidebar never renders this cluster's group. Widening the scope is
|
|
136
|
+
// what makes it appear; without it the wait below can only time out.
|
|
137
|
+
// ModelerHomePage.showAllWorkspaces() covers the project-folder paths,
|
|
138
|
+
// but nothing covered the app-switcher path until here.
|
|
139
|
+
await this.showAllWorkspaces(5000);
|
|
140
|
+
await this.expandHubSidebar();
|
|
141
|
+
await (0, test_1.expect)(toggle).toBeVisible({ timeout: 30000 });
|
|
142
|
+
}
|
|
143
|
+
// camunda-hub's own nav-shell-ds.spec.saas.tsx documents the actual
|
|
144
|
+
// shape: "the design system splits the group row into a label control
|
|
145
|
+
// and a chevron control, both labelled by the cluster name; either one
|
|
146
|
+
// opens it" -- there is no separate "Toggle <name>" control, and the
|
|
147
|
+
// child links do not exist in the DOM at all until expanded (not merely
|
|
148
|
+
// hidden), nor are they nested under any container matchable by cluster
|
|
149
|
+
// name once expanded. So: find the toggle by the cluster's own name
|
|
150
|
+
// (`.first()` because both controls share that name), click it, then
|
|
151
|
+
// look up the app link sidebar-wide rather than scoped to a "group"
|
|
152
|
+
// container that doesn't reliably exist.
|
|
153
|
+
// Check THIS cluster's own toggle state, not "is any app link with this
|
|
154
|
+
// name visible anywhere in the sidebar" -- with more than one cluster in
|
|
155
|
+
// the org, a DIFFERENT cluster's group can already be expanded (left over
|
|
156
|
+
// from an earlier action on the same page), and that cluster's own
|
|
157
|
+
// Operate/Tasklist/Optimize link would satisfy an unscoped visibility
|
|
158
|
+
// check, skipping expansion of the requested cluster entirely and then
|
|
159
|
+
// clicking the wrong cluster's link. The child links are not nested under
|
|
160
|
+
// any container matchable by cluster name once expanded (confirmed
|
|
161
|
+
// against camunda-hub's own nav-shell-ds.spec.saas.tsx), so the toggle's
|
|
162
|
+
// own aria-expanded is the only reliable per-cluster signal available.
|
|
163
|
+
// Not exact: the same suite asserts this row with a substring/regex
|
|
164
|
+
// match (`{name: /Production/}`), not an exact string -- and nightly
|
|
165
|
+
// traces confirm why: the rendered toggle's accessible name is not
|
|
166
|
+
// byte-for-byte the bare cluster name (it carries additional content,
|
|
167
|
+
// e.g. a trailing tag summary), so an exact-match lookup here finds
|
|
168
|
+
// nothing and the click below times out waiting on a locator that can
|
|
169
|
+
// never resolve.
|
|
170
|
+
const alreadyExpanded = (await toggle.getAttribute('aria-expanded').catch(() => null)) === 'true';
|
|
171
|
+
if (!alreadyExpanded) {
|
|
172
|
+
// This is the only expansion action in the Hub path -- swallowing its
|
|
173
|
+
// failure made a broken expansion look successful and left the caller
|
|
174
|
+
// to burn the full 30s below waiting on an app link that could never
|
|
175
|
+
// appear, hiding the actionable error (e.g. a toggle that doesn't
|
|
176
|
+
// exist or isn't clickable) behind an unrelated timeout.
|
|
177
|
+
await toggle.click({ timeout: 10000 });
|
|
178
|
+
}
|
|
179
|
+
// An app link resolved after this is sidebar-wide (see callers below), so
|
|
180
|
+
// if an EARLIER call in the same test left a different cluster's group
|
|
181
|
+
// expanded too, two "Operate" (say) links now exist and that becomes a
|
|
182
|
+
// strict-mode violation. Collapse every other expanded group before
|
|
183
|
+
// resolving it -- there is no container to scope the link to instead,
|
|
184
|
+
// since the child links aren't nested under anything matchable by
|
|
185
|
+
// cluster name once expanded.
|
|
186
|
+
// hasNotText filters DOM text content, not the accessible name -- the
|
|
187
|
+
// expanded chevron can be labelled via aria-label with no text content
|
|
188
|
+
// of its own, in which case hasNotText would fail to exclude the toggle
|
|
189
|
+
// we just expanded and this loop would immediately collapse it again.
|
|
190
|
+
// Match by accessible name (same algorithm getByRole's own name option
|
|
191
|
+
// uses) instead, excluding any name CONTAINING clusterName -- not just
|
|
192
|
+
// one exactly equal to it, since (per the toggle locator above) the
|
|
193
|
+
// current cluster's own toggle name is not exactly clusterName either,
|
|
194
|
+
// and an exact-equality exclusion would wrongly treat it as "other" and
|
|
195
|
+
// immediately re-collapse the group this method just expanded.
|
|
196
|
+
const otherExpandedToggles = this.hubSidebar.getByRole('button', {
|
|
197
|
+
expanded: true,
|
|
198
|
+
name: new RegExp(`^(?!.*${escapeRegExp(clusterName)}).+$`),
|
|
199
|
+
});
|
|
200
|
+
for (let i = 0; i < 5 && (await otherExpandedToggles.count()) > 0; i++) {
|
|
201
|
+
// Let a failed collapse throw rather than swallowing it: the app link
|
|
202
|
+
// is intentionally sidebar-wide, so a group left expanded here produces
|
|
203
|
+
// a strict-mode violation (or the wrong cluster's link) below anyway --
|
|
204
|
+
// failing at the actual cause is more useful than at that symptom.
|
|
205
|
+
await otherExpandedToggles.first().click({ timeout: 10000 });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Widens Hub's Workspaces scope filter to "All workspaces". Duplicated from
|
|
209
|
+
// ModelerHomePage.showAllWorkspaces() rather than imported: ModelerHomePage
|
|
210
|
+
// constructs page objects of its own, and pulling it in here would make the
|
|
211
|
+
// Apps -> Modeler navigation path circular. The control is absent outside
|
|
212
|
+
// Hub and for non-elevated users, where a membership-scoped list is already
|
|
213
|
+
// correct, so a missing control is a no-op.
|
|
214
|
+
// Opens Hub's collapsed left rail. While collapsed it shows only the
|
|
215
|
+
// top-level nav icons: the per-cluster group triggers are in the DOM
|
|
216
|
+
// (data-slot="app-sidebar-group-trigger", aria-label="<cluster>") but never
|
|
217
|
+
// visible, so a toBeVisible() wait on one can only time out. A failing run's
|
|
218
|
+
// trace carries "Expand sidebar" seven times and "Collapse sidebar" not
|
|
219
|
+
// once, i.e. the rail stayed shut for its whole duration. A no-op when the
|
|
220
|
+
// rail is already open, since the toggle is then labelled "Collapse sidebar".
|
|
221
|
+
async expandHubSidebar() {
|
|
222
|
+
const expandToggle = this.page
|
|
223
|
+
.getByRole('button', { name: 'Expand sidebar' })
|
|
224
|
+
.or(this.page
|
|
225
|
+
.locator('[data-slot="app-sidebar-collapse-toggle"]')
|
|
226
|
+
.and(this.page.getByLabel('Expand sidebar')))
|
|
227
|
+
.first();
|
|
228
|
+
if (!(await expandToggle.isVisible().catch(() => false))) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await expandToggle.click({ timeout: 10000 });
|
|
232
|
+
await (0, test_1.expect)(expandToggle).toBeHidden({ timeout: 10000 });
|
|
233
|
+
}
|
|
234
|
+
async showAllWorkspaces(probeTimeout) {
|
|
235
|
+
// The empty state offers an explicit "Switch to All workspaces" button
|
|
236
|
+
// next to "You don't have explicit membership in any workspace in this
|
|
237
|
+
// organization" -- prefer it. Driving the combobox alone was not enough:
|
|
238
|
+
// when its accessible name does not match, the probe below simply
|
|
239
|
+
// returns and the caller waits out its whole timeout on a page that is
|
|
240
|
+
// still scoped to "My workspaces" (observed live, twice).
|
|
241
|
+
const switchButton = this.page.getByRole('button', {
|
|
242
|
+
name: 'Switch to All workspaces',
|
|
243
|
+
});
|
|
244
|
+
const scopeFilter = this.page.getByRole('combobox', {
|
|
245
|
+
name: 'Filter workspaces',
|
|
246
|
+
});
|
|
247
|
+
const appeared = await switchButton
|
|
248
|
+
.or(scopeFilter)
|
|
249
|
+
.first()
|
|
250
|
+
.waitFor({ state: 'visible', timeout: probeTimeout })
|
|
251
|
+
.then(() => true)
|
|
252
|
+
.catch(() => false);
|
|
253
|
+
if (!appeared) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (await switchButton.isVisible().catch(() => false)) {
|
|
257
|
+
await switchButton.click({ timeout: 10000 });
|
|
258
|
+
await (0, test_1.expect)(switchButton).toBeHidden({ timeout: 10000 });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if ((await scopeFilter.innerText()).includes('All workspaces')) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
await scopeFilter.click({ timeout: 10000 });
|
|
265
|
+
await this.page
|
|
266
|
+
.getByRole('option', { name: 'All workspaces' })
|
|
267
|
+
.click({ timeout: 10000 });
|
|
268
|
+
await (0, test_1.expect)(scopeFilter).toContainText('All workspaces', {
|
|
269
|
+
timeout: 10000,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
88
272
|
async clickHubClusterApp(clusterName, appName) {
|
|
89
273
|
// When the sidebar is collapsed (the default on a fresh session), the
|
|
90
274
|
// group toggle opens a Radix popover -- a role="dialog" labelled with the
|
|
@@ -104,12 +288,12 @@ class AppsPage {
|
|
|
104
288
|
// multi-cluster org another cluster's group can already be expanded with
|
|
105
289
|
// the same app name, and the click would then fire on the wrong
|
|
106
290
|
// cluster's link.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
291
|
+
// Expansion, sidebar recovery and collapsing every OTHER expanded group
|
|
292
|
+
// all live in the ported helper: with more than one cluster in the org
|
|
293
|
+
// (this suite creates Test Cluster and AWS Cluster), a sidebar-wide app
|
|
294
|
+
// link can otherwise resolve to a different cluster's row, or become a
|
|
295
|
+
// strict-mode violation once two groups are open.
|
|
296
|
+
await this.expandHubClusterGroup(clusterName);
|
|
113
297
|
await (0, test_1.expect)(appLink).toBeVisible({ timeout: 30000 });
|
|
114
298
|
// Hub's cluster-app links carry target="_blank", so clicking them opens
|
|
115
299
|
// the app in a new tab while every caller keeps asserting on this.page.
|
|
@@ -122,6 +306,11 @@ class AppsPage {
|
|
|
122
306
|
}
|
|
123
307
|
await appLink.click({ timeout: 10000 });
|
|
124
308
|
}
|
|
309
|
+
// True once the tab is on a Camunda Hub host, where Modeler home is Hub's
|
|
310
|
+
// own home rather than a modeler.* hostname.
|
|
311
|
+
isOnHub() {
|
|
312
|
+
return new URL(this.page.url()).hostname.startsWith('hub.');
|
|
313
|
+
}
|
|
125
314
|
async clickModeler() {
|
|
126
315
|
// Camunda Hub (hub.*) replaced Console + Web Modeler on SaaS and has no
|
|
127
316
|
// app switcher. Its home is the Modeler/Workspaces home, but a hostname
|
|
@@ -132,22 +321,7 @@ class AppsPage {
|
|
|
132
321
|
// Navigate home through the header logo link instead, same as
|
|
133
322
|
// ModelerHomePage.clickHomeBreadcrumb -- clicking it when already on the
|
|
134
323
|
// Modeler home is a harmless in-SPA no-op.
|
|
135
|
-
if (
|
|
136
|
-
const header = this.page.locator('header[data-slot="app-header"]');
|
|
137
|
-
// The href stays literally "/login" until the client's auth state
|
|
138
|
-
// catches up just after login (camunda-hub NavShellDs.common.tsx) --
|
|
139
|
-
// clicking it in that window would log the test out instead of
|
|
140
|
-
// navigating home, so exclude that href and wait for the authenticated
|
|
141
|
-
// one rather than racing it.
|
|
142
|
-
const homeLink = header
|
|
143
|
-
.getByRole('link', { name: 'Camunda Modeler home' })
|
|
144
|
-
.and(this.page.locator(':not([href$="/login"])'))
|
|
145
|
-
.or(header
|
|
146
|
-
.getByRole('link', { name: 'Camunda Hub home' })
|
|
147
|
-
.and(this.page.locator(':not([href$="/login"])')))
|
|
148
|
-
.first();
|
|
149
|
-
await (0, test_1.expect)(homeLink).toBeVisible({ timeout: 30000 });
|
|
150
|
-
await homeLink.click({ timeout: 30000 });
|
|
324
|
+
if (await this.clickHubModelerHome()) {
|
|
151
325
|
return;
|
|
152
326
|
}
|
|
153
327
|
const maxRetries = 5;
|
|
@@ -160,6 +334,17 @@ class AppsPage {
|
|
|
160
334
|
}
|
|
161
335
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
162
336
|
try {
|
|
337
|
+
// Re-checked every iteration, not once before the loop. A caller can
|
|
338
|
+
// reach here from a cluster app (Operate/Tasklist/Identity), which is
|
|
339
|
+
// NOT a hub.* host, so the pre-loop check above takes the legacy
|
|
340
|
+
// branch -- but the reload in the catch below lands back on Hub, and
|
|
341
|
+
// the loop's own success signal (a modeler.* hostname) can never come
|
|
342
|
+
// true there. A nightly trace shows exactly that: every retry
|
|
343
|
+
// snapshot sits on hub.ultrawombat.com/workspaces, which IS Modeler
|
|
344
|
+
// home, while the loop kept failing until it ran out of attempts.
|
|
345
|
+
if (await this.clickHubModelerHome()) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
163
348
|
await this.clickCamundaApps();
|
|
164
349
|
await (0, test_1.expect)(this.modelerLink).toBeVisible({ timeout: 30000 });
|
|
165
350
|
await this.modelerLink.click({ timeout: 30000 });
|
|
@@ -194,6 +379,30 @@ class AppsPage {
|
|
|
194
379
|
}
|
|
195
380
|
throw new Error(`Failed to click the modeler link after ${maxRetries} attempts.`);
|
|
196
381
|
}
|
|
382
|
+
// Navigates to Hub's Modeler home through the header logo link. Returns
|
|
383
|
+
// false when this tab is not on a Hub host, so the caller falls through to
|
|
384
|
+
// the legacy Console app-switcher flow.
|
|
385
|
+
async clickHubModelerHome() {
|
|
386
|
+
if (!this.isOnHub()) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
const header = this.page.locator('header[data-slot="app-header"]');
|
|
390
|
+
// The href stays literally "/login" until the client's auth state
|
|
391
|
+
// catches up just after login (camunda-hub NavShellDs.common.tsx) --
|
|
392
|
+
// clicking it in that window would log the test out instead of
|
|
393
|
+
// navigating home, so exclude that href and wait for the authenticated
|
|
394
|
+
// one rather than racing it.
|
|
395
|
+
const homeLink = header
|
|
396
|
+
.getByRole('link', { name: 'Camunda Modeler home' })
|
|
397
|
+
.and(this.page.locator(':not([href$="/login"])'))
|
|
398
|
+
.or(header
|
|
399
|
+
.getByRole('link', { name: 'Camunda Hub home' })
|
|
400
|
+
.and(this.page.locator(':not([href$="/login"])')))
|
|
401
|
+
.first();
|
|
402
|
+
await (0, test_1.expect)(homeLink).toBeVisible({ timeout: 30000 });
|
|
403
|
+
await homeLink.click({ timeout: 30000 });
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
197
406
|
/**
|
|
198
407
|
* Waits for the Modeler app to actually render, and recovers the one landing
|
|
199
408
|
* state a hostname check cannot see: Modeler's OAuth `/login-callback`
|
|
@@ -22,8 +22,13 @@ declare class ClusterDetailsPage {
|
|
|
22
22
|
readonly clientCredentialsDialog: Locator;
|
|
23
23
|
readonly clustersLink: Locator;
|
|
24
24
|
readonly alertsTab: Locator;
|
|
25
|
+
readonly alertsPanel: Locator;
|
|
25
26
|
readonly activeTabPanel: Locator;
|
|
26
27
|
readonly alertsList: Locator;
|
|
28
|
+
readonly alertExpandRowButtons: Locator;
|
|
29
|
+
readonly alertRowActionsButtons: Locator;
|
|
30
|
+
readonly deleteAlertMenuItem: Locator;
|
|
31
|
+
readonly deleteAlertConfirmButton: Locator;
|
|
27
32
|
readonly createFirstAlertButton: Locator;
|
|
28
33
|
readonly createNewAlertButton: Locator;
|
|
29
34
|
readonly emailRadioButton: Locator;
|
|
@@ -37,6 +42,7 @@ declare class ClusterDetailsPage {
|
|
|
37
42
|
readonly expandButton: Locator;
|
|
38
43
|
readonly rbaEnabledMessage: Locator;
|
|
39
44
|
readonly authorizationsHeading: Locator;
|
|
45
|
+
readonly authorizationsSwitch: Locator;
|
|
40
46
|
readonly orchestrationClusterCheckbox: Locator;
|
|
41
47
|
readonly clientCredentialsLink: (clientCredentials: string) => Locator;
|
|
42
48
|
readonly clientRow: (name: string) => Locator;
|
|
@@ -64,6 +70,7 @@ declare class ClusterDetailsPage {
|
|
|
64
70
|
clickCloseModalButton(): Promise<void>;
|
|
65
71
|
clickSettingsTab(): Promise<void>;
|
|
66
72
|
private waitForAuthorizationsSection;
|
|
73
|
+
private setAuthorizationsViaSwitch;
|
|
67
74
|
enableAuthorizations(): Promise<void>;
|
|
68
75
|
disableAuthorizations(): Promise<void>;
|
|
69
76
|
searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
|
|
@@ -76,6 +83,8 @@ declare class ClusterDetailsPage {
|
|
|
76
83
|
selectEmailRadioButton(): Promise<void>;
|
|
77
84
|
clickCreateAlertButton(): Promise<void>;
|
|
78
85
|
deleteAlerts(): Promise<void>;
|
|
86
|
+
ensureAlertsTabSelected(): Promise<void>;
|
|
87
|
+
expandAlerts(): Promise<void>;
|
|
79
88
|
private confirmDelete;
|
|
80
89
|
private dismissDeleteDialog;
|
|
81
90
|
private doDelete;
|