@camunda/e2e-test-suite 0.0.843 → 0.0.845

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.
@@ -105,12 +105,17 @@ class ClusterPage {
105
105
  .filter({ hasNotText: 'Generation' }); //Filter out header row
106
106
  this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
107
107
  this.tasklistV2Api = page.getByText('Tasklist API v2');
108
- this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
108
+ this.cluster = (clusterName) => this.clustersList
109
+ .filter({
110
+ has: page.getByRole('link', { name: clusterName, exact: true }),
111
+ })
112
+ .first();
109
113
  this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
110
114
  exact: true,
111
115
  });
112
116
  this.clusterLink = (clusterName) => this.cluster(clusterName).getByRole('link', {
113
117
  name: clusterName,
118
+ exact: true,
114
119
  });
115
120
  this.clusterVersion = (clusterName, versionText) => this.cluster(clusterName).getByText(versionText);
116
121
  }
@@ -118,7 +118,7 @@ class FormJsPage {
118
118
  await cluster.click();
119
119
  await (0, test_1.expect)(this.dialog
120
120
  .locator('label')
121
- .filter({ hasText: clusterName })
121
+ .filter({ hasText: new RegExp(`^${clusterName}`) })
122
122
  .getByRole('switch')).toBeChecked({ timeout: 30000 });
123
123
  await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
124
124
  timeout: healthCheckTimeout,
@@ -138,7 +138,7 @@ declare class ModelerCreatePage {
138
138
  clickStartInstanceSubButton(): Promise<void>;
139
139
  runProcessInstance(clusterName: string, variables?: string, formVariables?: {
140
140
  [key: string]: string;
141
- }, textToVisible?: string): Promise<void>;
141
+ }, textToVisible?: string, expectRejection?: boolean): Promise<void>;
142
142
  selectCluster(clusterName: string): Promise<void>;
143
143
  clickViewProcessInstanceLink(): Promise<void>;
144
144
  clickNameInput(): Promise<void>;
@@ -554,12 +554,16 @@ class ModelerCreatePage {
554
554
  await this.deployAndRunSubButton.click({ timeout: 30000 }); //will be available from alpha7
555
555
  }
556
556
  }
557
- async runProcessInstance(clusterName, variables = '', formVariables = {}, textToVisible = '') {
558
- // After a 8.7->8.8 upgrade, orchestration-cluster authorization commands
559
- // propagate across partitions for several minutes; deploy & run is rejected
560
- // with FORBIDDEN until the deployer's grant lands. In migration runs, detect
561
- // that rejection and retry through the propagation window (more attempts),
562
- // rather than returning a silently-failed run.
557
+ async runProcessInstance(clusterName, variables = '', formVariables = {}, textToVisible = '', expectRejection = false) {
558
+ // After a 8.7->8.8 upgrade, or right after a cluster is freshly created
559
+ // (test-setup.spec.ts now deletes+recreates "Test Cluster"/"AWS Cluster"
560
+ // via the Console API on every run instead of reusing a long-lived one),
561
+ // orchestration-cluster authorization commands propagate across
562
+ // partitions for a short window; deploy & run is rejected with FORBIDDEN
563
+ // until the deployer's grant lands. Detect that rejection and retry
564
+ // through the propagation window, rather than returning a
565
+ // silently-failed run — this used to only matter for migration runs,
566
+ // but a just-created cluster hits the same window on every run now.
563
567
  const isMigration = process.env.IS_MIGRATION === 'true';
564
568
  const maxRetries = isMigration ? 20 : 10;
565
569
  const retryDelayMs = 20000;
@@ -592,14 +596,19 @@ class ModelerCreatePage {
592
596
  });
593
597
  }
594
598
  await this.clickStartInstanceSubButton();
595
- if (isMigration) {
596
- const deployRejected = await this.deployErrorMessage
597
- .waitFor({ state: 'visible', timeout: 5000 })
598
- .then(() => true)
599
- .catch(() => false);
600
- if (deployRejected) {
601
- throw new Error('Deploy & run rejected; authorization may still be propagating after upgrade');
599
+ const deployRejected = await this.deployErrorMessage
600
+ .waitFor({ state: 'visible', timeout: 5000 })
601
+ .then(() => true)
602
+ .catch(() => false);
603
+ if (deployRejected) {
604
+ // Some callers (e.g. under-privileged role flows) deliberately expect
605
+ // the deploy to be rejected -- that rejection is the assertion under
606
+ // test, not a transient propagation window to retry through. Return
607
+ // immediately so the caller's own UI assertion on the rejection can run.
608
+ if (expectRejection) {
609
+ return;
602
610
  }
611
+ throw new Error('Deploy & run rejected; authorization may still be propagating');
603
612
  }
604
613
  return;
605
614
  }
@@ -617,19 +626,38 @@ class ModelerCreatePage {
617
626
  await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
618
627
  const healthCheckTimeout = 30000;
619
628
  const healthyRegex = new RegExp(`${clusterName}.*?HealthydevManage`);
620
- const cluster = this.dialog.getByText(clusterName);
621
- await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
622
- await cluster.click();
623
- await (0, test_1.expect)(this.dialog
629
+ const clusterLabel = this.dialog
624
630
  .locator('label')
625
- .filter({ hasText: clusterName })
626
- .getByRole('switch')).toBeChecked({ timeout: 30000 });
631
+ .filter({ hasText: new RegExp(`^${clusterName}`) });
632
+ await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout: 30000 });
633
+ await clusterLabel.click();
634
+ await (0, test_1.expect)(clusterLabel.getByRole('switch')).toBeChecked({
635
+ timeout: 30000,
636
+ });
627
637
  await (0, test_1.expect)(this.dialog).toHaveText(healthyRegex, {
628
638
  timeout: healthCheckTimeout,
629
639
  });
630
640
  }
631
641
  async clickViewProcessInstanceLink() {
632
- await this.viewProcessInstanceLink.click({ timeout: 180000 });
642
+ // The notification containing this link keeps detaching and re-rendering
643
+ // continuously (observed as "element was detached from the DOM, retrying"
644
+ // recurring for the entire length of a 60s attempt, not just a one-off
645
+ // glitch), so waiting longer for it to "stabilize" doesn't help -- there
646
+ // may be no stable window at all. Skip the actionability/stability wait
647
+ // with force, and iterate quickly so more attempts land inside whatever
648
+ // brief windows do exist, instead of a few very long, equally-doomed ones.
649
+ const maxRetries = 10;
650
+ for (let retries = 0; retries < maxRetries; retries++) {
651
+ try {
652
+ await this.viewProcessInstanceLink.click({ force: true, timeout: 5000 });
653
+ return;
654
+ }
655
+ catch (error) {
656
+ console.error(`Click attempt ${retries + 1} failed: ${error}`);
657
+ await new Promise((resolve) => setTimeout(resolve, 2000));
658
+ }
659
+ }
660
+ throw new Error(`Failed to click "View process instance" after ${maxRetries} attempts.`);
633
661
  }
634
662
  async clickNameInput() {
635
663
  await this.nameInput.click({ timeout: 60000 });
@@ -1133,7 +1161,7 @@ class ModelerCreatePage {
1133
1161
  // before saving. This handles environments with multiple clusters.
1134
1162
  const clusterLabel = configureEnvDialog
1135
1163
  .locator('label')
1136
- .filter({ hasText: clusterName });
1164
+ .filter({ hasText: new RegExp(`^${clusterName}`) });
1137
1165
  const clusterSwitch = clusterLabel.getByRole('switch');
1138
1166
  await (0, test_1.expect)(clusterLabel).toBeVisible({ timeout });
1139
1167
  const alreadyChecked = (await clusterSwitch.getAttribute('aria-checked')) === 'true';
@@ -301,7 +301,7 @@ class ModelerHomePage {
301
301
  async selectCluster(clusterName) {
302
302
  //Select the correct cluster if multiple exist
303
303
  const baseClusterName = clusterName.split('(')[0].trim();
304
- const cluster = this.dialog.getByText(baseClusterName, { exact: false });
304
+ const cluster = this.dialog.getByText(new RegExp(`^${baseClusterName}`));
305
305
  await (0, test_1.expect)(cluster).toBeVisible({
306
306
  timeout: 30000,
307
307
  });
@@ -309,7 +309,7 @@ class ModelerHomePage {
309
309
  await (0, test_1.expect)(this.dialog
310
310
  .locator('label')
311
311
  .filter({
312
- hasText: clusterName,
312
+ hasText: new RegExp(`^${clusterName}`),
313
313
  })
314
314
  .getByRole('switch')).toBeChecked({ timeout: 30000 });
315
315
  }
@@ -32,7 +32,12 @@ class TaskPanelPage {
32
32
  }
33
33
  async openTask(name) {
34
34
  let attempts = 0;
35
- const maxAttempts = 5;
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();
@@ -34,6 +34,12 @@ async function forceLogoutIfNeeded(page) {
34
34
  exports.forceLogoutIfNeeded = forceLogoutIfNeeded;
35
35
  async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3) {
36
36
  let lastError;
37
+ // Stagger the first navigation across parallel workers. Without this, every
38
+ // worker's initial page.goto('/') fires within the same second -- with 18+
39
+ // workers logging in at once, that burst is enough to make the navigation
40
+ // itself time out well before any login form ever renders. Retries already
41
+ // back off via randomSleep below, so this only needs to run once.
42
+ await (0, sleep_1.sleep)(timeout);
37
43
  for (let attempt = 0; attempt < maxRetries; attempt++) {
38
44
  try {
39
45
  await page.context().clearCookies();
@@ -58,7 +64,6 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
58
64
  await page
59
65
  .waitForLoadState('networkidle', { timeout: 30000 })
60
66
  .catch(() => { });
61
- await (0, sleep_1.sleep)(timeout);
62
67
  await (0, test_1.expect)(loginPage.loginMessage.or(loginPage.passwordHeading)).toBeVisible({ timeout: 60000 });
63
68
  await loginPage.loginWithTestUser(testUser);
64
69
  return;
@@ -215,7 +215,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
215
215
  await modelerCreatePage.clickAppendElementButton();
216
216
  await modelerCreatePage.clickAppendEndEventButton();
217
217
  await (0, sleep_1.sleep)(20000);
218
- await modelerCreatePage.runProcessInstance(clusterName);
218
+ await modelerCreatePage.runProcessInstance(clusterName, '', {}, '', true);
219
219
  await (0, test_1.expect)(page.getByText('The diagram failed to deploy & run')).toBeVisible({ timeout: 60000 });
220
220
  await modelerCreatePage.clickCloseButton();
221
221
  });
@@ -69,6 +69,17 @@ _8_8_1.test.describe('Console User Flow Tests @tasklistV2', () => {
69
69
  await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTabProcessInstancePage, operateTabProcessInstancePage.completedIcon, 'completed icon in Operate', 60000);
70
70
  });
71
71
  });
72
+ (0, _8_8_1.test)('Create Cluster', async ({ homePage, clusterPage }) => {
73
+ _8_8_1.test.slow();
74
+ const newClusterName = 'UI Test Cluster';
75
+ await _8_8_1.test.step('Navigate to clusters and create cluster', async () => {
76
+ await homePage.clickClusters();
77
+ await clusterPage.createCluster(newClusterName);
78
+ });
79
+ await _8_8_1.test.step('Assert cluster is healthy', async () => {
80
+ await clusterPage.assertClusterHealthyStatusWithRetry(newClusterName, 300000, 1500000);
81
+ });
82
+ });
72
83
  (0, _8_8_1.test)('Alert Trigger Flow - Email Notification', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, clusterPage, clusterDetailsPage, connectorSettingsPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
73
84
  _8_8_1.test.slow();
74
85
  const processName = 'Email_Alert_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
@@ -13,6 +13,7 @@ const UtilitiesPage_1 = require("../../pages/8.8/UtilitiesPage");
13
13
  const sleep_1 = require("../../utils/sleep");
14
14
  const fileUpload_1 = require("../../utils/fileUpload");
15
15
  const resetSession_1 = require("../../utils/resetSession");
16
+ const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
16
17
  const expectTextWithPagination_1 = require("../../utils/assertionHelpers/expectTextWithPagination");
17
18
  const expectTextWithRetry_1 = require("../../utils/assertionHelpers/expectTextWithRetry");
18
19
  const users_1 = require("../../utils/users");
@@ -473,6 +474,18 @@ _8_8_1.test.describe('HTO User Flow Tests', () => {
473
474
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
474
475
  const processName = 'User_Task_Process_With_Public_Form' + randomString;
475
476
  const formName = 'Public form' + randomString;
477
+ // Public start forms are a v1-only feature; the shared "Test Cluster"
478
+ // used by the rest of this file is deliberately kept on v1 (via
479
+ // createClusterViaApi's tasklistV2Enabled: false) so job-worker-based
480
+ // user tasks keep working there. This assertion needs the opposite --
481
+ // a genuinely v2-upgraded backend -- so it gets its own dedicated
482
+ // cluster rather than sharing one that can never satisfy both.
483
+ const v2ClusterName = 'Public Form V2 Cluster';
484
+ await _8_8_1.test.step('Create Dedicated Tasklist V2 Cluster', async () => {
485
+ const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, v2ClusterName);
486
+ await (0, consoleApiHelpers_1.ensureTasklistV2Enabled)(page, clusterUuid);
487
+ await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid);
488
+ });
476
489
  await _8_8_1.test.step('Navigate to Web Modeler', async () => {
477
490
  await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
478
491
  timeout: 120000,
@@ -503,7 +516,7 @@ _8_8_1.test.describe('HTO User Flow Tests', () => {
503
516
  await modelerCreatePage.modelCamundaUserTaskDiagram(processName, processName);
504
517
  await modelerCreatePage.linkFormToStartEvent(formName);
505
518
  await modelerCreatePage.enablePublicAccessIfDisabled();
506
- await modelerCreatePage.deployDiagram(clusterName);
519
+ await modelerCreatePage.deployDiagram(v2ClusterName);
507
520
  await (0, sleep_1.sleep)(5000);
508
521
  });
509
522
  let publicFormLink;
@@ -7,6 +7,7 @@ const sleep_1 = require("../../utils/sleep");
7
7
  const connectorSecrets_1 = require("../../utils/connectorSecrets");
8
8
  const UtilitiesPage_1 = require("../../pages/8.8/UtilitiesPage");
9
9
  const users_1 = require("../../utils/users");
10
+ const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
10
11
  _8_8_1.test.describe.configure({ mode: 'parallel' });
11
12
  _8_8_1.test.describe('Cluster Setup Tests', () => {
12
13
  _8_8_1.test.afterEach(async ({ page }, testInfo) => {
@@ -31,9 +32,15 @@ _8_8_1.test.describe('Cluster Setup Tests', () => {
31
32
  const clusterName = 'Test Cluster';
32
33
  const apiClientName = 'Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
33
34
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
34
- await homePage.clickClusters();
35
- await clusterPage.createCluster(clusterName);
36
- await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
35
+ if (process.env.IS_PROD === 'true') {
36
+ await clusterPage.createCluster(clusterName);
37
+ await clusterPage.assertClusterHealthyStatusWithRetry(clusterName);
38
+ }
39
+ else {
40
+ const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName);
41
+ await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid);
42
+ await homePage.clickClusters();
43
+ }
37
44
  await clusterPage.clickClusterLink(clusterName);
38
45
  await clusterDetailsPage.assertComponentsHealth();
39
46
  if (process.env.IS_AG !== 'true' && process.env.IS_MIGRATION !== 'true') {
@@ -53,10 +60,16 @@ _8_8_1.test.describe('Cluster Setup Tests', () => {
53
60
  _8_8_1.test.slow();
54
61
  const clusterName = 'AWS Cluster';
55
62
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
56
- await homePage.clickClusters();
57
- await clusterPage.createCluster(clusterName, 'AWS');
58
- // AWS clusters on SaaS INT routinely need 10+ min to reach Healthy.
59
- await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 300000, 1500000);
63
+ if (process.env.IS_PROD === 'true') {
64
+ await clusterPage.createCluster(clusterName, 'AWS');
65
+ // AWS clusters on SaaS INT routinely need 10+ min to reach Healthy.
66
+ await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 300000, 1500000);
67
+ }
68
+ else {
69
+ const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName, 'AWS');
70
+ await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid, 1500000);
71
+ await homePage.clickClusters();
72
+ }
60
73
  await clusterPage.clickClusterLink(clusterName);
61
74
  await clusterDetailsPage.assertComponentsHealth();
62
75
  if (process.env.IS_AG !== 'true') {
@@ -2,4 +2,5 @@ import { Page } from '@playwright/test';
2
2
  export declare function deleteClusterViaApi(page: Page, clusterName: string): Promise<void>;
3
3
  export declare function createClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
4
4
  export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
5
+ export declare function ensureTasklistV2Enabled(page: Page, clusterUuid: string): Promise<void>;
5
6
  export declare function waitForClusterHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.waitForClusterHealthyViaApi = exports.ensureClusterViaApi = exports.createClusterViaApi = exports.deleteClusterViaApi = void 0;
3
+ exports.waitForClusterHealthyViaApi = exports.ensureTasklistV2Enabled = exports.ensureClusterViaApi = exports.createClusterViaApi = exports.deleteClusterViaApi = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const sleep_1 = require("./sleep");
6
6
  const PLAN_TYPE_ID = '574cde5f-54ae-43c1-851a-e6fb4f03423a';
@@ -169,6 +169,28 @@ async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
169
169
  return createClusterViaApi(page, clusterName, region);
170
170
  }
171
171
  exports.ensureClusterViaApi = ensureClusterViaApi;
172
+ async function ensureTasklistV2Enabled(page, clusterUuid) {
173
+ // Console's cluster settings UI frames this as a one-time "Upgrade to
174
+ // Tasklist v2" action performed after creation (new clusters start on v1),
175
+ // not something reliably controllable via the create payload alone. Call
176
+ // the same update endpoint that action uses, explicitly, so a dedicated
177
+ // cluster genuinely ends up v2-enabled regardless of the create call's own
178
+ // default. This is irreversible -- only use it on a cluster dedicated to
179
+ // tests that specifically require a real v2-upgraded backend, never on a
180
+ // cluster shared with anything that needs v1 (e.g. job-worker-based user
181
+ // tasks, draft variables, public start forms).
182
+ const token = await getUserToken(page);
183
+ const orgId = getOrgId();
184
+ const response = await page.request.patch(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${clusterUuid}`, {
185
+ headers: { Authorization: `Bearer ${token}` },
186
+ data: { tasklistV2Enabled: true },
187
+ });
188
+ if (response.status() !== 200 && response.status() !== 204) {
189
+ throw new Error(`Failed to upgrade cluster "${clusterUuid}" to Tasklist v2: ` +
190
+ `HTTP ${response.status()} - ${await response.text()}`);
191
+ }
192
+ }
193
+ exports.ensureTasklistV2Enabled = ensureTasklistV2Enabled;
172
194
  async function waitForClusterHealthyViaApi(page, clusterUuid, timeoutMs = 1500000) {
173
195
  const orgId = getOrgId();
174
196
  const url = `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${clusterUuid}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.843",
3
+ "version": "0.0.845",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",