@camunda/e2e-test-suite 0.0.1027 → 0.0.1029
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.7/FormJsPage.js +20 -1
- package/dist/pages/8.7/ModelerHomePage.js +69 -17
- package/dist/pages/8.7/ProcessApplicationPage.js +13 -0
- package/dist/pages/SM-8.10/ModelerCreatePage.d.ts +4 -1
- package/dist/pages/SM-8.10/ModelerCreatePage.js +67 -76
- package/dist/tests/SM-8.10/connectors-user-flows.spec.js +4 -2
- package/package.json +1 -1
|
@@ -194,7 +194,26 @@ class FormJsPage {
|
|
|
194
194
|
await (0, test_1.expect)(this.dialog).toBeVisible({
|
|
195
195
|
timeout: 30000,
|
|
196
196
|
});
|
|
197
|
-
|
|
197
|
+
// Same recovery as ModelerCreatePage.runProcessInstance: selectCluster
|
|
198
|
+
// clears the stage binding when the dialog does not offer the cluster, so
|
|
199
|
+
// ensureStageCluster rebinds here and reports that it navigated. Any other
|
|
200
|
+
// failure leaves the binding in place, it returns false, and the original
|
|
201
|
+
// error stands. This path has no surrounding retry loop, hence the one
|
|
202
|
+
// explicit second attempt.
|
|
203
|
+
try {
|
|
204
|
+
await this.selectCluster(clusterName);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const rebound = await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
208
|
+
if (!rebound) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
await this.clickDeployButton();
|
|
212
|
+
await (0, test_1.expect)(this.dialog).toBeVisible({
|
|
213
|
+
timeout: 30000,
|
|
214
|
+
});
|
|
215
|
+
await this.selectCluster(clusterName);
|
|
216
|
+
}
|
|
198
217
|
await this.selectOnlyThisResource();
|
|
199
218
|
await this.clickDeploySubButton();
|
|
200
219
|
await (0, test_1.expect)(this.deploySuccessNotification).toBeVisible({ timeout: 30000 });
|
|
@@ -98,7 +98,10 @@ class ModelerHomePage {
|
|
|
98
98
|
this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
|
|
99
99
|
this.dialog = page.getByRole('dialog');
|
|
100
100
|
this.messageBanner = page.locator('[data-test="close-top-banner"]');
|
|
101
|
-
|
|
101
|
+
// Matched on the shared "Got it" prefix so it covers both announcement
|
|
102
|
+
// dialogs hub currently ships: the release notes one ("Got it - Dismiss")
|
|
103
|
+
// and the process-application restructure one ("Got it").
|
|
104
|
+
this.closeButton = page.getByRole('button', { name: 'Got it' });
|
|
102
105
|
this.optionsButton = page.getByRole('button', { name: 'Options' });
|
|
103
106
|
this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
|
|
104
107
|
hasText: formName,
|
|
@@ -106,16 +109,31 @@ class ModelerHomePage {
|
|
|
106
109
|
this.formBreadcrumbButton = page.locator('button[data-test="breadcrumb-form"]');
|
|
107
110
|
this.renameFormMenuItem = page.locator('[data-test="rename-form"]');
|
|
108
111
|
}
|
|
112
|
+
// The release-announcement dialog ("Check out our new release") re-mounts
|
|
113
|
+
// after the projects list re-renders, so one dismissal pass is not enough.
|
|
114
|
+
// Repeat while a pass still finds something to dismiss; a pass that finds
|
|
115
|
+
// nothing returns immediately, so the cost is unchanged when no overlay is up.
|
|
109
116
|
async dismissOverlays() {
|
|
110
|
-
for (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
118
|
+
let dismissed = false;
|
|
119
|
+
for (const locator of [
|
|
120
|
+
this.closeButton,
|
|
121
|
+
this.page.getByRole('button', { name: 'Close' }).first(),
|
|
122
|
+
this.messageBanner,
|
|
123
|
+
]) {
|
|
124
|
+
const visible = await locator
|
|
125
|
+
.waitFor({ state: 'visible', timeout: 3000 })
|
|
126
|
+
.then(() => true)
|
|
127
|
+
.catch(() => false);
|
|
128
|
+
if (!visible) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
await locator.click({ timeout: 10000 }).catch(() => { });
|
|
132
|
+
dismissed = true;
|
|
133
|
+
}
|
|
134
|
+
if (!dismissed) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
async clickCreateNewProjectButton() {
|
|
@@ -127,10 +145,39 @@ class ModelerHomePage {
|
|
|
127
145
|
visibilityTimeout: 10000,
|
|
128
146
|
});
|
|
129
147
|
}
|
|
148
|
+
// The rename runs on a freshly created project, which is exactly when an
|
|
149
|
+
// announcement dialog mounts on top of it: it takes focus, the inline editor
|
|
150
|
+
// unmounts, and a single click on `[data-test="editable-input"]` then waits
|
|
151
|
+
// out its whole timeout on an element that never comes back — leaving the
|
|
152
|
+
// project named "New project". Dismiss overlays and re-enter the name until
|
|
153
|
+
// it commits.
|
|
154
|
+
//
|
|
155
|
+
// The committed name renders differently per page: the project page puts it
|
|
156
|
+
// in the breadcrumb menu button, and only the projects list carries a
|
|
157
|
+
// `[title="<name>"]` link. Accept either, and once the rename has been
|
|
158
|
+
// submitted treat the editor unmounting as done — hub keeps rendering the
|
|
159
|
+
// pre-rename fetch until a reload, which the caller's Home-page assertion
|
|
160
|
+
// already performs.
|
|
130
161
|
async enterNewProjectName(name) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
const committedName = this.page
|
|
163
|
+
.locator('[data-test="breadcrumb-project-menu"]', { hasText: name })
|
|
164
|
+
.or(this.page.getByTitle(name, { exact: true }))
|
|
165
|
+
.first();
|
|
166
|
+
let submitted = false;
|
|
167
|
+
await (0, test_1.expect)(async () => {
|
|
168
|
+
if (await committedName.isVisible()) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (submitted && !(await this.projectNameInput.isVisible())) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
await this.dismissOverlays();
|
|
175
|
+
await this.projectNameInput.click({ timeout: 15000 });
|
|
176
|
+
await this.projectNameInput.fill(name);
|
|
177
|
+
submitted = true;
|
|
178
|
+
await this.projectNameInput.press('Enter');
|
|
179
|
+
await (0, test_1.expect)(committedName).toBeVisible({ timeout: 10000 });
|
|
180
|
+
}).toPass({ timeout: 120000 });
|
|
134
181
|
}
|
|
135
182
|
async enterIdpApplicationName(name) {
|
|
136
183
|
await this.idpApplicationNameInput.click({ timeout: 60000 });
|
|
@@ -378,12 +425,17 @@ class ModelerHomePage {
|
|
|
378
425
|
await this.page.getByText('Home', { exact: true }).first().click();
|
|
379
426
|
}
|
|
380
427
|
}
|
|
428
|
+
// An announcement dialog covering the Modeler header makes every click on the
|
|
429
|
+
// org button bounce off the modal layer ("intercepts pointer events") until
|
|
430
|
+
// the timeout, so dismiss overlays before each attempt.
|
|
381
431
|
async clickOpenOrganizationsButton() {
|
|
382
|
-
await (0,
|
|
383
|
-
|
|
384
|
-
|
|
432
|
+
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.openOrganizationsButton, {
|
|
433
|
+
preAction: async () => {
|
|
434
|
+
await this.dismissOverlays();
|
|
435
|
+
},
|
|
436
|
+
totalTimeout: 120000,
|
|
437
|
+
visibilityTimeout: 30000,
|
|
385
438
|
});
|
|
386
|
-
await this.openOrganizationsButton.click({ timeout: 30000 });
|
|
387
439
|
}
|
|
388
440
|
async clickCrossComponentProjectFolder() {
|
|
389
441
|
await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.crossComponentProjectFolder, {
|
|
@@ -160,6 +160,19 @@ class ProcessApplicationPage {
|
|
|
160
160
|
.filter({ hasNotText: 'Select a development cluster' })
|
|
161
161
|
.first();
|
|
162
162
|
await option.click({ timeout: 30000 });
|
|
163
|
+
// The field defaults to the first cluster alphabetically and re-mounts as
|
|
164
|
+
// the cluster list settles, so an option click can be dropped and Save
|
|
165
|
+
// then persists that default. Nothing downstream re-reads the binding --
|
|
166
|
+
// ensureStageCluster records the requested cluster as fact -- so the
|
|
167
|
+
// symptom surfaces much later, as a deploy dialog offering a cluster the
|
|
168
|
+
// test never asked for. Confirm the field carries the chosen cluster
|
|
169
|
+
// before saving; ensureStageCluster's retry re-navigates and binds again
|
|
170
|
+
// when it does not.
|
|
171
|
+
if (clusterName) {
|
|
172
|
+
await (0, test_1.expect)(this.developmentClusterSelect).toContainText(clusterName, {
|
|
173
|
+
timeout: 15000,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
163
176
|
await this.defineStagesSaveButton.click({ timeout: 30000 });
|
|
164
177
|
await (0, test_1.expect)(this.defineStagesDialog).toBeHidden({ timeout: 60000 });
|
|
165
178
|
}
|
|
@@ -159,7 +159,10 @@ declare class ModelerCreatePage {
|
|
|
159
159
|
clickMarketPlaceButton(): Promise<void>;
|
|
160
160
|
completeDeploymentEndpointConfiguration(): Promise<void>;
|
|
161
161
|
completePlayConfiguration(): Promise<void>;
|
|
162
|
-
|
|
162
|
+
revealChangeElementTemplate(option: Locator, searchTerm: string): Promise<boolean>;
|
|
163
|
+
private isVisibleWithin;
|
|
164
|
+
private openChangeElementPopup;
|
|
165
|
+
private clickChangeElementTemplateOption;
|
|
163
166
|
clickWebhookMessageStartEventConnectorOption(): Promise<void>;
|
|
164
167
|
clickWebhookIdInput(): Promise<void>;
|
|
165
168
|
clearWebhookIdInput(): Promise<void>;
|
|
@@ -1128,76 +1128,93 @@ class ModelerCreatePage {
|
|
|
1128
1128
|
}
|
|
1129
1129
|
}
|
|
1130
1130
|
}
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
//
|
|
1134
|
-
// result list
|
|
1131
|
+
// Two independent paths put a connector template into the popup's queryable
|
|
1132
|
+
// DOM slice, and neither is reliable alone: selecting the "Reusable assets"
|
|
1133
|
+
// tab renders the template entries directly, while searching flattens the
|
|
1134
|
+
// result list. Probe the option after each step and stop at the first one
|
|
1135
|
+
// that worked, then keep re-issuing the filter while the popup stays open:
|
|
1136
|
+
// the project's connector-template list is fetched asynchronously, so a
|
|
1137
|
+
// filter typed before it lands renders "0 results found" and a single typed
|
|
1138
|
+
// filter never picks the template up afterwards.
|
|
1135
1139
|
// Use pressSequentially (not fill): the popup syncs its search state from a
|
|
1136
1140
|
// keyup listener only, and fill() dispatches just an input event, so the
|
|
1137
1141
|
// filter never applies and the unfiltered BPMN tab keeps rendering.
|
|
1138
|
-
async
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
// hid) with the template still absent from .djs-popup-results, so the
|
|
1144
|
-
// tab click runs first and the search backs it up.
|
|
1145
|
-
const hasTabs = await this.changeElementTabStrip
|
|
1146
|
-
.isVisible({ timeout: 5000 })
|
|
1147
|
-
.catch(() => false);
|
|
1148
|
-
if (hasTabs) {
|
|
1142
|
+
async revealChangeElementTemplate(option, searchTerm) {
|
|
1143
|
+
if (await this.isVisibleWithin(option, 5000)) {
|
|
1144
|
+
return true;
|
|
1145
|
+
}
|
|
1146
|
+
if (await this.isVisibleWithin(this.changeElementTabStrip, 5000)) {
|
|
1149
1147
|
// A failed tab click leaves the search path intact, so do not throw.
|
|
1150
1148
|
await this.reusableAssetsTab.click({ timeout: 30000 }).catch(() => { });
|
|
1149
|
+
if (await this.isVisibleWithin(option, 15000)) {
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1151
1152
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1153
|
+
if (!(await this.isVisibleWithin(this.changeElementSearchInput, 5000))) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
const deadline = Date.now() + 90000;
|
|
1157
|
+
do {
|
|
1158
|
+
await this.changeElementSearchInput.click();
|
|
1159
|
+
await this.changeElementSearchInput.fill('');
|
|
1160
|
+
await this.changeElementSearchInput.pressSequentially(searchTerm, {
|
|
1161
|
+
delay: 50,
|
|
1162
|
+
});
|
|
1163
|
+
if (await this.isVisibleWithin(option, 15000)) {
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
} while (Date.now() < deadline);
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
async isVisibleWithin(locator, timeout) {
|
|
1170
|
+
return locator
|
|
1171
|
+
.waitFor({ state: 'visible', timeout })
|
|
1172
|
+
.then(() => true)
|
|
1154
1173
|
.catch(() => false);
|
|
1155
|
-
|
|
1174
|
+
}
|
|
1175
|
+
// The specs reach the Marketplace through this popup's own empty-state link,
|
|
1176
|
+
// and the import modal closes the popup behind it, so the popup is routinely
|
|
1177
|
+
// gone by the time a template is clicked. Reopen it against the element being
|
|
1178
|
+
// replaced first: filtering a closed popup finds no search input and the
|
|
1179
|
+
// option assertion then times out against a popup that was never on screen.
|
|
1180
|
+
async openChangeElementPopup(element) {
|
|
1181
|
+
if (await this.isVisibleWithin(this.changeElementSearchInput, 5000)) {
|
|
1156
1182
|
return;
|
|
1157
1183
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
await
|
|
1161
|
-
|
|
1162
|
-
});
|
|
1163
|
-
// The popup hides its tab strip while searching. Wait on that as a
|
|
1164
|
-
// readiness signal, but do not assert it: with the tab already selected
|
|
1165
|
-
// the template is reachable whether or not the search registered.
|
|
1166
|
-
await (0, test_1.expect)(this.changeElementTabStrip)
|
|
1167
|
-
.toBeHidden({ timeout: 30000 })
|
|
1168
|
-
.catch(() => { });
|
|
1184
|
+
// The context pad only renders for a selected element, so "Change element"
|
|
1185
|
+
// would otherwise never appear.
|
|
1186
|
+
await (0, test_1.expect)(element).toBeVisible({ timeout: 60000 });
|
|
1187
|
+
await element.click({ force: true, timeout: 30000 });
|
|
1188
|
+
await this.changeTypeButton.click({ force: true, timeout: 30000 });
|
|
1169
1189
|
}
|
|
1170
|
-
async
|
|
1190
|
+
async clickChangeElementTemplateOption(option, element, searchTerm) {
|
|
1171
1191
|
const maxRetries = 3;
|
|
1192
|
+
let lastError;
|
|
1172
1193
|
for (let retries = 0; retries < maxRetries; retries++) {
|
|
1173
1194
|
try {
|
|
1174
|
-
if (retries
|
|
1175
|
-
await this.filterChangeElementPopup('Webhook Message Start Event Connector');
|
|
1176
|
-
await this.webhookMessageStartEventConnectorOption.click({
|
|
1177
|
-
timeout: 30000,
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
else {
|
|
1195
|
+
if (retries > 0) {
|
|
1181
1196
|
await this.page.reload();
|
|
1182
|
-
//
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
await (0, test_1.expect)(this.startEventElement).toBeVisible({ timeout: 60000 });
|
|
1186
|
-
await this.startEventElement.click({ force: true, timeout: 30000 });
|
|
1187
|
-
await this.changeTypeButton.click({ force: true, timeout: 30000 });
|
|
1188
|
-
await this.filterChangeElementPopup('Webhook Message Start Event Connector');
|
|
1189
|
-
await this.webhookMessageStartEventConnectorOption.click({
|
|
1190
|
-
timeout: 90000,
|
|
1191
|
-
});
|
|
1197
|
+
// domcontentloaded is more reliable than networkidle for Modeler,
|
|
1198
|
+
// which maintains persistent background connections (SSE/WebSocket).
|
|
1199
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
1192
1200
|
}
|
|
1201
|
+
await this.openChangeElementPopup(element);
|
|
1202
|
+
if (!(await this.revealChangeElementTemplate(option, searchTerm))) {
|
|
1203
|
+
throw new Error('The connector template never appeared in the change-element popup.');
|
|
1204
|
+
}
|
|
1205
|
+
await option.click({ timeout: 30000 });
|
|
1193
1206
|
return;
|
|
1194
1207
|
}
|
|
1195
1208
|
catch (error) {
|
|
1209
|
+
lastError = error;
|
|
1196
1210
|
console.error(`Click attempt ${retries + 1} failed: ${error}`);
|
|
1197
|
-
await
|
|
1211
|
+
await (0, sleep_1.sleep)(10000);
|
|
1198
1212
|
}
|
|
1199
1213
|
}
|
|
1200
|
-
throw new Error(`Failed to click the
|
|
1214
|
+
throw new Error(`Failed to click the connector template after ${maxRetries} attempts: ${lastError}`);
|
|
1215
|
+
}
|
|
1216
|
+
async clickWebhookMessageStartEventConnectorOption() {
|
|
1217
|
+
await this.clickChangeElementTemplateOption(this.webhookMessageStartEventConnectorOption, this.startEventElement, 'Webhook');
|
|
1201
1218
|
}
|
|
1202
1219
|
async clickWebhookIdInput() {
|
|
1203
1220
|
await this.webhookIdInput.click();
|
|
@@ -1354,33 +1371,7 @@ class ModelerCreatePage {
|
|
|
1354
1371
|
await this.appendViaPopup('append-none-intermediate-throwing', ['append-events', 'append-intermediate-throw-events'], 'Intermediate throw event');
|
|
1355
1372
|
}
|
|
1356
1373
|
async clickIntermediateWebhookConnectorOption() {
|
|
1357
|
-
|
|
1358
|
-
for (let i = 0; i < max; i++) {
|
|
1359
|
-
try {
|
|
1360
|
-
// Filter the popup to surface the webhook connector: it sits in the
|
|
1361
|
-
// "Reusable assets" tab, which the popup only flattens into the result
|
|
1362
|
-
// list while a search is active.
|
|
1363
|
-
await this.filterChangeElementPopup('Webhook');
|
|
1364
|
-
await (0, test_1.expect)(this.intermediateWebhookConnectorOption).toBeVisible({
|
|
1365
|
-
timeout: 30000,
|
|
1366
|
-
});
|
|
1367
|
-
await this.intermediateWebhookConnectorOption.click({ timeout: 30000 });
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
catch (err) {
|
|
1371
|
-
console.error(`clickIntermediateWebhookConnectorOption attempt ${i + 1} failed: ${err}`);
|
|
1372
|
-
if (i === max - 1)
|
|
1373
|
-
throw err;
|
|
1374
|
-
await this.page.reload();
|
|
1375
|
-
// domcontentloaded is more reliable than networkidle for Modeler
|
|
1376
|
-
// which maintains persistent background connections (SSE/WebSocket).
|
|
1377
|
-
await this.page.waitForLoadState('domcontentloaded');
|
|
1378
|
-
// Wait for modeler canvas to render after reload before re-selecting
|
|
1379
|
-
await (0, test_1.expect)(this.secondElement).toBeVisible({ timeout: 60000 });
|
|
1380
|
-
await this.secondElement.click({ force: true, timeout: 30000 });
|
|
1381
|
-
await this.changeTypeButton.click({ force: true, timeout: 30000 });
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1374
|
+
await this.clickChangeElementTemplateOption(this.intermediateWebhookConnectorOption, this.secondElement, 'Webhook');
|
|
1384
1375
|
}
|
|
1385
1376
|
async clickCorrelationKeyProcessInput() {
|
|
1386
1377
|
await this.correlationKeyProcessInput.click({ timeout: 60000 });
|
|
@@ -98,7 +98,7 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
|
|
|
98
98
|
await modelerCreatePage.clickStartEventElement();
|
|
99
99
|
await modelerCreatePage.clickChangeTypeButton();
|
|
100
100
|
try {
|
|
101
|
-
await modelerCreatePage.
|
|
101
|
+
await modelerCreatePage.revealChangeElementTemplate(modelerCreatePage.webhookMessageStartEventConnectorOption, 'Webhook');
|
|
102
102
|
await (0, test_1.expect)(modelerCreatePage.webhookMessageStartEventConnectorOption).toBeVisible({
|
|
103
103
|
timeout: 15000,
|
|
104
104
|
});
|
|
@@ -212,7 +212,9 @@ SM_8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
|
|
|
212
212
|
// Probe for the template this flow needs: an intermediate event's
|
|
213
213
|
// change-element list never offers the message start event connector,
|
|
214
214
|
// so probing for that one always fell through to the marketplace.
|
|
215
|
-
|
|
215
|
+
// 'Webhook' is the filter both webhook flows share; the entry is then
|
|
216
|
+
// picked by its template id, so a broader term cannot mis-select.
|
|
217
|
+
await modelerCreatePage.revealChangeElementTemplate(modelerCreatePage.intermediateWebhookConnectorOption, 'Webhook');
|
|
216
218
|
await (0, test_1.expect)(modelerCreatePage.intermediateWebhookConnectorOption).toBeVisible({
|
|
217
219
|
timeout: 15000,
|
|
218
220
|
});
|