@camunda/e2e-test-suite 0.0.647 → 0.0.649
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.8/ClusterPage.js +43 -32
- package/dist/pages/8.8/ModelerCreatePage.js +55 -0
- package/dist/pages/8.8/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
|
@@ -101,7 +101,7 @@ class ClusterPage {
|
|
|
101
101
|
.filter({ hasNotText: 'Generation' }); //Filter out header row
|
|
102
102
|
this.tasklistV1Api = page.getByText('Tasklist API v1 (legacy)');
|
|
103
103
|
this.tasklistV2Api = page.getByText('Tasklist API v2');
|
|
104
|
-
this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName });
|
|
104
|
+
this.cluster = (clusterName) => this.clustersList.filter({ hasText: clusterName }).first();
|
|
105
105
|
this.clusterHealthiness = (clusterName) => this.cluster(clusterName).getByText(`Healthy`, {
|
|
106
106
|
exact: true,
|
|
107
107
|
});
|
|
@@ -129,32 +129,41 @@ class ClusterPage {
|
|
|
129
129
|
});
|
|
130
130
|
while ((await deleteButtons.count()) > 0) {
|
|
131
131
|
const deleteButton = deleteButtons.nth(0);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
|
|
142
|
-
await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
|
|
143
|
-
await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
|
|
144
|
-
await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
|
|
145
|
-
const dangerDeleteButtons = await this.dangerDeleteButton.all();
|
|
146
|
-
const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
|
|
147
|
-
await lastDangerDeleteButton.click();
|
|
148
|
-
await (0, test_1.expect)(this.page.getByText('Deleting...')).not.toBeVisible({
|
|
149
|
-
timeout: 60000,
|
|
150
|
-
});
|
|
151
|
-
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
|
|
152
|
-
// Refresh the list of delete buttons
|
|
132
|
+
// Wait for the Delete button to become visible — a cluster in a transient
|
|
133
|
+
// state (e.g. just created by a concurrent setup worker) may have the row
|
|
134
|
+
// present in the DOM but the button not yet rendered. Without this wait
|
|
135
|
+
// the while-loop would spin forever since count() stays > 0.
|
|
136
|
+
try {
|
|
137
|
+
await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 10000 });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
153
141
|
deleteButtons = this.cluster(name).getByRole('button', {
|
|
154
142
|
name: 'Delete',
|
|
155
143
|
});
|
|
144
|
+
continue;
|
|
156
145
|
}
|
|
146
|
+
try {
|
|
147
|
+
await deleteButton.click({ force: true, timeout: 60000 });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
await this.clickClusterBanner();
|
|
151
|
+
await (0, test_1.expect)(deleteButton).toBeVisible({ timeout: 60000 });
|
|
152
|
+
await deleteButton.click({ force: true, timeout: 60000 });
|
|
153
|
+
}
|
|
154
|
+
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 60000 });
|
|
155
|
+
await (0, test_1.expect)(this.confirmDeleteInput).toBeVisible({ timeout: 60000 });
|
|
156
|
+
await this.confirmDeleteInput.click({ force: true, timeout: 60000 });
|
|
157
|
+
await this.confirmDeleteInput.fill('DELETE', { timeout: 60000 });
|
|
158
|
+
const dangerDeleteButtons = await this.dangerDeleteButton.all();
|
|
159
|
+
const lastDangerDeleteButton = dangerDeleteButtons[dangerDeleteButtons.length - 1];
|
|
160
|
+
await lastDangerDeleteButton.click();
|
|
161
|
+
await (0, test_1.expect)(this.cluster(name).getByText('Deleting...', { exact: true })).not.toBeVisible({ timeout: 60000 });
|
|
162
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
|
|
163
|
+
// Refresh the list of delete buttons
|
|
164
|
+
deleteButtons = this.cluster(name).getByRole('button', { name: 'Delete' });
|
|
157
165
|
}
|
|
166
|
+
await this.page.waitForTimeout(500);
|
|
158
167
|
}
|
|
159
168
|
async clickClusterTab() {
|
|
160
169
|
await Promise.race([
|
|
@@ -201,6 +210,12 @@ class ClusterPage {
|
|
|
201
210
|
async createCluster(name, region = 'GCP', tasklistV1Api) {
|
|
202
211
|
await this.clickClusterTab();
|
|
203
212
|
await this.deleteCluster(name);
|
|
213
|
+
// A concurrent setup worker (e.g. another version's test suite running in
|
|
214
|
+
// the same org) may have created a same-named cluster in the window between
|
|
215
|
+
// our deleteCluster check and now. Reload and delete again to catch it.
|
|
216
|
+
await this.page.waitForTimeout(3000);
|
|
217
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
218
|
+
await this.deleteCluster(name);
|
|
204
219
|
await this.clickCreateNewClusterButton();
|
|
205
220
|
await this.clickClusterNameInput();
|
|
206
221
|
await this.fillClusterNameInput(name);
|
|
@@ -253,22 +268,18 @@ class ClusterPage {
|
|
|
253
268
|
await this.createClusterButton.click();
|
|
254
269
|
}
|
|
255
270
|
async clickClustersBreadcrumb(clusterName) {
|
|
256
|
-
const
|
|
257
|
-
const
|
|
271
|
+
const clusterRow = this.cluster(clusterName);
|
|
272
|
+
const clusterRowVisible = clusterRow
|
|
273
|
+
.getByText('Creating', { exact: true })
|
|
274
|
+
.or(clusterRow.getByText('Healthy', { exact: true }));
|
|
258
275
|
try {
|
|
259
276
|
await (0, test_1.expect)(this.clustersBreadcrumb).toBeVisible({ timeout: 60000 });
|
|
260
277
|
await this.clustersBreadcrumb.click({ timeout: 60000 });
|
|
261
|
-
await (0, test_1.expect)(
|
|
262
|
-
timeout: 30000,
|
|
263
|
-
});
|
|
278
|
+
await (0, test_1.expect)(clusterRow.getByText('Creating', { exact: true })).toBeVisible({ timeout: 30000 });
|
|
264
279
|
}
|
|
265
280
|
catch (error) {
|
|
266
281
|
await this.clickClusterBanner();
|
|
267
|
-
await (0, test_1.expect)(
|
|
268
|
-
.getByText(creatingCheckRegex)
|
|
269
|
-
.or(this.page.getByText(healthyCheckRegex))).toBeVisible({
|
|
270
|
-
timeout: 30000,
|
|
271
|
-
});
|
|
282
|
+
await (0, test_1.expect)(clusterRowVisible).toBeVisible({ timeout: 30000 });
|
|
272
283
|
}
|
|
273
284
|
}
|
|
274
285
|
async clickClusterLink(name) {
|
|
@@ -1076,6 +1076,61 @@ class ModelerCreatePage {
|
|
|
1076
1076
|
}
|
|
1077
1077
|
async completePlayConfiguration(clusterName) {
|
|
1078
1078
|
const timeout = 30000;
|
|
1079
|
+
// New flow (8.10+): "Setup environment" panel with three steps:
|
|
1080
|
+
// 1. Connect cluster → "Configure environment" modal → select cluster → Save
|
|
1081
|
+
// 2. Deploy process → wait for success banner
|
|
1082
|
+
// 3. Configure scenario
|
|
1083
|
+
const setupDeployButton = this.page
|
|
1084
|
+
.getByText('Deploy process')
|
|
1085
|
+
.locator('..')
|
|
1086
|
+
.getByRole('button', { name: 'Deploy' });
|
|
1087
|
+
const configureScenarioButton = this.page.getByRole('button', {
|
|
1088
|
+
name: 'Configure scenario',
|
|
1089
|
+
});
|
|
1090
|
+
// Wait up to 15s for EITHER the new-flow setup panel OR the legacy Continue
|
|
1091
|
+
// button to appear. Using .or() avoids separate sequential timeouts that would
|
|
1092
|
+
// both expire before the page finishes loading on a slow cluster.
|
|
1093
|
+
const setupOrContinue = setupDeployButton.or(this.continueToPlayButton);
|
|
1094
|
+
const panelAppeared = await setupOrContinue
|
|
1095
|
+
.first()
|
|
1096
|
+
.isVisible({ timeout: 15000 })
|
|
1097
|
+
.catch(() => false);
|
|
1098
|
+
if (!panelAppeared) {
|
|
1099
|
+
// Neither appeared — Play is already configured and ready.
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const isNewFlow = await setupDeployButton.isVisible().catch(() => false);
|
|
1103
|
+
if (isNewFlow) {
|
|
1104
|
+
// Step 1: connect cluster if not yet connected (modal with cluster dropdown)
|
|
1105
|
+
const connectClusterButton = this.page.getByRole('button', {
|
|
1106
|
+
name: 'Connect cluster',
|
|
1107
|
+
});
|
|
1108
|
+
const needsConnect = await connectClusterButton
|
|
1109
|
+
.isVisible({ timeout: 3000 })
|
|
1110
|
+
.catch(() => false);
|
|
1111
|
+
if (needsConnect) {
|
|
1112
|
+
await connectClusterButton.click({ timeout });
|
|
1113
|
+
const configureEnvDialog = this.page
|
|
1114
|
+
.getByRole('dialog')
|
|
1115
|
+
.filter({ hasText: 'Configure environment' });
|
|
1116
|
+
await configureEnvDialog.getByRole('combobox').click({ timeout });
|
|
1117
|
+
await this.page
|
|
1118
|
+
.getByRole('option', { name: new RegExp(clusterName, 'i') })
|
|
1119
|
+
.click({ timeout });
|
|
1120
|
+
await configureEnvDialog
|
|
1121
|
+
.getByRole('button', { name: 'Save' })
|
|
1122
|
+
.click({ timeout });
|
|
1123
|
+
}
|
|
1124
|
+
// Step 2: deploy — wait for enabled; button stays disabled until the cluster
|
|
1125
|
+
// connection is confirmed by the backend after the Save in step 1.
|
|
1126
|
+
await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 30000 });
|
|
1127
|
+
await setupDeployButton.click({ timeout });
|
|
1128
|
+
await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
|
|
1129
|
+
// Step 3: configure scenario
|
|
1130
|
+
await configureScenarioButton.click({ timeout });
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
// Legacy flow: select cluster via dialog, then click Continue.
|
|
1079
1134
|
let attempts = 0;
|
|
1080
1135
|
const maxRetries = 2;
|
|
1081
1136
|
while (attempts < maxRetries) {
|
|
@@ -65,9 +65,9 @@ class ModelerHomePage {
|
|
|
65
65
|
this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
|
|
66
66
|
this.openOrganizationsButton = page.getByLabel('Open Organizations');
|
|
67
67
|
this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
|
|
68
|
-
this.crossComponentProjectFolder = page
|
|
69
|
-
exact: true
|
|
70
|
-
|
|
68
|
+
this.crossComponentProjectFolder = page
|
|
69
|
+
.getByTitle(this.defaultFolderName, { exact: true })
|
|
70
|
+
.first();
|
|
71
71
|
this.rows = page.getByRole('row');
|
|
72
72
|
this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
|
|
73
73
|
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';
|