@camunda/e2e-test-suite 0.0.646 → 0.0.648
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.9/ClusterPage.js +42 -32
- package/dist/pages/8.9/ModelerCreatePage.js +55 -0
- package/dist/pages/8.9/ModelerHomePage.js +3 -3
- package/dist/pages/SM-8.10/ModelerCreatePage.d.ts +2 -1
- package/dist/pages/SM-8.10/ModelerCreatePage.js +11 -3
- package/dist/pages/SM-8.10/OperateProcessInstancePage.d.ts +1 -0
- package/dist/pages/SM-8.10/OperateProcessInstancePage.js +7 -0
- package/dist/tests/SM-8.10/web-modeler-user-flows.spec.js +29 -0
- package/package.json +1 -1
|
@@ -100,7 +100,7 @@ class ClusterPage {
|
|
|
100
100
|
.filter({ hasNotText: 'Generation' }); //Filter out header row
|
|
101
101
|
this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
|
|
102
102
|
this.tasklistV2Api = page.getByText('Tasklist API v2');
|
|
103
|
-
this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName });
|
|
103
|
+
this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
|
|
104
104
|
this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
|
|
105
105
|
exact: true,
|
|
106
106
|
});
|
|
@@ -128,31 +128,39 @@ class ClusterPage {
|
|
|
128
128
|
});
|
|
129
129
|
while ((await deleteButtons.count()) > 0) {
|
|
130
130
|
const deleteButton = deleteButtons.nth(0);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
|
|
141
|
-
await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
|
|
142
|
-
await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
|
|
143
|
-
await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
|
|
144
|
-
const dangerDeleteButtons = await this.dangerDeleteButton.all();
|
|
145
|
-
const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
|
|
146
|
-
await lastDangerDeleteButton.click();
|
|
147
|
-
await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
|
|
148
|
-
timeout: 60000,
|
|
149
|
-
});
|
|
150
|
-
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
|
|
151
|
-
// Refresh the list of delete buttons
|
|
131
|
+
// Wait for the Delete button to become visible — a cluster in a transient
|
|
132
|
+
// state (e.g. just created by a concurrent setup worker) may have the row
|
|
133
|
+
// present in the DOM but the button not yet rendered. Without this wait
|
|
134
|
+
// the while-loop would spin forever since count() stays > 0.
|
|
135
|
+
try {
|
|
136
|
+
await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 10000 });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
152
140
|
deleteButtons = this.cluster(name).getByRole('button', {
|
|
153
141
|
name: 'Delete',
|
|
154
142
|
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
await deleteButton.click({ force: true, timeout: 60000 });
|
|
155
147
|
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
await this.clickClusterBanner();
|
|
150
|
+
await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
|
|
151
|
+
await deleteButton.click({ force: true, timeout: 60000 });
|
|
152
|
+
}
|
|
153
|
+
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
|
|
154
|
+
await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
|
|
155
|
+
await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
|
|
156
|
+
await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
|
|
157
|
+
const dangerDeleteButtons = await this.dangerDeleteButton.all();
|
|
158
|
+
const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
|
|
159
|
+
await lastDangerDeleteButton.click();
|
|
160
|
+
await (0, test_1.expect)(this.cluster(name).getByText('Deleting...', { exact: true })).not.toBeVisible({ timeout: 60000 });
|
|
161
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
|
|
162
|
+
// Refresh the list of delete buttons
|
|
163
|
+
deleteButtons = this.cluster(name).getByRole('button', { name: 'Delete' });
|
|
156
164
|
}
|
|
157
165
|
await this.page.waitForTimeout(500);
|
|
158
166
|
}
|
|
@@ -201,6 +209,12 @@ class ClusterPage {
|
|
|
201
209
|
async createCluster(name, region = 'GCP', tasklistV1Api) {
|
|
202
210
|
await this.clickClusterTab();
|
|
203
211
|
await this.deleteCluster(name);
|
|
212
|
+
// A concurrent setup worker (e.g. another version's test suite running in
|
|
213
|
+
// the same org) may have created a same-named cluster in the window between
|
|
214
|
+
// our deleteCluster check and now. Reload and delete again to catch it.
|
|
215
|
+
await this.page.waitForTimeout(3000);
|
|
216
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
217
|
+
await this.deleteCluster(name);
|
|
204
218
|
await this.clickCreateNewClusterButton();
|
|
205
219
|
await this.clickClusterNameInput();
|
|
206
220
|
await this.fillClusterNameInput(name);
|
|
@@ -235,22 +249,18 @@ class ClusterPage {
|
|
|
235
249
|
await this.createClusterButton.click();
|
|
236
250
|
}
|
|
237
251
|
async clickClustersBreadcrumb(clusterName) {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
252
|
+
const clusterRow = this.cluster(clusterName);
|
|
253
|
+
const clusterRowVisible = clusterRow
|
|
254
|
+
.getByText('Creating', { exact: true })
|
|
255
|
+
.or(clusterRow.getByText('Healthy', { exact: true }));
|
|
240
256
|
try {
|
|
241
257
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
242
258
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
243
|
-
await (0, test_1.expect)(
|
|
244
|
-
timeout: 30000,
|
|
245
|
-
});
|
|
259
|
+
await (0, test_1.expect)(clusterRow.getByText('Creating', { exact: true })).toBeVisible({ timeout: 30000 });
|
|
246
260
|
}
|
|
247
261
|
catch (error) {
|
|
248
262
|
await this.clickClusterBanner();
|
|
249
|
-
await (0, test_1.expect)(
|
|
250
|
-
.getByText(creatingCheckRegex)
|
|
251
|
-
.or(this.page.getByText(healthyCheckRegex))).toBeVisible({
|
|
252
|
-
timeout: 30000,
|
|
253
|
-
});
|
|
263
|
+
await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
|
|
254
264
|
}
|
|
255
265
|
}
|
|
256
266
|
async clickClusterLink(name) {
|
|
@@ -1023,6 +1023,61 @@ class ModelerCreatePage {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
async completePlayConfiguration(clusterName) {
|
|
1025
1025
|
const timeout = 30000;
|
|
1026
|
+
// New flow (8.10+): "Setup environment" panel with three steps:
|
|
1027
|
+
// 1. Connect cluster → "Configure environment" modal → select cluster → Save
|
|
1028
|
+
// 2. Deploy process → wait for success banner
|
|
1029
|
+
// 3. Configure scenario
|
|
1030
|
+
const setupDeployButton = this.page
|
|
1031
|
+
.getByText('Deploy process')
|
|
1032
|
+
.locator('..')
|
|
1033
|
+
.getByRole('button', { name: 'Deploy' });
|
|
1034
|
+
const configureScenarioButton = this.page.getByRole('button', {
|
|
1035
|
+
name: 'Configure scenario',
|
|
1036
|
+
});
|
|
1037
|
+
// Wait up to 15s for EITHER the new-flow setup panel OR the legacy Continue
|
|
1038
|
+
// button to appear. Using .or() avoids separate sequential timeouts that would
|
|
1039
|
+
// both expire before the page finishes loading on a slow cluster.
|
|
1040
|
+
const setupOrContinue = setupDeployButton.or(this.continueToPlayButton);
|
|
1041
|
+
const panelAppeared = await setupOrContinue
|
|
1042
|
+
.first()
|
|
1043
|
+
.isVisible({ timeout: 15000 })
|
|
1044
|
+
.catch(() => false);
|
|
1045
|
+
if (!panelAppeared) {
|
|
1046
|
+
// Neither appeared — Play is already configured and ready.
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const isNewFlow = await setupDeployButton.isVisible().catch(() => false);
|
|
1050
|
+
if (isNewFlow) {
|
|
1051
|
+
// Step 1: connect cluster if not yet connected (modal with cluster dropdown)
|
|
1052
|
+
const connectClusterButton = this.page.getByRole('button', {
|
|
1053
|
+
name: 'Connect cluster',
|
|
1054
|
+
});
|
|
1055
|
+
const needsConnect = await connectClusterButton
|
|
1056
|
+
.isVisible({ timeout: 3000 })
|
|
1057
|
+
.catch(() => false);
|
|
1058
|
+
if (needsConnect) {
|
|
1059
|
+
await connectClusterButton.click({ timeout });
|
|
1060
|
+
const configureEnvDialog = this.page
|
|
1061
|
+
.getByRole('dialog')
|
|
1062
|
+
.filter({ hasText: 'Configure environment' });
|
|
1063
|
+
await configureEnvDialog.getByRole('combobox').click({ timeout });
|
|
1064
|
+
await this.page
|
|
1065
|
+
.getByRole('option', { name: new RegExp(clusterName, 'i') })
|
|
1066
|
+
.click({ timeout });
|
|
1067
|
+
await configureEnvDialog
|
|
1068
|
+
.getByRole('button', { name: 'Save' })
|
|
1069
|
+
.click({ timeout });
|
|
1070
|
+
}
|
|
1071
|
+
// Step 2: deploy — wait for enabled; button stays disabled until the cluster
|
|
1072
|
+
// connection is confirmed by the backend after the Save in step 1.
|
|
1073
|
+
await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 30000 });
|
|
1074
|
+
await setupDeployButton.click({ timeout });
|
|
1075
|
+
await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
|
|
1076
|
+
// Step 3: configure scenario
|
|
1077
|
+
await configureScenarioButton.click({ timeout });
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
// Legacy flow: select cluster via dialog, then click Continue.
|
|
1026
1081
|
let attempts = 0;
|
|
1027
1082
|
const maxRetries = 2;
|
|
1028
1083
|
while (attempts < maxRetries) {
|
|
@@ -62,9 +62,9 @@ class ModelerHomePage {
|
|
|
62
62
|
this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
|
|
63
63
|
this.openOrganizationsButton = page.getByLabel('Open Organizations');
|
|
64
64
|
this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
|
|
65
|
-
this.crossComponentProjectFolder = page
|
|
66
|
-
exact: true
|
|
67
|
-
|
|
65
|
+
this.crossComponentProjectFolder = page
|
|
66
|
+
.getByTitle(this.defaultFolderName, { exact: true })
|
|
67
|
+
.first();
|
|
68
68
|
this.rows = page.getByRole('row');
|
|
69
69
|
this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
|
|
70
70
|
this.messageBanner = page.locator('[data-test="close-top-banner"]');
|
|
@@ -22,6 +22,7 @@ declare class ModelerCreatePage {
|
|
|
22
22
|
readonly renameDiagramNameButton: Locator;
|
|
23
23
|
readonly diagramNameInput: Locator;
|
|
24
24
|
readonly variableInput: Locator;
|
|
25
|
+
readonly businessIdInput: Locator;
|
|
25
26
|
readonly embedFormButton: Locator;
|
|
26
27
|
readonly embedButton: Locator;
|
|
27
28
|
readonly newForm: Locator;
|
|
@@ -92,7 +93,7 @@ declare class ModelerCreatePage {
|
|
|
92
93
|
modelJobWorkerDiagram(processName: string): Promise<void>;
|
|
93
94
|
assertThreeElementsVisible(): Promise<void>;
|
|
94
95
|
clickForm(name: string): Promise<void>;
|
|
95
|
-
runProcessInstance(variables?: string, tenant?: string): Promise<void>;
|
|
96
|
+
runProcessInstance(variables?: string, tenant?: string, businessId?: string): Promise<void>;
|
|
96
97
|
switchToPlay(): Promise<void>;
|
|
97
98
|
clickGeneralPropertiesPanel(): Promise<void>;
|
|
98
99
|
clickIdInput(): Promise<void>;
|
|
@@ -27,6 +27,7 @@ class ModelerCreatePage {
|
|
|
27
27
|
renameDiagramNameButton;
|
|
28
28
|
diagramNameInput;
|
|
29
29
|
variableInput;
|
|
30
|
+
businessIdInput;
|
|
30
31
|
embedFormButton;
|
|
31
32
|
embedButton;
|
|
32
33
|
newForm;
|
|
@@ -136,6 +137,7 @@ class ModelerCreatePage {
|
|
|
136
137
|
this.renameDiagramNameButton = page.getByText('Rename');
|
|
137
138
|
this.diagramNameInput = page.locator('[data-test="editable-input"]');
|
|
138
139
|
this.variableInput = page.locator('[id="variables-json"]');
|
|
140
|
+
this.businessIdInput = page.locator('[id^="business-id-input"]');
|
|
139
141
|
this.embedFormButton = page.getByRole('button', { name: 'Link form' });
|
|
140
142
|
this.embedButton = page.locator('[data-test="confirm-move"]');
|
|
141
143
|
this.newForm = page.locator('[data-test="item-New form"]');
|
|
@@ -333,7 +335,7 @@ class ModelerCreatePage {
|
|
|
333
335
|
.getByText(name)
|
|
334
336
|
.click({ timeout: 90000 });
|
|
335
337
|
}
|
|
336
|
-
async runProcessInstance(variables = '', tenant = '') {
|
|
338
|
+
async runProcessInstance(variables = '', tenant = '', businessId = '') {
|
|
337
339
|
await this.page.waitForTimeout(2000); // the canvas autosaves. This can take a second to save.
|
|
338
340
|
const maxRetries = 3;
|
|
339
341
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
@@ -353,18 +355,24 @@ class ModelerCreatePage {
|
|
|
353
355
|
await this.clickTenantIdInput();
|
|
354
356
|
await this.fillTenantIdInput(tenant);
|
|
355
357
|
}
|
|
358
|
+
if (businessId.length > 0) {
|
|
359
|
+
await (0, test_1.expect)(this.businessIdInput).toBeVisible({
|
|
360
|
+
timeout: 60000,
|
|
361
|
+
});
|
|
362
|
+
await this.businessIdInput.fill(businessId);
|
|
363
|
+
}
|
|
356
364
|
await this.clickDeployAndRunSubButton();
|
|
357
365
|
await this.instanceStartedAssertion();
|
|
358
366
|
return;
|
|
359
367
|
}
|
|
360
368
|
catch (error) {
|
|
361
369
|
if (attempt < maxRetries - 1) {
|
|
362
|
-
console.warn(`Attempt ${attempt + 1} failed for deploy & run running process. Retrying...`);
|
|
370
|
+
console.warn(`Attempt ${attempt + 1} failed for deploy & run running process: ${error instanceof Error ? error.message : error}. Retrying...`);
|
|
363
371
|
await this.page.reload();
|
|
364
372
|
await (0, sleep_1.sleep)(10000);
|
|
365
373
|
}
|
|
366
374
|
else {
|
|
367
|
-
throw new Error(`
|
|
375
|
+
throw new Error(`runProcessInstance failed after ${maxRetries} attempts. Last error: ${error instanceof Error ? error.message : error}`);
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
378
|
}
|
|
@@ -24,6 +24,7 @@ declare class OperateProcessInstancePage {
|
|
|
24
24
|
private captureVariableJsonValue;
|
|
25
25
|
assertResultVariableVisibleWithRetry(variableName: string): Promise<void>;
|
|
26
26
|
assertActiveTokenIsPresent(): Promise<void>;
|
|
27
|
+
assertBusinessIdVisible(businessId: string): Promise<void>;
|
|
27
28
|
assertVariablesListVisible(timeout?: number): Promise<void>;
|
|
28
29
|
}
|
|
29
30
|
export { OperateProcessInstancePage };
|
|
@@ -234,6 +234,13 @@ class OperateProcessInstancePage {
|
|
|
234
234
|
async assertActiveTokenIsPresent() {
|
|
235
235
|
await (0, test_1.expect)(this.activeIcon).toBeVisible({ timeout: 60000 });
|
|
236
236
|
}
|
|
237
|
+
async assertBusinessIdVisible(businessId) {
|
|
238
|
+
const instanceHeader = this.page.getByTestId('instance-header');
|
|
239
|
+
await (0, test_1.expect)(instanceHeader.getByText('Business ID')).toBeVisible({
|
|
240
|
+
timeout: 60000,
|
|
241
|
+
});
|
|
242
|
+
await (0, test_1.expect)(instanceHeader.getByText(businessId, { exact: true })).toBeVisible({ timeout: 60000 });
|
|
243
|
+
}
|
|
237
244
|
async assertVariablesListVisible(timeout = 30000) {
|
|
238
245
|
const startTime = Date.now();
|
|
239
246
|
while (Date.now() - startTime < timeout) {
|
|
@@ -141,6 +141,35 @@ SM_8_10_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
141
141
|
await operateProcessInstancePage.completedIconAssertion();
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
|
+
(0, SM_8_10_1.test)('Deploy and run process with run configuration and verify in Operate', async ({ modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, }) => {
|
|
145
|
+
SM_8_10_1.test.slow();
|
|
146
|
+
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
147
|
+
const processName = 'Run_Config_Process_' + randomString;
|
|
148
|
+
const businessId = `bid-${Date.now()}-${randomString}`;
|
|
149
|
+
const variables = '{"orderNumber": "A12BH98", "amount": 185.34}';
|
|
150
|
+
await SM_8_10_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
|
|
151
|
+
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
152
|
+
await modelerHomePage.clickDiagramTypeDropdown();
|
|
153
|
+
await modelerHomePage.clickBpmnTemplateOption();
|
|
154
|
+
});
|
|
155
|
+
await SM_8_10_1.test.step('Model a simple job worker diagram', async () => {
|
|
156
|
+
await modelerCreatePage.modelJobWorkerDiagram(processName);
|
|
157
|
+
});
|
|
158
|
+
await SM_8_10_1.test.step('Deploy and run process with variables and Business ID', async () => {
|
|
159
|
+
await modelerCreatePage.runProcessInstance(variables, '', businessId);
|
|
160
|
+
});
|
|
161
|
+
await SM_8_10_1.test.step('View process instance in Operate and assert run configuration is visible', async () => {
|
|
162
|
+
await navigationPage.goToOperate();
|
|
163
|
+
await (0, test_1.expect)(operateHomePage.processesTab).toBeVisible({
|
|
164
|
+
timeout: 120000,
|
|
165
|
+
});
|
|
166
|
+
await operateHomePage.clickProcessesTab();
|
|
167
|
+
await operateProcessesPage.clickProcessInstanceLink(processName);
|
|
168
|
+
await operateProcessInstancePage.assertBusinessIdVisible(businessId);
|
|
169
|
+
await operateProcessInstancePage.assertProcessVariableContainsText('orderNumber', '"A12BH98"');
|
|
170
|
+
await operateProcessInstancePage.assertProcessVariableContainsText('amount', '185.34');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
144
173
|
(0, SM_8_10_1.test)('Conditional Events - Deploy, verify in Operate and verify process in Optimize dashboard @tasklistV2', async ({ page, modelerHomePage, modelerCreatePage, navigationPage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeDashboardPage, }) => {
|
|
145
174
|
SM_8_10_1.test.slow();
|
|
146
175
|
const bpmnFileName = 'Conditional_Events_All.bpmn';
|