@camunda/e2e-test-suite 0.0.1424 → 0.0.1426

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;
@@ -11,6 +11,7 @@ const LoginPage_1 = require("./LoginPage");
11
11
  const fileUpload_1 = require("../../utils/fileUpload");
12
12
  const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
13
13
  const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
14
+ const hubOrganization_1 = require("../../utils/hubOrganization");
14
15
  async function clearWebStorage(page) {
15
16
  await page
16
17
  .evaluate(() => {
@@ -823,9 +824,7 @@ async function assertTestUsesCorrectOrganization(page) {
823
824
  // the URL. Its sidebar scopes the Clusters/Organization links to the active
824
825
  // org, so assert the expected org id there instead.
825
826
  if (await homePage.hubHeader.isVisible()) {
826
- await (0, test_1.expect)(homePage.hubSidebar
827
- .locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
828
- .first()).toBeVisible({ timeout: 180000 });
827
+ await (0, hubOrganization_1.ensureHubOrganization)(page, homePage.hubSidebar);
829
828
  return;
830
829
  }
831
830
  await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
@@ -11,6 +11,7 @@ const LoginPage_1 = require("./LoginPage");
11
11
  const fileUpload_1 = require("../../utils/fileUpload");
12
12
  const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
13
13
  const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
14
+ const hubOrganization_1 = require("../../utils/hubOrganization");
14
15
  async function clearWebStorage(page) {
15
16
  await page
16
17
  .evaluate(() => {
@@ -823,9 +824,7 @@ async function assertTestUsesCorrectOrganization(page) {
823
824
  // the URL. Its sidebar scopes the Clusters/Organization links to the active
824
825
  // org, so assert the expected org id there instead.
825
826
  if (await homePage.hubHeader.isVisible()) {
826
- await (0, test_1.expect)(homePage.hubSidebar
827
- .locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
828
- .first()).toBeVisible({ timeout: 180000 });
827
+ await (0, hubOrganization_1.ensureHubOrganization)(page, homePage.hubSidebar);
829
828
  return;
830
829
  }
831
830
  await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
@@ -10,6 +10,7 @@ const randomSleep_1 = require("../../utils/randomSleep");
10
10
  const SettingsPage_1 = require("./SettingsPage");
11
11
  const fileUpload_1 = require("../../utils/fileUpload");
12
12
  const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
13
+ const hubOrganization_1 = require("../../utils/hubOrganization");
13
14
  async function forceLogoutIfNeeded(page) {
14
15
  const settingsPage = new SettingsPage_1.SettingsPage(page);
15
16
  const loginMessage = page.getByText('Log in to continue to Camunda.');
@@ -526,9 +527,7 @@ async function assertTestUsesCorrectOrganization(page) {
526
527
  // the URL. Its sidebar scopes the Clusters/Organization links to the active
527
528
  // org, so assert the expected org id there instead.
528
529
  if (await homePage.hubHeader.isVisible()) {
529
- await (0, test_1.expect)(homePage.hubSidebar
530
- .locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
531
- .first()).toBeVisible({ timeout: 180000 });
530
+ await (0, hubOrganization_1.ensureHubOrganization)(page, homePage.hubSidebar);
532
531
  return;
533
532
  }
534
533
  await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
@@ -11,6 +11,7 @@ const fileUpload_1 = require("../../utils/fileUpload");
11
11
  const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
12
12
  const hubTableRow_1 = require("../../utils/hubTableRow");
13
13
  const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
14
+ const hubOrganization_1 = require("../../utils/hubOrganization");
14
15
  // Tracks which worker-derived `timeout` values have already paid the
15
16
  // stagger sleep below, so a worker only pays it once per process lifetime
16
17
  // rather than on every call -- see the comment at the call site.
@@ -523,9 +524,7 @@ async function assertTestUsesCorrectOrganization(page) {
523
524
  // the URL. Its sidebar scopes the Clusters/Organization links to the active
524
525
  // org, so assert the expected org id there instead.
525
526
  if (await homePage.hubHeader.isVisible()) {
526
- await (0, test_1.expect)(homePage.hubSidebar
527
- .locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
528
- .first()).toBeVisible({ timeout: 180000 });
527
+ await (0, hubOrganization_1.ensureHubOrganization)(page, homePage.hubSidebar);
529
528
  return;
530
529
  }
531
530
  await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
@@ -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
  });
@@ -0,0 +1,13 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ /**
3
+ * Makes Camunda Hub show the org the run is configured for.
4
+ *
5
+ * A user who belongs to several orgs lands in whichever one Hub opens first,
6
+ * which is not necessarily ORG_ID (prod's test user is in both "Automation QA"
7
+ * and "Automation QA 2"). Hub's header exposes a "Switch organization" menu,
8
+ * so pick the org by name when the sidebar's org-scoped links point elsewhere.
9
+ *
10
+ * Prod only: other environments use a throwaway org with a single membership,
11
+ * so there the sidebar assertion runs unchanged.
12
+ */
13
+ export declare function ensureHubOrganization(page: Page, sidebar: Locator): Promise<void>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureHubOrganization = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ /**
6
+ * Makes Camunda Hub show the org the run is configured for.
7
+ *
8
+ * A user who belongs to several orgs lands in whichever one Hub opens first,
9
+ * which is not necessarily ORG_ID (prod's test user is in both "Automation QA"
10
+ * and "Automation QA 2"). Hub's header exposes a "Switch organization" menu,
11
+ * so pick the org by name when the sidebar's org-scoped links point elsewhere.
12
+ *
13
+ * Prod only: other environments use a throwaway org with a single membership,
14
+ * so there the sidebar assertion runs unchanged.
15
+ */
16
+ async function ensureHubOrganization(page, sidebar) {
17
+ const orgId = process.env.ORG_ID;
18
+ const orgScopedLink = sidebar.locator(`a[href*="/org/${orgId}/"]`).first();
19
+ if (process.env.IS_PROD !== 'true') {
20
+ await (0, test_1.expect)(orgScopedLink).toBeVisible({ timeout: 180000 });
21
+ return;
22
+ }
23
+ const alreadyOnOrg = await orgScopedLink
24
+ .waitFor({ state: 'visible', timeout: 15000 })
25
+ .then(() => true)
26
+ .catch(() => false);
27
+ if (!alreadyOnOrg && process.env.ORG_NAME) {
28
+ await page
29
+ .getByRole('banner')
30
+ .getByRole('button', { name: 'Switch organization' })
31
+ .click({ timeout: 30000 });
32
+ await page
33
+ .getByRole('menuitem', { name: process.env.ORG_NAME, exact: true })
34
+ .click({ timeout: 30000 });
35
+ }
36
+ await (0, test_1.expect)(orgScopedLink).toBeVisible({ timeout: 180000 });
37
+ }
38
+ exports.ensureHubOrganization = ensureHubOrganization;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1424",
3
+ "version": "0.0.1426",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",