@camunda/e2e-test-suite 0.0.981 → 0.0.983

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.
@@ -101,9 +101,9 @@ declare class ModelerCreatePage {
101
101
  clickSecondPlacedGateway(): Promise<void>;
102
102
  private reloadAndWaitForCanvas;
103
103
  /**
104
- * Detects if the page has been redirected to the Keycloak login page
105
- * (session expiry) or is in the middle of an OIDC redirect, and performs
106
- * a re-login if necessary.
104
+ * Detects if the page has been redirected to a login page (session expiry)
105
+ * or is in the middle of an OIDC redirect, and performs a re-login if
106
+ * necessary.
107
107
  */
108
108
  private handleSessionExpiry;
109
109
  clickStartInstanceMainButton(): Promise<void>;
@@ -483,9 +483,9 @@ class ModelerCreatePage {
483
483
  await element.waitFor({ state: 'visible', timeout: 120000 });
484
484
  }
485
485
  /**
486
- * Detects if the page has been redirected to the Keycloak login page
487
- * (session expiry) or is in the middle of an OIDC redirect, and performs
488
- * a re-login if necessary.
486
+ * Detects if the page has been redirected to a login page (session expiry)
487
+ * or is in the middle of an OIDC redirect, and performs a re-login if
488
+ * necessary.
489
489
  */
490
490
  async handleSessionExpiry() {
491
491
  const currentUrl = this.page.url();
@@ -501,12 +501,30 @@ class ModelerCreatePage {
501
501
  const loginPage = new LoginPage_1.LoginPage(this.page);
502
502
  const keycloakBanner = this.page.locator('body#keycloak-bg[data-page-id="login-login"]');
503
503
  const usernameField = this.page.locator('#username');
504
- // Check if we landed on a login page (Keycloak form)
505
- const isOnLoginPage = await keycloakBanner
504
+ // `loginPage.usernameInput` covers the Camunda Identity form, which carries
505
+ // neither the Keycloak body id nor a `#username` input, so neither of the
506
+ // two locators above matches it.
507
+ const loginForm = keycloakBanner
506
508
  .or(usernameField)
507
- .isVisible({ timeout: 5000 })
508
- .catch(() => false);
509
- if (!isOnLoginPage) {
509
+ .or(loginPage.usernameInput)
510
+ .first();
511
+ // `Run` and the login form are mutually exclusive, so race them rather than
512
+ // sampling one: an expired session redirects through the OIDC callback
513
+ // after the reload has already resolved, so a point-in-time visibility
514
+ // check still sees the Modeler document and misses the login form that
515
+ // replaces it a moment later. The race also costs nothing on the happy
516
+ // path — it returns as soon as `Run` renders.
517
+ const landedOn = await Promise.race([
518
+ loginForm
519
+ .waitFor({ state: 'visible', timeout: 15000 })
520
+ .then(() => 'login')
521
+ .catch(() => 'unknown'),
522
+ this.startInstanceMainButton
523
+ .waitFor({ state: 'visible', timeout: 15000 })
524
+ .then(() => 'app')
525
+ .catch(() => 'unknown'),
526
+ ]);
527
+ if (landedOn !== 'login') {
510
528
  return;
511
529
  }
512
530
  console.warn('clickStartInstanceMainButton: session expired — performing re-login');
@@ -4,6 +4,7 @@ exports.TaskPanelPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const constants_1 = require("../../utils/constants");
6
6
  const sleep_1 = require("../../utils/sleep");
7
+ const gatewayErrors_1 = require("../../utils/gatewayErrors");
7
8
  class TaskPanelPage {
8
9
  page;
9
10
  availableTasks;
@@ -39,6 +40,9 @@ class TaskPanelPage {
39
40
  }
40
41
  async openTask(name, assignButton = true) {
41
42
  const maxAttempts = 10;
43
+ // Shared by every attempt so that a sustained Tasklist outage still fails
44
+ // this call rather than stretching it by ten times this budget.
45
+ const gatewayRecoveryDeadline = Date.now() + constants_1._1_SECOND_IN_MS * 60;
42
46
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
43
47
  try {
44
48
  await this.page.waitForLoadState('domcontentloaded');
@@ -56,7 +60,7 @@ class TaskPanelPage {
56
60
  catch (error) {
57
61
  if (attempt < maxAttempts) {
58
62
  console.warn(`Attempt ${attempt} failed. Reloading page...`);
59
- await this.page.reload();
63
+ await (0, gatewayErrors_1.reloadPastGatewayError)(this.page, gatewayRecoveryDeadline);
60
64
  await this.page.waitForLoadState('domcontentloaded');
61
65
  }
62
66
  else {
@@ -4,6 +4,8 @@ exports.modelDiagramFromFile = exports.modelAndRunConnectorsTimerEventDiagram =
4
4
  const test_1 = require("@playwright/test");
5
5
  const fileUpload_1 = require("../../utils/fileUpload");
6
6
  const sleep_1 = require("../../utils/sleep");
7
+ const gatewayErrors_1 = require("../../utils/gatewayErrors");
8
+ const constants_1 = require("../../utils/constants");
7
9
  // Tasklist renders "No permission for Tasklist" as a terminal page: it reads the
8
10
  // permissions off the token minted at login and never re-evaluates them, so a
9
11
  // user whose Identity role assignment has not propagated yet stays stuck there.
@@ -61,6 +63,9 @@ async function createAndRunProcess(modelerHomePage, modelerCreatePage, processNa
61
63
  }
62
64
  exports.createAndRunProcess = createAndRunProcess;
63
65
  async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskName, taskPriority, maxRetries = 5) {
66
+ // Shared by every attempt so that a sustained Tasklist outage still fails
67
+ // this call rather than stretching it by five times this budget.
68
+ const gatewayRecoveryDeadline = Date.now() + constants_1._1_SECOND_IN_MS * 60;
64
69
  for (let attempt = 0; attempt < maxRetries; attempt++) {
65
70
  try {
66
71
  await page.waitForLoadState('domcontentloaded');
@@ -98,7 +103,7 @@ async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskN
98
103
  catch (error) {
99
104
  console.warn(`Attempt ${attempt + 1} failed for completing task ${taskName}:`, error instanceof Error ? error.message : error);
100
105
  if (attempt < maxRetries - 1) {
101
- await page.reload();
106
+ await (0, gatewayErrors_1.reloadPastGatewayError)(page, gatewayRecoveryDeadline);
102
107
  await page.waitForLoadState('domcontentloaded');
103
108
  await (0, sleep_1.sleep)(1000);
104
109
  }
@@ -19,7 +19,8 @@ if (process.env.IS_MT === 'true') {
19
19
  await (0, _setup_1.captureScreenshot)(page, testInfo);
20
20
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
21
21
  });
22
- (0, SM_8_10_1.test)('Main User Can Create & Access Tenant & User Without Access Cannot - User Task Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, ocTenantPage, optimizeHomePage, optimizeDashboardPage, browser, ocIdentityHomePage, managementIdentityTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityMappingRulesPage, ocIdentityRolesPage, }) => {
22
+ // Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
23
+ SM_8_10_1.test.skip('Main User Can Create & Access Tenant & User Without Access Cannot - User Task Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, ocTenantPage, optimizeHomePage, optimizeDashboardPage, browser, ocIdentityHomePage, managementIdentityTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityMappingRulesPage, ocIdentityRolesPage, }) => {
23
24
  SM_8_10_1.test.slow();
24
25
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
25
26
  const processName = 'Main_User_Task_Flow_' + randomString;
@@ -127,7 +128,8 @@ if (process.env.IS_MT === 'true') {
127
128
  }
128
129
  });
129
130
  });
130
- (0, SM_8_10_1.test)('Main User Can Create & Access Tenant - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
131
+ // Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
132
+ SM_8_10_1.test.skip('Main User Can Create & Access Tenant - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
131
133
  SM_8_10_1.test.slow();
132
134
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
133
135
  const processName = 'Main_User_Connectors_Flow_' + randomString;
@@ -259,7 +261,8 @@ if (process.env.IS_MT === 'true') {
259
261
  });
260
262
  }
261
263
  });
262
- (0, SM_8_10_1.test)('Second User Can Access Tenant Created By Main User - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityRolesPage, ocIdentityMappingRulesPage, browser, }) => {
264
+ // Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
265
+ SM_8_10_1.test.skip('Second User Can Access Tenant Created By Main User - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityRolesPage, ocIdentityMappingRulesPage, browser, }) => {
263
266
  SM_8_10_1.test.slow();
264
267
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
265
268
  const processName = 'Second_User_Connectors_Flow_' + randomString;
@@ -330,7 +333,8 @@ if (process.env.IS_MT === 'true') {
330
333
  });
331
334
  }
332
335
  });
333
- (0, SM_8_10_1.test)('Default Tenant Is Available And Used When Tenant Not Set In Deployment', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
336
+ // Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
337
+ SM_8_10_1.test.skip('Default Tenant Is Available And Used When Tenant Not Set In Deployment', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
334
338
  SM_8_10_1.test.slow();
335
339
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
336
340
  const processName = 'Default_Tenant_Flow_' + randomString;
@@ -466,7 +470,8 @@ if (process.env.IS_MT === 'true') {
466
470
  await operateProcessInstancePage.tenantNameAssertion(tenantName2);
467
471
  });
468
472
  });
469
- (0, SM_8_10_1.test)('Revoking Tenant Access User Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
473
+ // Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
474
+ SM_8_10_1.test.skip('Revoking Tenant Access User Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
470
475
  SM_8_10_1.test.slow();
471
476
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
472
477
  const processName1 = 'Revoke_Tenant_Flow_1' + randomString;
@@ -37,7 +37,8 @@ SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
37
37
  await navigationPage.goToManagementIdentity(1000);
38
38
  console.log(`[${testInfo.title}] Test end: timeout = ${testInfo.timeout}`);
39
39
  });
40
- (0, SM_8_10_1.test)('Basic Navigation', async ({ context, navigationPage }, testInfo) => {
40
+ // Skipped due to bug #60833: https://github.com/camunda/camunda/issues/60833
41
+ SM_8_10_1.test.skip('Basic Navigation', async ({ context, navigationPage }, testInfo) => {
41
42
  console.log(`[${testInfo.title}] Test start: timeout = ${testInfo.timeout}`);
42
43
  await SM_8_10_1.test.step('Assert Web Modeler Navigation', async () => {
43
44
  await context.clearCookies();
@@ -63,7 +64,8 @@ SM_8_10_1.test.describe.parallel('Smoke Tests', () => {
63
64
  }
64
65
  console.log(`[${testInfo.title}] Test end: timeout = ${testInfo.timeout}`);
65
66
  });
66
- (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) => {
67
+ // Skipped due to bug #60833: https://github.com/camunda/camunda/issues/60833
68
+ SM_8_10_1.test.skip('Most Common Flow User Flow With All Apps', async ({ page, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeCollectionsPage, optimizeReportPage, taskDetailsPage, taskPanelPage, }, testInfo) => {
67
69
  console.log(`[${testInfo.title}] Test start: timeout = ${testInfo.timeout}`);
68
70
  const randomName = await (0, _setup_1.generateRandomStringAsync)(3);
69
71
  const processName = 'Zeebe_User_Task_Process' + randomName;
@@ -173,7 +173,8 @@ SM_8_10_1.test.describe('Web Modeler User Flow Tests', () => {
173
173
  await operateProcessInstancePage.assertProcessVariableContainsText('amount', '185.34');
174
174
  });
175
175
  });
176
- (0, SM_8_10_1.test)('Conditional Events - Deploy, verify in Operate and verify process in Optimize dashboard @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeDashboardPage, }) => {
176
+ // Skipped due to bug #60833: https://github.com/camunda/camunda/issues/60833
177
+ SM_8_10_1.test.skip('Conditional Events - Deploy, verify in Operate and verify process in Optimize dashboard @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeDashboardPage, }) => {
177
178
  SM_8_10_1.test.slow();
178
179
  const bpmnFileName = 'Conditional_Events_All.bpmn';
179
180
  const processName = 'Conditional Events Auto-Run';
@@ -0,0 +1,3 @@
1
+ import { Page } from '@playwright/test';
2
+ export declare function isGatewayErrorPage(page: Page): Promise<boolean>;
3
+ export declare function reloadPastGatewayError(page: Page, deadline: number): Promise<void>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reloadPastGatewayError = exports.isGatewayErrorPage = void 0;
4
+ const constants_1 = require("./constants");
5
+ const GATEWAY_ERROR_HEADING = /50[234]\s+(Bad Gateway|Service Temporarily Unavailable|Gateway Time-?out)/i;
6
+ async function isGatewayErrorPage(page) {
7
+ return page
8
+ .getByRole('heading', { name: GATEWAY_ERROR_HEADING })
9
+ .first()
10
+ .isVisible()
11
+ .catch(() => false);
12
+ }
13
+ exports.isGatewayErrorPage = isGatewayErrorPage;
14
+ // While a component's pod restarts, its ingress answers with an nginx 50x page
15
+ // that carries none of the app. A 5xx does not reject `page.reload`, so a retry
16
+ // loop that reloads after every failed attempt keeps re-rendering that error
17
+ // page and spends its whole attempt budget in seconds, long before the
18
+ // component is back. Reload until the component answers again, bounded by
19
+ // `deadline` so a sustained outage still fails the caller.
20
+ async function reloadPastGatewayError(page, deadline) {
21
+ for (let attempt = 1;; attempt++) {
22
+ const response = await page.reload({ waitUntil: 'domcontentloaded' });
23
+ const status = response?.status();
24
+ const unavailable = (status !== undefined && status >= 500) ||
25
+ (await isGatewayErrorPage(page));
26
+ if (!unavailable || Date.now() >= deadline) {
27
+ return;
28
+ }
29
+ console.warn(`reloadPastGatewayError: ${page.url()} answered ${status} — waiting for the component to come back (attempt ${attempt})`);
30
+ await page.waitForTimeout(Math.min(constants_1._1_SECOND_IN_MS * 5 * attempt, deadline - Date.now()));
31
+ }
32
+ }
33
+ exports.reloadPastGatewayError = reloadPastGatewayError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.981",
3
+ "version": "0.0.983",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",