@camunda/e2e-test-suite 0.0.867 → 0.0.869

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.
@@ -61,6 +61,7 @@ declare class ClusterDetailsPage {
61
61
  clickCreateButton(): Promise<void>;
62
62
  clickCloseModalButton(): Promise<void>;
63
63
  clickSettingsTab(): Promise<void>;
64
+ private waitForAuthorizationsSection;
64
65
  enableAuthorizations(): Promise<void>;
65
66
  disableAuthorizations(): Promise<void>;
66
67
  searchAndClickClientCredentialsLink(name: string): Promise<ClientCredentialsDetailsPage>;
@@ -234,9 +234,28 @@ class ClusterDetailsPage {
234
234
  async clickSettingsTab() {
235
235
  await this.settingsTab.click({ timeout: 60000 });
236
236
  }
237
+ // The Settings tab renders its Authorizations toggle only once the
238
+ // cluster-detail payload has loaded. When those requests fail the tab shell
239
+ // still renders, so a single-shot wait on the heading can never succeed no
240
+ // matter how long it waits: the nightly trace shows every
241
+ // `/api/orgs/<org>/clusters/<id>*` fetch ending in `net::ERR_FAILED` with no
242
+ // `Access-Control-Allow-Origin` header. Reload the settings route between
243
+ // attempts so the fetches are re-issued and the section can render.
244
+ async waitForAuthorizationsSection() {
245
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.authorizationsHeading, {
246
+ visibilityTimeout: 60000,
247
+ totalTimeout: 240000,
248
+ maxRetries: 4,
249
+ postAction: async () => {
250
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
251
+ await this.settingsTab.waitFor({ state: 'visible', timeout: 60000 });
252
+ await this.clickSettingsTab();
253
+ },
254
+ });
255
+ }
237
256
  async enableAuthorizations() {
238
257
  await (0, sleep_1.sleep)(5000);
239
- await (0, test_1.expect)(this.authorizationsHeading).toBeVisible({ timeout: 60000 });
258
+ await this.waitForAuthorizationsSection();
240
259
  // Locate all elements with class .cds--toggle__text
241
260
  const toggleTextElements = await this.page.$$('.cds--toggle__text');
242
261
  // Check if toggleTextElements has at least 2 elements
@@ -274,7 +293,7 @@ class ClusterDetailsPage {
274
293
  }
275
294
  }
276
295
  async disableAuthorizations() {
277
- await (0, test_1.expect)(this.authorizationsHeading).toBeVisible({ timeout: 60000 });
296
+ await this.waitForAuthorizationsSection();
278
297
  // Locate all elements with class .cds--toggle__text
279
298
  const toggleTextElements = await this.page.$$('.cds--toggle__text');
280
299
  // Check if toggleTextElements has at least 2 elements
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const test_1 = require("@playwright/test");
4
+ const _8_10_1 = require("../../fixtures/8.10");
5
+ const apiHelpers_1 = require("../../utils/apiHelpers");
6
+ const randomName_1 = require("../../utils/randomName");
7
+ const sleep_1 = require("../../utils/sleep");
8
+ _8_10_1.test.describe.configure({ mode: 'serial' });
9
+ const VARIABLE_DEFINITION_FILE = './resources/Job_Worker_Process.bpmn';
10
+ const VARIABLE_DEFINITION_KEY = 'Job_Worker_Process';
11
+ const OPTIMIZE_MODE_DEFINITION_FILE = './resources/Play_Test_User_Service_Task.bpmn';
12
+ const OPTIMIZE_MODE_DEFINITION_KEY = 'Play_Test_User_Service_Task';
13
+ const TYPE_STRING_VARIABLE = 'stringVar';
14
+ const TYPE_OBJECT_VARIABLE = 'objectVar';
15
+ const TYPE_CONTROL_VARIABLE = 'business_marker';
16
+ const VALUE_TYPE_EXCLUSION_KEY = 'variableValueTypeExclusion';
17
+ const OPTIMIZE_MODE_KEY = 'optimizeModeEnabled';
18
+ const FILTER_REFLECT_TIMEOUT_MS = 30000;
19
+ const FILTER_REFLECT_INTERVAL_MS = 3000;
20
+ const IMPORT_POLL_TIMEOUT_MS = 180000;
21
+ const IMPORT_POLL_INTERVAL_MS = 10000;
22
+ let managementToken;
23
+ let clusterUuid;
24
+ let originalFilters = null;
25
+ let dataFiltersAvailable = false;
26
+ let zeebeToken;
27
+ let optimizeCookie;
28
+ let optimizeExportToken;
29
+ let optimizeBaseUrl;
30
+ const createdCollectionIds = [];
31
+ const reportIdByDefinitionKey = new Map();
32
+ const skipIfUnavailable = () => {
33
+ _8_10_1.test.skip(!dataFiltersAvailable, 'Console Data Filters API not reachable on this cluster (endpoint returned non-2xx or is not yet enabled). See epic camunda/product-hub#3288.');
34
+ };
35
+ const setFilters = async (config) => {
36
+ let current = {};
37
+ const currentResponse = await (0, apiHelpers_1.getClusterDataFilters)(managementToken, clusterUuid);
38
+ if (currentResponse.status() >= 200 && currentResponse.status() < 300) {
39
+ try {
40
+ current = (await currentResponse.json());
41
+ }
42
+ catch {
43
+ current = {};
44
+ }
45
+ }
46
+ const response = await (0, apiHelpers_1.updateClusterDataFilters)(managementToken, clusterUuid, {
47
+ ...current,
48
+ ...config,
49
+ });
50
+ return response.status();
51
+ };
52
+ const waitForFilterConfig = async (predicate) => {
53
+ const deadline = Date.now() + FILTER_REFLECT_TIMEOUT_MS;
54
+ let lastStatus = 0;
55
+ let lastBody = '';
56
+ while (Date.now() < deadline) {
57
+ const response = await (0, apiHelpers_1.getClusterDataFilters)(managementToken, clusterUuid);
58
+ lastStatus = response.status();
59
+ lastBody = await response.text().catch(() => '');
60
+ if (lastStatus >= 200 && lastStatus < 300) {
61
+ let body = null;
62
+ try {
63
+ body = JSON.parse(lastBody);
64
+ }
65
+ catch {
66
+ body = null;
67
+ }
68
+ if (body && predicate(body)) {
69
+ return;
70
+ }
71
+ }
72
+ await (0, sleep_1.sleep)(FILTER_REFLECT_INTERVAL_MS);
73
+ }
74
+ throw new Error(`Timed out after ${FILTER_REFLECT_TIMEOUT_MS}ms waiting for the cluster data-filters config to reflect the requested update (last HTTP status=${lastStatus}, last body=${lastBody.slice(0, 500)}).`);
75
+ };
76
+ const getOrCreateReport = async (request, definitionKey) => {
77
+ const cachedReportId = reportIdByDefinitionKey.get(definitionKey);
78
+ if (cachedReportId) {
79
+ return cachedReportId;
80
+ }
81
+ const collectionId = await (0, apiHelpers_1.createCollection)(request, {
82
+ name: await (0, randomName_1.randomNameAgregator)('ExporterFilters E2E'),
83
+ optimizeCookie,
84
+ });
85
+ createdCollectionIds.push(collectionId);
86
+ await (0, apiHelpers_1.updateCollectionScope)(request, {
87
+ optimizeCookie,
88
+ collectionId,
89
+ data: [
90
+ {
91
+ definitionKey,
92
+ definitionType: 'process',
93
+ tenants: ['<default>'],
94
+ },
95
+ ],
96
+ });
97
+ const reportResponse = await request.post(`${optimizeBaseUrl}/api/report/process/single`, {
98
+ headers: {
99
+ Cookie: optimizeCookie,
100
+ 'Content-Type': 'application/json',
101
+ },
102
+ data: {
103
+ collectionId,
104
+ name: await (0, randomName_1.randomNameAgregator)('Raw Data'),
105
+ description: null,
106
+ data: {
107
+ definitions: [
108
+ { key: definitionKey, versions: ['all'], tenantIds: ['<default>'] },
109
+ ],
110
+ view: { entity: null, properties: ['rawData'] },
111
+ groupBy: { type: 'none', value: null },
112
+ distributedBy: { type: 'none', value: null },
113
+ visualization: 'table',
114
+ filter: [],
115
+ },
116
+ },
117
+ });
118
+ (0, test_1.expect)(reportResponse.status()).toBe(200);
119
+ const reportId = (await reportResponse.json()).id;
120
+ reportIdByDefinitionKey.set(definitionKey, reportId);
121
+ return reportId;
122
+ };
123
+ const fetchImported = async (request, definitionKey) => {
124
+ const reportId = await getOrCreateReport(request, definitionKey);
125
+ const exportResponse = await request.get(`${optimizeBaseUrl}/api/public/export/report/${reportId}/result/json?paginationTimeout=60&limit=1000`, { headers: { Authorization: optimizeExportToken } });
126
+ const variableNames = new Set();
127
+ if (!exportResponse.ok()) {
128
+ const rawBody = await exportResponse.text().catch(() => '');
129
+ throw new Error(`Optimize export API returned ${exportResponse.status()} for report ${reportId}: ${rawBody.slice(0, 500)}`);
130
+ }
131
+ const body = (await exportResponse.json());
132
+ const result = (body.result ?? body);
133
+ const rows = (result.data ?? []);
134
+ for (const row of rows) {
135
+ const vars = row.variables;
136
+ if (Array.isArray(vars)) {
137
+ for (const entry of vars) {
138
+ if (typeof entry?.name === 'string') {
139
+ variableNames.add(entry.name);
140
+ }
141
+ }
142
+ }
143
+ else if (vars && typeof vars === 'object') {
144
+ for (const name of Object.keys(vars)) {
145
+ variableNames.add(name);
146
+ }
147
+ }
148
+ }
149
+ const totalNumberOfRecords = typeof body.totalNumberOfRecords === 'number'
150
+ ? body.totalNumberOfRecords
151
+ : typeof result.totalNumberOfRecords === 'number'
152
+ ? result.totalNumberOfRecords
153
+ : rows.length;
154
+ return { instanceCount: totalNumberOfRecords, variableNames };
155
+ };
156
+ const waitForImportedInstance = async (request, definitionKey, predicate = (imported) => imported.instanceCount > 0) => {
157
+ const deadline = Date.now() + IMPORT_POLL_TIMEOUT_MS;
158
+ let latest = { instanceCount: 0, variableNames: new Set() };
159
+ while (Date.now() < deadline) {
160
+ latest = await fetchImported(request, definitionKey);
161
+ if (predicate(latest)) {
162
+ return latest;
163
+ }
164
+ await (0, sleep_1.sleep)(IMPORT_POLL_INTERVAL_MS);
165
+ }
166
+ throw new Error(`Timed out after ${IMPORT_POLL_TIMEOUT_MS}ms waiting for Optimize to import definition "${definitionKey}" (latest instanceCount=${latest.instanceCount}, variables=[${[...latest.variableNames].join(', ')}]).`);
167
+ };
168
+ _8_10_1.test.beforeAll(async ({ browser }) => {
169
+ try {
170
+ managementToken = await (0, apiHelpers_1.authConsoleManagementAPI)();
171
+ clusterUuid = (0, apiHelpers_1.getTestClusterUuid)();
172
+ const probe = await (0, apiHelpers_1.getClusterDataFilters)(managementToken, clusterUuid);
173
+ dataFiltersAvailable = probe.status() >= 200 && probe.status() < 300;
174
+ if (!dataFiltersAvailable) {
175
+ return;
176
+ }
177
+ try {
178
+ originalFilters = (await probe.json());
179
+ }
180
+ catch {
181
+ throw new Error(`Data Filters API probe returned ${probe.status()} but the response body was not valid JSON; refusing to mutate cluster filters without a restorable baseline.`);
182
+ }
183
+ zeebeToken = await (0, apiHelpers_1.authSaasAPI)();
184
+ optimizeExportToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
185
+ optimizeBaseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
186
+ const page = await browser.newPage();
187
+ optimizeCookie = await (0, apiHelpers_1.getOptimizeCoockie)(page);
188
+ await page.close();
189
+ }
190
+ catch (error) {
191
+ if (dataFiltersAvailable) {
192
+ throw error;
193
+ }
194
+ console.warn(`Exporter filters E2E precheck failed before the Data Filters endpoint was confirmed reachable; skipping suite: ${error instanceof Error ? error.message : String(error)}`);
195
+ }
196
+ });
197
+ _8_10_1.test.afterAll(async ({ request }) => {
198
+ for (const collectionId of createdCollectionIds) {
199
+ try {
200
+ await request.delete(`${optimizeBaseUrl}/api/public/collection/${collectionId}`, { headers: { Authorization: optimizeExportToken } });
201
+ }
202
+ catch (error) {
203
+ console.warn(`Failed to delete Optimize collection ${collectionId}: ${error instanceof Error ? error.message : String(error)}`);
204
+ }
205
+ }
206
+ if (dataFiltersAvailable && originalFilters) {
207
+ try {
208
+ await (0, apiHelpers_1.updateClusterDataFilters)(managementToken, clusterUuid, originalFilters);
209
+ }
210
+ catch (error) {
211
+ console.warn(`Failed to restore original data filters: ${error instanceof Error ? error.message : String(error)}`);
212
+ }
213
+ }
214
+ });
215
+ _8_10_1.test.describe('Optimize exporter filters — variable value type exclusion @optimize-exporter-filters-importer-e2e @8.10', () => {
216
+ _8_10_1.test.slow();
217
+ _8_10_1.test.beforeAll(async () => {
218
+ if (!dataFiltersAvailable) {
219
+ return;
220
+ }
221
+ const status = await setFilters({
222
+ variableFilter: { enabled: true },
223
+ [VALUE_TYPE_EXCLUSION_KEY]: ['Object'],
224
+ });
225
+ (0, test_1.expect)([200, 201, 204]).toContain(status);
226
+ await waitForFilterConfig((filters) => {
227
+ const exclusion = filters[VALUE_TYPE_EXCLUSION_KEY];
228
+ return Array.isArray(exclusion) && exclusion.includes('Object');
229
+ });
230
+ });
231
+ (0, _8_10_1.test)('string variable reaches Optimize', async ({ request }) => {
232
+ skipIfUnavailable();
233
+ await _8_10_1.test.step('start a process instance carrying a string variable and a positive control', async () => {
234
+ const processKey = await (0, apiHelpers_1.deployProcess)(VARIABLE_DEFINITION_FILE, zeebeToken);
235
+ (0, test_1.expect)(processKey).not.toBeNull();
236
+ await (0, apiHelpers_1.createProcessInstance)(String(processKey), zeebeToken, undefined, undefined, {
237
+ [TYPE_STRING_VARIABLE]: 'hello',
238
+ [TYPE_CONTROL_VARIABLE]: 'present',
239
+ });
240
+ });
241
+ await _8_10_1.test.step('assert the string variable appears in the exported data', async () => {
242
+ const imported = await waitForImportedInstance(request, VARIABLE_DEFINITION_KEY, (result) => result.variableNames.has(TYPE_STRING_VARIABLE));
243
+ (0, test_1.expect)(imported.variableNames).toContain(TYPE_STRING_VARIABLE);
244
+ });
245
+ });
246
+ (0, _8_10_1.test)('object variable is excluded while the positive control is kept', async ({ request, }) => {
247
+ skipIfUnavailable();
248
+ let baseline = 0;
249
+ await _8_10_1.test.step('capture the current imported instance count for the process definition', async () => {
250
+ baseline = (await fetchImported(request, VARIABLE_DEFINITION_KEY))
251
+ .instanceCount;
252
+ });
253
+ await _8_10_1.test.step('start a process instance carrying an object variable and a positive control', async () => {
254
+ const processKey = await (0, apiHelpers_1.deployProcess)(VARIABLE_DEFINITION_FILE, zeebeToken);
255
+ (0, test_1.expect)(processKey).not.toBeNull();
256
+ await (0, apiHelpers_1.createProcessInstance)(String(processKey), zeebeToken, undefined, undefined, {
257
+ [TYPE_OBJECT_VARIABLE]: { nested: true },
258
+ [TYPE_CONTROL_VARIABLE]: 'present',
259
+ });
260
+ });
261
+ await _8_10_1.test.step('wait until the new instance is imported to prove the import cycle ran past this point', async () => {
262
+ const imported = await waitForImportedInstance(request, VARIABLE_DEFINITION_KEY, (result) => result.instanceCount > baseline);
263
+ (0, test_1.expect)(imported.instanceCount).toBeGreaterThan(baseline);
264
+ });
265
+ await _8_10_1.test.step('assert the object variable is absent from the exported data', async () => {
266
+ const imported = await fetchImported(request, VARIABLE_DEFINITION_KEY);
267
+ (0, test_1.expect)(imported.variableNames).toContain(TYPE_CONTROL_VARIABLE);
268
+ (0, test_1.expect)(imported.variableNames.has(TYPE_OBJECT_VARIABLE)).toBe(false);
269
+ });
270
+ });
271
+ });
272
+ _8_10_1.test.describe('Optimize exporter filters — Optimize mode restricted record export @optimize-exporter-filters-importer-e2e @8.10', () => {
273
+ _8_10_1.test.slow();
274
+ _8_10_1.test.beforeAll(async () => {
275
+ if (!dataFiltersAvailable) {
276
+ return;
277
+ }
278
+ const status = await setFilters({
279
+ variableFilter: { enabled: true },
280
+ [OPTIMIZE_MODE_KEY]: true,
281
+ });
282
+ (0, test_1.expect)([200, 201, 204]).toContain(status);
283
+ await waitForFilterConfig((filters) => filters[OPTIMIZE_MODE_KEY] === true);
284
+ });
285
+ (0, _8_10_1.test)('assert process instance reached Optimize (implies Optimize mode passed required record types)', async ({ request, }) => {
286
+ skipIfUnavailable();
287
+ let baseline = 0;
288
+ await _8_10_1.test.step('capture the current imported instance count for the process definition', async () => {
289
+ baseline = (await fetchImported(request, OPTIMIZE_MODE_DEFINITION_KEY))
290
+ .instanceCount;
291
+ });
292
+ await _8_10_1.test.step('start a process instance', async () => {
293
+ const processKey = await (0, apiHelpers_1.deployProcess)(OPTIMIZE_MODE_DEFINITION_FILE, zeebeToken);
294
+ (0, test_1.expect)(processKey).not.toBeNull();
295
+ await (0, apiHelpers_1.createProcessInstance)(String(processKey), zeebeToken);
296
+ });
297
+ await _8_10_1.test.step('assert a new process instance reached Optimize (implies Optimize mode passed required record types)', async () => {
298
+ const imported = await waitForImportedInstance(request, OPTIMIZE_MODE_DEFINITION_KEY, (result) => result.instanceCount > baseline);
299
+ (0, test_1.expect)(imported.instanceCount).toBeGreaterThan(baseline);
300
+ });
301
+ });
302
+ });
@@ -17,6 +17,11 @@ _8_8_1.test.describe('User Roles User Flow', () => {
17
17
  'Admin Role User Flow': 'Admin Role Cluster',
18
18
  };
19
19
  _8_8_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage, clusterDetailsPage, appsPage }, testInfo) => {
20
+ // Claim the slow-test budget here rather than in each test body: this
21
+ // hook creates a cluster, toggles authorizations off and then polls for
22
+ // the cluster to return to Healthy, which on its own can outlast the
23
+ // default per-test timeout before the body ever starts.
24
+ _8_8_1.test.slow();
20
25
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
21
26
  await (0, test_1.expect)(homePage.clusterTab).toBeVisible({ timeout: 120000 });
22
27
  await homePage.clickClusters();
@@ -32,7 +37,6 @@ _8_8_1.test.describe('User Roles User Flow', () => {
32
37
  });
33
38
  //Skipped due to bug 39350: https://github.com/camunda/camunda/issues/39350
34
39
  _8_8_1.test.skip('Modeler Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, context, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
35
- _8_8_1.test.slow();
36
40
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
37
41
  const processName = 'User Roles User Flow' + randomString;
38
42
  const clusterName = clusterNames['Modeler Role User Flow'];
@@ -130,7 +134,6 @@ _8_8_1.test.describe('User Roles User Flow', () => {
130
134
  });
131
135
  });
132
136
  (0, _8_8_1.test)('Analyst Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, browser, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
133
- _8_8_1.test.slow();
134
137
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
135
138
  const processName = 'User Roles User Flow' + randomString;
136
139
  const clusterName = clusterNames['Analyst Role User Flow'];
@@ -221,7 +224,6 @@ _8_8_1.test.describe('User Roles User Flow', () => {
221
224
  });
222
225
  });
223
226
  (0, _8_8_1.test)('Admin Role User Flow', async ({ page, modelerHomePage, appsPage, homePage, clusterPage, clusterDetailsPage, browser, consoleOrganizationsPage, settingsPage, operateHomePage, ocIdentityHomePage, taskPanelPage, optimizeHomePage, modelerCreatePage, }) => {
224
- _8_8_1.test.slow();
225
227
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
226
228
  const processName = 'User Roles User Flow' + randomString;
227
229
  const clusterName = clusterNames['Admin Role User Flow'];
@@ -149,8 +149,7 @@ SM_8_9_1.test.describe('HTO User Flow Tests', () => {
149
149
  await (0, test_1.expect)(page.getByText('"testValue"')).not.toBeVisible();
150
150
  });
151
151
  });
152
- //Skipped due to bug 4920: https://github.com/camunda/camunda-platform-helm/issues/4920
153
- SM_8_9_1.test.skip('User Task Restrictions Enabled Flow - Candidate Group @tasklistV1', async ({ page, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessInstancePage, taskDetailsPage, taskPanelPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityHomePage, ocIdentityMappingRulesPage, ocIdentityRolesPage, ocIdentityGroupsPage, browser, }) => {
152
+ (0, SM_8_9_1.test)('User Task Restrictions Enabled Flow - Candidate Group @tasklistV1', async ({ page, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessInstancePage, taskDetailsPage, taskPanelPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityHomePage, ocIdentityMappingRulesPage, ocIdentityRolesPage, ocIdentityGroupsPage, browser, }) => {
154
153
  SM_8_9_1.test.slow();
155
154
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
156
155
  const userTaskName = 'candidateGroupTask' + randomString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.867",
3
+ "version": "0.0.869",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",