@camunda/e2e-test-suite 0.0.868 → 0.0.870
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|