@camunda/e2e-test-suite 0.0.838 → 0.0.840
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.9/ClusterDetailsPage.js +88 -16
- package/dist/pages/8.9/ClusterPage.js +6 -1
- package/dist/pages/8.9/FormJsPage.js +1 -1
- package/dist/pages/8.9/HomePage.js +5 -1
- package/dist/pages/8.9/ModelerCreatePage.js +8 -7
- package/dist/pages/8.9/ModelerHomePage.js +2 -2
- package/dist/pages/8.9/TaskPanelPage.js +12 -1
- package/dist/tests/8.9/console-user-flows.spec.js +11 -0
- package/dist/tests/8.9/test-setup.spec.js +22 -9
- package/dist/tests/SM-8.9/web-modeler-user-flows.spec.js +0 -11
- package/dist/utils/consoleApiHelpers.js +7 -0
- package/package.json +1 -1
|
@@ -476,22 +476,94 @@ class ClusterDetailsPage {
|
|
|
476
476
|
return variables;
|
|
477
477
|
}
|
|
478
478
|
async createAPIClient(name) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
479
|
+
const maxRetries = 3;
|
|
480
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
481
|
+
// Use a suffixed name on retries so duplicate-name validation never
|
|
482
|
+
// leaves the Create button permanently disabled. A prior attempt may
|
|
483
|
+
// have created the client before we could detect the credentials dialog.
|
|
484
|
+
const attemptName = attempt === 0 ? name : `${name}-r${attempt}`;
|
|
485
|
+
try {
|
|
486
|
+
await this.clickCreateClientButton();
|
|
487
|
+
await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible({
|
|
488
|
+
timeout: 30000,
|
|
489
|
+
});
|
|
490
|
+
await this.fillAPIClientName(attemptName);
|
|
491
|
+
await this.checkOrchestrationClusterCheckbox();
|
|
492
|
+
await this.checkOptimizeCheckbox();
|
|
493
|
+
await this.checkSecretsCheckbox();
|
|
494
|
+
await this.clickCreateButton();
|
|
495
|
+
// The POST behind Create occasionally returns "fetch error" — the
|
|
496
|
+
// create dialog stays open with an in-modal "Error fetch error"
|
|
497
|
+
// banner and the success ("Client credentials" / "The Client
|
|
498
|
+
// Secret will not be shown again.") dialog never appears. Detect
|
|
499
|
+
// that state and retry instead of burning 60s on a visibility
|
|
500
|
+
// assertion that will never resolve.
|
|
501
|
+
const credentialsShown = await this.clientCredentialsDialog
|
|
502
|
+
.isVisible({ timeout: 30000 })
|
|
503
|
+
.catch(() => false);
|
|
504
|
+
if (!credentialsShown) {
|
|
505
|
+
const stuckCreateDialog = await this.createClientCredentialsDialog
|
|
506
|
+
.isVisible({ timeout: 500 })
|
|
507
|
+
.catch(() => false);
|
|
508
|
+
const fetchError = stuckCreateDialog
|
|
509
|
+
? await this.createClientCredentialsDialog
|
|
510
|
+
.getByText(/fetch error/i)
|
|
511
|
+
.isVisible({ timeout: 500 })
|
|
512
|
+
.catch(() => false)
|
|
513
|
+
: false;
|
|
514
|
+
if (stuckCreateDialog && fetchError) {
|
|
515
|
+
throw new Error('createAPIClient: create dialog stuck with "fetch error"');
|
|
516
|
+
}
|
|
517
|
+
// Not the fetch-error path — fall through to the original
|
|
518
|
+
// visibility expectation so the failure surface stays informative.
|
|
519
|
+
await (0, test_1.expect)(this.clientCredentialsDialog).toBeVisible({
|
|
520
|
+
timeout: 30000,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
await (0, test_1.expect)(this.clientCredentialsDialog
|
|
524
|
+
.getByText('The Client Secret will not be shown again.')
|
|
525
|
+
.first()).toBeVisible();
|
|
526
|
+
try {
|
|
527
|
+
await (0, test_1.expect)(this.clientsList.filter({ hasText: attemptName })).toContainText(/(?=.*Orchestration)(?=.*Optimize)(?=.*Secrets)/, {
|
|
528
|
+
timeout: 10000,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
catch (err) {
|
|
532
|
+
// The clients list panel can land in "Oops ... something went wrong."
|
|
533
|
+
// while the post-create modal is still open. The modal blocks pointer
|
|
534
|
+
// events so we can't click Reload here; the modal's "Client Secret will
|
|
535
|
+
// not be shown again" message above already proves the client exists.
|
|
536
|
+
const oopsVisible = await this.page
|
|
537
|
+
.getByRole('heading', { name: /Oops/i })
|
|
538
|
+
.isVisible({ timeout: 1000 })
|
|
539
|
+
.catch(() => false);
|
|
540
|
+
if (!oopsVisible)
|
|
541
|
+
throw err;
|
|
542
|
+
console.warn(`createAPIClient: clients list panel is in Oops state; skipping row scope assertion for "${attemptName}".`);
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
if (attempt < maxRetries - 1) {
|
|
548
|
+
console.warn(`createAPIClient attempt ${attempt + 1} for "${name}" failed: ${error}; cancelling any stuck dialog and reloading.`);
|
|
549
|
+
// Cancel the stuck create dialog if it's still open, then reload
|
|
550
|
+
// the API tab so the next attempt starts from a clean state.
|
|
551
|
+
const cancel = this.createClientCredentialsDialog.getByRole('button', { name: 'Cancel' });
|
|
552
|
+
if (await cancel.isVisible({ timeout: 500 }).catch(() => false)) {
|
|
553
|
+
await cancel.click({ timeout: 5000 }).catch(() => { });
|
|
554
|
+
}
|
|
555
|
+
await this.page.keyboard.press('Escape').catch(() => { });
|
|
556
|
+
await this.page
|
|
557
|
+
.reload({ waitUntil: 'domcontentloaded' })
|
|
558
|
+
.catch(() => { });
|
|
559
|
+
// The API tab needs to be re-selected after reload.
|
|
560
|
+
await this.clickAPITab().catch(() => { });
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
throw new Error(`Creating API client "${name}" failed after ${maxRetries} attempts: ${error}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
495
567
|
}
|
|
496
568
|
async clickEnvVarsButton() {
|
|
497
569
|
await (0, test_1.expect)(this.envVarsTab).toBeVisible({ timeout: 40000 });
|
|
@@ -104,12 +104,17 @@ class ClusterPage {
|
|
|
104
104
|
.filter({ hasNotText: 'Generation' }); //Filter out header row
|
|
105
105
|
this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
|
|
106
106
|
this.tasklistV2Api = page.getByText('Tasklist API v2');
|
|
107
|
-
this.cluster = (clusterName) => this.clustersList
|
|
107
|
+
this.cluster = (clusterName) => this.clustersList
|
|
108
|
+
.filter({
|
|
109
|
+
has: page.getByRole('link', { name: clusterName, exact: true }),
|
|
110
|
+
})
|
|
111
|
+
.first();
|
|
108
112
|
this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
|
|
109
113
|
exact: true,
|
|
110
114
|
});
|
|
111
115
|
this.clusterLink = (clusterName) => this.cluster(clusterName).getByRole('link', {
|
|
112
116
|
name: clusterName,
|
|
117
|
+
exact: true,
|
|
113
118
|
});
|
|
114
119
|
this.clusterVersion = (clusterName, versionText) => this.cluster(clusterName).getByText(versionText);
|
|
115
120
|
}
|
|
@@ -130,7 +130,7 @@ class FormJsPage {
|
|
|
130
130
|
await cluster.click();
|
|
131
131
|
await (0, test_1.expect)(this.dialog
|
|
132
132
|
.locator('label')
|
|
133
|
-
.filter({ hasText: clusterName })
|
|
133
|
+
.filter({ hasText: new RegExp(`^${clusterName}`) })
|
|
134
134
|
.getByRole('switch')).toBeChecked({ timeout: 30000 });
|
|
135
135
|
await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
|
|
136
136
|
timeout: healthCheckTimeout,
|
|
@@ -94,7 +94,11 @@ class HomePage {
|
|
|
94
94
|
state: 'visible',
|
|
95
95
|
timeout: 30000,
|
|
96
96
|
});
|
|
97
|
-
|
|
97
|
+
// Best-effort: dismiss any Carbon modal that has a named close button.
|
|
98
|
+
await this.closeInformationDialog();
|
|
99
|
+
// force: true bypasses the "pointer events intercepted by modal backdrop"
|
|
100
|
+
// check — the same pattern used by closeInformationDialog itself.
|
|
101
|
+
await this.buttonSkipCustomization.click({ timeout: 60000, force: true });
|
|
98
102
|
await this.closeInformationDialog();
|
|
99
103
|
}
|
|
100
104
|
catch {
|
|
@@ -604,13 +604,14 @@ class ModelerCreatePage {
|
|
|
604
604
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
|
|
605
605
|
const healthCheckTimeout = 30000;
|
|
606
606
|
const healthyRegex = new RegExp(`${clusterName}.*?HealthydevManage`);
|
|
607
|
-
const
|
|
608
|
-
await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
|
|
609
|
-
await cluster.click();
|
|
610
|
-
await (0, test_1.expect)(this.dialog
|
|
607
|
+
const clusterLabel = this.dialog
|
|
611
608
|
.locator('label')
|
|
612
|
-
.filter({ hasText: clusterName })
|
|
613
|
-
|
|
609
|
+
.filter({ hasText: new RegExp(`^${clusterName}`) });
|
|
610
|
+
await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout: 30000 });
|
|
611
|
+
await clusterLabel.click();
|
|
612
|
+
await (0, test_1.expect)(clusterLabel.getByRole('switch')).toBeChecked({
|
|
613
|
+
timeout: 30000,
|
|
614
|
+
});
|
|
614
615
|
await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
|
|
615
616
|
timeout: healthCheckTimeout,
|
|
616
617
|
});
|
|
@@ -1102,7 +1103,7 @@ class ModelerCreatePage {
|
|
|
1102
1103
|
// before saving. This handles environments with multiple clusters.
|
|
1103
1104
|
const clusterLabel = configureEnvDialog
|
|
1104
1105
|
.locator('label')
|
|
1105
|
-
.filter({ hasText: clusterName });
|
|
1106
|
+
.filter({ hasText: new RegExp(`^${clusterName}`) });
|
|
1106
1107
|
const clusterSwitch = clusterLabel.getByRole('switch');
|
|
1107
1108
|
await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout });
|
|
1108
1109
|
const alreadyChecked = (await clusterSwitch.getAttribute('aria-checked')) === 'true';
|
|
@@ -300,7 +300,7 @@ class ModelerHomePage {
|
|
|
300
300
|
async selectCluster(clusterName) {
|
|
301
301
|
//Select the correct cluster if multiple exist
|
|
302
302
|
const baseClusterName = clusterName.split('(')[0].trim();
|
|
303
|
-
const cluster = this.dialog.getByText(
|
|
303
|
+
const cluster = this.dialog.getByText(new RegExp(`^${baseClusterName}`));
|
|
304
304
|
await (0, test_1.expect)(cluster).toBeVisible({
|
|
305
305
|
timeout: 30000,
|
|
306
306
|
});
|
|
@@ -308,7 +308,7 @@ class ModelerHomePage {
|
|
|
308
308
|
await (0, test_1.expect)(this.dialog
|
|
309
309
|
.locator('label')
|
|
310
310
|
.filter({
|
|
311
|
-
hasText: clusterName,
|
|
311
|
+
hasText: new RegExp(`^${clusterName}`),
|
|
312
312
|
})
|
|
313
313
|
.getByRole('switch')).toBeChecked({ timeout: 30000 });
|
|
314
314
|
}
|
|
@@ -32,7 +32,12 @@ class TaskPanelPage {
|
|
|
32
32
|
}
|
|
33
33
|
async openTask(name) {
|
|
34
34
|
let attempts = 0;
|
|
35
|
-
|
|
35
|
+
// Tasklist's importer can lag well behind Zeebe under heavy concurrent
|
|
36
|
+
// load from the sibling chromium-v1/chromium-v2 projects sharing one
|
|
37
|
+
// cluster -- trace evidence from failing runs showed other tests' tasks
|
|
38
|
+
// only appearing 15-17 minutes after creation. 10 x 120s gives 20 minutes
|
|
39
|
+
// of budget, comfortably covering that observed lag.
|
|
40
|
+
const maxAttempts = 10;
|
|
36
41
|
while (attempts < maxAttempts) {
|
|
37
42
|
try {
|
|
38
43
|
const task = this.availableTasks.getByText(name, { exact: true }).first();
|
|
@@ -46,6 +51,12 @@ class TaskPanelPage {
|
|
|
46
51
|
throw error;
|
|
47
52
|
}
|
|
48
53
|
await this.page.reload();
|
|
54
|
+
// Tasklist polls continuously for new tasks, so `networkidle` never
|
|
55
|
+
// fires and the wait times out at 60s -- burning the retry budget
|
|
56
|
+
// without ever re-attempting the task click. `domcontentloaded`
|
|
57
|
+
// resolves once the reloaded HTML has parsed, which is the actual
|
|
58
|
+
// signal we need before trying to find the task again.
|
|
59
|
+
await this.page.waitForLoadState('domcontentloaded', { timeout: 60000 });
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
62
|
}
|
|
@@ -71,6 +71,17 @@ _8_9_1.test.describe('Console User Flow Tests @tasklistV2', () => {
|
|
|
71
71
|
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'completed icon in Operate', 60000);
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
|
+
(0, _8_9_1.test)('Create Cluster', async ({ homePage, clusterPage }) => {
|
|
75
|
+
_8_9_1.test.slow();
|
|
76
|
+
const newClusterName = 'UI Test Cluster';
|
|
77
|
+
await _8_9_1.test.step('Navigate to clusters and create cluster', async () => {
|
|
78
|
+
await homePage.clickClusters();
|
|
79
|
+
await clusterPage.createCluster(newClusterName);
|
|
80
|
+
});
|
|
81
|
+
await _8_9_1.test.step('Assert cluster is healthy', async () => {
|
|
82
|
+
await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
74
85
|
(0, _8_9_1.test)('Alert Trigger Flow - Email Notification', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, connectorSettingsPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
|
|
75
86
|
_8_9_1.test.slow();
|
|
76
87
|
const processName = 'Email_Alert_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
@@ -7,6 +7,7 @@ const connectorSecrets_1 = require("../../utils/connectorSecrets");
|
|
|
7
7
|
const mcpSecrets_1 = require("../../utils/mcpSecrets");
|
|
8
8
|
const UtilitiesPage_1 = require("../../pages/8.9/UtilitiesPage");
|
|
9
9
|
const users_1 = require("../../utils/users");
|
|
10
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
10
11
|
_8_9_1.test.describe.configure({ mode: 'parallel' });
|
|
11
12
|
_8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
12
13
|
_8_9_1.test.afterEach(async ({ page }, testInfo) => {
|
|
@@ -26,9 +27,15 @@ _8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
|
26
27
|
const clusterName = 'Test Cluster';
|
|
27
28
|
const apiClientName = 'Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
28
29
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (process.env.IS_PROD === 'true') {
|
|
31
|
+
await clusterPage.createCluster(clusterName);
|
|
32
|
+
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName);
|
|
36
|
+
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid);
|
|
37
|
+
await homePage.clickClusters();
|
|
38
|
+
}
|
|
32
39
|
await clusterPage.clickClusterLink(clusterName);
|
|
33
40
|
await clusterDetailsPage.assertComponentsHealth();
|
|
34
41
|
if (process.env.IS_AG !== 'true') {
|
|
@@ -51,9 +58,9 @@ _8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
|
51
58
|
const apiClientName = 'Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
52
59
|
const mcpClientName = 'MCP_Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
53
60
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
61
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName);
|
|
62
|
+
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid, 1500000);
|
|
54
63
|
await homePage.clickClusters();
|
|
55
|
-
await clusterPage.createCluster(clusterName);
|
|
56
|
-
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
|
|
57
64
|
await clusterPage.clickClusterLink(clusterName);
|
|
58
65
|
await clusterDetailsPage.assertComponentsHealth();
|
|
59
66
|
await clusterDetailsPage.clickSettingsTab();
|
|
@@ -75,10 +82,16 @@ _8_9_1.test.describe('Cluster Setup Tests', () => {
|
|
|
75
82
|
_8_9_1.test.slow();
|
|
76
83
|
const clusterName = 'AWS Cluster';
|
|
77
84
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
if (process.env.IS_PROD === 'true') {
|
|
86
|
+
await clusterPage.createCluster(clusterName, 'AWS');
|
|
87
|
+
// AWS clusters on SaaS INT routinely need 10+ min to reach Healthy.
|
|
88
|
+
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 300000, 1500000);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName, 'AWS');
|
|
92
|
+
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid, 1500000);
|
|
93
|
+
await homePage.clickClusters();
|
|
94
|
+
}
|
|
82
95
|
await clusterPage.clickClusterLink(clusterName);
|
|
83
96
|
await clusterDetailsPage.assertComponentsHealth();
|
|
84
97
|
if (process.env.IS_AG !== 'true') {
|
|
@@ -175,17 +175,6 @@ SM_8_9_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
175
175
|
await operateProcessInstancePage.assertProcessVariableContainsText('myVar', '8');
|
|
176
176
|
});
|
|
177
177
|
if (process.env.IS_OPTIMIZE !== 'false') {
|
|
178
|
-
await SM_8_9_1.test.step('Navigate to Optimize and verify process in dashboard', async () => {
|
|
179
|
-
await navigationPage.goToOptimize();
|
|
180
|
-
await optimizeHomePage.clickDashboardLink();
|
|
181
|
-
await (0, sleep_1.sleep)(3000); // Brief wait for Optimize dashboard to initialize
|
|
182
|
-
await page.reload();
|
|
183
|
-
await optimizeDashboardPage.clickFilterTable();
|
|
184
|
-
await optimizeDashboardPage.fillFilterTable(processName);
|
|
185
|
-
await optimizeDashboardPage.processAssertion(processName, {
|
|
186
|
-
timeout: 60000,
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
178
|
await SM_8_9_1.test.step('Navigate to Optimize and verify process appears in dashboard (CE-OPT-01)', async () => {
|
|
190
179
|
await navigationPage.goToOptimize();
|
|
191
180
|
await optimizeHomePage.clickDashboardLink();
|
|
@@ -123,6 +123,13 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
|
123
123
|
k8sContextId,
|
|
124
124
|
autoUpdate: true,
|
|
125
125
|
stageLabel: 'dev',
|
|
126
|
+
// Upgrading a cluster to Tasklist v2 is a one-way, irreversible switch,
|
|
127
|
+
// and v2 cannot run job-worker-based user tasks, draft variables, or
|
|
128
|
+
// public start forms -- all of which the @tasklistV1-tagged suites
|
|
129
|
+
// exercise. Explicitly request v1 rather than relying on whatever the
|
|
130
|
+
// Console API defaults new clusters to, since a cluster that ends up
|
|
131
|
+
// v2-enabled can never be brought back to v1 for the rest of its life.
|
|
132
|
+
tasklistV2Enabled: false,
|
|
126
133
|
},
|
|
127
134
|
});
|
|
128
135
|
if (createResponse.status() !== 200 && createResponse.status() !== 201) {
|