@camunda/e2e-test-suite 0.0.1402 → 0.0.1404
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/ClusterPage.d.ts +1 -0
- package/dist/pages/8.7/ClusterPage.js +72 -14
- package/dist/resources/workspace-templates/camunda-nightlies/camunda-nightlies.guidance.md +1 -1
- package/dist/tests/8.7/console-user-flows.spec.js +15 -3
- package/package.json +3 -3
- package/resources/workspace-templates/camunda-nightlies/camunda-nightlies.guidance.md +1 -1
|
@@ -36,6 +36,7 @@ declare class ClusterPage {
|
|
|
36
36
|
readonly dialog: Locator;
|
|
37
37
|
constructor(page: Page);
|
|
38
38
|
private createdClusterRow;
|
|
39
|
+
private filterClustersTable;
|
|
39
40
|
clickClusterBanner(): Promise<void>;
|
|
40
41
|
deleteCluster(name: string): Promise<void>;
|
|
41
42
|
clickClusterTab(): Promise<void>;
|
|
@@ -131,6 +131,15 @@ class ClusterPage {
|
|
|
131
131
|
.filter({ has: this.page.locator(`a[href$="/cluster/${clusterId}"]`) })
|
|
132
132
|
.first();
|
|
133
133
|
}
|
|
134
|
+
// Hub's clusters table shows 25 rows per page, so once the org holds more
|
|
135
|
+
// clusters than that the wanted row can sit on a later page and never match.
|
|
136
|
+
// Narrow the table to the cluster name via its search box first.
|
|
137
|
+
async filterClustersTable(name) {
|
|
138
|
+
const search = this.page.getByPlaceholder('Search', { exact: true });
|
|
139
|
+
if (await search.isVisible({ timeout: 10000 }).catch(() => false)) {
|
|
140
|
+
await search.fill(name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
134
143
|
async clickClusterBanner() {
|
|
135
144
|
// Camunda Hub has no `banner` landmark; "Clusters" instead lives in its
|
|
136
145
|
// persistent "Main navigation" sidebar, and once the org has environments
|
|
@@ -162,10 +171,19 @@ class ClusterPage {
|
|
|
162
171
|
// provider and /login-callback (~35s) before the SPA pushes its returnUrl
|
|
163
172
|
// back to /clusters. Counting rows mid-bounce throws "Execution context
|
|
164
173
|
// was destroyed", so wait for the clusters page to have settled first.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
const onClustersPage = (url) => /\/clusters\/?$/.test(url.pathname);
|
|
175
|
+
try {
|
|
176
|
+
await this.page.waitForURL(onClustersPage, { timeout: 120000 });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// The bounce can hand back a different route, and a caller that failed
|
|
180
|
+
// mid-flow (e.g. the test's cleanup step) may sit on a cluster's detail
|
|
181
|
+
// page. Navigate to the clusters list rather than failing the cleanup.
|
|
182
|
+
await this.clickClusterBanner();
|
|
183
|
+
await this.page.waitForURL(onClustersPage, { timeout: 60000 });
|
|
184
|
+
}
|
|
168
185
|
await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
|
|
186
|
+
await this.filterClustersTable(name);
|
|
169
187
|
// Hub's clusters data table renders skeleton rows (no links) while it
|
|
170
188
|
// fetches, and the "Create new cluster" button is visible before the data
|
|
171
189
|
// arrives. Counting matching rows at that point always returned 0, so the
|
|
@@ -207,6 +225,8 @@ class ClusterPage {
|
|
|
207
225
|
// A cluster in a transient state (e.g. just created by a concurrent
|
|
208
226
|
// setup worker) may have its row present before the action renders.
|
|
209
227
|
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
228
|
+
await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
|
|
229
|
+
await this.filterClustersTable(name);
|
|
210
230
|
await this.clustersList.getByRole('link').first().waitFor({
|
|
211
231
|
timeout: 30000,
|
|
212
232
|
});
|
|
@@ -217,23 +237,41 @@ class ClusterPage {
|
|
|
217
237
|
const clusterHref = await row
|
|
218
238
|
.getByRole('link', { name, exact: true })
|
|
219
239
|
.getAttribute('href');
|
|
240
|
+
const pendingRow = this.clustersList
|
|
241
|
+
.filter({ has: this.page.locator(`a[href="${clusterHref}"]`) })
|
|
242
|
+
.filter({ hasNotText: /Deleting/ });
|
|
220
243
|
// The kebab menu animates open and the table re-renders while polling
|
|
221
244
|
// cluster status, detaching the menu item mid-click ("element is not
|
|
222
|
-
// stable").
|
|
245
|
+
// stable"). The same re-render also unmounts the confirm dialog after
|
|
246
|
+
// DELETE is typed, leaving the danger button detached for good. Run the
|
|
247
|
+
// whole open-confirm-delete sequence as one retried unit, and stop once
|
|
248
|
+
// this cluster's row is gone or deleting (an earlier click landed).
|
|
223
249
|
await (0, test_1.expect)(async () => {
|
|
250
|
+
if (!(await this.dialog.isVisible()) &&
|
|
251
|
+
(await pendingRow.count()) === 0) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
224
254
|
if (!(await this.dialog.isVisible())) {
|
|
225
255
|
if (!(await deleteMenuItem.isVisible())) {
|
|
226
256
|
await rowAction.click({ timeout: 10000 });
|
|
227
257
|
}
|
|
228
258
|
if (await deleteMenuItem.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
229
|
-
|
|
259
|
+
try {
|
|
260
|
+
await deleteMenuItem.click({ timeout: 10000 });
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// For a row near the bottom of a long table the menu renders
|
|
264
|
+
// outside the viewport and cannot be scrolled to, so the click
|
|
265
|
+
// never lands. Select the focused item via keyboard instead.
|
|
266
|
+
await deleteMenuItem.press('Enter', { timeout: 10000 });
|
|
267
|
+
}
|
|
230
268
|
}
|
|
231
269
|
}
|
|
232
270
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 5000 });
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
271
|
+
await (0, test_1.expect)(confirmInput.first()).toBeVisible({ timeout: 10000 });
|
|
272
|
+
await confirmInput.first().fill('DELETE', { timeout: 10000 });
|
|
273
|
+
await this.dangerDeleteButton.last().click({ timeout: 10000 });
|
|
274
|
+
}).toPass({ timeout: 180000 });
|
|
237
275
|
// When a concurrent worker already deleted this same cluster, the modal
|
|
238
276
|
// stays open with "We're having trouble deleting the cluster". Close it;
|
|
239
277
|
// the row check below still fails if the cluster was not removed.
|
|
@@ -247,6 +285,10 @@ class ClusterPage {
|
|
|
247
285
|
if (await deleteError.isVisible()) {
|
|
248
286
|
await this.dialog.getByRole('button', { name: 'Cancel' }).click();
|
|
249
287
|
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
288
|
+
await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({
|
|
289
|
+
timeout: 120000,
|
|
290
|
+
});
|
|
291
|
+
await this.filterClustersTable(name);
|
|
250
292
|
await this.clustersList.getByRole('link').first().waitFor({
|
|
251
293
|
timeout: 120000,
|
|
252
294
|
});
|
|
@@ -255,9 +297,7 @@ class ClusterPage {
|
|
|
255
297
|
// Wait until the deleted row is gone or flagged as deleting. The table
|
|
256
298
|
// is paginated, so the row count alone does not drop: the next page's
|
|
257
299
|
// rows slide in to fill the gap.
|
|
258
|
-
await (0, test_1.expect)(
|
|
259
|
-
.filter({ has: this.page.locator(`a[href="${clusterHref}"]`) })
|
|
260
|
-
.filter({ hasNotText: /Deleting/ })).toHaveCount(0, { timeout: 60000 });
|
|
300
|
+
await (0, test_1.expect)(pendingRow).toHaveCount(0, { timeout: 60000 });
|
|
261
301
|
}
|
|
262
302
|
}
|
|
263
303
|
async clickClusterTab() {
|
|
@@ -279,8 +319,13 @@ class ClusterPage {
|
|
|
279
319
|
}
|
|
280
320
|
async clickClusterNameInput() {
|
|
281
321
|
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.clusterNameInput, {
|
|
322
|
+
visibilityTimeout: 30000,
|
|
323
|
+
totalTimeout: 180000,
|
|
282
324
|
postAction: async () => {
|
|
325
|
+
// Going back to the clusters list closes the create form, so reopen
|
|
326
|
+
// it; otherwise every later attempt looks for an input that is gone.
|
|
283
327
|
await this.clickClusterBanner();
|
|
328
|
+
await this.clickCreateNewClusterButton();
|
|
284
329
|
},
|
|
285
330
|
});
|
|
286
331
|
}
|
|
@@ -375,11 +420,22 @@ class ClusterPage {
|
|
|
375
420
|
try {
|
|
376
421
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
377
422
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
423
|
+
await this.filterClustersTable(clusterName);
|
|
378
424
|
await (0, test_1.expect)(creatingStatus).toBeVisible({ timeout: 30000 });
|
|
379
425
|
}
|
|
380
426
|
catch (error) {
|
|
381
|
-
|
|
382
|
-
|
|
427
|
+
// Hub serves the clusters table from its cached list after an in-app
|
|
428
|
+
// navigation, so the just-created cluster is missing until a reload.
|
|
429
|
+
// The new row can take more than one reload to show up, so retry.
|
|
430
|
+
await (0, test_1.expect)(async () => {
|
|
431
|
+
await this.clickClusterBanner();
|
|
432
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
433
|
+
await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({
|
|
434
|
+
timeout: 120000,
|
|
435
|
+
});
|
|
436
|
+
await this.filterClustersTable(clusterName);
|
|
437
|
+
await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 60000 });
|
|
438
|
+
}).toPass({ timeout: 420000 });
|
|
383
439
|
}
|
|
384
440
|
}
|
|
385
441
|
async clickClusterLink(name) {
|
|
@@ -489,6 +545,8 @@ class ClusterPage {
|
|
|
489
545
|
maxRetries: 5,
|
|
490
546
|
preAction: async () => {
|
|
491
547
|
await this.page.reload();
|
|
548
|
+
await (0, test_1.expect)(this.createNewClusterButton).toBeVisible({ timeout: 60000 });
|
|
549
|
+
await this.filterClustersTable(name);
|
|
492
550
|
await (0, test_1.expect)(clusterRow).toBeVisible({ timeout: 30000 });
|
|
493
551
|
},
|
|
494
552
|
});
|
|
@@ -112,4 +112,4 @@ A change is considered done only when all of the following hold:
|
|
|
112
112
|
- **Goldens** (SM only): regenerate with `make` targets only; hand-edits are rejected on review.
|
|
113
113
|
- **c8Run — no cluster after nightly**: the binary ran locally on the CI runner and is gone. Never attempt `kubectl`, Teleport, or cluster credential steps for c8Run failures.
|
|
114
114
|
- **Credentials** (SM only): nightly clusters live under `*.ci.distro.ultrawombat.com`; never commit `.env` files. Generate on demand with `scripts/render-e2e-env.sh`.
|
|
115
|
-
- **Toolchain**: Helm repo — Go 1.26.1, Helm 3.20.1 (see `.tool-versions`). E2E repo — Node per `.nvmrc`, Playwright ^1.
|
|
115
|
+
- **Toolchain**: Helm repo — Go 1.26.1, Helm 3.20.1 (see `.tool-versions`). E2E repo — Node per `.nvmrc`, Playwright ^1.63.0. camunda monorepo — Java 21, Maven wrapper `./mvnw`.
|
|
@@ -33,9 +33,21 @@ _8_7_1.test.describe('Console User Flow Tests', () => {
|
|
|
33
33
|
await homePage.clickClusters();
|
|
34
34
|
await clusterPage.createCluster(newClusterName);
|
|
35
35
|
});
|
|
36
|
-
|
|
37
|
-
await
|
|
38
|
-
|
|
36
|
+
try {
|
|
37
|
+
await _8_7_1.test.step('Assert cluster is healthy', async () => {
|
|
38
|
+
await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
// Suffixed repeat names are never pre-deleted by a later run, so each
|
|
43
|
+
// repeat leaked one G3-Dev cluster until the org ran out of G3-Dev
|
|
44
|
+
// reservations and the type tile rendered disabled. Remove them here.
|
|
45
|
+
if (repeatIndex) {
|
|
46
|
+
await _8_7_1.test.step('Delete created cluster', async () => {
|
|
47
|
+
await clusterPage.deleteCluster(newClusterName);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
});
|
|
40
52
|
//Waiting infra team to provide DNS configuration for email testing, skipping for now
|
|
41
53
|
_8_7_1.test.skip('Alert Trigger Flow - Email Notification', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, connectorSettingsPage, }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camunda/e2e-test-suite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1404",
|
|
4
4
|
"description": "End-to-end test helpers for Camunda 8",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@camunda/gremlin": "file:tools/gremlin",
|
|
47
47
|
"@inquirer/prompts": "^3.2.0",
|
|
48
|
-
"@playwright/test": "^1.
|
|
48
|
+
"@playwright/test": "^1.63.0",
|
|
49
49
|
"@types/axios": "^0.14.0",
|
|
50
50
|
"@types/node": "^20.9.0",
|
|
51
51
|
"@typescript-eslint/eslint-plugin": "6.10.0",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.4.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@playwright/test": "^1.
|
|
68
|
+
"@playwright/test": "^1.63.0"
|
|
69
69
|
},
|
|
70
70
|
"lint-staged": {
|
|
71
71
|
"*": [
|
|
@@ -112,4 +112,4 @@ A change is considered done only when all of the following hold:
|
|
|
112
112
|
- **Goldens** (SM only): regenerate with `make` targets only; hand-edits are rejected on review.
|
|
113
113
|
- **c8Run — no cluster after nightly**: the binary ran locally on the CI runner and is gone. Never attempt `kubectl`, Teleport, or cluster credential steps for c8Run failures.
|
|
114
114
|
- **Credentials** (SM only): nightly clusters live under `*.ci.distro.ultrawombat.com`; never commit `.env` files. Generate on demand with `scripts/render-e2e-env.sh`.
|
|
115
|
-
- **Toolchain**: Helm repo — Go 1.26.1, Helm 3.20.1 (see `.tool-versions`). E2E repo — Node per `.nvmrc`, Playwright ^1.
|
|
115
|
+
- **Toolchain**: Helm repo — Go 1.26.1, Helm 3.20.1 (see `.tool-versions`). E2E repo — Node per `.nvmrc`, Playwright ^1.63.0. camunda monorepo — Java 21, Maven wrapper `./mvnw`.
|