@camunda/e2e-test-suite 0.0.982 → 0.0.984
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/SM-8.7/ModelerCreatePage.d.ts +3 -3
- package/dist/pages/SM-8.7/ModelerCreatePage.js +26 -8
- package/dist/pages/SM-8.7/TaskPanelPage.js +5 -1
- package/dist/pages/SM-8.7/UtlitiesPage.js +6 -1
- 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/tests/SM-8.10/mt-enabled-user-flows.spec.js +10 -5
- package/dist/utils/apiHelpers.d.ts +13 -0
- package/dist/utils/apiHelpers.js +124 -13
- package/dist/utils/gatewayErrors.d.ts +3 -0
- package/dist/utils/gatewayErrors.js +33 -0
- package/package.json +1 -1
|
@@ -101,9 +101,9 @@ declare class ModelerCreatePage {
|
|
|
101
101
|
clickSecondPlacedGateway(): Promise<void>;
|
|
102
102
|
private reloadAndWaitForCanvas;
|
|
103
103
|
/**
|
|
104
|
-
* Detects if the page has been redirected to
|
|
105
|
-
*
|
|
106
|
-
*
|
|
104
|
+
* Detects if the page has been redirected to a login page (session expiry)
|
|
105
|
+
* or is in the middle of an OIDC redirect, and performs a re-login if
|
|
106
|
+
* necessary.
|
|
107
107
|
*/
|
|
108
108
|
private handleSessionExpiry;
|
|
109
109
|
clickStartInstanceMainButton(): Promise<void>;
|
|
@@ -483,9 +483,9 @@ class ModelerCreatePage {
|
|
|
483
483
|
await element.waitFor({ state: 'visible', timeout: 120000 });
|
|
484
484
|
}
|
|
485
485
|
/**
|
|
486
|
-
* Detects if the page has been redirected to
|
|
487
|
-
*
|
|
488
|
-
*
|
|
486
|
+
* Detects if the page has been redirected to a login page (session expiry)
|
|
487
|
+
* or is in the middle of an OIDC redirect, and performs a re-login if
|
|
488
|
+
* necessary.
|
|
489
489
|
*/
|
|
490
490
|
async handleSessionExpiry() {
|
|
491
491
|
const currentUrl = this.page.url();
|
|
@@ -501,12 +501,30 @@ class ModelerCreatePage {
|
|
|
501
501
|
const loginPage = new LoginPage_1.LoginPage(this.page);
|
|
502
502
|
const keycloakBanner = this.page.locator('body#keycloak-bg[data-page-id="login-login"]');
|
|
503
503
|
const usernameField = this.page.locator('#username');
|
|
504
|
-
//
|
|
505
|
-
|
|
504
|
+
// `loginPage.usernameInput` covers the Camunda Identity form, which carries
|
|
505
|
+
// neither the Keycloak body id nor a `#username` input, so neither of the
|
|
506
|
+
// two locators above matches it.
|
|
507
|
+
const loginForm = keycloakBanner
|
|
506
508
|
.or(usernameField)
|
|
507
|
-
.
|
|
508
|
-
.
|
|
509
|
-
|
|
509
|
+
.or(loginPage.usernameInput)
|
|
510
|
+
.first();
|
|
511
|
+
// `Run` and the login form are mutually exclusive, so race them rather than
|
|
512
|
+
// sampling one: an expired session redirects through the OIDC callback
|
|
513
|
+
// after the reload has already resolved, so a point-in-time visibility
|
|
514
|
+
// check still sees the Modeler document and misses the login form that
|
|
515
|
+
// replaces it a moment later. The race also costs nothing on the happy
|
|
516
|
+
// path — it returns as soon as `Run` renders.
|
|
517
|
+
const landedOn = await Promise.race([
|
|
518
|
+
loginForm
|
|
519
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
520
|
+
.then(() => 'login')
|
|
521
|
+
.catch(() => 'unknown'),
|
|
522
|
+
this.startInstanceMainButton
|
|
523
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
524
|
+
.then(() => 'app')
|
|
525
|
+
.catch(() => 'unknown'),
|
|
526
|
+
]);
|
|
527
|
+
if (landedOn !== 'login') {
|
|
510
528
|
return;
|
|
511
529
|
}
|
|
512
530
|
console.warn('clickStartInstanceMainButton: session expired — performing re-login');
|
|
@@ -4,6 +4,7 @@ exports.TaskPanelPage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const constants_1 = require("../../utils/constants");
|
|
6
6
|
const sleep_1 = require("../../utils/sleep");
|
|
7
|
+
const gatewayErrors_1 = require("../../utils/gatewayErrors");
|
|
7
8
|
class TaskPanelPage {
|
|
8
9
|
page;
|
|
9
10
|
availableTasks;
|
|
@@ -39,6 +40,9 @@ class TaskPanelPage {
|
|
|
39
40
|
}
|
|
40
41
|
async openTask(name, assignButton = true) {
|
|
41
42
|
const maxAttempts = 10;
|
|
43
|
+
// Shared by every attempt so that a sustained Tasklist outage still fails
|
|
44
|
+
// this call rather than stretching it by ten times this budget.
|
|
45
|
+
const gatewayRecoveryDeadline = Date.now() + constants_1._1_SECOND_IN_MS * 60;
|
|
42
46
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
43
47
|
try {
|
|
44
48
|
await this.page.waitForLoadState('domcontentloaded');
|
|
@@ -56,7 +60,7 @@ class TaskPanelPage {
|
|
|
56
60
|
catch (error) {
|
|
57
61
|
if (attempt < maxAttempts) {
|
|
58
62
|
console.warn(`Attempt ${attempt} failed. Reloading page...`);
|
|
59
|
-
await this.page
|
|
63
|
+
await (0, gatewayErrors_1.reloadPastGatewayError)(this.page, gatewayRecoveryDeadline);
|
|
60
64
|
await this.page.waitForLoadState('domcontentloaded');
|
|
61
65
|
}
|
|
62
66
|
else {
|
|
@@ -4,6 +4,8 @@ exports.modelDiagramFromFile = exports.modelAndRunConnectorsTimerEventDiagram =
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
6
6
|
const sleep_1 = require("../../utils/sleep");
|
|
7
|
+
const gatewayErrors_1 = require("../../utils/gatewayErrors");
|
|
8
|
+
const constants_1 = require("../../utils/constants");
|
|
7
9
|
// Tasklist renders "No permission for Tasklist" as a terminal page: it reads the
|
|
8
10
|
// permissions off the token minted at login and never re-evaluates them, so a
|
|
9
11
|
// user whose Identity role assignment has not propagated yet stays stuck there.
|
|
@@ -61,6 +63,9 @@ async function createAndRunProcess(modelerHomePage, modelerCreatePage, processNa
|
|
|
61
63
|
}
|
|
62
64
|
exports.createAndRunProcess = createAndRunProcess;
|
|
63
65
|
async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskName, taskPriority, maxRetries = 5) {
|
|
66
|
+
// Shared by every attempt so that a sustained Tasklist outage still fails
|
|
67
|
+
// this call rather than stretching it by five times this budget.
|
|
68
|
+
const gatewayRecoveryDeadline = Date.now() + constants_1._1_SECOND_IN_MS * 60;
|
|
64
69
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
65
70
|
try {
|
|
66
71
|
await page.waitForLoadState('domcontentloaded');
|
|
@@ -98,7 +103,7 @@ async function completeTaskWithRetry(page, taskPanelPage, taskDetailsPage, taskN
|
|
|
98
103
|
catch (error) {
|
|
99
104
|
console.warn(`Attempt ${attempt + 1} failed for completing task ${taskName}:`, error instanceof Error ? error.message : error);
|
|
100
105
|
if (attempt < maxRetries - 1) {
|
|
101
|
-
await
|
|
106
|
+
await (0, gatewayErrors_1.reloadPastGatewayError)(page, gatewayRecoveryDeadline);
|
|
102
107
|
await page.waitForLoadState('domcontentloaded');
|
|
103
108
|
await (0, sleep_1.sleep)(1000);
|
|
104
109
|
}
|
|
@@ -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) => {
|
|
@@ -19,7 +19,8 @@ if (process.env.IS_MT === 'true') {
|
|
|
19
19
|
await (0, _setup_1.captureScreenshot)(page, testInfo);
|
|
20
20
|
await (0, _setup_1.captureFailureVideo)(page, testInfo);
|
|
21
21
|
});
|
|
22
|
-
|
|
22
|
+
// Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
|
|
23
|
+
SM_8_10_1.test.skip('Main User Can Create & Access Tenant & User Without Access Cannot - User Task Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, ocTenantPage, optimizeHomePage, optimizeDashboardPage, browser, ocIdentityHomePage, managementIdentityTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityMappingRulesPage, ocIdentityRolesPage, }) => {
|
|
23
24
|
SM_8_10_1.test.slow();
|
|
24
25
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
25
26
|
const processName = 'Main_User_Task_Flow_' + randomString;
|
|
@@ -127,7 +128,8 @@ if (process.env.IS_MT === 'true') {
|
|
|
127
128
|
}
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
|
-
|
|
131
|
+
// Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
|
|
132
|
+
SM_8_10_1.test.skip('Main User Can Create & Access Tenant - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
|
|
131
133
|
SM_8_10_1.test.slow();
|
|
132
134
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
133
135
|
const processName = 'Main_User_Connectors_Flow_' + randomString;
|
|
@@ -259,7 +261,8 @@ if (process.env.IS_MT === 'true') {
|
|
|
259
261
|
});
|
|
260
262
|
}
|
|
261
263
|
});
|
|
262
|
-
|
|
264
|
+
// Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
|
|
265
|
+
SM_8_10_1.test.skip('Second User Can Access Tenant Created By Main User - Connectors Flow @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, operateProcessInstancePage, operateProcessesPage, operateHomePage, navigationPage, managementIdentityTenantPage, connectorSettingsPage, connectorMarketplacePage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, managementIdentityPage, keycloakAdminPage, keycloakLoginPage, ocIdentityRolesPage, ocIdentityMappingRulesPage, browser, }) => {
|
|
263
266
|
SM_8_10_1.test.slow();
|
|
264
267
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
265
268
|
const processName = 'Second_User_Connectors_Flow_' + randomString;
|
|
@@ -330,7 +333,8 @@ if (process.env.IS_MT === 'true') {
|
|
|
330
333
|
});
|
|
331
334
|
}
|
|
332
335
|
});
|
|
333
|
-
|
|
336
|
+
// Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
|
|
337
|
+
SM_8_10_1.test.skip('Default Tenant Is Available And Used When Tenant Not Set In Deployment', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
|
|
334
338
|
SM_8_10_1.test.slow();
|
|
335
339
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
336
340
|
const processName = 'Default_Tenant_Flow_' + randomString;
|
|
@@ -466,7 +470,8 @@ if (process.env.IS_MT === 'true') {
|
|
|
466
470
|
await operateProcessInstancePage.tenantNameAssertion(tenantName2);
|
|
467
471
|
});
|
|
468
472
|
});
|
|
469
|
-
|
|
473
|
+
// Skipped due to bug #60836: https://github.com/camunda/camunda/issues/60836
|
|
474
|
+
SM_8_10_1.test.skip('Revoking Tenant Access User Flow', async ({ page, modelerHomePage, modelerCreatePage, taskPanelPage, operateProcessInstancePage, operateProcessesPage, taskDetailsPage, operateHomePage, navigationPage, managementIdentityTenantPage, optimizeHomePage, optimizeDashboardPage, ocIdentityHomePage, ocTenantPage, }) => {
|
|
470
475
|
SM_8_10_1.test.slow();
|
|
471
476
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
472
477
|
const processName1 = 'Revoke_Tenant_Flow_1' + randomString;
|
|
@@ -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,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reloadPastGatewayError = exports.isGatewayErrorPage = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const GATEWAY_ERROR_HEADING = /50[234]\s+(Bad Gateway|Service Temporarily Unavailable|Gateway Time-?out)/i;
|
|
6
|
+
async function isGatewayErrorPage(page) {
|
|
7
|
+
return page
|
|
8
|
+
.getByRole('heading', { name: GATEWAY_ERROR_HEADING })
|
|
9
|
+
.first()
|
|
10
|
+
.isVisible()
|
|
11
|
+
.catch(() => false);
|
|
12
|
+
}
|
|
13
|
+
exports.isGatewayErrorPage = isGatewayErrorPage;
|
|
14
|
+
// While a component's pod restarts, its ingress answers with an nginx 50x page
|
|
15
|
+
// that carries none of the app. A 5xx does not reject `page.reload`, so a retry
|
|
16
|
+
// loop that reloads after every failed attempt keeps re-rendering that error
|
|
17
|
+
// page and spends its whole attempt budget in seconds, long before the
|
|
18
|
+
// component is back. Reload until the component answers again, bounded by
|
|
19
|
+
// `deadline` so a sustained outage still fails the caller.
|
|
20
|
+
async function reloadPastGatewayError(page, deadline) {
|
|
21
|
+
for (let attempt = 1;; attempt++) {
|
|
22
|
+
const response = await page.reload({ waitUntil: 'domcontentloaded' });
|
|
23
|
+
const status = response?.status();
|
|
24
|
+
const unavailable = (status !== undefined && status >= 500) ||
|
|
25
|
+
(await isGatewayErrorPage(page));
|
|
26
|
+
if (!unavailable || Date.now() >= deadline) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.warn(`reloadPastGatewayError: ${page.url()} answered ${status} — waiting for the component to come back (attempt ${attempt})`);
|
|
30
|
+
await page.waitForTimeout(Math.min(constants_1._1_SECOND_IN_MS * 5 * attempt, deadline - Date.now()));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.reloadPastGatewayError = reloadPastGatewayError;
|