@camunda/e2e-test-suite 0.0.983 → 0.0.985
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.
- package/dist/pages/8.7/ModelerCreatePage.js +35 -1
- package/dist/pages/8.7/ModelerHomePage.d.ts +3 -0
- package/dist/pages/8.7/ModelerHomePage.js +43 -3
- package/dist/tests/8.10/optimize-api-tests.spec.js +45 -7
- package/dist/tests/8.10/optimize-collections-api.spec.js +11 -1
- package/dist/tests/8.10/optimize-exporter-filters-importer-e2e.spec.js +5 -1
- package/dist/tests/8.10/optimize-sharing-alerts-export-api.spec.js +29 -4
- package/dist/tests/8.10/optimize-sharing-alerts-export-ui.spec.js +15 -2
- package/dist/utils/apiHelpers.d.ts +13 -0
- package/dist/utils/apiHelpers.js +124 -13
- package/package.json +1 -1
|
@@ -589,8 +589,42 @@ class ModelerCreatePage {
|
|
|
589
589
|
async fillNamedInput(name) {
|
|
590
590
|
await this.nameInput.fill(name, { timeout: 60000 });
|
|
591
591
|
}
|
|
592
|
+
// The breadcrumb is clicked one second after the just-uploaded diagram is
|
|
593
|
+
// opened, while the hub is still importing the BPMN - the canvas shows a
|
|
594
|
+
// spinner and the properties panel is empty. Clicking into that still-loading
|
|
595
|
+
// editor either hangs Playwright's actionability/scroll step for the whole
|
|
596
|
+
// timeout (seen locally) or lands without opening the dropdown, so the
|
|
597
|
+
// follow-up "Rename" lookup times out instead (seen in the nightly). Wait for
|
|
598
|
+
// the properties panel to finish mounting, then retry until the dropdown is
|
|
599
|
+
// really open.
|
|
592
600
|
async clickDiagramBreadcrumb() {
|
|
593
|
-
await this.
|
|
601
|
+
await (0, test_1.expect)(this.generalPanel).toBeVisible({ timeout: 120000 });
|
|
602
|
+
const maxAttempts = 3;
|
|
603
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
604
|
+
// A second click would toggle an already-open dropdown shut, so never
|
|
605
|
+
// re-click when the previous attempt did open it.
|
|
606
|
+
const isOpen = await this.renameDiagramNameButton
|
|
607
|
+
.isVisible()
|
|
608
|
+
.catch(() => false);
|
|
609
|
+
if (isOpen) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
await this.diagramBreadcrumb.click({ timeout: 20000 });
|
|
614
|
+
await this.renameDiagramNameButton.waitFor({
|
|
615
|
+
state: 'visible',
|
|
616
|
+
timeout: 20000,
|
|
617
|
+
});
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
if (attempt === maxAttempts) {
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
console.warn(`Diagram breadcrumb dropdown did not open ` +
|
|
625
|
+
`(attempt ${attempt}/${maxAttempts}). Retrying.`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
594
628
|
}
|
|
595
629
|
async clickEditDiagramNameButton() {
|
|
596
630
|
await this.renameDiagramNameButton.click({ timeout: 60000 });
|
|
@@ -26,6 +26,8 @@ declare class ModelerHomePage {
|
|
|
26
26
|
readonly messageBanner: Locator;
|
|
27
27
|
readonly closeButton: Locator;
|
|
28
28
|
readonly formNameBreadcrumb: (formName: string) => Locator;
|
|
29
|
+
readonly formBreadcrumbButton: Locator;
|
|
30
|
+
readonly renameFormMenuItem: Locator;
|
|
29
31
|
constructor(page: Page);
|
|
30
32
|
dismissOverlays(): Promise<void>;
|
|
31
33
|
clickCreateNewProjectButton(): Promise<void>;
|
|
@@ -43,6 +45,7 @@ declare class ModelerHomePage {
|
|
|
43
45
|
clickIdpApplicationTemplateOption(): Promise<void>;
|
|
44
46
|
clickFormOption(): Promise<void>;
|
|
45
47
|
enterFormName(name: string): Promise<void>;
|
|
48
|
+
openFormRenameEditor(): Promise<void>;
|
|
46
49
|
clickProjectBreadcrumb(): Promise<void>;
|
|
47
50
|
clickHomeBreadcrumb(): Promise<void>;
|
|
48
51
|
clickOpenOrganizationsButton(): Promise<void>;
|
|
@@ -31,6 +31,8 @@ class ModelerHomePage {
|
|
|
31
31
|
messageBanner;
|
|
32
32
|
closeButton;
|
|
33
33
|
formNameBreadcrumb;
|
|
34
|
+
formBreadcrumbButton;
|
|
35
|
+
renameFormMenuItem;
|
|
34
36
|
constructor(page) {
|
|
35
37
|
this.page = page;
|
|
36
38
|
this.modelerPageBanner = page.getByRole('banner', {
|
|
@@ -77,6 +79,8 @@ class ModelerHomePage {
|
|
|
77
79
|
this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
|
|
78
80
|
hasText: formName,
|
|
79
81
|
});
|
|
82
|
+
this.formBreadcrumbButton = page.locator('button[data-test="breadcrumb-form"]');
|
|
83
|
+
this.renameFormMenuItem = page.locator('[data-test="rename-form"]');
|
|
80
84
|
}
|
|
81
85
|
async dismissOverlays() {
|
|
82
86
|
for (const locator of [
|
|
@@ -177,10 +181,46 @@ class ModelerHomePage {
|
|
|
177
181
|
async clickFormOption() {
|
|
178
182
|
await this.formTemplateOption.click();
|
|
179
183
|
}
|
|
184
|
+
// Right after a form is created the hub renders its breadcrumb name in
|
|
185
|
+
// inline-edit mode, but once the form store finishes loading it re-renders
|
|
186
|
+
// the breadcrumb as a plain dropdown button and the inline input is gone for
|
|
187
|
+
// good - Playwright then reports "element was detached from the DOM,
|
|
188
|
+
// retrying" until the click times out. The editor is re-openable through the
|
|
189
|
+
// breadcrumb's own Rename item (camunda-hub
|
|
190
|
+
// frontend/apps/hub/src/app/pages/form/Header.tsx), so fall back to that
|
|
191
|
+
// whenever the inline input is not there, and confirm the name landed.
|
|
180
192
|
async enterFormName(name) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
const maxAttempts = 3;
|
|
194
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
195
|
+
try {
|
|
196
|
+
const editorOpen = await this.formNameInput
|
|
197
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
198
|
+
.then(() => true)
|
|
199
|
+
.catch(() => false);
|
|
200
|
+
if (!editorOpen) {
|
|
201
|
+
await this.openFormRenameEditor();
|
|
202
|
+
}
|
|
203
|
+
await this.formNameInput.click({ timeout: 30000 });
|
|
204
|
+
await this.formNameInput.fill(name);
|
|
205
|
+
await this.formNameInput.press('Enter');
|
|
206
|
+
await this.formNameBreadcrumb(name).waitFor({
|
|
207
|
+
state: 'visible',
|
|
208
|
+
timeout: 30000,
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (attempt === maxAttempts) {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
console.warn(`Form rename to "${name}" did not commit ` +
|
|
217
|
+
`(attempt ${attempt}/${maxAttempts}). Retrying.`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async openFormRenameEditor() {
|
|
222
|
+
await this.formBreadcrumbButton.click({ timeout: 60000 });
|
|
223
|
+
await this.renameFormMenuItem.click({ timeout: 30000 });
|
|
184
224
|
}
|
|
185
225
|
async clickProjectBreadcrumb() {
|
|
186
226
|
await this.projectBreadcrumb.click({ timeout: 60000 });
|
|
@@ -33,6 +33,7 @@ const assertUnauthorizedResponseBody = async (response) => {
|
|
|
33
33
|
};
|
|
34
34
|
_8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
35
35
|
let optimizeCookie;
|
|
36
|
+
let csrfToken;
|
|
36
37
|
let optimizeBearerToken;
|
|
37
38
|
let collectionIdValue;
|
|
38
39
|
let dashboardIdValue;
|
|
@@ -41,16 +42,18 @@ _8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
|
41
42
|
let baseUrl;
|
|
42
43
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
43
44
|
const page = await browser.newPage();
|
|
44
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
45
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
45
46
|
await page.close();
|
|
46
47
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
47
48
|
collectionIdValue = await (0, apiHelpers_1.createCollection)(request, {
|
|
48
49
|
name: await (0, randomName_1.randomNameAgregator)('Test Collection'),
|
|
49
50
|
optimizeCookie,
|
|
51
|
+
csrfToken,
|
|
50
52
|
});
|
|
51
53
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
52
54
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
53
55
|
optimizeCookie,
|
|
56
|
+
csrfToken,
|
|
54
57
|
collectionId: collectionIdValue,
|
|
55
58
|
data: [
|
|
56
59
|
{
|
|
@@ -64,10 +67,12 @@ _8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
|
64
67
|
dashboardIdValue = await (0, apiHelpers_1.createDashboard)(request, {
|
|
65
68
|
name: dashboardNameValue,
|
|
66
69
|
optimizeCookie,
|
|
70
|
+
csrfToken,
|
|
67
71
|
collectionId: collectionIdValue,
|
|
68
72
|
});
|
|
69
73
|
reportId = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
70
74
|
optimizeCookie,
|
|
75
|
+
csrfToken,
|
|
71
76
|
collectionId: collectionIdValue,
|
|
72
77
|
name: 'Blank report',
|
|
73
78
|
definitions: [
|
|
@@ -189,6 +194,7 @@ _8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
|
189
194
|
const dashboardToDeleteId = await (0, apiHelpers_1.createDashboard)(request, {
|
|
190
195
|
name: await (0, randomName_1.randomNameAgregator)('Dashboard To Delete'),
|
|
191
196
|
optimizeCookie,
|
|
197
|
+
csrfToken,
|
|
192
198
|
collectionId: collectionIdValue,
|
|
193
199
|
});
|
|
194
200
|
await _8_10_1.test.step('Delete dashboard successfully (200)', async () => {
|
|
@@ -284,6 +290,7 @@ _8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
|
284
290
|
(0, _8_10_1.test)('Delete report and verify it is no longer retrievable', async ({ request, }) => {
|
|
285
291
|
const reportToDeleteId = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
286
292
|
optimizeCookie,
|
|
293
|
+
csrfToken,
|
|
287
294
|
collectionId: collectionIdValue,
|
|
288
295
|
name: await (0, randomName_1.randomNameAgregator)('Report To Delete'),
|
|
289
296
|
definitions: [
|
|
@@ -375,6 +382,7 @@ _8_10_1.test.describe('API optimize SaaS Tests', () => {
|
|
|
375
382
|
});
|
|
376
383
|
_8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
377
384
|
let optimizeCookie;
|
|
385
|
+
let csrfToken;
|
|
378
386
|
let optimizeBearerToken;
|
|
379
387
|
let conditionalEventsCollectionId;
|
|
380
388
|
let conditionalEventsReportId;
|
|
@@ -383,15 +391,17 @@ _8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
|
383
391
|
const CONDITIONAL_EVENTS_PROCESS_KEY = 'conditional-events-auto-process';
|
|
384
392
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
385
393
|
const page = await browser.newPage();
|
|
386
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
394
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
387
395
|
await page.close();
|
|
388
396
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
389
397
|
conditionalEventsCollectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
390
398
|
name: await (0, randomName_1.randomNameAgregator)('Conditional Events Collection'),
|
|
391
399
|
optimizeCookie,
|
|
400
|
+
csrfToken,
|
|
392
401
|
});
|
|
393
402
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
394
403
|
optimizeCookie,
|
|
404
|
+
csrfToken,
|
|
395
405
|
collectionId: conditionalEventsCollectionId,
|
|
396
406
|
data: [
|
|
397
407
|
{
|
|
@@ -404,6 +414,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
|
404
414
|
conditionalEventsReportName = await (0, randomName_1.randomNameAgregator)('Conditional Events Report');
|
|
405
415
|
conditionalEventsReportId = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
406
416
|
optimizeCookie,
|
|
417
|
+
csrfToken,
|
|
407
418
|
collectionId: conditionalEventsCollectionId,
|
|
408
419
|
name: conditionalEventsReportName,
|
|
409
420
|
definitions: [
|
|
@@ -428,6 +439,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
|
428
439
|
const response = await request.post(`${baseUrl}/api/report/process/single`, {
|
|
429
440
|
headers: {
|
|
430
441
|
Cookie: optimizeCookie,
|
|
442
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
431
443
|
'Content-Type': 'application/json',
|
|
432
444
|
},
|
|
433
445
|
data: {
|
|
@@ -507,6 +519,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
|
507
519
|
(0, _8_10_1.test)('CE-OPT-13: Deleting the Conditional Events report via API succeeds with HTTP 200', async ({ request, }) => {
|
|
508
520
|
const reportToDeleteId = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
509
521
|
optimizeCookie,
|
|
522
|
+
csrfToken,
|
|
510
523
|
collectionId: conditionalEventsCollectionId,
|
|
511
524
|
name: await (0, randomName_1.randomNameAgregator)('CE Report To Delete'),
|
|
512
525
|
definitions: [
|
|
@@ -527,6 +540,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Conditional Events', () => {
|
|
|
527
540
|
(0, _8_10_1.test)('CE-OPT-14: A deleted Conditional Events report is no longer retrievable via the API', async ({ request, }) => {
|
|
528
541
|
const reportToDeleteId = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
529
542
|
optimizeCookie,
|
|
543
|
+
csrfToken,
|
|
530
544
|
collectionId: conditionalEventsCollectionId,
|
|
531
545
|
name: await (0, randomName_1.randomNameAgregator)('CE Report Delete Verify'),
|
|
532
546
|
definitions: [
|
|
@@ -566,13 +580,13 @@ _8_10_1.test.describe('API optimize SaaS Tests - Health Readiness', () => {
|
|
|
566
580
|
(0, test_1.expect)(response.status()).toBe(200);
|
|
567
581
|
});
|
|
568
582
|
});
|
|
569
|
-
(0, _8_10_1.test)('Health readiness
|
|
570
|
-
await _8_10_1.test.step('GET /api/readyz with Authorization header
|
|
583
|
+
(0, _8_10_1.test)('Health readiness returns 200 when request includes an Authorization header', async ({ request, }) => {
|
|
584
|
+
await _8_10_1.test.step('GET /api/readyz with Authorization header still returns 200', async () => {
|
|
571
585
|
const token = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
572
586
|
const response = await request.get(`${baseUrl}/api/readyz`, {
|
|
573
587
|
headers: { Authorization: token },
|
|
574
588
|
});
|
|
575
|
-
(0, test_1.expect)(
|
|
589
|
+
(0, test_1.expect)(response.status()).toBe(200);
|
|
576
590
|
});
|
|
577
591
|
});
|
|
578
592
|
});
|
|
@@ -583,6 +597,15 @@ _8_10_1.test.describe('API optimize SaaS Tests - Sharing', () => {
|
|
|
583
597
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
584
598
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
585
599
|
});
|
|
600
|
+
// Sharing is cluster-global, so leaving it off would break every
|
|
601
|
+
// concurrently running spec that shares a report or dashboard.
|
|
602
|
+
_8_10_1.test.afterAll(async ({ request }) => {
|
|
603
|
+
await (0, apiHelpers_1.setOptimizeSharing)(request, {
|
|
604
|
+
baseUrl,
|
|
605
|
+
bearerToken: optimizeBearerToken,
|
|
606
|
+
enabled: true,
|
|
607
|
+
});
|
|
608
|
+
});
|
|
586
609
|
(0, _8_10_1.test)('Enable sharing with valid token returns 200 or 204', async ({ request, }) => {
|
|
587
610
|
await _8_10_1.test.step('POST /api/public/share/enable with valid token (200 or 204)', async () => {
|
|
588
611
|
const response = await request.post(`${baseUrl}/api/public/share/enable`, { headers: { Authorization: optimizeBearerToken } });
|
|
@@ -606,6 +629,13 @@ _8_10_1.test.describe('API optimize SaaS Tests - Sharing', () => {
|
|
|
606
629
|
const response = await request.post(`${baseUrl}/api/public/share/disable`, { headers: { Authorization: optimizeBearerToken } });
|
|
607
630
|
(0, test_1.expect)([200, 204]).toContain(response.status());
|
|
608
631
|
});
|
|
632
|
+
await _8_10_1.test.step('Re-enable sharing to restore the cluster-wide setting', async () => {
|
|
633
|
+
await (0, apiHelpers_1.setOptimizeSharing)(request, {
|
|
634
|
+
baseUrl,
|
|
635
|
+
bearerToken: optimizeBearerToken,
|
|
636
|
+
enabled: true,
|
|
637
|
+
});
|
|
638
|
+
});
|
|
609
639
|
});
|
|
610
640
|
(0, _8_10_1.test)('Disable sharing without token returns 401', async ({ request }) => {
|
|
611
641
|
await _8_10_1.test.step('POST /api/public/share/disable without token (401)', async () => {
|
|
@@ -637,17 +667,19 @@ _8_10_1.test.describe('API optimize SaaS Tests - Sharing', () => {
|
|
|
637
667
|
_8_10_1.test.describe('API optimize SaaS Tests - Dashboard and Report GET edge cases', () => {
|
|
638
668
|
let optimizeBearerToken;
|
|
639
669
|
let optimizeCookie;
|
|
670
|
+
let csrfToken;
|
|
640
671
|
let collectionId;
|
|
641
672
|
let baseUrl;
|
|
642
673
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
643
674
|
const page = await browser.newPage();
|
|
644
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
675
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
645
676
|
await page.close();
|
|
646
677
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
647
678
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
648
679
|
collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
649
680
|
name: await (0, randomName_1.randomNameAgregator)('GET Edge Cases Collection'),
|
|
650
681
|
optimizeCookie,
|
|
682
|
+
csrfToken,
|
|
651
683
|
});
|
|
652
684
|
});
|
|
653
685
|
_8_10_1.test.afterAll(async ({ request }) => {
|
|
@@ -695,6 +727,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Dashboard and Report GET edge c
|
|
|
695
727
|
_8_10_1.test.describe('API optimize SaaS Tests - Export edge cases', () => {
|
|
696
728
|
let optimizeBearerToken;
|
|
697
729
|
let optimizeCookie;
|
|
730
|
+
let csrfToken;
|
|
698
731
|
let collectionId;
|
|
699
732
|
let dashboardId1;
|
|
700
733
|
let dashboardId2;
|
|
@@ -703,26 +736,30 @@ _8_10_1.test.describe('API optimize SaaS Tests - Export edge cases', () => {
|
|
|
703
736
|
let baseUrl;
|
|
704
737
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
705
738
|
const page = await browser.newPage();
|
|
706
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
739
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
707
740
|
await page.close();
|
|
708
741
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
709
742
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
710
743
|
collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
711
744
|
name: await (0, randomName_1.randomNameAgregator)('Export Edge Cases Collection'),
|
|
712
745
|
optimizeCookie,
|
|
746
|
+
csrfToken,
|
|
713
747
|
});
|
|
714
748
|
dashboardId1 = await (0, apiHelpers_1.createDashboard)(request, {
|
|
715
749
|
name: await (0, randomName_1.randomNameAgregator)('Export Dashboard 1'),
|
|
716
750
|
optimizeCookie,
|
|
751
|
+
csrfToken,
|
|
717
752
|
collectionId,
|
|
718
753
|
});
|
|
719
754
|
dashboardId2 = await (0, apiHelpers_1.createDashboard)(request, {
|
|
720
755
|
name: await (0, randomName_1.randomNameAgregator)('Export Dashboard 2'),
|
|
721
756
|
optimizeCookie,
|
|
757
|
+
csrfToken,
|
|
722
758
|
collectionId,
|
|
723
759
|
});
|
|
724
760
|
reportId1 = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
725
761
|
optimizeCookie,
|
|
762
|
+
csrfToken,
|
|
726
763
|
collectionId,
|
|
727
764
|
name: await (0, randomName_1.randomNameAgregator)('Export Report 1'),
|
|
728
765
|
definitions: [
|
|
@@ -737,6 +774,7 @@ _8_10_1.test.describe('API optimize SaaS Tests - Export edge cases', () => {
|
|
|
737
774
|
});
|
|
738
775
|
reportId2 = await (0, apiHelpers_1.createSingleProcessReport)(request, {
|
|
739
776
|
optimizeCookie,
|
|
777
|
+
csrfToken,
|
|
740
778
|
collectionId,
|
|
741
779
|
name: await (0, randomName_1.randomNameAgregator)('Export Report 2'),
|
|
742
780
|
definitions: [
|
|
@@ -7,11 +7,12 @@ const randomName_1 = require("../../utils/randomName");
|
|
|
7
7
|
_8_10_1.test.describe.configure({ mode: 'parallel' });
|
|
8
8
|
_8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collections-api @8.10', () => {
|
|
9
9
|
let optimizeCookie;
|
|
10
|
+
let csrfToken;
|
|
10
11
|
let optimizeBearerToken;
|
|
11
12
|
let baseUrl;
|
|
12
13
|
_8_10_1.test.beforeAll(async ({ browser }) => {
|
|
13
14
|
const page = await browser.newPage();
|
|
14
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
15
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
15
16
|
await page.close();
|
|
16
17
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
17
18
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
@@ -21,6 +22,7 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
21
22
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
22
23
|
name: collectionName,
|
|
23
24
|
optimizeCookie,
|
|
25
|
+
csrfToken,
|
|
24
26
|
});
|
|
25
27
|
await _8_10_1.test.step('GET /api/collection/{id} returns the created collection (200)', async () => {
|
|
26
28
|
const response = await request.get(`${baseUrl}/api/collection/${collectionId}`, { headers: { Cookie: optimizeCookie } });
|
|
@@ -38,12 +40,14 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
38
40
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
39
41
|
name: originalName,
|
|
40
42
|
optimizeCookie,
|
|
43
|
+
csrfToken,
|
|
41
44
|
});
|
|
42
45
|
const renamedName = await (0, randomName_1.randomNameAgregator)('Collection Renamed');
|
|
43
46
|
await _8_10_1.test.step('PUT /api/collection/{id} updates the collection name', async () => {
|
|
44
47
|
const response = await request.put(`${baseUrl}/api/collection/${collectionId}`, {
|
|
45
48
|
headers: {
|
|
46
49
|
Cookie: optimizeCookie,
|
|
50
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
47
51
|
'Content-Type': 'application/json',
|
|
48
52
|
},
|
|
49
53
|
data: { name: renamedName },
|
|
@@ -65,11 +69,13 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
65
69
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
66
70
|
name: collectionName,
|
|
67
71
|
optimizeCookie,
|
|
72
|
+
csrfToken,
|
|
68
73
|
});
|
|
69
74
|
const definitionKey = 'customer_onboarding_en';
|
|
70
75
|
await _8_10_1.test.step('PUT /api/collection/{id}/scope adds a process definition', async () => {
|
|
71
76
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
72
77
|
optimizeCookie,
|
|
78
|
+
csrfToken,
|
|
73
79
|
collectionId,
|
|
74
80
|
data: [
|
|
75
81
|
{
|
|
@@ -97,6 +103,7 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
97
103
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
98
104
|
name: collectionName,
|
|
99
105
|
optimizeCookie,
|
|
106
|
+
csrfToken,
|
|
100
107
|
});
|
|
101
108
|
let deleteSucceeded = false;
|
|
102
109
|
await _8_10_1.test.step('DELETE /api/public/collection/{id} is accepted (200; SaaS may respond 500)', async () => {
|
|
@@ -126,6 +133,7 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
126
133
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
127
134
|
name: collectionName,
|
|
128
135
|
optimizeCookie,
|
|
136
|
+
csrfToken,
|
|
129
137
|
});
|
|
130
138
|
await _8_10_1.test.step('GET /api/collection/{id} without cookie (401)', async () => {
|
|
131
139
|
const response = await request.get(`${baseUrl}/api/collection/${collectionId}`);
|
|
@@ -140,6 +148,7 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
140
148
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
141
149
|
name: collectionName,
|
|
142
150
|
optimizeCookie,
|
|
151
|
+
csrfToken,
|
|
143
152
|
});
|
|
144
153
|
await _8_10_1.test.step('DELETE /api/public/collection/{id} without token (401)', async () => {
|
|
145
154
|
const response = await request.delete(`${baseUrl}/api/public/collection/${collectionId}`);
|
|
@@ -154,6 +163,7 @@ _8_10_1.test.describe('API Optimize Collections Lifecycle Tests @optimize-collec
|
|
|
154
163
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
155
164
|
name: collectionName,
|
|
156
165
|
optimizeCookie,
|
|
166
|
+
csrfToken,
|
|
157
167
|
});
|
|
158
168
|
await _8_10_1.test.step('DELETE /api/public/collection/{id} with invalid token (401)', async () => {
|
|
159
169
|
const response = await request.delete(`${baseUrl}/api/public/collection/${collectionId}`, { headers: { Authorization: 'Bearer invalid_token' } });
|
|
@@ -25,6 +25,7 @@ let originalFilters = null;
|
|
|
25
25
|
let dataFiltersAvailable = false;
|
|
26
26
|
let zeebeToken;
|
|
27
27
|
let optimizeCookie;
|
|
28
|
+
let csrfToken;
|
|
28
29
|
let optimizeExportToken;
|
|
29
30
|
let optimizeBaseUrl;
|
|
30
31
|
const createdCollectionIds = [];
|
|
@@ -81,10 +82,12 @@ const getOrCreateReport = async (request, definitionKey) => {
|
|
|
81
82
|
const collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
82
83
|
name: await (0, randomName_1.randomNameAgregator)('ExporterFilters E2E'),
|
|
83
84
|
optimizeCookie,
|
|
85
|
+
csrfToken,
|
|
84
86
|
});
|
|
85
87
|
createdCollectionIds.push(collectionId);
|
|
86
88
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
87
89
|
optimizeCookie,
|
|
90
|
+
csrfToken,
|
|
88
91
|
collectionId,
|
|
89
92
|
data: [
|
|
90
93
|
{
|
|
@@ -97,6 +100,7 @@ const getOrCreateReport = async (request, definitionKey) => {
|
|
|
97
100
|
const reportResponse = await request.post(`${optimizeBaseUrl}/api/report/process/single`, {
|
|
98
101
|
headers: {
|
|
99
102
|
Cookie: optimizeCookie,
|
|
103
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
100
104
|
'Content-Type': 'application/json',
|
|
101
105
|
},
|
|
102
106
|
data: {
|
|
@@ -184,7 +188,7 @@ _8_10_1.test.beforeAll(async ({ browser }) => {
|
|
|
184
188
|
optimizeExportToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
185
189
|
optimizeBaseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
186
190
|
const page = await browser.newPage();
|
|
187
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
191
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
188
192
|
await page.close();
|
|
189
193
|
}
|
|
190
194
|
catch (error) {
|
|
@@ -36,6 +36,7 @@ const createNumberReport = async (request, options) => {
|
|
|
36
36
|
const response = await request.post(`${options.baseUrl}/api/report/process/single`, {
|
|
37
37
|
headers: {
|
|
38
38
|
Cookie: options.optimizeCookie,
|
|
39
|
+
'X-CSRF-TOKEN': options.csrfToken,
|
|
39
40
|
'Content-Type': 'application/json',
|
|
40
41
|
},
|
|
41
42
|
data: {
|
|
@@ -67,6 +68,7 @@ const createRawDataReport = async (request, options) => {
|
|
|
67
68
|
const response = await request.post(`${options.baseUrl}/api/report/process/single`, {
|
|
68
69
|
headers: {
|
|
69
70
|
Cookie: options.optimizeCookie,
|
|
71
|
+
'X-CSRF-TOKEN': options.csrfToken,
|
|
70
72
|
'Content-Type': 'application/json',
|
|
71
73
|
},
|
|
72
74
|
data: {
|
|
@@ -96,6 +98,7 @@ const createRawDataReport = async (request, options) => {
|
|
|
96
98
|
};
|
|
97
99
|
_8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sharing-alerts-export-api @8.10', () => {
|
|
98
100
|
let optimizeCookie;
|
|
101
|
+
let csrfToken;
|
|
99
102
|
let optimizeBearerToken;
|
|
100
103
|
let baseUrl;
|
|
101
104
|
let collectionId;
|
|
@@ -104,16 +107,18 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
104
107
|
let dashboardId;
|
|
105
108
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
106
109
|
const page = await browser.newPage();
|
|
107
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
110
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(page));
|
|
108
111
|
await page.close();
|
|
109
112
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
110
113
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
111
114
|
collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
112
115
|
name: await (0, randomName_1.randomNameAgregator)('Sharing Alerts Collection'),
|
|
113
116
|
optimizeCookie,
|
|
117
|
+
csrfToken,
|
|
114
118
|
});
|
|
115
119
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
116
120
|
optimizeCookie,
|
|
121
|
+
csrfToken,
|
|
117
122
|
collectionId,
|
|
118
123
|
data: [
|
|
119
124
|
{
|
|
@@ -126,18 +131,21 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
126
131
|
numberReportId = await createNumberReport(request, {
|
|
127
132
|
baseUrl,
|
|
128
133
|
optimizeCookie,
|
|
134
|
+
csrfToken,
|
|
129
135
|
collectionId,
|
|
130
136
|
name: await (0, randomName_1.randomNameAgregator)('Number Report'),
|
|
131
137
|
});
|
|
132
138
|
rawDataReportId = await createRawDataReport(request, {
|
|
133
139
|
baseUrl,
|
|
134
140
|
optimizeCookie,
|
|
141
|
+
csrfToken,
|
|
135
142
|
collectionId,
|
|
136
143
|
name: await (0, randomName_1.randomNameAgregator)('Raw Data Report'),
|
|
137
144
|
});
|
|
138
145
|
dashboardId = await (0, apiHelpers_1.createDashboard)(request, {
|
|
139
146
|
name: await (0, randomName_1.randomNameAgregator)('Sharing Dashboard'),
|
|
140
147
|
optimizeCookie,
|
|
148
|
+
csrfToken,
|
|
141
149
|
collectionId,
|
|
142
150
|
});
|
|
143
151
|
});
|
|
@@ -188,10 +196,18 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
188
196
|
});
|
|
189
197
|
(0, _8_10_1.test)('Enable, evaluate and delete a report share', async ({ request }) => {
|
|
190
198
|
let reportShareId = '';
|
|
199
|
+
await _8_10_1.test.step('Ensure sharing is enabled cluster-wide', async () => {
|
|
200
|
+
await (0, apiHelpers_1.setOptimizeSharing)(request, {
|
|
201
|
+
baseUrl,
|
|
202
|
+
bearerToken: optimizeBearerToken,
|
|
203
|
+
enabled: true,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
191
206
|
await _8_10_1.test.step('POST /api/share/report enables sharing (200)', async () => {
|
|
192
207
|
const response = await request.post(`${baseUrl}/api/share/report`, {
|
|
193
208
|
headers: {
|
|
194
209
|
Cookie: optimizeCookie,
|
|
210
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
195
211
|
'Content-Type': 'application/json',
|
|
196
212
|
},
|
|
197
213
|
data: { reportId: numberReportId },
|
|
@@ -210,7 +226,7 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
210
226
|
(0, test_1.expect)([200, 201]).toContain(response.status());
|
|
211
227
|
});
|
|
212
228
|
await _8_10_1.test.step('DELETE /api/share/report/{shareId} removes the share', async () => {
|
|
213
|
-
const response = await request.delete(`${baseUrl}/api/share/report/${reportShareId}`, { headers: { Cookie: optimizeCookie } });
|
|
229
|
+
const response = await request.delete(`${baseUrl}/api/share/report/${reportShareId}`, { headers: { Cookie: optimizeCookie, 'X-CSRF-TOKEN': csrfToken } });
|
|
214
230
|
(0, test_1.expect)([200, 204]).toContain(response.status());
|
|
215
231
|
});
|
|
216
232
|
});
|
|
@@ -225,10 +241,18 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
225
241
|
});
|
|
226
242
|
(0, _8_10_1.test)('Enable and delete a dashboard share', async ({ request }) => {
|
|
227
243
|
let dashboardShareId = '';
|
|
244
|
+
await _8_10_1.test.step('Ensure sharing is enabled cluster-wide', async () => {
|
|
245
|
+
await (0, apiHelpers_1.setOptimizeSharing)(request, {
|
|
246
|
+
baseUrl,
|
|
247
|
+
bearerToken: optimizeBearerToken,
|
|
248
|
+
enabled: true,
|
|
249
|
+
});
|
|
250
|
+
});
|
|
228
251
|
await _8_10_1.test.step('POST /api/share/dashboard enables sharing (200)', async () => {
|
|
229
252
|
const response = await request.post(`${baseUrl}/api/share/dashboard`, {
|
|
230
253
|
headers: {
|
|
231
254
|
Cookie: optimizeCookie,
|
|
255
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
232
256
|
'Content-Type': 'application/json',
|
|
233
257
|
},
|
|
234
258
|
data: { dashboardId },
|
|
@@ -240,7 +264,7 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
240
264
|
(0, test_1.expect)(dashboardShareId.length).toBeGreaterThan(0);
|
|
241
265
|
});
|
|
242
266
|
await _8_10_1.test.step('DELETE /api/share/dashboard/{shareId} removes the share', async () => {
|
|
243
|
-
const response = await request.delete(`${baseUrl}/api/share/dashboard/${dashboardShareId}`, { headers: { Cookie: optimizeCookie } });
|
|
267
|
+
const response = await request.delete(`${baseUrl}/api/share/dashboard/${dashboardShareId}`, { headers: { Cookie: optimizeCookie, 'X-CSRF-TOKEN': csrfToken } });
|
|
244
268
|
(0, test_1.expect)([200, 204]).toContain(response.status());
|
|
245
269
|
});
|
|
246
270
|
});
|
|
@@ -251,6 +275,7 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
251
275
|
const response = await request.post(`${baseUrl}/api/alert`, {
|
|
252
276
|
headers: {
|
|
253
277
|
Cookie: optimizeCookie,
|
|
278
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
254
279
|
'Content-Type': 'application/json',
|
|
255
280
|
},
|
|
256
281
|
data: {
|
|
@@ -281,7 +306,7 @@ _8_10_1.test.describe('API Optimize Sharing, Alerts & Export Tests @optimize-sha
|
|
|
281
306
|
});
|
|
282
307
|
await _8_10_1.test.step('DELETE /api/alert/{id} removes the alert', async () => {
|
|
283
308
|
const response = await request.delete(`${baseUrl}/api/alert/${alertId}`, {
|
|
284
|
-
headers: { Cookie: optimizeCookie },
|
|
309
|
+
headers: { Cookie: optimizeCookie, 'X-CSRF-TOKEN': csrfToken },
|
|
285
310
|
});
|
|
286
311
|
(0, test_1.expect)([200, 204]).toContain(response.status());
|
|
287
312
|
});
|
|
@@ -14,6 +14,7 @@ _8_10_1.test.describe.configure({ mode: 'parallel' });
|
|
|
14
14
|
_8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-alerts-export-ui @8.10', () => {
|
|
15
15
|
let baseUrl;
|
|
16
16
|
let optimizeCookie;
|
|
17
|
+
let csrfToken;
|
|
17
18
|
let optimizeBearerToken;
|
|
18
19
|
let collectionId;
|
|
19
20
|
let reportId;
|
|
@@ -22,15 +23,17 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
|
|
|
22
23
|
_8_10_1.test.beforeAll(async ({ browser, request }) => {
|
|
23
24
|
baseUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
24
25
|
const seedPage = await browser.newPage();
|
|
25
|
-
optimizeCookie = await (0, apiHelpers_1.
|
|
26
|
+
({ optimizeCookie, csrfToken } = await (0, apiHelpers_1.loginToOptimizeSession)(seedPage));
|
|
26
27
|
await seedPage.close();
|
|
27
28
|
optimizeBearerToken = await (0, apiHelpers_1.authSaasAPI)(process.env.OPTIMIZE_API_TOKEN_AUDIENCE);
|
|
28
29
|
collectionId = await (0, apiHelpers_1.createCollection)(request, {
|
|
29
30
|
name: await (0, randomName_1.randomNameAgregator)('Sharing UI Collection'),
|
|
30
31
|
optimizeCookie,
|
|
32
|
+
csrfToken,
|
|
31
33
|
});
|
|
32
34
|
await (0, apiHelpers_1.updateCollectionScope)(request, {
|
|
33
35
|
optimizeCookie,
|
|
36
|
+
csrfToken,
|
|
34
37
|
collectionId,
|
|
35
38
|
data: [
|
|
36
39
|
{
|
|
@@ -47,6 +50,7 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
|
|
|
47
50
|
const reportResponse = await (0, apiHelpers_1.retryOn500)(() => request.post(`${baseUrl}/api/report/process/single`, {
|
|
48
51
|
headers: {
|
|
49
52
|
Cookie: optimizeCookie,
|
|
53
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
50
54
|
'Content-Type': 'application/json',
|
|
51
55
|
},
|
|
52
56
|
data: {
|
|
@@ -75,6 +79,7 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
|
|
|
75
79
|
dashboardId = await (0, apiHelpers_1.createDashboard)(request, {
|
|
76
80
|
name: await (0, randomName_1.randomNameAgregator)('Sharing UI Dashboard'),
|
|
77
81
|
optimizeCookie,
|
|
82
|
+
csrfToken,
|
|
78
83
|
collectionId,
|
|
79
84
|
});
|
|
80
85
|
// Optimize only renders the CSV download action on raw-data reports
|
|
@@ -85,6 +90,7 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
|
|
|
85
90
|
const csvReportResponse = await (0, apiHelpers_1.retryOn500)(() => request.post(`${baseUrl}/api/report/process/single`, {
|
|
86
91
|
headers: {
|
|
87
92
|
Cookie: optimizeCookie,
|
|
93
|
+
'X-CSRF-TOKEN': csrfToken,
|
|
88
94
|
'Content-Type': 'application/json',
|
|
89
95
|
},
|
|
90
96
|
data: {
|
|
@@ -111,7 +117,14 @@ _8_10_1.test.describe('Optimize Sharing & Export UI Tests @optimize-sharing-aler
|
|
|
111
117
|
(0, test_1.expect)(csvReportResponse.status()).toBe(200);
|
|
112
118
|
csvReportId = (await csvReportResponse.json()).id;
|
|
113
119
|
});
|
|
114
|
-
|
|
120
|
+
// The share button is rendered from ui-configuration, which the app reads once
|
|
121
|
+
// per page load, so the setting has to be right before we navigate.
|
|
122
|
+
_8_10_1.test.beforeEach(async ({ page, loginPage, request }, testInfo) => {
|
|
123
|
+
await (0, apiHelpers_1.setOptimizeSharing)(request, {
|
|
124
|
+
baseUrl,
|
|
125
|
+
bearerToken: optimizeBearerToken,
|
|
126
|
+
enabled: true,
|
|
127
|
+
});
|
|
115
128
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, (testInfo.workerIndex + 1) * 1000);
|
|
116
129
|
});
|
|
117
130
|
_8_10_1.test.afterEach(async ({ page }, testInfo) => {
|
|
@@ -35,26 +35,39 @@ interface ReportDefinition {
|
|
|
35
35
|
}
|
|
36
36
|
interface CreateSingleProcessReportOptions {
|
|
37
37
|
optimizeCookie: string;
|
|
38
|
+
csrfToken?: string;
|
|
38
39
|
collectionId: string;
|
|
39
40
|
name: string;
|
|
40
41
|
description?: string | null;
|
|
41
42
|
definitions?: ReportDefinition[];
|
|
42
43
|
configuration?: Record<string, unknown>;
|
|
43
44
|
}
|
|
45
|
+
export declare function loginToOptimizeSession(page: Page): Promise<{
|
|
46
|
+
optimizeCookie: string;
|
|
47
|
+
csrfToken: string;
|
|
48
|
+
}>;
|
|
44
49
|
export declare function getOptimizeCoockie(page: Page): Promise<string>;
|
|
45
50
|
export declare function getOptimizeCookieSm(page: Page): Promise<string>;
|
|
46
51
|
export declare function createCollection(request: APIRequestContext, options: {
|
|
47
52
|
name: string;
|
|
48
53
|
optimizeCookie: string;
|
|
54
|
+
csrfToken?: string;
|
|
49
55
|
}): Promise<string>;
|
|
50
56
|
export declare function createDashboard(request: APIRequestContext, options: {
|
|
51
57
|
name: string;
|
|
52
58
|
optimizeCookie: string;
|
|
59
|
+
csrfToken?: string;
|
|
53
60
|
collectionId?: string;
|
|
54
61
|
}): Promise<any>;
|
|
62
|
+
export declare function setOptimizeSharing(request: APIRequestContext, options: {
|
|
63
|
+
baseUrl: string;
|
|
64
|
+
bearerToken: string;
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
}): Promise<void>;
|
|
55
67
|
export declare function createSingleProcessReport(request: APIRequestContext, options: CreateSingleProcessReportOptions): Promise<any>;
|
|
56
68
|
export declare function updateCollectionScope(request: APIRequestContext, options: {
|
|
57
69
|
optimizeCookie: string;
|
|
70
|
+
csrfToken?: string;
|
|
58
71
|
collectionId: string;
|
|
59
72
|
data: Array<{
|
|
60
73
|
definitionKey: string;
|
package/dist/utils/apiHelpers.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.updateClusterDataFilters = exports.getClusterDataFilters = exports.getTestClusterUuid = exports.authConsoleManagementAPI = exports.updateCollectionScope = exports.createSingleProcessReport = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForOrchestrationClusterReady = exports.waitForInboundWebhookRegistered = exports.waitForConnectorsReady = exports.validateMcpServerHealth = exports.authC8runAPI = exports.authSmAPI = exports.authSaasAPI = exports.authAPI = exports.buildZeebeApiUrl = exports.retryOn500 = exports.sendRequestAndAssertResponse = exports.assertUnauthorizedResponseBody = exports.expectUnauthorizedErrorBody = exports.assertResponseStatus = exports.getApiRequestContext = void 0;
|
|
6
|
+
exports.updateClusterDataFilters = exports.getClusterDataFilters = exports.getTestClusterUuid = exports.authConsoleManagementAPI = exports.updateCollectionScope = exports.createSingleProcessReport = exports.setOptimizeSharing = exports.createDashboard = exports.createCollection = exports.getOptimizeCookieSm = exports.getOptimizeCoockie = exports.loginToOptimizeSession = exports.createStringClusterVariable = exports.createJsonClusterVariable = exports.waitForProcessInstanceVisible = exports.createProcessInstance = exports.deployProcess = exports.waitForOrchestrationClusterReady = exports.waitForInboundWebhookRegistered = exports.waitForConnectorsReady = exports.validateMcpServerHealth = exports.authC8runAPI = exports.authSmAPI = exports.authSaasAPI = exports.authAPI = exports.buildZeebeApiUrl = exports.retryOn500 = exports.sendRequestAndAssertResponse = exports.assertUnauthorizedResponseBody = exports.expectUnauthorizedErrorBody = exports.assertResponseStatus = exports.getApiRequestContext = void 0;
|
|
7
7
|
const test_1 = require("@playwright/test");
|
|
8
8
|
const sleep_1 = require("./sleep");
|
|
9
9
|
const randomSleep_1 = require("./randomSleep");
|
|
@@ -666,7 +666,50 @@ async function createStringClusterVariable(authToken, environment = 'saas', cust
|
|
|
666
666
|
await createClusterVariableInternal(authToken ?? '', environment, variableName, variableValue, 'String', clusterType);
|
|
667
667
|
}
|
|
668
668
|
exports.createStringClusterVariable = createStringClusterVariable;
|
|
669
|
-
|
|
669
|
+
// CSL (8.10+) protects writes with a CSRF token in addition to the session
|
|
670
|
+
// cookie (see optimize/client/src/modules/csrf.ts); the legacy security stack
|
|
671
|
+
// used by getOptimizeCoockie/getOptimizeCookieSm (8.8/8.9/SM) never issues
|
|
672
|
+
// one, so it stays undefined there and this header is simply omitted.
|
|
673
|
+
function optimizeCookieHeaders(options) {
|
|
674
|
+
const headers = { Cookie: options.optimizeCookie };
|
|
675
|
+
if (options.csrfToken)
|
|
676
|
+
headers['X-CSRF-TOKEN'] = options.csrfToken;
|
|
677
|
+
return headers;
|
|
678
|
+
}
|
|
679
|
+
const OPTIMIZE_SESSION_UNAUTHENTICATED = 'OPTIMIZE_SESSION_UNAUTHENTICATED';
|
|
680
|
+
// Verifying the session is part of logging in, not a wait that can be retried
|
|
681
|
+
// afterwards: on SaaS the OIDC callback (GET /{clusterId}/sso-callback) answers
|
|
682
|
+
// 401 for a share of concurrent logins, and because that 401 page is still
|
|
683
|
+
// served from the Optimize base URL, loginToApp's
|
|
684
|
+
// waitForURL(url.startsWith(appName), {waitUntil: 'commit'}) resolves on it and
|
|
685
|
+
// reports success. The session then stays unauthenticated for good -- no amount
|
|
686
|
+
// of polling recovers it, only a fresh login does. So probe an endpoint that is
|
|
687
|
+
// on the CSL API chain and let performOptimizeInteractiveLogin's retry loop
|
|
688
|
+
// start over when it comes back unauthenticated.
|
|
689
|
+
//
|
|
690
|
+
// The probe doubles as the CSRF-token source: CSL's CSRF setup is a
|
|
691
|
+
// double-submit cookie (Spring's CookieCsrfTokenRepository), so the first
|
|
692
|
+
// authenticated GET is what issues the X-CSRF-TOKEN cookie, echoing the token
|
|
693
|
+
// as an x-csrf-token response header. Returns that token when authenticated,
|
|
694
|
+
// null when not.
|
|
695
|
+
async function probeOptimizeSession(page) {
|
|
696
|
+
const optimizeUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
697
|
+
const response = await page
|
|
698
|
+
.context()
|
|
699
|
+
.request.get(`${optimizeUrl}/api/identity/current/user`);
|
|
700
|
+
// Require both a successful response AND a token, mirroring the product's
|
|
701
|
+
// own rule for when to trust the header (optimize/client/src/modules/csrf.ts
|
|
702
|
+
// storeCsrfToken): a non-2xx (a transient 5xx, or a 401/403 from a stale
|
|
703
|
+
// session) or a 2xx with no token yet must be treated as unauthenticated,
|
|
704
|
+
// not as "authenticated with an empty token" -- the latter would exit the
|
|
705
|
+
// login retry loop with a cookie that then fails every write.
|
|
706
|
+
const token = response.headers()['x-csrf-token'];
|
|
707
|
+
if (!response.ok() || !token) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
return token;
|
|
711
|
+
}
|
|
712
|
+
async function performOptimizeInteractiveLogin(page, callerLabel, verifySession) {
|
|
670
713
|
const optimizeUrl = process.env.CAMUNDA_OPTIMIZE_BASE_URL;
|
|
671
714
|
const maxLoginAttempts = 4;
|
|
672
715
|
// This hook runs once per worker that picks up any test from an Optimize
|
|
@@ -675,7 +718,13 @@ async function getOptimizeCoockie(page) {
|
|
|
675
718
|
// spread that burst -- it is enough on its own to trip Auth0's tenant-wide
|
|
676
719
|
// rate limit, which then fails every worker at once. Retries back off via
|
|
677
720
|
// randomSleep below, so this only needs to run once.
|
|
678
|
-
|
|
721
|
+
//
|
|
722
|
+
// The window has to be wider than a rejection burst, not just wider than the
|
|
723
|
+
// instant the workers start: 8.10 has eight Optimize describes each doing one
|
|
724
|
+
// interactive login, and the sso-callback rejections below come in bursts
|
|
725
|
+
// lasting tens of seconds, so a 15s spread put every login inside the same
|
|
726
|
+
// burst and the losers then spent their whole attempt budget in it.
|
|
727
|
+
await (0, randomSleep_1.randomSleep)(0, 60000);
|
|
679
728
|
// Auth0 can fail or rate-limit concurrent logins. Retry the whole flow if
|
|
680
729
|
// loginToApp throws (e.g. waitForURL times out after a bad Auth0 redirect).
|
|
681
730
|
for (let loginAttempt = 0; loginAttempt < maxLoginAttempts; loginAttempt++) {
|
|
@@ -683,12 +732,16 @@ async function getOptimizeCoockie(page) {
|
|
|
683
732
|
await page.goto('about:blank');
|
|
684
733
|
await page.context().clearCookies();
|
|
685
734
|
await (0, utils_1.loginToApp)(optimizeUrl, page);
|
|
686
|
-
|
|
735
|
+
if (verifySession && !(await verifySession(page))) {
|
|
736
|
+
throw new Error(OPTIMIZE_SESSION_UNAUTHENTICATED);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
687
739
|
}
|
|
688
740
|
catch (error) {
|
|
689
741
|
if (loginAttempt < maxLoginAttempts - 1) {
|
|
690
742
|
const rateLimited = String(error).includes(utils_1.AUTH0_RATE_LIMIT);
|
|
691
|
-
|
|
743
|
+
const unauthenticated = String(error).includes(OPTIMIZE_SESSION_UNAUTHENTICATED);
|
|
744
|
+
console.warn(`${callerLabel}: login attempt ${loginAttempt + 1} failed, retrying after backoff...`, error);
|
|
692
745
|
await page.goto('about:blank');
|
|
693
746
|
if (rateLimited) {
|
|
694
747
|
// Auth0 rate-limit windows commonly last 30-90s. Back off long
|
|
@@ -696,6 +749,19 @@ async function getOptimizeCoockie(page) {
|
|
|
696
749
|
// the same window and burns the attempt budget for nothing.
|
|
697
750
|
await (0, randomSleep_1.randomSleep)(60000, 90000);
|
|
698
751
|
}
|
|
752
|
+
else if (unauthenticated) {
|
|
753
|
+
// The rejected callbacks come in bursts lasting tens of seconds
|
|
754
|
+
// (every login inside the burst window fails, isolated ones all
|
|
755
|
+
// succeed), so back off past the burst rather than retrying inside
|
|
756
|
+
// it. Widen the band on each attempt instead of re-drawing from a
|
|
757
|
+
// fixed one: every worker caught by the same burst backs off from
|
|
758
|
+
// the same distribution at the same moment, so a fixed band keeps
|
|
759
|
+
// the herd synchronised and they re-collide on the next attempt.
|
|
760
|
+
// Growing the ceiling fans them out while the floor still lets a
|
|
761
|
+
// lucky worker recover quickly. Worst case stays well inside the
|
|
762
|
+
// 12-minute hook timeout.
|
|
763
|
+
await (0, randomSleep_1.randomSleep)(20000, 45000 + loginAttempt * 45000);
|
|
764
|
+
}
|
|
699
765
|
else {
|
|
700
766
|
await (0, randomSleep_1.randomSleep)(10000, 20000);
|
|
701
767
|
}
|
|
@@ -705,6 +771,38 @@ async function getOptimizeCoockie(page) {
|
|
|
705
771
|
}
|
|
706
772
|
}
|
|
707
773
|
}
|
|
774
|
+
}
|
|
775
|
+
// 8.10+ (CSL) replaces the legacy X-Optimize-* cookies below with its own
|
|
776
|
+
// stateful OIDC webapp session (ADR-0038:
|
|
777
|
+
// https://github.com/camunda/camunda-security-library/blob/main/docs/adr/0038-optimize-reuses-stateful-oidc-webapp-chain.md).
|
|
778
|
+
// The session cookie is just as portable as the legacy one -- extract it as a
|
|
779
|
+
// plain string (and the accompanying CSRF token) and the caller can close the
|
|
780
|
+
// page immediately, exactly like getOptimizeCoockie below.
|
|
781
|
+
async function loginToOptimizeSession(page) {
|
|
782
|
+
// A write is only valid if the X-CSRF-TOKEN cookie AND a matching header are
|
|
783
|
+
// both present, and it is the probe inside the login that issues that cookie
|
|
784
|
+
// -- so the probe has to run before cookies() is read here, which it does.
|
|
785
|
+
let csrfToken = '';
|
|
786
|
+
await performOptimizeInteractiveLogin(page, 'loginToOptimizeSession', async (loggedInPage) => {
|
|
787
|
+
const token = await probeOptimizeSession(loggedInPage);
|
|
788
|
+
if (token === null) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
csrfToken = token;
|
|
792
|
+
return true;
|
|
793
|
+
});
|
|
794
|
+
// Scoped to optimizeUrl: an unscoped cookies() call returns the whole
|
|
795
|
+
// browser context jar, including cookies set on the Auth0/Console origins
|
|
796
|
+
// visited during the interactive login, which would otherwise get flattened
|
|
797
|
+
// into a Cookie header Optimize never asked for.
|
|
798
|
+
const optimizeCookie = (await page.context().cookies(process.env.CAMUNDA_OPTIMIZE_BASE_URL))
|
|
799
|
+
.map((cookie) => `${cookie.name}=${cookie.value}`)
|
|
800
|
+
.join('; ');
|
|
801
|
+
return { optimizeCookie, csrfToken };
|
|
802
|
+
}
|
|
803
|
+
exports.loginToOptimizeSession = loginToOptimizeSession;
|
|
804
|
+
async function getOptimizeCoockie(page) {
|
|
805
|
+
await performOptimizeInteractiveLogin(page, 'getOptimizeCoockie');
|
|
708
806
|
const context = page.context();
|
|
709
807
|
// Optimize sets its session cookies asynchronously after the Auth0 redirect.
|
|
710
808
|
// Poll storage state until both cookies are present (up to ~30 s).
|
|
@@ -782,9 +880,7 @@ exports.getOptimizeCookieSm = getOptimizeCookieSm;
|
|
|
782
880
|
async function createCollection(request, options) {
|
|
783
881
|
return retryOnTransientApiError(async () => {
|
|
784
882
|
const optimizeResponse = await request.post(`${process.env.CAMUNDA_OPTIMIZE_BASE_URL}/api/collection`, {
|
|
785
|
-
headers:
|
|
786
|
-
Cookie: options.optimizeCookie,
|
|
787
|
-
},
|
|
883
|
+
headers: optimizeCookieHeaders(options),
|
|
788
884
|
data: {
|
|
789
885
|
name: options.name,
|
|
790
886
|
},
|
|
@@ -798,9 +894,7 @@ exports.createCollection = createCollection;
|
|
|
798
894
|
async function createDashboard(request, options) {
|
|
799
895
|
return retryOnTransientApiError(async () => {
|
|
800
896
|
const optimizeResponse = await request.post(`${process.env.CAMUNDA_OPTIMIZE_BASE_URL}/api/dashboard`, {
|
|
801
|
-
headers:
|
|
802
|
-
Cookie: options.optimizeCookie,
|
|
803
|
-
},
|
|
897
|
+
headers: optimizeCookieHeaders(options),
|
|
804
898
|
data: {
|
|
805
899
|
collectionId: options.collectionId,
|
|
806
900
|
name: options.name,
|
|
@@ -819,6 +913,23 @@ async function createDashboard(request, options) {
|
|
|
819
913
|
}, 'createDashboard');
|
|
820
914
|
}
|
|
821
915
|
exports.createDashboard = createDashboard;
|
|
916
|
+
// Sharing is a cluster-global Optimize setting, not a per-entity one: with it
|
|
917
|
+
// off, POST /api/share/{report,dashboard} answers 500 (SharingNotAllowedException
|
|
918
|
+
// has no exception mapper) and the UI renders the share button disabled. Specs
|
|
919
|
+
// that toggle it therefore have to restore it, and specs that depend on it have
|
|
920
|
+
// to assert the precondition themselves -- otherwise they see each other's
|
|
921
|
+
// writes when they run concurrently.
|
|
922
|
+
async function setOptimizeSharing(request, options) {
|
|
923
|
+
const action = options.enabled ? 'enable' : 'disable';
|
|
924
|
+
return retryOnTransientApiError(async () => {
|
|
925
|
+
const response = await request.post(`${options.baseUrl}/api/public/share/${action}`, { headers: { Authorization: options.bearerToken } });
|
|
926
|
+
if (![200, 204].includes(response.status())) {
|
|
927
|
+
throw new Error(`Expected 200 or 204 from /api/public/share/${action}, Received: ` +
|
|
928
|
+
`${response.status()} ${await response.text()}`);
|
|
929
|
+
}
|
|
930
|
+
}, `setOptimizeSharing(${action})`);
|
|
931
|
+
}
|
|
932
|
+
exports.setOptimizeSharing = setOptimizeSharing;
|
|
822
933
|
async function createSingleProcessReport(request, options) {
|
|
823
934
|
// Default configuration (can be overridden)
|
|
824
935
|
const defaultConfiguration = {
|
|
@@ -880,7 +991,7 @@ async function createSingleProcessReport(request, options) {
|
|
|
880
991
|
return retryOnTransientApiError(async () => {
|
|
881
992
|
const response = await request.post(`${process.env.CAMUNDA_OPTIMIZE_BASE_URL}/api/report/process/single`, {
|
|
882
993
|
headers: {
|
|
883
|
-
|
|
994
|
+
...optimizeCookieHeaders(options),
|
|
884
995
|
'Content-Type': 'application/json',
|
|
885
996
|
},
|
|
886
997
|
data: payload,
|
|
@@ -899,7 +1010,7 @@ async function updateCollectionScope(request, options) {
|
|
|
899
1010
|
return retryOnTransientApiError(async () => {
|
|
900
1011
|
const response = await request.put(url, {
|
|
901
1012
|
headers: {
|
|
902
|
-
|
|
1013
|
+
...optimizeCookieHeaders(options),
|
|
903
1014
|
'Content-Type': 'application/json',
|
|
904
1015
|
},
|
|
905
1016
|
data: options.data,
|