@camunda/e2e-test-suite 0.0.1173 → 0.0.1174
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/ClusterSecretsPage.d.ts +2 -0
- package/dist/pages/8.10/ClusterSecretsPage.js +29 -3
- package/dist/pages/8.10/FormJsPage.js +8 -1
- package/dist/pages/8.10/ModelerCreatePage.js +14 -0
- package/dist/pages/8.10/TaskDetailsPage.d.ts +2 -0
- package/dist/pages/8.10/TaskDetailsPage.js +46 -0
- package/package.json +1 -1
|
@@ -13,6 +13,8 @@ declare class ClusterSecretsPage {
|
|
|
13
13
|
readonly fromFileButton: Locator;
|
|
14
14
|
readonly bulkImportTextArea: Locator;
|
|
15
15
|
readonly dialogImportButton: Locator;
|
|
16
|
+
readonly bulkImportCancelButton: Locator;
|
|
17
|
+
readonly bulkImportErrorBanner: Locator;
|
|
16
18
|
readonly secretsSearchBox: Locator;
|
|
17
19
|
readonly clusterBanner: Locator;
|
|
18
20
|
readonly clusterLink: (name: string) => Locator;
|
|
@@ -18,6 +18,8 @@ class ClusterSecretsPage {
|
|
|
18
18
|
fromFileButton;
|
|
19
19
|
bulkImportTextArea;
|
|
20
20
|
dialogImportButton;
|
|
21
|
+
bulkImportCancelButton;
|
|
22
|
+
bulkImportErrorBanner;
|
|
21
23
|
secretsSearchBox;
|
|
22
24
|
clusterBanner;
|
|
23
25
|
clusterLink;
|
|
@@ -50,6 +52,11 @@ class ClusterSecretsPage {
|
|
|
50
52
|
this.dialogImportButton = this.page
|
|
51
53
|
.getByRole('dialog')
|
|
52
54
|
.getByRole('button', { name: 'Import', exact: true });
|
|
55
|
+
this.bulkImportCancelButton = this.dialog.getByRole('button', {
|
|
56
|
+
name: 'Cancel',
|
|
57
|
+
exact: true,
|
|
58
|
+
});
|
|
59
|
+
this.bulkImportErrorBanner = this.dialog.getByText(/Imported \d+ secrets?, \d+ failed/);
|
|
53
60
|
this.secretsSearchBox = page.getByRole('searchbox', { name: 'Filter table' });
|
|
54
61
|
this.clusterBanner = this.page
|
|
55
62
|
.getByRole('banner')
|
|
@@ -210,9 +217,28 @@ class ClusterSecretsPage {
|
|
|
210
217
|
await this.bulkImportTextArea.click();
|
|
211
218
|
await this.bulkImportTextArea.fill(mapped);
|
|
212
219
|
await this.dialogImportButton.click();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
// This suite intentionally reuses a fixed cluster across nightly runs
|
|
221
|
+
// (see ensureClusterViaApi in test-setup.spec.ts), so some or all of
|
|
222
|
+
// these secrets can already exist from a previous run. The Console then
|
|
223
|
+
// leaves the dialog open with an "Imported X secrets, Y failed" banner
|
|
224
|
+
// instead of closing it, which used to hang here for 60s and fail setup
|
|
225
|
+
// outright. Tolerate that banner and close the dialog ourselves --
|
|
226
|
+
// assertSecretsOrCreate() (the very next call in createSetOfSecrets)
|
|
227
|
+
// independently verifies every requested secret is present and creates
|
|
228
|
+
// any that are genuinely missing, so this doesn't mask a real failure,
|
|
229
|
+
// it just defers the correctness check to the step that already owns it.
|
|
230
|
+
try {
|
|
231
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 60000 });
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (await this.bulkImportErrorBanner.isVisible().catch(() => false)) {
|
|
235
|
+
await this.bulkImportCancelButton.click();
|
|
236
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 10000 });
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({
|
|
217
243
|
timeout: 60000,
|
|
218
244
|
});
|
|
@@ -61,7 +61,14 @@ class FormJsPage {
|
|
|
61
61
|
.locator('.cds--toast-notification--success')
|
|
62
62
|
.filter({ hasText: 'Form deployed!' });
|
|
63
63
|
}
|
|
64
|
-
async generateAIForm(
|
|
64
|
+
async generateAIForm(
|
|
65
|
+
// "date only, no time": asked for a plain date the generator sometimes
|
|
66
|
+
// emits a datetime component in its "date and time" subtype, which adds a
|
|
67
|
+
// second required time subfield that then blocks task completion. The
|
|
68
|
+
// task form fills it anyway (TaskDetailsPage.fillTimeSubfield), but asking
|
|
69
|
+
// for the date-only variant keeps the generated form matching what the
|
|
70
|
+
// tests model.
|
|
71
|
+
request = 'Create a form with the following fields: 1. A text field with the label "Full Name" 2. A number with the label "Count" 3. A date input (date only, no time) with the label "Date of birth" 4. A Checkbox with the label "Agree"') {
|
|
65
72
|
// The "Successfully generated smart form." toast is delivered over a Pusher
|
|
66
73
|
// websocket. A transient websocket/network drop during generation means the
|
|
67
74
|
// toast never renders even though generation finished, leaving the old
|
|
@@ -421,6 +421,20 @@ class ModelerCreatePage {
|
|
|
421
421
|
if (processId.length > 0) {
|
|
422
422
|
await this.clickStartEventElement();
|
|
423
423
|
await this.clickGeneralPropertiesPanel();
|
|
424
|
+
// Renaming the file does not reliably rename the process: hub only syncs
|
|
425
|
+
// the new file name into the BPMN process name from inside
|
|
426
|
+
// `renameDiagram`, which returns early when the submitted name already
|
|
427
|
+
// equals the stored one (camunda-hub
|
|
428
|
+
// `stores/current-diagram-store.tsx` -> `checkAndSyncProcessName`). Any
|
|
429
|
+
// rename attempt that commits server-side but is not observed in time --
|
|
430
|
+
// the case `enterDiagramName`'s retry loop exists for -- therefore
|
|
431
|
+
// leaves the process named "New BPMN diagram" while the breadcrumb shows
|
|
432
|
+
// the new name, which is exactly what the nightly screenshot showed.
|
|
433
|
+
// Tasklist and Operate list the process *name*, so the task can then
|
|
434
|
+
// never be found by `processName`. Set it explicitly, the same way
|
|
435
|
+
// `UtilitiesPage` already does for uploaded diagrams.
|
|
436
|
+
await this.clickNameInput();
|
|
437
|
+
await this.fillNamedInput(processName);
|
|
424
438
|
await this.clickIdInput();
|
|
425
439
|
await this.fillIdInput(processId);
|
|
426
440
|
await this.clickStartEventElement();
|
|
@@ -24,6 +24,7 @@ declare class TaskDetailsPage {
|
|
|
24
24
|
readonly decrementButton: Locator;
|
|
25
25
|
readonly dateInput: Locator;
|
|
26
26
|
readonly timeInput: Locator;
|
|
27
|
+
readonly timeSubfield: Locator;
|
|
27
28
|
readonly checkbox: Locator;
|
|
28
29
|
readonly selectDropdown: Locator;
|
|
29
30
|
readonly tagList: Locator;
|
|
@@ -55,6 +56,7 @@ declare class TaskDetailsPage {
|
|
|
55
56
|
clickIncrementButton(): Promise<void>;
|
|
56
57
|
clickDecrementButton(): Promise<void>;
|
|
57
58
|
fillDate(date: string): Promise<void>;
|
|
59
|
+
private fillTimeSubfield;
|
|
58
60
|
enterTime(time: string): Promise<void>;
|
|
59
61
|
checkCheckbox(): Promise<void>;
|
|
60
62
|
selectDropdownValue(value: string): Promise<void>;
|
|
@@ -44,6 +44,7 @@ class TaskDetailsPage {
|
|
|
44
44
|
decrementButton;
|
|
45
45
|
dateInput;
|
|
46
46
|
timeInput;
|
|
47
|
+
timeSubfield;
|
|
47
48
|
checkbox;
|
|
48
49
|
selectDropdown;
|
|
49
50
|
tagList;
|
|
@@ -91,6 +92,7 @@ class TaskDetailsPage {
|
|
|
91
92
|
this.decrementButton = page.getByRole('button', { name: 'Decrement' });
|
|
92
93
|
this.dateInput = page.getByLabel('Date of Birth');
|
|
93
94
|
this.timeInput = page.getByPlaceholder('hh:mm ?m');
|
|
95
|
+
this.timeSubfield = this.form.locator('.fjs-timepicker input').first();
|
|
94
96
|
this.checkbox = this.form.getByLabel('Agree');
|
|
95
97
|
this.selectDropdown = this.form.getByText('Select').last();
|
|
96
98
|
this.tagList = page.getByPlaceholder('Search');
|
|
@@ -174,6 +176,50 @@ class TaskDetailsPage {
|
|
|
174
176
|
await this.dateInput.click();
|
|
175
177
|
await this.dateInput.fill(date);
|
|
176
178
|
await this.dateInput.press('Enter');
|
|
179
|
+
await this.fillTimeSubfield();
|
|
180
|
+
}
|
|
181
|
+
// The AI form generator is non-deterministic about how it renders the
|
|
182
|
+
// requested "Date of birth" input: it sometimes emits a form-js datetime
|
|
183
|
+
// component in its "date and time" subtype, which renders a second - and
|
|
184
|
+
// equally required - time subfield next to the date input. The nightly
|
|
185
|
+
// trace shows form validation rejecting the completion on exactly that
|
|
186
|
+
// empty subfield ("Field is required." / "Please review 1 field"), so the
|
|
187
|
+
// task was never completed and no completion confirmation could appear.
|
|
188
|
+
// Fill the time subfield whenever the rendered form has one; the fixed,
|
|
189
|
+
// date-only forms every other spec deploys have no timepicker and skip this.
|
|
190
|
+
async fillTimeSubfield() {
|
|
191
|
+
const hasTimeSubfield = await this.timeSubfield
|
|
192
|
+
.waitFor({ state: 'visible', timeout: 5000 })
|
|
193
|
+
.then(() => true)
|
|
194
|
+
.catch(() => false);
|
|
195
|
+
if (!hasTimeSubfield) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Whether the component is configured for 12h or 24h notation is up to the
|
|
199
|
+
// generator, so prefer picking one of the times the timepicker itself
|
|
200
|
+
// offers over typing a notation that may not parse.
|
|
201
|
+
await this.timeSubfield.click({ timeout: 30000 });
|
|
202
|
+
const offeredTime = this.page.locator('.fjs-dropdownlist-item').first();
|
|
203
|
+
const timeIsOffered = await offeredTime
|
|
204
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
205
|
+
.then(() => true)
|
|
206
|
+
.catch(() => false);
|
|
207
|
+
if (timeIsOffered) {
|
|
208
|
+
await offeredTime.click({ timeout: 30000 });
|
|
209
|
+
}
|
|
210
|
+
if ((await this.timeSubfield.inputValue()) !== '') {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// No dropdown, or it committed nothing: type instead. form-js discards a
|
|
214
|
+
// value it cannot parse on blur, so the input keeping the value is what
|
|
215
|
+
// tells us the notation was accepted.
|
|
216
|
+
for (const time of ['09:00 AM', '09:00']) {
|
|
217
|
+
await this.timeSubfield.fill(time);
|
|
218
|
+
await this.timeSubfield.press('Tab');
|
|
219
|
+
if ((await this.timeSubfield.inputValue()) !== '') {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
177
223
|
}
|
|
178
224
|
async enterTime(time) {
|
|
179
225
|
await this.timeInput.click();
|