@camunda/e2e-test-suite 0.0.754 → 0.0.756

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.
@@ -34,6 +34,7 @@ import { OCIdentityRolesPage } from '../pages/8.10/OCIdentityRolesPage';
34
34
  import { OCIdentityAuthorizationsPage } from '../pages/8.10/OCIdentityAuthorizationsPage';
35
35
  import { OCIdentityClusterVariablesPage } from '../pages/8.10/OCIdentityClusterVariablesPage';
36
36
  import { ClientCredentialsDetailsPage } from '../pages/8.10/ClientCredentialsDetailsPage';
37
+ import { AuditLogPage } from '../pages/8.10/AuditLogPage';
37
38
  type PlaywrightFixtures = {
38
39
  makeAxeBuilder: () => AxeBuilder;
39
40
  loginPage: LoginPage;
@@ -71,6 +72,7 @@ type PlaywrightFixtures = {
71
72
  ocIdentityAuthorizationsPage: OCIdentityAuthorizationsPage;
72
73
  ocIdentityClusterVariablesPage: OCIdentityClusterVariablesPage;
73
74
  clientCredentialsDetailsPage: ClientCredentialsDetailsPage;
75
+ auditLogPage: AuditLogPage;
74
76
  overrideTrackingScripts: void;
75
77
  };
76
78
  declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & PlaywrightFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -41,6 +41,7 @@ const OCIdentityRolesPage_1 = require("../pages/8.10/OCIdentityRolesPage");
41
41
  const OCIdentityAuthorizationsPage_1 = require("../pages/8.10/OCIdentityAuthorizationsPage");
42
42
  const OCIdentityClusterVariablesPage_1 = require("../pages/8.10/OCIdentityClusterVariablesPage");
43
43
  const ClientCredentialsDetailsPage_1 = require("../pages/8.10/ClientCredentialsDetailsPage");
44
+ const AuditLogPage_1 = require("../pages/8.10/AuditLogPage");
44
45
  const test = test_1.test.extend({
45
46
  makeAxeBuilder: async ({ page }, use) => {
46
47
  const makeAxeBuilder = () => new playwright_1.default({ page }).withTags([
@@ -158,6 +159,9 @@ const test = test_1.test.extend({
158
159
  clientCredentialsDetailsPage: async ({ page }, use) => {
159
160
  await use(new ClientCredentialsDetailsPage_1.ClientCredentialsDetailsPage(page));
160
161
  },
162
+ auditLogPage: async ({ page }, use) => {
163
+ await use(new AuditLogPage_1.AuditLogPage(page));
164
+ },
161
165
  overrideTrackingScripts: [
162
166
  async ({ context }, use) => {
163
167
  await context.route('https://cmp.osano.com/16CVvwSNKHi9t1grQ/9403708a-488b-4f3b-aea6-613825dec79f/osano.js', (route) => route.fulfill({
@@ -0,0 +1,14 @@
1
+ import { Page, Locator } from '@playwright/test';
2
+ type AuditLogCategory = 'Administration' | 'Deployed resources' | 'User tasks';
3
+ declare class AuditLogPage {
4
+ readonly auditLogTab: Locator;
5
+ readonly updateButton: Locator;
6
+ readonly clientOperationsSection: Locator;
7
+ readonly clientCategoryCheckbox: (category: AuditLogCategory) => Locator;
8
+ readonly clientCategoryLabel: (category: AuditLogCategory) => Locator;
9
+ constructor(page: Page);
10
+ clickAuditLogTab(): Promise<void>;
11
+ private ensureChecked;
12
+ enableClientOperationLogging(): Promise<void>;
13
+ }
14
+ export { AuditLogPage };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuditLogPage = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class AuditLogPage {
6
+ auditLogTab;
7
+ updateButton;
8
+ clientOperationsSection;
9
+ clientCategoryCheckbox;
10
+ clientCategoryLabel;
11
+ constructor(page) {
12
+ this.auditLogTab = page.getByRole('tab', { name: 'Audit log' });
13
+ const auditLogPanel = page.getByRole('tabpanel', { name: 'Audit log' });
14
+ this.updateButton = auditLogPanel.getByRole('button', {
15
+ name: 'Update',
16
+ exact: true,
17
+ });
18
+ // Scope to the Client block: the category labels also exist under the User section.
19
+ this.clientOperationsSection = auditLogPanel
20
+ .getByText('Client operations to log', { exact: true })
21
+ .locator('..');
22
+ this.clientCategoryCheckbox = (category) => this.clientOperationsSection.getByRole('checkbox', { name: category });
23
+ // Carbon hides the real <input>; toggle via the visible label.
24
+ this.clientCategoryLabel = (category) => this.clientOperationsSection.locator('label').filter({ hasText: category });
25
+ }
26
+ async clickAuditLogTab() {
27
+ await (0, test_1.expect)(this.auditLogTab).toBeVisible({ timeout: 60000 });
28
+ await this.auditLogTab.click({ timeout: 60000 });
29
+ }
30
+ async ensureChecked(category) {
31
+ const checkbox = this.clientCategoryCheckbox(category);
32
+ await (0, test_1.expect)(checkbox).toBeAttached({ timeout: 30000 });
33
+ if (!(await checkbox.isChecked())) {
34
+ await this.clientCategoryLabel(category).click({ timeout: 30000 });
35
+ await (0, test_1.expect)(checkbox).toBeChecked();
36
+ }
37
+ }
38
+ async enableClientOperationLogging() {
39
+ const categories = [
40
+ 'Administration',
41
+ 'Deployed resources',
42
+ 'User tasks',
43
+ ];
44
+ for (const category of categories) {
45
+ await this.ensureChecked(category);
46
+ }
47
+ if (await this.updateButton.isEnabled()) {
48
+ await this.updateButton.click({ timeout: 60000 });
49
+ await (0, test_1.expect)(this.updateButton).toBeDisabled({ timeout: 60000 });
50
+ }
51
+ }
52
+ }
53
+ exports.AuditLogPage = AuditLogPage;
@@ -1,6 +1,5 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
2
  import { LoginPage } from './LoginPage';
3
- import { AppsPage } from './AppsPage';
4
3
  declare class ModelerHomePage {
5
4
  private defaultFolderName;
6
5
  private page;
@@ -55,15 +54,23 @@ declare class ModelerHomePage {
55
54
  assertFormBreadcrumbVisible(formName: string): Promise<void>;
56
55
  /**
57
56
  * Handles navigation after clicking an invitation link in email.
58
- * The invitation link may auto-authenticate the second user and land them in
59
- * the correct org's Modeler. If the Modeler banner is visible, navigate to
60
- * the Modeler home within the same org context clearing cookies and
61
- * re-logging in via the app switcher lands in the user's personal org where
62
- * the shared project is not listed.
63
- * If auto-authentication did not occur (page ended up on Auth0), falls back
64
- * to the explicit login path.
57
+ *
58
+ * The invited user follows the link while logged out, so the invitation flow
59
+ * presents the Auth0 login. Completing the login ON the invitation flow lets
60
+ * SaaS accept the invite and land the user directly in the SHARED org's
61
+ * Modeler, where the shared project is listed.
62
+ *
63
+ * We deliberately do NOT clear cookies and re-login via the app switcher:
64
+ * that path authenticates into the user's PERSONAL org, where the shared
65
+ * project does not exist and the folder can never appear — the previous
66
+ * fallback raced a 15s window and, whenever the (normal) login + invite-accept
67
+ * redirect chain took longer, dropped into that personal-org path and hung.
68
+ *
69
+ * If the link auto-authenticated (the banner is already up), no login is
70
+ * needed. The redirect chain routinely takes longer than a few seconds, so
71
+ * wait generously for the banner instead of racing a short window.
65
72
  */
66
- navigateAfterInvitationLink(loginPage: LoginPage, appsPage: AppsPage, credentials: {
73
+ navigateAfterInvitationLink(loginPage: LoginPage, credentials: {
67
74
  username: string;
68
75
  password: string;
69
76
  }): Promise<void>;
@@ -4,7 +4,6 @@ exports.ModelerHomePage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const sleep_1 = require("../../utils/sleep");
6
6
  const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
7
- const UtilitiesPage_1 = require("./UtilitiesPage");
8
7
  class ModelerHomePage {
9
8
  defaultFolderName = 'Cross Component Test Project';
10
9
  page;
@@ -276,34 +275,44 @@ class ModelerHomePage {
276
275
  }
277
276
  /**
278
277
  * Handles navigation after clicking an invitation link in email.
279
- * The invitation link may auto-authenticate the second user and land them in
280
- * the correct org's Modeler. If the Modeler banner is visible, navigate to
281
- * the Modeler home within the same org context clearing cookies and
282
- * re-logging in via the app switcher lands in the user's personal org where
283
- * the shared project is not listed.
284
- * If auto-authentication did not occur (page ended up on Auth0), falls back
285
- * to the explicit login path.
278
+ *
279
+ * The invited user follows the link while logged out, so the invitation flow
280
+ * presents the Auth0 login. Completing the login ON the invitation flow lets
281
+ * SaaS accept the invite and land the user directly in the SHARED org's
282
+ * Modeler, where the shared project is listed.
283
+ *
284
+ * We deliberately do NOT clear cookies and re-login via the app switcher:
285
+ * that path authenticates into the user's PERSONAL org, where the shared
286
+ * project does not exist and the folder can never appear — the previous
287
+ * fallback raced a 15s window and, whenever the (normal) login + invite-accept
288
+ * redirect chain took longer, dropped into that personal-org path and hung.
289
+ *
290
+ * If the link auto-authenticated (the banner is already up), no login is
291
+ * needed. The redirect chain routinely takes longer than a few seconds, so
292
+ * wait generously for the banner instead of racing a short window.
286
293
  */
287
- async navigateAfterInvitationLink(loginPage, appsPage, credentials) {
288
- const autoAuthenticated = await this.modelerPageBanner
289
- .waitFor({ state: 'visible', timeout: 15000 })
294
+ async navigateAfterInvitationLink(loginPage, credentials) {
295
+ // If the invitation flow shows the Auth0 login, authenticate in place so
296
+ // the invite is accepted in the shared-org context. If it doesn't appear,
297
+ // the link already auto-authenticated us and we skip straight to the banner.
298
+ const onLogin = await loginPage.usernameInput
299
+ .waitFor({ state: 'visible', timeout: 60000 })
290
300
  .then(() => true)
291
301
  .catch(() => false);
292
- if (autoAuthenticated) {
293
- try {
294
- await this.clickHomeBreadcrumb();
295
- }
296
- catch {
297
- // Already on the Modeler home page
298
- }
302
+ if (onLogin) {
303
+ await loginPage.fillUsername(credentials.username);
304
+ await loginPage.clickContinueButton();
305
+ await loginPage.fillPassword(credentials.password);
306
+ await loginPage.clickLoginButton();
299
307
  }
300
- else {
301
- await this.page.context().clearCookies();
302
- await (0, UtilitiesPage_1.loginWithRetry)(this.page, loginPage, credentials, 1000, 3, true);
303
- await appsPage.clickCamundaApps();
304
- await appsPage.clickModeler();
308
+ await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout: 180000 });
309
+ try {
310
+ await this.clickHomeBreadcrumb();
311
+ }
312
+ catch {
313
+ // Already on the Modeler home page
305
314
  }
306
- await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout: 120000 });
315
+ await (0, test_1.expect)(this.modelerPageBanner).toBeVisible({ timeout: 30000 });
307
316
  }
308
317
  }
309
318
  exports.ModelerHomePage = ModelerHomePage;
@@ -29,6 +29,7 @@ export declare function runMultipleProcesses(clusterName: string, page: Page, mo
29
29
  numberOfProcesses?: number;
30
30
  processName?: string;
31
31
  uploadFromFile?: string;
32
+ settleBetween?: (processId: string) => Promise<void>;
32
33
  }): Promise<void>;
33
34
  export declare function runProcess(clusterName: string, page: Page, modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, processId?: string, uploadFromFile?: string): Promise<void>;
34
35
  export declare function modelDiagramFromFile(page: Page, modelerHomePage: ModelerHomePage, modelerCreatePage: ModelerCreatePage, processName: string, fileName?: string, nrOfRenamedUserTasks?: number, taskName?: string): Promise<void>;
@@ -219,10 +219,20 @@ async function completeTaskWithRetry(taskPanelPage, taskDetailsPage, taskName, t
219
219
  }
220
220
  exports.completeTaskWithRetry = completeTaskWithRetry;
221
221
  async function runMultipleProcesses(clusterName, page, modelerHomePage, modelerCreatePage, options) {
222
- const { processName = 'Camunda_User_Task', numberOfProcesses = 2, uploadFromFile = '', } = options || {};
222
+ const { processName = 'Camunda_User_Task', numberOfProcesses = 2, uploadFromFile = '', settleBetween, } = options || {};
223
223
  for (let i = 1; i <= numberOfProcesses; i++) {
224
224
  await runProcess(clusterName, page, modelerHomePage, modelerCreatePage, processName + i, processName + i, uploadFromFile);
225
225
  await modelerHomePage.clickProjectBreadcrumb();
226
+ // When RBA is enabled, deploying a process auto-provisions a resource
227
+ // authorization for the deployer keyed on the process definition id. The
228
+ // identity service writes that grant with a read-modify-write, so deploying
229
+ // the next process before this grant lands clobbers it (lost update) and it
230
+ // is permanently missing. When a settleBetween hook is supplied, wait for
231
+ // the just-deployed grant to be durably provisioned before the next deploy —
232
+ // a deterministic wait, not a fixed sleep.
233
+ if (settleBetween && i < numberOfProcesses) {
234
+ await settleBetween(processName + i);
235
+ }
226
236
  }
227
237
  }
228
238
  exports.runMultipleProcesses = runMultipleProcesses;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
4
+ * one or more contributor license agreements. See the NOTICE file distributed
5
+ * with this work for additional information regarding copyright ownership.
6
+ * Licensed under the Camunda License 1.0. You may not use this file
7
+ * except in compliance with the Camunda License 1.0.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ /*
11
+ * MCP audit-log inbound channel provenance — camunda/camunda#56234
12
+ * (SC-AUD-01/02/03).
13
+ */
14
+ const _8_10_1 = require("../../../../fixtures/8.10");
15
+ const test_1 = require("@playwright/test");
16
+ const apiHelpers_1 = require("../../../../utils/apiHelpers");
17
+ const mcpHelpers_1 = require("../../../../utils/mcpHelpers");
18
+ const constants_1 = require("../../../../utils/constants");
19
+ // Raw BPMN `io.camunda.tool:name` annotation — what the audit log stores in
20
+ // inboundChannelToolName (not the composite tools/list name).
21
+ const TOOL_NAME_RAW = 'mcp-as-tool-process';
22
+ const MCP_PROCESS_DEFINITION_ID = 'mcpAsToolProcess';
23
+ let authToken;
24
+ let mcpProcessesUrl;
25
+ let messageSubscriptionsSearchUrl;
26
+ let auditLogsSearchUrl;
27
+ let mcpSessionId = null;
28
+ let restProcessDefinitionKey;
29
+ let compositeToolName;
30
+ _8_10_1.test.describe.configure({ mode: 'parallel' });
31
+ _8_10_1.test.describe('Orchestration Cluster MCP Server — audit log inbound channel', () => {
32
+ _8_10_1.test.beforeAll(async () => {
33
+ authToken = await (0, apiHelpers_1.authSaasAPI)(undefined, 'agentic_cluster');
34
+ mcpProcessesUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/mcp/processes', 'saas', 'agentic_cluster');
35
+ messageSubscriptionsSearchUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/v2/message-subscriptions/search', 'saas', 'agentic_cluster');
36
+ auditLogsSearchUrl = (0, apiHelpers_1.buildZeebeApiUrl)('/v2/audit-logs/search', 'saas', 'agentic_cluster');
37
+ const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
38
+ mcpSessionId = await (0, mcpHelpers_1.initializeMcpSession)(apiContext, mcpProcessesUrl, authToken, mcpSessionId);
39
+ const [mcpKey, restKey] = await Promise.all([
40
+ (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_as_tool_process.bpmn', authToken, 'saas', 'agentic_cluster'),
41
+ (0, apiHelpers_1.deployProcess)('./resources/mcp_server_saas/mcp_process_instance_test_file.bpmn', authToken, 'saas', 'agentic_cluster'),
42
+ ]);
43
+ if (mcpKey == null) {
44
+ throw new Error('Failed to deploy mcp_as_tool_process.bpmn');
45
+ }
46
+ if (restKey == null) {
47
+ throw new Error('Failed to deploy mcp_process_instance_test_file.bpmn');
48
+ }
49
+ restProcessDefinitionKey = String(restKey);
50
+ // Composite tool name is `<toolName>_<messageSubscriptionKey>`.
51
+ await (0, test_1.expect)(async () => {
52
+ const res = await apiContext.post(messageSubscriptionsSearchUrl, {
53
+ headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
54
+ data: {
55
+ filter: {
56
+ processDefinitionId: MCP_PROCESS_DEFINITION_ID,
57
+ messageSubscriptionType: 'START_EVENT',
58
+ },
59
+ sort: [{ field: 'processDefinitionVersion', order: 'desc' }],
60
+ page: { limit: 1 },
61
+ },
62
+ });
63
+ await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
64
+ const json = (await res.json());
65
+ (0, test_1.expect)(json.items.length).toBeGreaterThanOrEqual(1);
66
+ compositeToolName = `${TOOL_NAME_RAW}_${json.items[0].messageSubscriptionKey}`;
67
+ }).toPass(constants_1.deletionAssertionOptions);
68
+ });
69
+ (0, _8_10_1.test)('SC-AUD-01 — MCP tools/call produces audit entry with inboundChannelType=MCP', async () => {
70
+ const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
71
+ const callBody = await (0, mcpHelpers_1.callTool)(apiContext, mcpProcessesUrl, authToken, mcpSessionId, compositeToolName, {});
72
+ (0, test_1.expect)(callBody.result).toBeDefined();
73
+ (0, test_1.expect)(callBody.result.isError).not.toBe(true);
74
+ const pik = (0, mcpHelpers_1.extractProcessInstanceKey)(callBody.result.content[0]?.text ?? '');
75
+ (0, test_1.expect)(pik, 'Expected processInstanceKey from MCP tools/call').not.toBeNull();
76
+ await (0, test_1.expect)(async () => {
77
+ const res = await apiContext.post(auditLogsSearchUrl, {
78
+ headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
79
+ data: {
80
+ filter: {
81
+ processInstanceKey: pik,
82
+ operationType: 'CREATE',
83
+ },
84
+ page: { limit: 10 },
85
+ },
86
+ });
87
+ await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
88
+ const json = (await res.json());
89
+ const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
90
+ (0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for pik ${pik}`).toBeDefined();
91
+ (0, test_1.expect)(entry.inboundChannelType).toBe('MCP');
92
+ }).toPass(constants_1.auditLogAssertionOptions);
93
+ });
94
+ (0, _8_10_1.test)('SC-AUD-02 — MCP audit entry captures the raw tool name in inboundChannelToolName', async () => {
95
+ const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
96
+ const callBody = await (0, mcpHelpers_1.callTool)(apiContext, mcpProcessesUrl, authToken, mcpSessionId, compositeToolName, {});
97
+ (0, test_1.expect)(callBody.result).toBeDefined();
98
+ (0, test_1.expect)(callBody.result.isError).not.toBe(true);
99
+ const pik = (0, mcpHelpers_1.extractProcessInstanceKey)(callBody.result.content[0]?.text ?? '');
100
+ (0, test_1.expect)(pik).not.toBeNull();
101
+ await (0, test_1.expect)(async () => {
102
+ const res = await apiContext.post(auditLogsSearchUrl, {
103
+ headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
104
+ data: {
105
+ filter: {
106
+ processInstanceKey: pik,
107
+ operationType: 'CREATE',
108
+ },
109
+ page: { limit: 10 },
110
+ },
111
+ });
112
+ await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
113
+ const json = (await res.json());
114
+ const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
115
+ (0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for pik ${pik}`).toBeDefined();
116
+ // Raw annotation value, not the composite tools/list name.
117
+ (0, test_1.expect)(entry.inboundChannelToolName).toBe(TOOL_NAME_RAW);
118
+ (0, test_1.expect)(entry.inboundChannelType).toBe('MCP');
119
+ }).toPass(constants_1.auditLogAssertionOptions);
120
+ });
121
+ (0, _8_10_1.test)('SC-AUD-03 — REST-triggered start is distinguishable from MCP (inboundChannelType and tool name both null)', async () => {
122
+ const apiContext = await (0, apiHelpers_1.getApiRequestContext)();
123
+ // NONE-start BPMN started via REST — bypasses MCP entirely.
124
+ const restPik = await (0, apiHelpers_1.createProcessInstance)(restProcessDefinitionKey, authToken, 'saas', 'agentic_cluster');
125
+ (0, test_1.expect)(restPik).toBeTruthy();
126
+ await (0, test_1.expect)(async () => {
127
+ const res = await apiContext.post(auditLogsSearchUrl, {
128
+ headers: (0, mcpHelpers_1.buildMcpHeaders)(authToken, mcpSessionId),
129
+ data: {
130
+ filter: {
131
+ processInstanceKey: restPik,
132
+ operationType: 'CREATE',
133
+ },
134
+ page: { limit: 10 },
135
+ },
136
+ });
137
+ await (0, mcpHelpers_1.assertResponseStatus)(res, 200);
138
+ const json = (await res.json());
139
+ const entry = json.items.find((i) => i.entityType === 'PROCESS_INSTANCE');
140
+ (0, test_1.expect)(entry, `Expected a PROCESS_INSTANCE CREATE audit entry for REST pik ${restPik}`).toBeDefined();
141
+ // REST-triggered entries have both fields null (verified on 8.10-SNAPSHOT).
142
+ (0, test_1.expect)(entry.inboundChannelType).toBeNull();
143
+ (0, test_1.expect)(entry.inboundChannelToolName).toBeNull();
144
+ }).toPass(constants_1.auditLogAssertionOptions);
145
+ });
146
+ });
@@ -47,7 +47,7 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
47
47
  await clusterSecretsPage.createSetOfSecrets(clusterName, connectorSecrets_1.bedrockCodeInterpreterSecretsValues);
48
48
  }
49
49
  });
50
- (0, _8_10_1.test)('Create Agentic Orchestration Cluster', async ({ page, loginPage, homePage, clusterPage, clusterDetailsPage, clusterSecretsPage, }, testInfo) => {
50
+ (0, _8_10_1.test)('Create Agentic Orchestration Cluster', async ({ page, loginPage, homePage, clusterPage, clusterDetailsPage, clusterSecretsPage, auditLogPage, }, testInfo) => {
51
51
  _8_10_1.test.skip(process.env.IS_PROD === 'true', 'Skipping test because not required on PROD test org');
52
52
  _8_10_1.test.skip(process.env.IS_AG === 'true', 'Skipping test because not required when IS_AG is true');
53
53
  _8_10_1.test.slow();
@@ -60,6 +60,8 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
60
60
  await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 300000, 1500000);
61
61
  await clusterPage.clickClusterLink(clusterName);
62
62
  await clusterDetailsPage.assertComponentsHealth();
63
+ await auditLogPage.clickAuditLogTab();
64
+ await auditLogPage.enableClientOperationLogging();
63
65
  await clusterDetailsPage.clickSettingsTab();
64
66
  await clusterDetailsPage.checkMcpSupportToggle();
65
67
  await homePage.clickClusters();
@@ -236,6 +236,31 @@ _8_7_1.test.describe('RBA Enabled User Flows Test', () => {
236
236
  await (0, UtilitiesPage_1.runMultipleProcesses)(clusterName, page, modelerHomePage, modelerCreatePage, {
237
237
  processName: processName,
238
238
  uploadFromFile: 'Camunda_Simple_User_Task_1',
239
+ // RBA is enabled before this deploy, so each process start
240
+ // auto-provisions the deployer's resource authorization via a
241
+ // read-modify-write. Deploying the second process before the first
242
+ // grant lands clobbers it (lost update), permanently dropping one of
243
+ // the two process ids — which no downstream wait can recover (see
244
+ // authorizedResourcesAssertion). Before deploying the next process,
245
+ // wait for the just-deployed grant to appear in Console, then return
246
+ // to the project. Deterministic, unlike a fixed sleep.
247
+ settleBetween: async (processId) => {
248
+ await appsPage.clickCamundaApps();
249
+ await appsPage.clickConsoleLink();
250
+ await homePage.clickOrganization();
251
+ await consoleOrganizationsPage.clickUsersTab();
252
+ await consoleOrganizationsPage.clickMainUser(mainUser.username);
253
+ await consoleOrganizationsPage.clickAuthorizations();
254
+ await consoleOrganizationsPage.authorizedResourcesAssertion([
255
+ processId,
256
+ ]);
257
+ await appsPage.clickCamundaApps();
258
+ await appsPage.clickModeler();
259
+ await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
260
+ timeout: 120000,
261
+ });
262
+ await modelerHomePage.clickCrossComponentProjectFolder();
263
+ },
239
264
  });
240
265
  await (0, sleep_1.sleep)(60000);
241
266
  });
@@ -514,12 +514,17 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
514
514
  });
515
515
  await _8_7_1.test.step('Log in with Second User and Navigate to Cross Component Test project as Collaborator', async () => {
516
516
  await (0, UtilitiesPage_1.clickInvitationLinkInEmail)(page, id);
517
- await (0, sleep_1.sleep)(60000);
518
- await modelerHomePage.navigateAfterInvitationLink(loginPage, appsPage, {
517
+ await modelerHomePage.navigateAfterInvitationLink(loginPage, {
519
518
  username: emailAddress,
520
519
  password: password,
521
520
  });
522
- await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(modelerHomePage, modelerHomePage.crossComponentProjectFolder, 'Cross Component Test Project', 90000, 20);
521
+ // navigateAfterInvitationLink lands the invited user in the SHARED org's
522
+ // Modeler home (it logs in on the invitation flow rather than
523
+ // re-authenticating into the personal org), so the shared project is
524
+ // present. Assert its visibility directly — no session-breaking reload.
525
+ await (0, test_1.expect)(modelerHomePage.crossComponentProjectFolder).toBeVisible({
526
+ timeout: 120000,
527
+ });
523
528
  });
524
529
  await _8_7_1.test.step('Log out from Second User', async () => {
525
530
  await settingsPage.clickOpenSettingsButton();
@@ -10,6 +10,10 @@ export declare const deletionAssertionOptions: {
10
10
  intervals: number[];
11
11
  timeout: number;
12
12
  };
13
+ export declare const auditLogAssertionOptions: {
14
+ intervals: number[];
15
+ timeout: number;
16
+ };
13
17
  export declare const TIMEOUT: {
14
18
  short: number;
15
19
  medium: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TIMEOUT = exports.deletionAssertionOptions = exports.defaultAssertionOptions = exports._3_MINUTES_IN_MS = exports._1_MINUTE_IN_MS = exports._1_SECOND_IN_MS = exports._100_MS = void 0;
3
+ exports.TIMEOUT = exports.auditLogAssertionOptions = exports.deletionAssertionOptions = exports.defaultAssertionOptions = exports._3_MINUTES_IN_MS = exports._1_MINUTE_IN_MS = exports._1_SECOND_IN_MS = exports._100_MS = void 0;
4
4
  exports._100_MS = 100;
5
5
  exports._1_SECOND_IN_MS = 1000;
6
6
  exports._1_MINUTE_IN_MS = 60 * 1000;
@@ -14,6 +14,12 @@ exports.deletionAssertionOptions = {
14
14
  intervals: [5000, 10000, 15000, 30000],
15
15
  timeout: 60000,
16
16
  };
17
+ // Longest options for audit-log assertions: entries land in secondary storage
18
+ // only after the audit config propagates to the exporters.
19
+ exports.auditLogAssertionOptions = {
20
+ intervals: [5000, 10000, 15000, 15000],
21
+ timeout: 90000,
22
+ };
17
23
  exports.TIMEOUT = {
18
24
  short: 15000,
19
25
  medium: 30000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.754",
3
+ "version": "0.0.756",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",