@camunda/e2e-test-suite 0.0.1175 → 0.0.1177
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.10/ConsoleOrganizationPage.js +6 -2
- package/dist/tests/8.10/optimize-agentic-control-panel-api.spec.js +10 -0
- package/dist/tests/8.10/optimize-data-filters-api.spec.js +10 -0
- package/dist/tests/8.10/optimize-data-filters-ui.spec.js +7 -0
- package/dist/tests/8.10/optimize-exporter-filters-importer-e2e.spec.js +10 -0
- package/dist/tests/8.10/test-setup.spec.js +4 -0
- package/dist/tests/8.10/web-modeler-user-flows.spec.js +39 -3
- package/dist/utils/consoleApiHelpers.d.ts +2 -0
- package/dist/utils/consoleApiHelpers.js +47 -1
- package/package.json +1 -1
|
@@ -256,8 +256,12 @@ class ConsoleOrganizationPage {
|
|
|
256
256
|
});
|
|
257
257
|
const alphaFeature = this.rows.filter({ hasText: name }).first();
|
|
258
258
|
if ((await alphaFeature.count()) < 1) {
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
// Returning here used to report the feature as enabled while nothing
|
|
260
|
+
// had been enabled at all, so the org silently stayed opted out and
|
|
261
|
+
// every dependent spec failed later on a missing UI affordance.
|
|
262
|
+
// Throw instead: the surrounding loop reloads and retries, and a
|
|
263
|
+
// genuinely absent row fails this step where the cause is visible.
|
|
264
|
+
throw new Error(`No alpha feature(${name}) is found.`);
|
|
261
265
|
}
|
|
262
266
|
//Opt-in The Alpha Feature
|
|
263
267
|
const alphaFeatureOptInButton = alphaFeature.getByRole('button', {
|
|
@@ -8,6 +8,16 @@ const users_1 = require("../../utils/users");
|
|
|
8
8
|
const agenticDashboardHelpers_1 = require("../../utils/agenticDashboardHelpers");
|
|
9
9
|
const testUser = (0, users_1.getTestUser)('twentyFirstUser');
|
|
10
10
|
const clusterName = 'Test Cluster';
|
|
11
|
+
// No reason for serial has been established here, and it is kept rather than
|
|
12
|
+
// flipped only because nobody has measured either way. All five tests are
|
|
13
|
+
// read-only against the agentic dashboard (the one interaction,
|
|
14
|
+
// changeDashboardDateRange, is a view-local Carbon dropdown), so there is no
|
|
15
|
+
// shared state to protect. Serial does NOT cut login volume either: beforeEach
|
|
16
|
+
// runs per test, so it is five logins as twentyFirstUser either way, and that
|
|
17
|
+
// user is also used by navigation, optimize-agentic-control-panel-ui and
|
|
18
|
+
// saas-migration-path-user-flows, none of which are serial. Rate-limit hits are
|
|
19
|
+
// absorbed by loginWithRetry and have never been traced to a failure
|
|
20
|
+
// (docs/saas-login-investigation.md). See docs/serial-specs.md.
|
|
11
21
|
_8_10_1.test.describe.configure({ mode: 'serial' });
|
|
12
22
|
_8_10_1.test.describe('Agentic Control Plane Dashboard API Tests @agentic-dashboard-api @8.10', () => {
|
|
13
23
|
_8_10_1.test.beforeEach(async ({ page, loginPage }, testInfo) => {
|
|
@@ -4,6 +4,16 @@ const test_1 = require("@playwright/test");
|
|
|
4
4
|
const _8_10_1 = require("../../fixtures/8.10");
|
|
5
5
|
const apiHelpers_1 = require("../../utils/apiHelpers");
|
|
6
6
|
const sleep_1 = require("../../utils/sleep");
|
|
7
|
+
// Serial is load-bearing twice over. Ordering: T11728029 asserts a business_*
|
|
8
|
+
// pattern is present in the default, untouched includeVariableNames, and the
|
|
9
|
+
// three variableFilter writers below (T11728020, T11728034, T11735087) replace
|
|
10
|
+
// that list -- so T11728029 has to run before them, which parallel mode cannot
|
|
11
|
+
// guarantee. (T11735088/T11735089 write processDefinitionFilter instead, and
|
|
12
|
+
// T11728030/T11728019 write nothing.) Snapshot:
|
|
13
|
+
// beforeAll reads the live config into originalFilters and afterAll writes it
|
|
14
|
+
// back; in parallel each worker runs beforeAll, so a worker starting second
|
|
15
|
+
// would snapshot another worker's mutation and restore that as "original",
|
|
16
|
+
// leaving the shared cluster dirty after the run. See docs/serial-specs.md.
|
|
7
17
|
_8_10_1.test.describe.configure({ mode: 'serial' });
|
|
8
18
|
let managementToken;
|
|
9
19
|
let clusterUuid;
|
|
@@ -17,6 +17,13 @@ let dataFiltersAvailable = false;
|
|
|
17
17
|
const skipIfUnavailable = () => {
|
|
18
18
|
_8_10_1.test.skip(!dataFiltersAvailable, 'Data Filters section is not available on this cluster (feature not yet enabled)');
|
|
19
19
|
};
|
|
20
|
+
// Serial is load-bearing: these 18 tests drive the same Console Data Filters
|
|
21
|
+
// form for the same cluster, and several of them Save and reload to assert
|
|
22
|
+
// persistence -- the same whole-config write the API spec issues, just from the
|
|
23
|
+
// browser. The Read-only operator describe also asserts the fields are not
|
|
24
|
+
// editable, which only holds if nobody else is mid-edit. The client-side
|
|
25
|
+
// negative tests would survive parallelising on their own, but they share the
|
|
26
|
+
// form with the ones that save. See docs/serial-specs.md.
|
|
20
27
|
_8_10_1.test.describe.configure({ mode: 'serial' });
|
|
21
28
|
_8_10_1.test.describe('Optimize Data Filters UI @optimize-data-filters-ui @8.10', () => {
|
|
22
29
|
_8_10_1.test.slow();
|
|
@@ -5,6 +5,16 @@ const _8_10_1 = require("../../fixtures/8.10");
|
|
|
5
5
|
const apiHelpers_1 = require("../../utils/apiHelpers");
|
|
6
6
|
const randomName_1 = require("../../utils/randomName");
|
|
7
7
|
const sleep_1 = require("../../utils/sleep");
|
|
8
|
+
// Serial is load-bearing: the two describes below each request a DIFFERENT
|
|
9
|
+
// exporter setting on the same cluster-wide singleton in beforeAll
|
|
10
|
+
// (variableValueTypeExclusion: ['Object'] vs optimizeModeEnabled: true), then
|
|
11
|
+
// assert on what Optimize actually imported over a ~180s poll. These are
|
|
12
|
+
// PARTIAL updates, not whole configs -- setFilters below re-reads the live
|
|
13
|
+
// config and sends {...current, ...config}, so in declaration order the second
|
|
14
|
+
// beforeAll inherits ['Object'] from the first. Serial order is what makes that
|
|
15
|
+
// inheritance deterministic; run concurrently, each beforeAll read-modify-writes
|
|
16
|
+
// on top of whatever the other left behind and which settings are active during
|
|
17
|
+
// an assertion depends on interleaving. See docs/serial-specs.md.
|
|
8
18
|
_8_10_1.test.describe.configure({ mode: 'serial' });
|
|
9
19
|
const VARIABLE_DEFINITION_FILE = './resources/Job_Worker_Process.bpmn';
|
|
10
20
|
const VARIABLE_DEFINITION_KEY = 'Job_Worker_Process';
|
|
@@ -25,6 +25,10 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
25
25
|
await homePage.clickOrganization();
|
|
26
26
|
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(page, consoleOrganizationsPage.settingsTab, 'settings tab', 60000, false, 2);
|
|
27
27
|
await consoleOrganizationsPage.enableAlphaFeature('AI-powered features');
|
|
28
|
+
// Confirm the opt-in against Accounts rather than trusting the Console
|
|
29
|
+
// toggle: the AI specs depend on this flag, and a setup that passes without
|
|
30
|
+
// it leaves them failing on a button Web Modeler never renders.
|
|
31
|
+
await (0, consoleApiHelpers_1.waitForAiFeaturesEnabledViaApi)(page);
|
|
28
32
|
});
|
|
29
33
|
(0, _8_10_1.test)('Create Default Cluster', async ({ page, loginPage, homePage, clusterPage, clusterDetailsPage, clusterSecretsPage, }, testInfo) => {
|
|
30
34
|
_8_10_1.test.slow();
|
|
@@ -11,6 +11,7 @@ const OptimizeDashboardPage_1 = require("../../pages/8.10/OptimizeDashboardPage"
|
|
|
11
11
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
12
12
|
const _setup_1 = require("../../test-setup.js");
|
|
13
13
|
const UtilitiesPage_1 = require("../../pages/8.10/UtilitiesPage");
|
|
14
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
14
15
|
const deleteOrg_1 = require("../../utils/deleteOrg");
|
|
15
16
|
const sleep_1 = require("../../utils/sleep");
|
|
16
17
|
const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
|
|
@@ -34,11 +35,25 @@ _8_10_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
34
35
|
await (0, mailSlurpClient_1.deleteInbox)(inboxes[testInfo.title]);
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
|
-
(0, _8_10_1.test)('Form.js Integration with User Task and AI Generated Form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
|
|
38
|
+
(0, _8_10_1.test)('Form.js Integration with User Task and AI Generated Form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, consoleOrganizationsPage, }) => {
|
|
38
39
|
_8_10_1.test.slow();
|
|
39
40
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
40
41
|
const processName = 'User_Task_Process_With_AI_Form' + randomString;
|
|
41
42
|
const formName = 'New form' + randomString;
|
|
43
|
+
// Web Modeler renders the AI Form Generator only while the org carries the
|
|
44
|
+
// 'aifeatures' early-access flag, which the 'Enable Alpha Features' setup
|
|
45
|
+
// test is responsible for. When that opt-in does not take, the palette
|
|
46
|
+
// button is simply absent and this test times out on it with nothing
|
|
47
|
+
// pointing at the org. Confirm the prerequisite here, and opt in when it is
|
|
48
|
+
// missing, so the flow no longer depends on setup having succeeded silently.
|
|
49
|
+
await _8_10_1.test.step('Ensure AI Features Are Enabled For The Organization', async () => {
|
|
50
|
+
if (await (0, consoleApiHelpers_1.areAiFeaturesEnabledViaApi)(page)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
await homePage.clickOrganization();
|
|
54
|
+
await consoleOrganizationsPage.enableAlphaFeature('AI-powered features');
|
|
55
|
+
await (0, consoleApiHelpers_1.waitForAiFeaturesEnabledViaApi)(page);
|
|
56
|
+
});
|
|
42
57
|
await _8_10_1.test.step('Navigate to Web Modeler', async () => {
|
|
43
58
|
await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
|
|
44
59
|
timeout: 120000,
|
|
@@ -70,12 +85,27 @@ _8_10_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
70
85
|
await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
|
|
71
86
|
timeout: 180000,
|
|
72
87
|
});
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
// Latch the Operate instance deep link on the CONTEXT before the click.
|
|
89
|
+
// Playwright raises `popup` only once the new tab's initial response has
|
|
90
|
+
// started loading, and a session-less Operate tab's auth redirect chain
|
|
91
|
+
// drops the instance path before anything commits -- so no listener
|
|
92
|
+
// attached to the popup afterwards ever sees that URL. Without it
|
|
93
|
+
// `waitForInstanceLocator` has nothing to re-open and can only poll
|
|
94
|
+
// whatever the tab was left on, where the instance page can never
|
|
95
|
+
// render: "Assertion of Instance History panel in Operate timed out
|
|
96
|
+
// after 480000ms (instance header not rendered,
|
|
97
|
+
// url=<cluster>/operate?continue, instance deep link never captured)".
|
|
98
|
+
// `openAppTabWithRetry` also registers the popup wait BEFORE the click,
|
|
99
|
+
// which `waitForEvent` after the click cannot, and re-clicks a click
|
|
100
|
+
// that resolved without opening a tab. Same pattern as
|
|
101
|
+
// tests/8.10/connectors-user-flows.spec.ts and smoke-tests.spec.ts.
|
|
102
|
+
const watchedInstanceUrl = (0, OperateProcessInstancePage_1.watchOperateInstanceUrl)(page.context());
|
|
103
|
+
const operateTab = await (0, UtilitiesPage_1.openAppTabWithRetry)(page, modelerCreatePage.viewProcessInstanceLink);
|
|
75
104
|
const operateTabAppsPage = new AppsPage_1.AppsPage(operateTab);
|
|
76
105
|
const operateTabOperateProcessInstancePage = new OperateProcessInstancePage_1.OperateProcessInstancePage(operateTab);
|
|
77
106
|
const operateTabTaskPanelPage = new TaskPanelPage_1.TaskPanelPage(operateTab);
|
|
78
107
|
const operateTabTaskDetailsPage = new TaskDetailsPage_1.TaskDetailsPage(operateTab);
|
|
108
|
+
await operateTabOperateProcessInstancePage.rememberInstanceUrl(60000, watchedInstanceUrl);
|
|
79
109
|
await operateTabOperateProcessInstancePage.closePopOverIfVisible();
|
|
80
110
|
await (0, test_1.expect)(operateTabOperateProcessInstancePage.activeIcon).toBeVisible({ timeout: 180000 });
|
|
81
111
|
await operateTabAppsPage.clickCamundaApps();
|
|
@@ -85,6 +115,12 @@ _8_10_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
85
115
|
await (0, test_1.expect)(operateTabTaskDetailsPage.assignedToMeText).toBeVisible({
|
|
86
116
|
timeout: 60000,
|
|
87
117
|
});
|
|
118
|
+
// Tasklist mounts the embedded form asynchronously once the task is
|
|
119
|
+
// assigned, so the task pane is briefly empty. fillName() inherits the
|
|
120
|
+
// 10s global actionTimeout, which the form does not always beat -- wait
|
|
121
|
+
// for its first field (reloading between attempts, since a form that
|
|
122
|
+
// failed to mount stays empty) instead of racing it.
|
|
123
|
+
await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(operateTab, operateTabTaskDetailsPage.nameInput, 'AI generated form name field', 60000, false, 3);
|
|
88
124
|
await operateTabTaskDetailsPage.fillName('test user');
|
|
89
125
|
await operateTabTaskDetailsPage.checkCheckbox();
|
|
90
126
|
await operateTabTaskDetailsPage.fillDate('11/11/1999');
|
|
@@ -6,3 +6,5 @@ export declare function ensureTasklistV2Enabled(page: Page, clusterUuid: string)
|
|
|
6
6
|
export declare function ensureClientAuditLoggingViaApi(page: Page, clusterUuid: string): Promise<void>;
|
|
7
7
|
export declare function waitForClusterHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
|
|
8
8
|
export declare function waitForClusterRestartHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
|
|
9
|
+
export declare function areAiFeaturesEnabledViaApi(page: Page): Promise<boolean>;
|
|
10
|
+
export declare function waitForAiFeaturesEnabledViaApi(page: Page, timeoutMs?: number): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.waitForClusterRestartHealthyViaApi = exports.waitForClusterHealthyViaApi = exports.ensureClientAuditLoggingViaApi = exports.ensureTasklistV2Enabled = exports.ensureClusterViaApi = exports.createClusterViaApi = exports.deleteClusterViaApi = void 0;
|
|
3
|
+
exports.waitForAiFeaturesEnabledViaApi = exports.areAiFeaturesEnabledViaApi = exports.waitForClusterRestartHealthyViaApi = exports.waitForClusterHealthyViaApi = exports.ensureClientAuditLoggingViaApi = exports.ensureTasklistV2Enabled = exports.ensureClusterViaApi = exports.createClusterViaApi = exports.deleteClusterViaApi = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("./sleep");
|
|
6
6
|
const PLAN_TYPE_ID = '574cde5f-54ae-43c1-851a-e6fb4f03423a';
|
|
@@ -552,3 +552,49 @@ async function waitForClusterRestartHealthyViaApi(page, clusterUuid, timeoutMs =
|
|
|
552
552
|
}).toPass({ timeout: timeoutMs, intervals: [30000, 60000] });
|
|
553
553
|
}
|
|
554
554
|
exports.waitForClusterRestartHealthyViaApi = waitForClusterRestartHealthyViaApi;
|
|
555
|
+
// Web Modeler only mounts the AI form assistant -- and with it the
|
|
556
|
+
// "AI Form Generator" palette button -- when the organization has accepted the
|
|
557
|
+
// alpha/AI terms AND carries the 'aifeatures' early-access flag. camunda-hub
|
|
558
|
+
// gates the whole module on that pair in
|
|
559
|
+
// frontend/apps/hub/src/experiments/ai-features/utils/are-ai-features-enabled.saas.ts.
|
|
560
|
+
// The 'Enable Alpha Features' setup test drives the opt-in through Console, but
|
|
561
|
+
// it can finish green without the opt-in having taken (see
|
|
562
|
+
// ConsoleOrganizationPage.enableAlphaFeature), and then every AI spec in that
|
|
563
|
+
// org fails on a button that is simply never rendered. Reading the flag back
|
|
564
|
+
// from Accounts turns that invisible state into an assertable one.
|
|
565
|
+
const AI_EARLY_ACCESS_FEATURE = 'aifeatures';
|
|
566
|
+
function getAccountsBaseUrl() {
|
|
567
|
+
if (process.env.IS_PROD === 'true') {
|
|
568
|
+
return 'https://accounts.cloud.camunda.io';
|
|
569
|
+
}
|
|
570
|
+
const uiUrl = process.env.PLAYWRIGHT_BASE_URL ?? 'https://console.ultrawombat.com';
|
|
571
|
+
return uiUrl
|
|
572
|
+
.replace('console.ultrawombat.com', 'accounts.cloud.ultrawombat.com')
|
|
573
|
+
.replace('console.camunda.io', 'accounts.cloud.camunda.io');
|
|
574
|
+
}
|
|
575
|
+
async function areAiFeaturesEnabledViaApi(page) {
|
|
576
|
+
const response = await clusterApiRequest(page, 'GET', `${getAccountsBaseUrl()}/api/organizations/${getOrgId()}`);
|
|
577
|
+
if (!response.ok()) {
|
|
578
|
+
throw new Error(`Failed to read organization info: HTTP ${response.status()} ` +
|
|
579
|
+
`${await response.text()}`);
|
|
580
|
+
}
|
|
581
|
+
const organization = (await response.json());
|
|
582
|
+
return (organization.hasAcceptedBetaTerms === true &&
|
|
583
|
+
(organization.earlyAccessFeatures ?? []).includes(AI_EARLY_ACCESS_FEATURE));
|
|
584
|
+
}
|
|
585
|
+
exports.areAiFeaturesEnabledViaApi = areAiFeaturesEnabledViaApi;
|
|
586
|
+
// Console reports the opt-in as done before Accounts serves the updated
|
|
587
|
+
// organization record, so poll rather than reading once.
|
|
588
|
+
async function waitForAiFeaturesEnabledViaApi(page, timeoutMs = 180000) {
|
|
589
|
+
await test_1.expect
|
|
590
|
+
.poll(async () => await areAiFeaturesEnabledViaApi(page), {
|
|
591
|
+
timeout: timeoutMs,
|
|
592
|
+
intervals: [5000],
|
|
593
|
+
message: `Organization ${getOrgId()} does not have the ` +
|
|
594
|
+
`'${AI_EARLY_ACCESS_FEATURE}' early-access feature enabled, so Web ` +
|
|
595
|
+
'Modeler will not render the AI Form Generator. The alpha/AI terms ' +
|
|
596
|
+
'opt-in in organization settings did not take.',
|
|
597
|
+
})
|
|
598
|
+
.toBe(true);
|
|
599
|
+
}
|
|
600
|
+
exports.waitForAiFeaturesEnabledViaApi = waitForAiFeaturesEnabledViaApi;
|