@camunda/e2e-test-suite 0.0.915 → 0.0.917

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.
@@ -2,12 +2,14 @@ import { IdentityPage } from '../SM-8.7/IdentityPage';
2
2
  import { NavigationPage } from '../SM-8.7/NavigationPage';
3
3
  import { ModelerHomePage } from './ModelerHomePage';
4
4
  import { ModelerCreatePage } from './ModelerCreatePage';
5
- import { Locator, Page } from '@playwright/test';
5
+ import { BrowserContext, Locator, Page } from '@playwright/test';
6
6
  import { TaskDetailsPage } from '../SM-8.7/TaskDetailsPage';
7
7
  import { TaskPanelPage } from '../SM-8.7/TaskPanelPage';
8
+ import { LoginPage } from '../SM-8.7/LoginPage';
8
9
  import { ConnectorSettingsPage } from '../SM-8.7/ConnectorSettingsPage';
9
10
  import { ConnectorMarketplacePage } from '../SM-8.7/ConnectorMarketplacePage';
10
11
  import { ConnectorTemplatePage } from './ConnectorTemplatePage';
12
+ export declare function loginToTasklistWithRetry(page: Page, context: BrowserContext, loginPage: LoginPage, taskPanelPage: TaskPanelPage, username: string, password: string, maxAttempts?: number, attemptTimeout?: number): Promise<void>;
11
13
  export declare function deleteAllUserGroups(navigationPage: NavigationPage, identityPage: IdentityPage): Promise<void>;
12
14
  export declare function runMultipleProcesses(modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, numberOfProcesses: number, processName: string, processId?: string): Promise<void>;
13
15
  export declare function createAndRunProcess(modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, processId?: string): Promise<void>;
@@ -1,9 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.modelDiagramFromFile = exports.modelAndRunConnectorsTimerEventDiagram = exports.assertPageTextWithRetry = exports.assertLocatorVisibleWithRetry = exports.modelWebhookConnector = exports.modelIntermediateWebhookConnector = exports.modelRestConnector = exports.completeTaskWithRetry = exports.createAndRunProcess = exports.runMultipleProcesses = exports.deleteAllUserGroups = void 0;
3
+ exports.modelDiagramFromFile = exports.modelAndRunConnectorsTimerEventDiagram = exports.assertPageTextWithRetry = exports.assertLocatorVisibleWithRetry = exports.modelWebhookConnector = exports.modelIntermediateWebhookConnector = exports.modelRestConnector = exports.completeTaskWithRetry = exports.createAndRunProcess = exports.runMultipleProcesses = exports.deleteAllUserGroups = exports.loginToTasklistWithRetry = void 0;
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
+ // Tasklist renders "No permission for Tasklist" as a terminal page: it reads the
8
+ // permissions off the token minted at login and never re-evaluates them, so a
9
+ // user whose Identity role assignment has not propagated yet stays stuck there.
10
+ // Recovering therefore needs a fresh authentication, not a longer wait.
11
+ async function loginToTasklistWithRetry(page, context, loginPage, taskPanelPage, username, password, maxAttempts = 3, attemptTimeout = 60000) {
12
+ const noTasklistPermission = page.getByText('No permission for Tasklist - Please check your configuration.');
13
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
14
+ await loginPage.fillUsername(username);
15
+ await (0, test_1.expect)(loginPage.usernameInput).toHaveValue(username);
16
+ await loginPage.fillPassword(password);
17
+ await loginPage.clickLoginButton();
18
+ if (attempt === maxAttempts) {
19
+ await (0, test_1.expect)(taskPanelPage.tasklistBanner).toBeVisible({
20
+ timeout: attemptTimeout,
21
+ });
22
+ return;
23
+ }
24
+ const reachedTasklist = await taskPanelPage.tasklistBanner
25
+ .waitFor({ state: 'visible', timeout: attemptTimeout })
26
+ .then(() => true)
27
+ .catch(() => false);
28
+ if (reachedTasklist) {
29
+ return;
30
+ }
31
+ const permissionDenied = await noTasklistPermission
32
+ .isVisible()
33
+ .catch(() => false);
34
+ console.warn(`loginToTasklistWithRetry: attempt ${attempt} for "${username}" did not ` +
35
+ `reach Tasklist${permissionDenied ? ' (no-permission page)' : ''}. ` +
36
+ 're-authenticating to obtain a freshly minted token.');
37
+ await context.clearCookies();
38
+ await page.goto('/tasklist', { waitUntil: 'domcontentloaded' });
39
+ await loginPage.detectLoginForm();
40
+ }
41
+ }
42
+ exports.loginToTasklistWithRetry = loginToTasklistWithRetry;
7
43
  async function deleteAllUserGroups(navigationPage, identityPage) {
8
44
  await navigationPage.goToIdentity();
9
45
  await identityPage.clickGroupsTab();
@@ -10,11 +10,25 @@ const axios_1 = __importDefault(require("axios"));
10
10
  const constants_1 = require("./utils/constants");
11
11
  async function captureScreenshot(page, testInfo) {
12
12
  const screenshotPath = `test-results/screenshots/screenshot-${(0, crypto_1.randomUUID)()}.png`;
13
- await page.screenshot({
14
- path: screenshotPath,
15
- fullPage: true,
16
- timeout: 200000,
17
- });
13
+ try {
14
+ await page.screenshot({
15
+ path: screenshotPath,
16
+ fullPage: true,
17
+ timeout: 60000,
18
+ });
19
+ }
20
+ catch (fullPageError) {
21
+ console.warn(`captureScreenshot: full-page capture failed (${fullPageError}). ` +
22
+ 'Falling back to a viewport-only screenshot.');
23
+ try {
24
+ await page.screenshot({ path: screenshotPath, timeout: 30000 });
25
+ }
26
+ catch (viewportError) {
27
+ console.warn(`captureScreenshot: viewport capture also failed (${viewportError}). ` +
28
+ 'Continuing without a screenshot; the test verdict is unaffected.');
29
+ return;
30
+ }
31
+ }
18
32
  await testInfo.attach('screenshot', {
19
33
  path: screenshotPath,
20
34
  contentType: 'image/png',
@@ -474,12 +474,11 @@ _8_8_1.test.describe('HTO User Flow Tests', () => {
474
474
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
475
475
  const processName = 'User_Task_Process_With_Public_Form' + randomString;
476
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.
477
+ // This assertion needs a genuinely v2-upgraded backend, and the switch to
478
+ // v2 is irreversible, so it keeps its own dedicated cluster instead of
479
+ // relying on the mode of the shared "Test Cluster" (which follows the
480
+ // Playwright project the setup ran under -- v1 in the chromium-v1 leg, so
481
+ // that job-worker-based user tasks keep working for the rest of this file).
483
482
  const v2ClusterName = 'Public Form V2 Cluster';
484
483
  await _8_8_1.test.step('Create Dedicated Tasklist V2 Cluster', async () => {
485
484
  const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, v2ClusterName);
@@ -335,11 +335,7 @@ SM_8_7_1.test.describe.parallel('HTO User Flow Tests', () => {
335
335
  await SM_8_7_1.test.step('Logout and Login with "lisa" User and Check Tasks in Tasklist', async () => {
336
336
  await settingsPage.clickOpenSettingsButton();
337
337
  await settingsPage.clickLogoutButton();
338
- await loginPage.fillUsername(navigationPage.user1);
339
- await (0, test_1.expect)(loginPage.usernameInput).toHaveValue(navigationPage.user1);
340
- await loginPage.fillPassword('lisa');
341
- await loginPage.clickLoginButton();
342
- await (0, test_1.expect)(taskPanelPage.tasklistBanner).toBeVisible({ timeout: 120000 });
338
+ await (0, UtlitiesPage_1.loginToTasklistWithRetry)(page, context, loginPage, taskPanelPage, navigationPage.user1, 'lisa');
343
339
  });
344
340
  await SM_8_7_1.test.step('Navigate to Tasklist and Assert User Tasks ', async () => {
345
341
  await (0, test_1.expect)(page.getByText(`${userTaskName}1`)).toBeVisible({
@@ -352,11 +348,7 @@ SM_8_7_1.test.describe.parallel('HTO User Flow Tests', () => {
352
348
  await SM_8_7_1.test.step('Logout and Login with "bart" User and Check Tasks in Tasklist', async () => {
353
349
  await settingsPage.clickOpenSettingsButton();
354
350
  await settingsPage.clickLogoutButton();
355
- await loginPage.fillUsername(navigationPage.user2);
356
- await (0, test_1.expect)(loginPage.usernameInput).toHaveValue(navigationPage.user2);
357
- await loginPage.fillPassword('bart');
358
- await loginPage.clickLoginButton();
359
- await (0, test_1.expect)(taskPanelPage.tasklistBanner).toBeVisible({ timeout: 120000 });
351
+ await (0, UtlitiesPage_1.loginToTasklistWithRetry)(page, context, loginPage, taskPanelPage, navigationPage.user2, 'bart');
360
352
  });
361
353
  await SM_8_7_1.test.step('Navigate to Tasklist and Assert User Tasks ', async () => {
362
354
  await (0, test_1.expect)(page.getByText(`${userTaskName}1`)).not.toBeVisible({
@@ -1,7 +1,7 @@
1
1
  import { Page } from '@playwright/test';
2
2
  export declare function deleteClusterViaApi(page: Page, clusterName: string): Promise<void>;
3
- export declare function createClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
4
- export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
3
+ export declare function createClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
4
+ export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
5
5
  export declare function ensureTasklistV2Enabled(page: Page, clusterUuid: string): Promise<void>;
6
6
  export declare function waitForClusterHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
7
7
  export declare function waitForClusterRestartHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
@@ -233,7 +233,33 @@ function requestedGenerationName() {
233
233
  return undefined;
234
234
  return clusterVersion;
235
235
  }
236
- async function createClusterViaApi(page, clusterName, region = 'GCP') {
236
+ // Which Tasklist mode a cluster must be created in is decided by the Playwright
237
+ // project the setup ran under, exactly as the UI path already does it in
238
+ // ClusterPage.determineTasklistAPI(): chromium-v1 selects "Tasklist API v1
239
+ // (legacy)", chromium-v2 leaves the v2 default. Mirror that here so the
240
+ // chromium-v2 leg does not end up driving a v1-mode backend.
241
+ //
242
+ // For generations below 8.10 Console turns this into
243
+ // CAMUNDA_TASKLIST_V2_MODE_ENABLED on Tasklist and the gateway, so 8.8/8.9 are
244
+ // the versions where it changes what is under test; 8.10+ is v2-only and
245
+ // ignores the field, and 8.7 predates the mode entirely (TASKLIST_VERSION is
246
+ // unset there, which resolves to v1 as before).
247
+ function resolveTasklistV2Enabled() {
248
+ let projectName = '';
249
+ try {
250
+ projectName = test_1.test.info().project.name;
251
+ }
252
+ catch {
253
+ // Called outside a running test (no test.info() available); fall back to
254
+ // the env var the SaaS workflows export from matrix.tasklist_version.
255
+ }
256
+ if (projectName.includes('v1'))
257
+ return false;
258
+ if (projectName.includes('v2'))
259
+ return true;
260
+ return (process.env.TASKLIST_VERSION ?? '').trim().toLowerCase() === 'v2';
261
+ }
262
+ async function createClusterViaApi(page, clusterName, region = 'GCP', tasklistV2Enabled = resolveTasklistV2Enabled()) {
237
263
  const orgId = getOrgId();
238
264
  // Some trigger flows (e.g. playwright_saas_pr_trigger_monorepo.yml) create a
239
265
  // throwaway cluster generation per run, built from that specific commit's
@@ -276,13 +302,14 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
276
302
  k8sContextId,
277
303
  autoUpdate: true,
278
304
  stageLabel: 'dev',
279
- // Upgrading a cluster to Tasklist v2 is a one-way, irreversible switch,
280
- // and v2 cannot run job-worker-based user tasks, draft variables, or
281
- // public start forms -- all of which the @tasklistV1-tagged suites
282
- // exercise. Explicitly request v1 rather than relying on whatever the
283
- // Console API defaults new clusters to, since a cluster that ends up
284
- // v2-enabled can never be brought back to v1 for the rest of its life.
285
- tasklistV2Enabled: false,
305
+ // Always sent explicitly rather than omitted: Console defaults an unset
306
+ // field to v2-enabled for any generation >= 8.8, and the switch to v2 is
307
+ // one-way -- a cluster that ends up v2-enabled can never be brought back
308
+ // to v1 for the rest of its life. v2 cannot run job-worker-based user
309
+ // tasks, draft variables, or public start forms, so the v1 leg
310
+ // (@tasklistV1-tagged suites) depends on this being false, and the v2 leg
311
+ // equally depends on it being true.
312
+ tasklistV2Enabled,
286
313
  });
287
314
  if (createResponse.status() !== 200 && createResponse.status() !== 201) {
288
315
  throw new Error(`Failed to create cluster "${clusterName}": ` +
@@ -300,11 +327,12 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
300
327
  if (!clusterId) {
301
328
  throw new Error(`Cluster creation response missing UUID: ${JSON.stringify(result)}`);
302
329
  }
303
- console.log(`Created cluster via API: clusterId=${clusterId} namespace=${clusterId}-zeebe orgId=${orgId} name="${clusterName}"`);
330
+ console.log(`Created cluster via API: clusterId=${clusterId} namespace=${clusterId}-zeebe ` +
331
+ `orgId=${orgId} name="${clusterName}" tasklistMode=${tasklistV2Enabled ? 'v2' : 'v1'}`);
304
332
  return clusterId;
305
333
  }
306
334
  exports.createClusterViaApi = createClusterViaApi;
307
- async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
335
+ async function ensureClusterViaApi(page, clusterName, region = 'GCP', tasklistV2Enabled = resolveTasklistV2Enabled()) {
308
336
  // Versions with more than one Tasklist-mode Playwright project (e.g. 8.8,
309
337
  // 8.9's chromium-v1/chromium-v2) run test-setup.spec.ts once per project,
310
338
  // concurrently, against the same org -- and both target a cluster with
@@ -319,11 +347,15 @@ async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
319
347
  // setup already created it. Adopt it instead of destroying it -- whichever
320
348
  // project's setup gets here first "wins" and creates it, the other one
321
349
  // just reuses it.
350
+ //
351
+ // The nightly runs the two Tasklist-mode legs in separate orgs (c8_org_1 for
352
+ // v1, c8_org_2 for v2), so an adopted cluster always carries the mode this
353
+ // leg asked for.
322
354
  const existing = await findClusterByName(page, clusterName);
323
355
  if (existing) {
324
356
  return existing.uuid;
325
357
  }
326
- return createClusterViaApi(page, clusterName, region);
358
+ return createClusterViaApi(page, clusterName, region, tasklistV2Enabled);
327
359
  }
328
360
  exports.ensureClusterViaApi = ensureClusterViaApi;
329
361
  async function ensureTasklistV2Enabled(page, clusterUuid) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.915",
3
+ "version": "0.0.917",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",