@camunda/e2e-test-suite 0.0.1254 → 0.0.1255
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.7/AppsPage.js +65 -11
- package/dist/pages/8.7/ClusterPage.js +22 -20
- package/dist/pages/8.7/ConsoleOrganizationPage.js +37 -6
- package/dist/pages/8.7/FormJsPage.js +36 -9
- package/dist/pages/8.7/LoginPage.js +4 -0
- package/dist/pages/8.7/ModelerCreatePage.js +72 -24
- package/dist/tests/8.7/test-setup.spec.js +16 -0
- package/package.json +1 -1
|
@@ -81,6 +81,40 @@ class AppsPage {
|
|
|
81
81
|
// (see clickHubClusterApp and isHubClusterAppPresent below) gets at most
|
|
82
82
|
// one match, scoped to this cluster.
|
|
83
83
|
async expandHubClusterGroup(clusterName) {
|
|
84
|
+
const toggle = this.hubSidebar
|
|
85
|
+
.getByRole('button', { name: clusterName })
|
|
86
|
+
.first();
|
|
87
|
+
// The diagram editor (and other full-screen views reached from it, e.g.
|
|
88
|
+
// right after runProcessInstance/deployDiagram) has no "Main navigation"
|
|
89
|
+
// sidebar at all -- confirmed from a nightly trace's DOM snapshot: the
|
|
90
|
+
// page's only <nav> is aria-label="Context navigation" (the breadcrumb),
|
|
91
|
+
// and neither "Operate"/"Tasklist"/"Optimize" nor any app-switcher text
|
|
92
|
+
// appears anywhere on it. clickCamundaApps() is a no-op on Hub (there is
|
|
93
|
+
// no switcher panel to open), so a caller reaching clickOperate/
|
|
94
|
+
// clickTasklist/clickOptimize straight from a diagram was left on a page
|
|
95
|
+
// this sidebar could never resolve on, no matter how the toggle itself
|
|
96
|
+
// is matched. Navigate home first, the same way clickModeler() does,
|
|
97
|
+
// whenever the sidebar isn't already there.
|
|
98
|
+
//
|
|
99
|
+
// A freshly re-authenticated page can also land on a workspace-agnostic
|
|
100
|
+
// route (e.g. bare /workspaces right after loginWithTestUser(), which
|
|
101
|
+
// clickCamundaApps() does nothing to fix -- it is a no-op on Hub) whose
|
|
102
|
+
// sidebar renders but has not resolved this cluster's own group yet.
|
|
103
|
+
// Confirmed from a nightly trace: both the getAttribute and click below
|
|
104
|
+
// timed out (10s each) against this exact toggle while the URL sat on
|
|
105
|
+
// /workspaces, right after a mid-test re-login. Route through the same
|
|
106
|
+
// clickModeler() recovery whenever the toggle itself hasn't shown up
|
|
107
|
+
// either, not only when the whole sidebar is missing.
|
|
108
|
+
const sidebarReady = (await this.hubSidebar.isVisible({ timeout: 5000 }).catch(() => false)) &&
|
|
109
|
+
(await toggle
|
|
110
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
111
|
+
.then(() => true)
|
|
112
|
+
.catch(() => false));
|
|
113
|
+
if (!sidebarReady) {
|
|
114
|
+
await this.clickModeler();
|
|
115
|
+
await (0, test_1.expect)(this.hubSidebar).toBeVisible({ timeout: 30000 });
|
|
116
|
+
await (0, test_1.expect)(toggle).toBeVisible({ timeout: 30000 });
|
|
117
|
+
}
|
|
84
118
|
// camunda-hub's own nav-shell-ds.spec.saas.tsx documents the actual
|
|
85
119
|
// shape: "the design system splits the group row into a label control
|
|
86
120
|
// and a chevron control, both labelled by the cluster name; either one
|
|
@@ -101,9 +135,13 @@ class AppsPage {
|
|
|
101
135
|
// any container matchable by cluster name once expanded (confirmed
|
|
102
136
|
// against camunda-hub's own nav-shell-ds.spec.saas.tsx), so the toggle's
|
|
103
137
|
// own aria-expanded is the only reliable per-cluster signal available.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
138
|
+
// Not exact: the same suite asserts this row with a substring/regex
|
|
139
|
+
// match (`{name: /Production/}`), not an exact string -- and nightly
|
|
140
|
+
// traces confirm why: the rendered toggle's accessible name is not
|
|
141
|
+
// byte-for-byte the bare cluster name (it carries additional content,
|
|
142
|
+
// e.g. a trailing tag summary), so an exact-match lookup here finds
|
|
143
|
+
// nothing and the click below times out waiting on a locator that can
|
|
144
|
+
// never resolve.
|
|
107
145
|
const alreadyExpanded = (await toggle.getAttribute('aria-expanded').catch(() => null)) === 'true';
|
|
108
146
|
if (!alreadyExpanded) {
|
|
109
147
|
// This is the only expansion action in the Hub path -- swallowing its
|
|
@@ -125,10 +163,14 @@ class AppsPage {
|
|
|
125
163
|
// of its own, in which case hasNotText would fail to exclude the toggle
|
|
126
164
|
// we just expanded and this loop would immediately collapse it again.
|
|
127
165
|
// Match by accessible name (same algorithm getByRole's own name option
|
|
128
|
-
// uses) instead, excluding
|
|
166
|
+
// uses) instead, excluding any name CONTAINING clusterName -- not just
|
|
167
|
+
// one exactly equal to it, since (per the toggle locator above) the
|
|
168
|
+
// current cluster's own toggle name is not exactly clusterName either,
|
|
169
|
+
// and an exact-equality exclusion would wrongly treat it as "other" and
|
|
170
|
+
// immediately re-collapse the group this method just expanded.
|
|
129
171
|
const otherExpandedToggles = this.hubSidebar.getByRole('button', {
|
|
130
172
|
expanded: true,
|
|
131
|
-
name: new RegExp(`^(
|
|
173
|
+
name: new RegExp(`^(?!.*${escapeRegExp(clusterName)}).+$`),
|
|
132
174
|
});
|
|
133
175
|
for (let i = 0; i < 5 && (await otherExpandedToggles.count()) > 0; i++) {
|
|
134
176
|
// Let a failed collapse throw rather than swallowing it: the app link
|
|
@@ -151,12 +193,15 @@ class AppsPage {
|
|
|
151
193
|
// match carries no ambiguity risk.
|
|
152
194
|
const appLink = this.hubAppLink(clusterName, appName);
|
|
153
195
|
await (0, test_1.expect)(appLink).toBeVisible({ timeout: 30000 });
|
|
154
|
-
//
|
|
155
|
-
// app
|
|
156
|
-
//
|
|
196
|
+
// Hub's cluster-app links carry target="_blank", so clicking them opens
|
|
197
|
+
// the app in a new tab while every caller keeps asserting on this.page.
|
|
198
|
+
// Follow the href in the current tab instead, matching the same-tab
|
|
199
|
+
// navigation the Console app switcher gave these callers.
|
|
157
200
|
const href = await appLink.getAttribute('href');
|
|
158
201
|
if (href && (await appLink.getAttribute('target')) === '_blank') {
|
|
159
|
-
await this.page.goto(href, {
|
|
202
|
+
await this.page.goto(new URL(href, this.page.url()).toString(), {
|
|
203
|
+
waitUntil: 'domcontentloaded',
|
|
204
|
+
});
|
|
160
205
|
return;
|
|
161
206
|
}
|
|
162
207
|
await appLink.click({ timeout: 10000 });
|
|
@@ -164,11 +209,18 @@ class AppsPage {
|
|
|
164
209
|
// Inline (expanded rail) the app link carries the app's name. The rail can
|
|
165
210
|
// also start collapsed (camunda-hub seeds SidebarProvider's defaultExpanded
|
|
166
211
|
// from whether its items had loaded at first render); the group toggle then
|
|
167
|
-
// opens a
|
|
168
|
-
//
|
|
212
|
+
// opens a Radix popover -- a role="dialog" labelled with the cluster name
|
|
213
|
+
// and portalled OUTSIDE "Main navigation" -- whose links are plainly named.
|
|
214
|
+
// With Hub's Environments feature on, the sidebar instead lists
|
|
215
|
+
// environments whose app links render inline but are named
|
|
216
|
+
// "<app> in <cluster>" (opens in a new tab). Try the expanded-sidebar
|
|
217
|
+
// layout first, then each of these fallbacks.
|
|
169
218
|
hubAppLink(clusterName, appName) {
|
|
170
219
|
return this.hubSidebar
|
|
171
220
|
.getByRole('link', { name: appName })
|
|
221
|
+
.or(this.page
|
|
222
|
+
.getByRole('dialog', { name: clusterName, exact: true })
|
|
223
|
+
.getByRole('link', { name: appName }))
|
|
172
224
|
.or(this.page.getByRole('link', {
|
|
173
225
|
name: new RegExp(`^${escapeRegExp(appName)} in ${escapeRegExp(clusterName)}\\b`),
|
|
174
226
|
}))
|
|
@@ -182,6 +234,8 @@ class AppsPage {
|
|
|
182
234
|
// rather than a disabled control -- so this never throws on a miss.
|
|
183
235
|
async isHubClusterAppPresent(clusterName, appName) {
|
|
184
236
|
await this.expandHubClusterGroup(clusterName);
|
|
237
|
+
// Same collapsed-sidebar popover / environment-row fallbacks as
|
|
238
|
+
// clickHubClusterApp -- see hubAppLink.
|
|
185
239
|
const appLink = this.hubAppLink(clusterName, appName);
|
|
186
240
|
return appLink.isVisible({ timeout: 10000 }).catch(() => false);
|
|
187
241
|
}
|
|
@@ -79,28 +79,27 @@ class ClusterPage {
|
|
|
79
79
|
exact: true,
|
|
80
80
|
});
|
|
81
81
|
this.generationSearchInput = page.getByPlaceholder('Search generations');
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
});
|
|
92
|
-
this.
|
|
82
|
+
// Camunda Hub's create-cluster form replaced the Carbon tiles (a <label>
|
|
83
|
+
// per option) and tabs with design-system choice cards and toggle
|
|
84
|
+
// groups, all exposed as role="radio" named by their title. Each locator
|
|
85
|
+
// below accepts either shape via this helper.
|
|
86
|
+
const choice = (legacy, name) => legacy.or(page.getByRole('radio', { name, exact: true }));
|
|
87
|
+
this.g3DevClusterType = choice(page.locator('label').filter({ hasText: this.defaultClusterType }), this.defaultClusterType);
|
|
88
|
+
this.alphaTab = choice(page.getByRole('tab', { name: 'Alpha' }), 'Alpha');
|
|
89
|
+
this.devTab = choice(page.getByRole('tab', { name: 'Internal Dev' }), 'Internal Dev');
|
|
90
|
+
this.stableTab = choice(page.getByRole('tab', { name: 'Stable' }), 'Stable');
|
|
91
|
+
this.qaGenerationsTab = choice(page.getByRole('tab', { name: 'QA Generations' }), 'QA Generations');
|
|
92
|
+
this.gcpRegion = choice(page.getByRole('tab', { name: 'GCP' }), 'GCP');
|
|
93
|
+
this.belgiumRegion = choice(page.locator('label').filter({
|
|
93
94
|
hasText: 'Chaos, Belgium, Europe (europe-west1)',
|
|
94
|
-
});
|
|
95
|
-
this.finlandRegion = page.locator('label').filter({
|
|
95
|
+
}), 'Chaos, Belgium, Europe (europe-west1)');
|
|
96
|
+
this.finlandRegion = choice(page.locator('label').filter({
|
|
96
97
|
hasText: 'Finland, Europe (europe-north1)',
|
|
97
|
-
});
|
|
98
|
-
this.londonRegion = page.locator('label').filter({
|
|
99
|
-
|
|
100
|
-
});
|
|
101
|
-
this.ohioRegion = page.locator('label').filter({
|
|
98
|
+
}), 'Finland, Europe (europe-north1)');
|
|
99
|
+
this.londonRegion = choice(page.locator('label').filter({ hasText: 'London, Europe (europe-west2)' }), 'London, Europe (europe-west2)');
|
|
100
|
+
this.ohioRegion = choice(page.locator('label').filter({
|
|
102
101
|
hasText: 'Ohio, North America (us-east-2)',
|
|
103
|
-
});
|
|
102
|
+
}), 'Ohio, North America (us-east-2)');
|
|
104
103
|
this.clustersList = page
|
|
105
104
|
.getByRole('row')
|
|
106
105
|
.filter({ hasNotText: 'Generation' }); //Filter out header row
|
|
@@ -239,7 +238,10 @@ class ClusterPage {
|
|
|
239
238
|
await this.clickClusterNameInput();
|
|
240
239
|
await this.fillClusterNameInput(name);
|
|
241
240
|
await this.clickClusterType();
|
|
242
|
-
await this.page
|
|
241
|
+
await this.page
|
|
242
|
+
.getByRole('tab', { name: region })
|
|
243
|
+
.or(this.page.getByRole('radio', { name: region, exact: true }))
|
|
244
|
+
.click();
|
|
243
245
|
await this.selectSubRegion(region);
|
|
244
246
|
await this.clickClusterOption(process.env.CLUSTER_VERSION);
|
|
245
247
|
await this.clickCreateClusterButton();
|
|
@@ -4,6 +4,7 @@ exports.ConsoleOrganizationPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
|
|
7
|
+
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
7
8
|
class ConsoleOrganizationPage {
|
|
8
9
|
page;
|
|
9
10
|
usersTab;
|
|
@@ -137,10 +138,26 @@ class ConsoleOrganizationPage {
|
|
|
137
138
|
name: 'assign',
|
|
138
139
|
})
|
|
139
140
|
.last();
|
|
140
|
-
|
|
141
|
+
// The "Assign members" dialog now renders through cmdk (camunda-hub's
|
|
142
|
+
// Command component: data-slot="command"/"command-input") instead of the
|
|
143
|
+
// legacy Carbon combobox. The input keeps the same placeholder text, but
|
|
144
|
+
// its role is "combobox", not "searchbox" -- and the wrapping
|
|
145
|
+
// <label cmdk-label> that aria-labelledby points at is rendered empty, so
|
|
146
|
+
// the accessible-name lookup itself is unreliable; match by placeholder
|
|
147
|
+
// instead, which cmdk always sets from the same string. Nightly trace:
|
|
148
|
+
// getByRole('searchbox', {name: '...'}) found nothing and the click
|
|
149
|
+
// timed out even though the input was on screen.
|
|
150
|
+
this.assignMembersSearchbox = page
|
|
151
|
+
.getByPlaceholder('Search by full name or email address')
|
|
152
|
+
.or(page.getByRole('searchbox', {
|
|
141
153
|
name: 'search by full name or email address',
|
|
142
|
-
})
|
|
143
|
-
|
|
154
|
+
}))
|
|
155
|
+
.first();
|
|
156
|
+
// cmdk's CommandItem renders role="option" (cmdk-item); keep the legacy
|
|
157
|
+
// Carbon class as a fallback.
|
|
158
|
+
this.assignMembersSearchResult = page
|
|
159
|
+
.getByRole('option')
|
|
160
|
+
.or(page.locator('.cds--list-box__menu-item'));
|
|
144
161
|
this.organizationManagementLink = page.getByRole('link', {
|
|
145
162
|
name: 'organization management',
|
|
146
163
|
});
|
|
@@ -148,9 +165,15 @@ class ConsoleOrganizationPage {
|
|
|
148
165
|
this.deleteSubButton = page
|
|
149
166
|
.getByRole('dialog')
|
|
150
167
|
.getByRole('button', { name: 'delete' });
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
168
|
+
// Hub's org-management "Users" tab (hub.ultrawombat.com/org/.../
|
|
169
|
+
// management/users) renders its own table filter as a design-system
|
|
170
|
+
// input labelled plainly "Search", not "Filter table" -- confirmed from
|
|
171
|
+
// the nightly trace's DOM snapshot (an <input type="search"
|
|
172
|
+
// aria-label="Search"> directly inside the "Users" tab panel). Keep the
|
|
173
|
+
// legacy name as a fallback.
|
|
174
|
+
this.filterTableSearchbox = page
|
|
175
|
+
.getByRole('searchbox', { name: 'Search', exact: true })
|
|
176
|
+
.or(page.getByRole('searchbox', { name: 'Filter table' }));
|
|
154
177
|
}
|
|
155
178
|
async clickCreateAuthorizedResourceButton() {
|
|
156
179
|
await (0, test_1.expect)(this.createButton).toBeVisible({ timeout: 60000 });
|
|
@@ -433,6 +456,14 @@ class ConsoleOrganizationPage {
|
|
|
433
456
|
async clickGroupsTab() {
|
|
434
457
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
435
458
|
try {
|
|
459
|
+
// Hub can drop the SPA session mid-test and bounce this tab to
|
|
460
|
+
// /login-callback then its own hung /login?returnUrl=... instead of
|
|
461
|
+
// rendering Organization management -- the same session loss
|
|
462
|
+
// utils/sessionRecovery.ts already recovers from elsewhere. Recover it
|
|
463
|
+
// before clicking, or every retry below just reloads the dead login
|
|
464
|
+
// page and re-fails the same way.
|
|
465
|
+
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
466
|
+
await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page);
|
|
436
467
|
await this.groupsTab.click({ timeout: 60000 });
|
|
437
468
|
return;
|
|
438
469
|
}
|
|
@@ -122,11 +122,21 @@ class FormJsPage {
|
|
|
122
122
|
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
123
123
|
}
|
|
124
124
|
// Hub's environment-based dialog also renders a <label> per row, which
|
|
125
|
-
// the legacy toggle detection below would mistake for its own.
|
|
125
|
+
// the legacy toggle detection below would mistake for its own. A bare
|
|
126
|
+
// isVisible() here does not wait -- it can race the dialog's own content
|
|
127
|
+
// rendering (this.dialog itself is visible a beat before its rows are)
|
|
128
|
+
// and report false even though this IS the environment-based dialog,
|
|
129
|
+
// falling through to the legacy branch below and failing on a
|
|
130
|
+
// [title="<cluster>"] locator that dialog never had. Give it a real
|
|
131
|
+
// window to render before deciding.
|
|
126
132
|
const environmentRow = this.dialog
|
|
127
133
|
.locator('[data-test^="environment-row-"]')
|
|
128
134
|
.first();
|
|
129
|
-
|
|
135
|
+
const hasEnvironmentRow = await environmentRow
|
|
136
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
137
|
+
.then(() => true)
|
|
138
|
+
.catch(() => false);
|
|
139
|
+
if (hasEnvironmentRow) {
|
|
130
140
|
try {
|
|
131
141
|
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
132
142
|
}
|
|
@@ -163,10 +173,12 @@ class FormJsPage {
|
|
|
163
173
|
// Hub's environment-based dialog lists every Environment assigned to
|
|
164
174
|
// the workspace and preselects the first, which need not be this one.
|
|
165
175
|
const environmentRows = this.dialog.locator('[data-test^="environment-row-"]');
|
|
166
|
-
|
|
176
|
+
const hasEnvironmentRows = await environmentRows
|
|
167
177
|
.first()
|
|
168
|
-
.
|
|
169
|
-
.
|
|
178
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
179
|
+
.then(() => true)
|
|
180
|
+
.catch(() => false);
|
|
181
|
+
if (hasEnvironmentRows) {
|
|
170
182
|
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
171
183
|
}
|
|
172
184
|
return;
|
|
@@ -175,10 +187,16 @@ class FormJsPage {
|
|
|
175
187
|
const cluster = this.dialog.locator(`[title="${clusterName}"]`);
|
|
176
188
|
await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
|
|
177
189
|
await cluster.click();
|
|
178
|
-
|
|
190
|
+
// ClustersListDs.tsx (camunda-hub) replaced the legacy Carbon toggle's
|
|
191
|
+
// role="switch" with a Radix RadioGroupItem (role="radio") -- the
|
|
192
|
+
// wrapping <label> is unchanged, but a role="switch" lookup now finds
|
|
193
|
+
// nothing. Accept either role.
|
|
194
|
+
const selectedClusterControl = this.dialog
|
|
179
195
|
.locator('label')
|
|
180
|
-
.filter({ hasText: new RegExp(`^${clusterName}`) })
|
|
181
|
-
|
|
196
|
+
.filter({ hasText: new RegExp(`^${clusterName}`) });
|
|
197
|
+
await (0, test_1.expect)(selectedClusterControl
|
|
198
|
+
.getByRole('switch')
|
|
199
|
+
.or(selectedClusterControl.getByRole('radio'))).toBeChecked({ timeout: 30000 });
|
|
182
200
|
await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
|
|
183
201
|
timeout: healthCheckTimeout,
|
|
184
202
|
});
|
|
@@ -208,8 +226,17 @@ class FormJsPage {
|
|
|
208
226
|
return;
|
|
209
227
|
}
|
|
210
228
|
await scopeSelect.click();
|
|
211
|
-
|
|
229
|
+
// camunda-hub's DeployScopeDs.tsx renders this combobox through the
|
|
230
|
+
// design system's Select, whose SelectContent mounts inside a Radix
|
|
231
|
+
// Portal (see @camunda/c4-components select.tsx) -- the option list
|
|
232
|
+
// renders as a direct child of <body>, OUTSIDE the dialog's own DOM
|
|
233
|
+
// subtree. A dialog-scoped lookup for the option therefore times out
|
|
234
|
+
// even though the click above opened it. Look page-wide first; keep the
|
|
235
|
+
// dialog-scoped lookup for the legacy Carbon Dropdown (DeployScope.tsx),
|
|
236
|
+
// which renders its menu inline.
|
|
237
|
+
await this.page
|
|
212
238
|
.getByRole('option', { name: 'Only this resource' })
|
|
239
|
+
.or(this.dialog.getByRole('option', { name: 'Only this resource' }))
|
|
213
240
|
.or(this.dialog.getByText('Only this resource', { exact: true }))
|
|
214
241
|
.first()
|
|
215
242
|
.click({ timeout: 30000 });
|
|
@@ -4,6 +4,7 @@ exports.LoginPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const UtilitiesPage_1 = require("./UtilitiesPage");
|
|
6
6
|
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
7
|
+
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
7
8
|
class LoginPage {
|
|
8
9
|
page;
|
|
9
10
|
usernameInput;
|
|
@@ -53,6 +54,7 @@ class LoginPage {
|
|
|
53
54
|
}
|
|
54
55
|
async login(credentials = {}) {
|
|
55
56
|
const { username = process.env.C8_USERNAME, password = process.env.C8_PASSWORD, } = credentials;
|
|
57
|
+
(0, sessionRecovery_1.trackSignedInUser)(this.page, username);
|
|
56
58
|
// See loginWithTestUser below: this re-authenticates the same page and
|
|
57
59
|
// must clear the Hub bearer-token cache too, or getUserToken() can
|
|
58
60
|
// return whatever user was previously logged in on it.
|
|
@@ -68,6 +70,7 @@ class LoginPage {
|
|
|
68
70
|
}
|
|
69
71
|
async loginWithTestUser(credentials = {}) {
|
|
70
72
|
const { username = process.env.C8_USERNAME_TEST, password = process.env.C8_PASSWORD_TEST, } = credentials;
|
|
73
|
+
(0, sessionRecovery_1.trackSignedInUser)(this.page, username);
|
|
71
74
|
// See loginWithRetry in UtilitiesPage.ts: the Hub bearer-token cache in
|
|
72
75
|
// consoleApiHelpers.ts is keyed by Page for the tab's whole lifetime, so
|
|
73
76
|
// this re-authentication (a different user than whatever was logged in
|
|
@@ -123,6 +126,7 @@ class LoginPage {
|
|
|
123
126
|
}
|
|
124
127
|
async loginWithoutOrgAssertion(credentials = {}) {
|
|
125
128
|
const { username = process.env.C8_USERNAME, password = process.env.C8_PASSWORD, } = credentials;
|
|
129
|
+
(0, sessionRecovery_1.trackSignedInUser)(this.page, username);
|
|
126
130
|
// See loginWithTestUser above: this re-authenticates the same page and
|
|
127
131
|
// must clear the Hub bearer-token cache too, or getUserToken() can
|
|
128
132
|
// return whatever user was previously logged in on it.
|
|
@@ -477,14 +477,19 @@ class ModelerCreatePage {
|
|
|
477
477
|
await this.userTaskOption.click();
|
|
478
478
|
}
|
|
479
479
|
async clickForm(name) {
|
|
480
|
+
// The "Link a form" picker renders each entry as a button keyed by the
|
|
481
|
+
// form's resource id (data-test="link-target-file-<uuid>"), not by name --
|
|
482
|
+
// `item-${name}` never matches any element the picker renders. The name is
|
|
483
|
+
// only exposed as this accessible name, so match on that instead.
|
|
484
|
+
//
|
|
480
485
|
// Baseline upgrade runs use fixed form names, so a Playwright retry re-creates
|
|
481
486
|
// a form with the same name and the picker then lists two identical entries.
|
|
482
487
|
// Scope to the first matching item so the link step stays deterministic on retry
|
|
483
488
|
// instead of failing with a strict-mode violation.
|
|
484
489
|
await this.page
|
|
485
|
-
.locator(
|
|
490
|
+
.locator('[data-test="link-target-list"]')
|
|
491
|
+
.getByRole('button', { name: `Select ${name}` })
|
|
486
492
|
.first()
|
|
487
|
-
.getByText(name)
|
|
488
493
|
.click({ timeout: 90000 });
|
|
489
494
|
}
|
|
490
495
|
async clickUserTask(id) {
|
|
@@ -525,20 +530,49 @@ class ModelerCreatePage {
|
|
|
525
530
|
await (0, test_1.expect)(this.deployDiagramDialog).toBeVisible({
|
|
526
531
|
timeout: 30000,
|
|
527
532
|
});
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
533
|
+
// Hub's environment-based deploy dialog (camunda-hub#29210) lists the
|
|
534
|
+
// Environments assigned to the workspace as a role="radiogroup" under
|
|
535
|
+
// #environment-selection-heading, one row per
|
|
536
|
+
// [data-test="environment-row-*"] -- confirmed from a nightly trace's DOM
|
|
537
|
+
// snapshot: the radio button and its <label> are SIBLINGS inside the row
|
|
538
|
+
// (the label is no longer a wrapping parent), so a label-scoped
|
|
539
|
+
// getByRole('switch'|'radio') lookup finds nothing, and the row carries
|
|
540
|
+
// no "Manage" link at all, so the old concatenated-text health regex
|
|
541
|
+
// could never match either. Prefer this dialog; fall back to the legacy
|
|
542
|
+
// per-deploy cluster toggle list otherwise.
|
|
543
|
+
// A bare isVisible() does not wait -- it can race the dialog's own
|
|
544
|
+
// content rendering (the dialog itself becomes visible a beat before its
|
|
545
|
+
// rows do) and report false even though this IS the environment-based
|
|
546
|
+
// dialog, falling through to the legacy branch below and failing on a
|
|
547
|
+
// locator that dialog never had. Give it a real window to render before
|
|
548
|
+
// deciding.
|
|
549
|
+
const environmentRows = this.deployDiagramDialog.locator('[data-test^="environment-row-"]');
|
|
550
|
+
const hasEnvironmentRows = await environmentRows
|
|
551
|
+
.first()
|
|
552
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
553
|
+
.then(() => true)
|
|
554
|
+
.catch(() => false);
|
|
555
|
+
if (hasEnvironmentRows) {
|
|
556
|
+
await (0, modelerCluster_1.selectDeployEnvironment)(this.deployDiagramDialog, clusterName);
|
|
557
|
+
const targetRow = environmentRows.filter({ hasText: clusterName }).first();
|
|
558
|
+
await (0, test_1.expect)(targetRow.getByText('Healthy', { exact: true })).toBeVisible({
|
|
559
|
+
timeout: 120000,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
//Select the correct cluster if multiple exist
|
|
564
|
+
const clusterLabel = this.deployDiagramDialog
|
|
565
|
+
.locator('label')
|
|
566
|
+
.filter({ hasText: new RegExp(`^${clusterName}`) });
|
|
567
|
+
await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout: 30000 });
|
|
568
|
+
await clusterLabel.click();
|
|
569
|
+
await (0, test_1.expect)(clusterLabel.getByRole('switch').or(clusterLabel.getByRole('radio'))).toBeChecked({ timeout: 30000 });
|
|
570
|
+
//Check healthy status of the selected cluster
|
|
571
|
+
const regex = new RegExp(`${clusterName}.*?HealthydevManage`);
|
|
572
|
+
await (0, test_1.expect)(this.deployDiagramDialog).toHaveText(regex, {
|
|
573
|
+
timeout: 120000,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
542
576
|
await (0, test_1.expect)(this.deploySubButton).toBeVisible({
|
|
543
577
|
timeout: 120000,
|
|
544
578
|
});
|
|
@@ -1647,11 +1681,21 @@ class ModelerCreatePage {
|
|
|
1647
1681
|
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
1648
1682
|
}
|
|
1649
1683
|
// Hub's environment-based dialog also renders a <label> per row, which
|
|
1650
|
-
// the legacy toggle detection below would mistake for its own.
|
|
1684
|
+
// the legacy toggle detection below would mistake for its own. A bare
|
|
1685
|
+
// isVisible() here does not wait -- it can race the dialog's own content
|
|
1686
|
+
// rendering (this.dialog itself is visible a beat before its rows are)
|
|
1687
|
+
// and report false even though this IS the environment-based dialog,
|
|
1688
|
+
// falling through to the legacy branch below and failing on a
|
|
1689
|
+
// [title="<cluster>"] locator that dialog never had. Give it a real
|
|
1690
|
+
// window to render before deciding.
|
|
1651
1691
|
const environmentRow = this.dialog
|
|
1652
1692
|
.locator('[data-test^="environment-row-"]')
|
|
1653
1693
|
.first();
|
|
1654
|
-
|
|
1694
|
+
const hasEnvironmentRow = await environmentRow
|
|
1695
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
1696
|
+
.then(() => true)
|
|
1697
|
+
.catch(() => false);
|
|
1698
|
+
if (hasEnvironmentRow) {
|
|
1655
1699
|
try {
|
|
1656
1700
|
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
1657
1701
|
}
|
|
@@ -1688,10 +1732,12 @@ class ModelerCreatePage {
|
|
|
1688
1732
|
// Hub's environment-based dialog lists every Environment assigned to
|
|
1689
1733
|
// the workspace and preselects the first, which need not be this one.
|
|
1690
1734
|
const environmentRows = this.dialog.locator('[data-test^="environment-row-"]');
|
|
1691
|
-
|
|
1735
|
+
const hasEnvironmentRows = await environmentRows
|
|
1692
1736
|
.first()
|
|
1693
|
-
.
|
|
1694
|
-
.
|
|
1737
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
1738
|
+
.then(() => true)
|
|
1739
|
+
.catch(() => false);
|
|
1740
|
+
if (hasEnvironmentRows) {
|
|
1695
1741
|
await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, clusterName);
|
|
1696
1742
|
}
|
|
1697
1743
|
return;
|
|
@@ -1702,9 +1748,11 @@ class ModelerCreatePage {
|
|
|
1702
1748
|
.filter({ hasText: new RegExp(`^${clusterName}`) });
|
|
1703
1749
|
await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout: 30000 });
|
|
1704
1750
|
await clusterLabel.click();
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1751
|
+
// ClustersListDs.tsx (camunda-hub) replaced the legacy Carbon toggle's
|
|
1752
|
+
// role="switch" with a Radix RadioGroupItem (role="radio") -- the
|
|
1753
|
+
// wrapping <label> is unchanged, but a role="switch" lookup now finds
|
|
1754
|
+
// nothing. Accept either role.
|
|
1755
|
+
await (0, test_1.expect)(clusterLabel.getByRole('switch').or(clusterLabel.getByRole('radio'))).toBeChecked({ timeout: 30000 });
|
|
1708
1756
|
await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
|
|
1709
1757
|
timeout: healthCheckTimeout,
|
|
1710
1758
|
});
|
|
@@ -58,6 +58,22 @@ _8_7_1.test.describe('Cluster Setup Tests', () => {
|
|
|
58
58
|
for (const [index, user] of (0, users_1.getTestUsers)().entries()) {
|
|
59
59
|
(0, _8_7_1.test)(`Create Project Folder for User ${index + 1}`, async ({ page, loginPage, homePage, appsPage, modelerHomePage, }, testInfo) => {
|
|
60
60
|
await _8_7_1.test.step('Log in', async () => {
|
|
61
|
+
// getTestUsers() resolves straight from process.env.C8_USERNAME_TEST_N /
|
|
62
|
+
// C8_PASSWORD_TEST_N. When this SaaS org was created without one of
|
|
63
|
+
// those slots the password half comes back '', and loginWithRetry then
|
|
64
|
+
// spends its full backoff budget (~13 minutes across both Playwright
|
|
65
|
+
// retries) re-submitting a login Auth0 rejects with "Password is
|
|
66
|
+
// required" every single attempt, before surfacing the generic "Login
|
|
67
|
+
// failed after 5 attempts" -- no mention of which slot or that a
|
|
68
|
+
// password was even missing. Fail immediately instead, naming the
|
|
69
|
+
// slot, the same way requireTestUser() already does for specs that
|
|
70
|
+
// reach a numbered credential slot directly.
|
|
71
|
+
if (!user.username || !user.password) {
|
|
72
|
+
throw new Error(`User ${index + 1} (${user.username || 'username also missing'}) has no ` +
|
|
73
|
+
'password configured -- this SaaS org was created without that ' +
|
|
74
|
+
'user slot. See scripts/flakiness-saas-create-org.sh, which sizes ' +
|
|
75
|
+
'the org from the slots each spec names.');
|
|
76
|
+
}
|
|
61
77
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, user, (testInfo.workerIndex + 1) * 1000);
|
|
62
78
|
});
|
|
63
79
|
await _8_7_1.test.step('Navigate to Web Modeler', async () => {
|