@camunda/e2e-test-suite 0.0.1425 → 0.0.1427

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.
@@ -20,6 +20,7 @@ declare class ClusterPage {
20
20
  readonly dangerDeleteButton: Locator;
21
21
  readonly testClusterLink: Locator;
22
22
  readonly connectorSecretsTab: Locator;
23
+ readonly connectorSecretsTabSelected: Locator;
23
24
  readonly createAClusterButton: Locator;
24
25
  readonly clusterVersionOption: Locator;
25
26
  readonly generationSearchInput: Locator;
@@ -25,6 +25,7 @@ class ClusterPage {
25
25
  dangerDeleteButton;
26
26
  testClusterLink;
27
27
  connectorSecretsTab;
28
+ connectorSecretsTabSelected;
28
29
  createAClusterButton;
29
30
  clusterVersionOption;
30
31
  generationSearchInput;
@@ -80,6 +81,13 @@ class ClusterPage {
80
81
  .or(page.getByRole('tab', {
81
82
  name: 'Connector Secrets',
82
83
  }));
84
+ // Same tab, but only when it is the active one. Hub renders the cluster
85
+ // tabs as Radix triggers whose active tab is controlled React state, so a
86
+ // visually successful click is not proof the panel switched -- see
87
+ // clickConnectorSecretsTab.
88
+ this.connectorSecretsTabSelected = page
89
+ .getByRole('tab', { name: 'Cluster secrets', selected: true })
90
+ .or(page.getByRole('tab', { name: 'Connector Secrets', selected: true }));
83
91
  // Camunda Hub's create-cluster form replaced the Carbon tiles (a
84
92
  // <label> per option) and tabs with design-system choice cards and toggle
85
93
  // groups, all exposed as role="radio" named by their title. Each locator
@@ -441,12 +449,41 @@ class ClusterPage {
441
449
  });
442
450
  }
443
451
  async clickConnectorSecretsTab(clusterName) {
444
- await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.connectorSecretsTab, {
445
- postAction: async () => {
446
- await this.clickClusterBanner();
447
- await this.clickClusterLink(clusterName);
448
- },
452
+ // Hub's cluster tabs are Radix triggers whose active tab is controlled
453
+ // React state. A background refetch landing right after the click can
454
+ // reset that state back to the default Overview tab, so a single
455
+ // successful click sometimes leaves the panel on Overview and the secrets
456
+ // Import button never mounts (AlwaysGreen run 38259225730: the tab was
457
+ // clicked on a visible/enabled/stable element yet Overview stayed
458
+ // selected for the full 30s Import-button wait). Click, confirm the
459
+ // secrets tab actually became selected, and re-click until it does.
460
+ const maxAttempts = 3;
461
+ const importButton = this.page.getByRole('button', {
462
+ name: 'Import',
463
+ exact: true,
464
+ });
465
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
466
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.connectorSecretsTab, {
467
+ postAction: async () => {
468
+ await this.clickClusterBanner();
469
+ await this.clickClusterLink(clusterName);
470
+ },
471
+ });
472
+ const activated = await importButton
473
+ .waitFor({ state: 'visible', timeout: 10000 })
474
+ .then(() => this.connectorSecretsTabSelected.isVisible())
475
+ .catch(() => false);
476
+ if (activated) {
477
+ return;
478
+ }
479
+ }
480
+ // Still on Overview after re-clicking: fail loudly rather than proceeding
481
+ // to a secrets panel that never mounted -- this keeps a genuinely broken
482
+ // panel red instead of masking it.
483
+ await (0, test_1.expect)(this.connectorSecretsTabSelected).toBeVisible({
484
+ timeout: 10000,
449
485
  });
486
+ await (0, test_1.expect)(importButton).toBeVisible({ timeout: 10000 });
450
487
  }
451
488
  async clickLatestStableForMinorVersion() {
452
489
  const minorVersion = process.env.MINOR_VERSION;
@@ -19,6 +19,7 @@ declare class ClusterSecretsPage {
19
19
  readonly clusterBanner: Locator;
20
20
  readonly clusterLink: (name: string) => Locator;
21
21
  readonly clusterSecretTab: Locator;
22
+ readonly secretsFetchErrorReloadButton: Locator;
22
23
  readonly confirmDeleteInput: Locator;
23
24
  constructor(page: Page);
24
25
  deleteConnectorSecretsIfExist(): Promise<void>;
@@ -44,5 +45,6 @@ declare class ClusterSecretsPage {
44
45
  name: string;
45
46
  value: string;
46
47
  }[]): Promise<void>;
48
+ private restoreClusterPageIfSignedOut;
47
49
  }
48
50
  export { ClusterSecretsPage };
@@ -4,6 +4,7 @@ exports.ClusterSecretsPage = 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 ClusterSecretsPage {
8
9
  page;
9
10
  createNewSecretButton;
@@ -24,6 +25,7 @@ class ClusterSecretsPage {
24
25
  clusterBanner;
25
26
  clusterLink;
26
27
  clusterSecretTab;
28
+ secretsFetchErrorReloadButton;
27
29
  confirmDeleteInput;
28
30
  constructor(page) {
29
31
  this.page = page;
@@ -112,6 +114,11 @@ class ClusterSecretsPage {
112
114
  this.clusterSecretTab = this.page
113
115
  .getByRole('tab', { name: 'Cluster secrets' })
114
116
  .or(this.page.getByRole('tab', { name: 'Connector Secrets' }));
117
+ // c4's secrets tab replaces the list with an "Oops ... something went
118
+ // wrong." card when its GET of the secrets fails; this button refetches.
119
+ this.secretsFetchErrorReloadButton = page
120
+ .getByRole('tabpanel', { name: /^(Cluster secrets|Connector Secrets)$/ })
121
+ .getByRole('button', { name: 'Reload', exact: true });
115
122
  this.confirmDeleteInput = page.getByRole('textbox', {
116
123
  name: 'Type the word DELETE to confirm',
117
124
  });
@@ -390,6 +397,7 @@ class ClusterSecretsPage {
390
397
  await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({
391
398
  timeout: 60000,
392
399
  });
400
+ const clusterUrl = this.page.url();
393
401
  await this.page.reload();
394
402
  // Hub keeps the selected cluster tab out of the URL, so the reload lands
395
403
  // on Overview, where assertSecretsOrCreate finds no search box and tries
@@ -397,10 +405,32 @@ class ClusterSecretsPage {
397
405
  // The cluster page can still be hydrating after the reload and snap back
398
406
  // to Overview after the click lands (CI trace: click succeeded, the
399
407
  // screenshot still showed Overview), so re-click until the panel renders.
408
+ // Re-clicking the already selected tab does not refetch a failed secrets
409
+ // list, so use the error card's own Reload button for that case.
400
410
  await (0, test_1.expect)(async () => {
401
- await this.clusterSecretTab.first().click({ timeout: 15000 });
411
+ await this.restoreClusterPageIfSignedOut(clusterUrl);
412
+ if (await this.secretsFetchErrorReloadButton.isVisible()) {
413
+ await this.secretsFetchErrorReloadButton.click({ timeout: 15000 });
414
+ }
415
+ else {
416
+ await this.clusterSecretTab.first().click({ timeout: 15000 });
417
+ }
402
418
  await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({ timeout: 10000 });
403
- }).toPass({ timeout: 90000 });
419
+ }).toPass({ timeout: 180000 });
420
+ }
421
+ // A reload is a full Hub navigation through Auth0 and /login-callback. When
422
+ // Hub's session hand-off fails it calls Auth0 logout, which parks the tab on
423
+ // the identity provider's login form (PR run 38354316061). Sign back in as
424
+ // the same user, then open the cluster with a fresh goto: the callback code
425
+ // is single use, so that URL cannot be reloaded.
426
+ async restoreClusterPageIfSignedOut(clusterUrl) {
427
+ if (!(0, sessionRecovery_1.isAtIdentityProvider)(this.page) ||
428
+ !(await (0, sessionRecovery_1.restoreSessionIfLoggedOut)(this.page))) {
429
+ return;
430
+ }
431
+ await this.page.goto(clusterUrl, { waitUntil: 'domcontentloaded' });
432
+ await this.page.waitForURL((url) => url.host === new URL(clusterUrl).host &&
433
+ !/^\/login(-callback)?\/?$/.test(url.pathname), { timeout: 120000 });
404
434
  }
405
435
  }
406
436
  exports.ClusterSecretsPage = ClusterSecretsPage;
@@ -5,4 +5,4 @@ export declare function navigateToApp(page: Page, appName: string): Promise<void
5
5
  export declare function openUserTaskOfProcessInstance(page: Page, request: APIRequestContext, processInstanceKey: string, maxRetries?: number, retryDelayMs?: number): Promise<string>;
6
6
  export declare function assertLocatorVisibleWithRetry(page: Page, locator: Locator, text: string, timeout?: number, maxRetries?: number): Promise<void>;
7
7
  export declare function validateURL(page: Page, URL: RegExp): Promise<void>;
8
- export declare function completeTaskWithRetry(taskPanelPage: TaskPanelPage, taskDetailsPage: TaskDetailsPage, taskName: string, taskPriority: string, userTaskName?: string, textInput?: string, maxRetries?: number): Promise<void>;
8
+ export declare function completeTaskWithRetry(taskPanelPage: TaskPanelPage, taskDetailsPage: TaskDetailsPage, taskName: string, taskPriority: string, userTaskName?: string, textInput?: string, maxRetries?: number, openTask?: () => Promise<string>): Promise<void>;
@@ -68,10 +68,10 @@ async function validateURL(page, URL) {
68
68
  (0, test_1.expect)(page).toHaveURL(URL);
69
69
  }
70
70
  exports.validateURL = validateURL;
71
- async function completeTaskWithRetry(taskPanelPage, taskDetailsPage, taskName, taskPriority, userTaskName = '', textInput = '', maxRetries = 5) {
71
+ async function completeTaskWithRetry(taskPanelPage, taskDetailsPage, taskName, taskPriority, userTaskName = '', textInput = '', maxRetries = 5, openTask = () => taskPanelPage.openTask(taskName, userTaskName)) {
72
72
  for (let attempt = 0; attempt < maxRetries; attempt++) {
73
73
  try {
74
- const userTaskKey = await taskPanelPage.openTask(taskName, userTaskName);
74
+ const userTaskKey = await openTask();
75
75
  await (0, sleep_1.sleep)(500);
76
76
  if (!(await taskDetailsPage.assignedToMeText.isVisible())) {
77
77
  await taskDetailsPage.clickAssignToMeButton();
@@ -10,7 +10,7 @@ const resetSession_1 = require("../../utils/resetSession");
10
10
  const KeycloakUtils_1 = require("../../pages/SM-8.10/KeycloakUtils");
11
11
  const constants_1 = require("../../utils/constants");
12
12
  SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
13
- SM_8_10_1.test.beforeEach(async ({ navigationPage, managementIdentityPage, managementIdentityTenantPage, keycloakLoginPage, keycloakAdminPage, page, browser, loginPage, ocIdentityHomePage, ocIdentityRolesPage, }, testInfo) => {
13
+ SM_8_10_1.test.beforeEach(async ({ navigationPage, managementIdentityPage, managementIdentityTenantPage, keycloakLoginPage, keycloakAdminPage, page, browser, ocIdentityHomePage, ocIdentityRolesPage, }, testInfo) => {
14
14
  if (process.env.IS_MIGRATION === 'true') {
15
15
  await navigationPage.goToOCAdmin((testInfo.workerIndex + 1) * 1000);
16
16
  }
@@ -20,7 +20,14 @@ SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
20
20
  const { testUsername, testPassword } = await (0, KeycloakUtils_1.setupKeycloakUser)(navigationPage, managementIdentityPage, keycloakLoginPage, keycloakAdminPage, ocIdentityHomePage, ocIdentityRolesPage, process.env.IS_MT === 'true', managementIdentityTenantPage);
21
21
  console.log(`BeforeEach end: timeout = ${testInfo.timeout}`);
22
22
  await (0, resetSession_1.resetSession)(browser, page);
23
- await loginPage.login(testUsername, testPassword);
23
+ // Not loginPage.login() on the page resetSession leaves us on: where
24
+ // "/" serves Console it never redirects to Keycloak, so login()
25
+ // returned false unnoticed and later goTo() calls fell back to the
26
+ // first user (an org admin) instead of this test user.
27
+ await navigationPage.goToManagementIdentity(undefined, {
28
+ username: testUsername,
29
+ password: testPassword,
30
+ });
24
31
  }
25
32
  });
26
33
  SM_8_10_1.test.afterEach(async ({ page }, testInfo) => {
@@ -64,7 +71,7 @@ SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
64
71
  }
65
72
  console.log(`[${testInfo.title}] Test end: timeout = ${testInfo.timeout}`);
66
73
  });
67
- (0, SM_8_10_1.test)('Most Common Flow User Flow With All Apps', async ({ page, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeCollectionsPage, optimizeReportPage, taskDetailsPage, taskPanelPage, }, testInfo) => {
74
+ (0, SM_8_10_1.test)('Most Common Flow User Flow With All Apps', async ({ page, browser, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeCollectionsPage, optimizeReportPage, taskDetailsPage, taskPanelPage, }, testInfo) => {
68
75
  console.log(`[${testInfo.title}] Test start: timeout = ${testInfo.timeout}`);
69
76
  const randomName = await (0, _setup_1.generateRandomStringAsync)(3);
70
77
  const processName = 'Zeebe_User_Task_Process' + randomName;
@@ -72,6 +79,7 @@ SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
72
79
  await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
73
80
  await navigationPage.goToModeler();
74
81
  await modelerHomePage.createCrossComponentProjectFolder();
82
+ await modelerHomePage.assignWorkspaceEnvironmentAsOrgAdmin(browser);
75
83
  await modelerHomePage.clickDiagramTypeDropdown();
76
84
  await modelerHomePage.clickBpmnTemplateOption();
77
85
  });
@@ -11,6 +11,8 @@ const _setup_1 = require("../../test-setup.js");
11
11
  // the same process -- another worker, a --repeat-each copy, a leftover from an
12
12
  // earlier run -- can no longer be picked up by mistake.
13
13
  let variableEditingInstanceKey;
14
+ let zeebeUserTaskInstanceKey;
15
+ let formUserTaskInstanceKey;
14
16
  c8Run_8_10_1.test.beforeAll(async () => {
15
17
  await Promise.all([
16
18
  (0, zeebeClient_1.deploy)('./resources/User_Task_Editing_Variables.bpmn'),
@@ -20,7 +22,7 @@ c8Run_8_10_1.test.beforeAll(async () => {
20
22
  (0, zeebeClient_1.deploy)('./resources/Start_Form_Process.bpmn'),
21
23
  (0, zeebeClient_1.deploy)('./resources/Zeebe_User_Task_Process_With_Priority.bpmn'),
22
24
  ]);
23
- const [, variableEditingInstances] = await Promise.all([
25
+ const [zeebeUserTaskInstances, variableEditingInstances, formInstances] = await Promise.all([
24
26
  (0, zeebeClient_1.createInstances)('Zeebe_User_Task', 1, 1),
25
27
  (0, zeebeClient_1.createInstances)('User_Task_Editing_Variables', 1, 1, {
26
28
  testVariable: 'testValue',
@@ -30,6 +32,8 @@ c8Run_8_10_1.test.beforeAll(async () => {
30
32
  (0, zeebeClient_1.createInstances)('Zeebe_User_Task_Process_With_Priority', 1, 1),
31
33
  ]);
32
34
  variableEditingInstanceKey = String(variableEditingInstances[0].processInstanceKey);
35
+ zeebeUserTaskInstanceKey = String(zeebeUserTaskInstances[0].processInstanceKey);
36
+ formUserTaskInstanceKey = String(formInstances[0].processInstanceKey);
33
37
  await (0, sleep_1.sleep)(5000);
34
38
  });
35
39
  c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
@@ -80,23 +84,23 @@ c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
80
84
  await taskDetailsPage.clickCompleteTaskButton();
81
85
  });
82
86
  });
83
- (0, c8Run_8_10_1.test)('Form.js Integration with User Task', async ({ taskDetailsPage, taskPanelPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskListLoginPage, operateLoginPage, page, }) => {
87
+ (0, c8Run_8_10_1.test)('Form.js Integration with User Task', async ({ request, taskDetailsPage, taskPanelPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskListLoginPage, operateLoginPage, page, }) => {
84
88
  await c8Run_8_10_1.test.step('View Process Instance in Operate and complete User Task in Tasklist', async () => {
85
89
  await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
86
90
  await operateLoginPage.login('demo', 'demo');
87
91
  await (0, test_1.expect)(operateHomePage.processesTab).toBeVisible({ timeout: 180000 });
88
92
  await operateHomePage.clickProcessesTab();
89
- await operateProcessesPage.clickProcessInstanceLink('Form_User_Task_3');
93
+ await operateProcessesPage.clickProcessInstanceLink('Form_User_Task_3', formUserTaskInstanceKey);
90
94
  await operateProcessInstancePage.activeIconAssertion();
91
95
  await (0, UtilitiesPage_1.navigateToApp)(page, 'tasklist');
92
96
  await taskListLoginPage.login('demo', 'demo');
93
- await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, 'Form_User_Task_3', 'Medium', 'Activity_1hj42el', 'Test User');
97
+ await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, 'Form_User_Task_3', 'Medium', 'Activity_1hj42el', 'Test User', 5, () => (0, UtilitiesPage_1.openUserTaskOfProcessInstance)(page, request, formUserTaskInstanceKey));
94
98
  await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
95
99
  await operateLoginPage.login('demo', 'demo');
96
100
  await operateHomePage.clickProcessesTab();
97
101
  await operateProcessesPage.clickProcessCompletedCheckbox();
98
102
  await (0, sleep_1.sleep)(1000);
99
- await operateProcessesPage.clickProcessInstanceLink('Form_User_Task_3');
103
+ await operateProcessesPage.clickProcessInstanceLink('Form_User_Task_3', formUserTaskInstanceKey);
100
104
  await operateProcessInstancePage.completedIconAssertion();
101
105
  });
102
106
  });
@@ -112,25 +116,25 @@ c8Run_8_10_1.test.describe('HTO User Flow Tests', () => {
112
116
  await operateProcessInstancePage.completedIconAssertion();
113
117
  });
114
118
  });
115
- (0, c8Run_8_10_1.test)('Zeebe User Task User Flow', async ({ page, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskDetailsPage, taskPanelPage, operateLoginPage, taskListLoginPage, }) => {
119
+ (0, c8Run_8_10_1.test)('Zeebe User Task User Flow', async ({ page, request, operateHomePage, operateProcessesPage, operateProcessInstancePage, taskDetailsPage, taskPanelPage, operateLoginPage, taskListLoginPage, }) => {
116
120
  await c8Run_8_10_1.test.step('View Process Instance in Operate, complete User Task in Tasklist', async () => {
117
121
  await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
118
122
  await operateLoginPage.login('demo', 'demo');
119
123
  await operateHomePage.clickProcessesTab();
120
- await operateProcessesPage.clickProcessInstanceLink('Zeebe_User_Task');
124
+ await operateProcessesPage.clickProcessInstanceLink('Zeebe_User_Task', zeebeUserTaskInstanceKey);
121
125
  await operateProcessInstancePage.activeIconAssertion();
122
126
  await (0, UtilitiesPage_1.navigateToApp)(page, 'tasklist');
123
127
  await taskListLoginPage.login('demo', 'demo');
124
128
  await (0, test_1.expect)(page.getByText('Zeebe_User_Task').first()).toBeVisible({
125
129
  timeout: 60000,
126
130
  });
127
- await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, 'Zeebe_User_Task', 'Medium');
131
+ await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, 'Zeebe_User_Task', 'Medium', '', '', 5, () => (0, UtilitiesPage_1.openUserTaskOfProcessInstance)(page, request, zeebeUserTaskInstanceKey));
128
132
  await (0, UtilitiesPage_1.navigateToApp)(page, 'operate');
129
133
  await operateLoginPage.login('demo', 'demo');
130
134
  await (0, test_1.expect)(operateHomePage.processesTab).toBeVisible({ timeout: 120000 });
131
135
  await operateHomePage.clickProcessesTab();
132
136
  await operateProcessesPage.clickProcessCompletedCheckbox();
133
- await operateProcessesPage.clickProcessInstanceLink('Zeebe_User_Task');
137
+ await operateProcessesPage.clickProcessInstanceLink('Zeebe_User_Task', zeebeUserTaskInstanceKey);
134
138
  await operateProcessInstancePage.completedIconAssertion();
135
139
  });
136
140
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1425",
3
+ "version": "0.0.1427",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",