@camunda/e2e-test-suite 0.0.1026 → 0.0.1028

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.
@@ -8,6 +8,8 @@ declare class Authorization {
8
8
  protected readonly createAuthorizationOwnerTypeOption: (name: string) => Locator;
9
9
  protected readonly createAuthorizationOwnerComboBox: Locator;
10
10
  protected readonly createAuthorizationOwnerOption: (name: string) => Locator;
11
+ protected readonly createAuthorizationOwnerSearchInput: Locator;
12
+ protected readonly createAuthorizationOwnerSearchOption: (name: string) => Locator;
11
13
  protected readonly createAuthorizationResourceTypeDropdown: Locator;
12
14
  protected readonly createAuthorizationResourceTypeOption: (name: string) => Locator;
13
15
  protected readonly createAuthorizationResourceIdField: Locator;
@@ -19,6 +21,7 @@ declare class Authorization {
19
21
  protected readonly deleteAuthorizationModalDeleteButton: Locator;
20
22
  protected readonly tableFilter: Locator;
21
23
  constructor(page: Page);
24
+ private selectAuthorizationOwner;
22
25
  createAuthorization(authorization: {
23
26
  ownerType: string;
24
27
  ownerId: string;
@@ -14,6 +14,8 @@ class Authorization {
14
14
  createAuthorizationOwnerTypeOption;
15
15
  createAuthorizationOwnerComboBox;
16
16
  createAuthorizationOwnerOption;
17
+ createAuthorizationOwnerSearchInput;
18
+ createAuthorizationOwnerSearchOption;
17
19
  createAuthorizationResourceTypeDropdown;
18
20
  createAuthorizationResourceTypeOption;
19
21
  createAuthorizationResourceIdField;
@@ -51,6 +53,16 @@ class Authorization {
51
53
  name,
52
54
  exact: true,
53
55
  });
56
+ // Every owner type now renders a server-backed search dropdown instead of
57
+ // the combobox that listed all owners up front. The dropdown is a Carbon
58
+ // Search inside a ListBox, so it has no combobox role; its results are
59
+ // list-box menu items that carry the owner ID as their title.
60
+ this.createAuthorizationOwnerSearchInput =
61
+ this.createAuthorizationModal.getByPlaceholder('Search by owner ID');
62
+ this.createAuthorizationOwnerSearchOption = (name) => this.createAuthorizationModal
63
+ .locator('.cds--list-box__menu-item')
64
+ .filter({ hasText: name })
65
+ .first();
54
66
  this.createAuthorizationResourceTypeDropdown =
55
67
  this.createAuthorizationModal.getByRole('combobox', {
56
68
  name: 'Resource type',
@@ -79,18 +91,38 @@ class Authorization {
79
91
  });
80
92
  this.tableFilter = page.getByRole('searchbox', { name: 'Filter table' });
81
93
  }
82
- async createAuthorization(authorization) {
83
- await this.createAuthorizationButton.click();
84
- await (0, test_1.expect)(this.createAuthorizationModal).toBeVisible();
85
- await this.createAuthorizationOwnerTypeCombobox.click();
86
- await this.createAuthorizationOwnerTypeOption(authorization.ownerType).click();
94
+ // Released 8.10 alpha images still render the Carbon combobox that lists
95
+ // every owner, so accept whichever owner field this build shows.
96
+ async selectAuthorizationOwner(ownerId) {
97
+ await (0, test_1.expect)(this.createAuthorizationOwnerSearchInput.or(this.createAuthorizationOwnerComboBox)).toBeVisible({ timeout: 30000 });
98
+ if (await this.createAuthorizationOwnerSearchInput.isVisible()) {
99
+ await this.createAuthorizationOwnerSearchInput.fill(ownerId);
100
+ const ownerOption = this.createAuthorizationOwnerSearchOption(ownerId);
101
+ // The search is debounced and served by the API, so a just-created
102
+ // owner can take a while to surface.
103
+ await (0, test_1.expect)(ownerOption).toBeVisible({ timeout: 60000 });
104
+ await ownerOption.click({ timeout: 30000 });
105
+ // Picking an owner replaces the search input with a tag holding the
106
+ // selection, which is how we know the choice was committed.
107
+ await (0, test_1.expect)(this.createAuthorizationOwnerSearchInput).toBeHidden({
108
+ timeout: 30000,
109
+ });
110
+ return;
111
+ }
87
112
  await this.createAuthorizationOwnerComboBox.click();
88
- await (0, test_1.expect)(this.createAuthorizationOwnerOption(authorization.ownerId)).toBeVisible({
113
+ await (0, test_1.expect)(this.createAuthorizationOwnerOption(ownerId)).toBeVisible({
89
114
  timeout: 30000,
90
115
  });
91
- await this.createAuthorizationOwnerOption(authorization.ownerId).click({
116
+ await this.createAuthorizationOwnerOption(ownerId).click({
92
117
  timeout: 30000,
93
118
  });
119
+ }
120
+ async createAuthorization(authorization) {
121
+ await this.createAuthorizationButton.click();
122
+ await (0, test_1.expect)(this.createAuthorizationModal).toBeVisible();
123
+ await this.createAuthorizationOwnerTypeCombobox.click();
124
+ await this.createAuthorizationOwnerTypeOption(authorization.ownerType).click();
125
+ await this.selectAuthorizationOwner(authorization.ownerId);
94
126
  // The Resource type is pre-selected and disabled when the dialog is
95
127
  // opened from a specific resource-type tab. Only pick it from the dropdown
96
128
  // when the field is still enabled (editable).
@@ -159,7 +159,10 @@ declare class ModelerCreatePage {
159
159
  clickMarketPlaceButton(): Promise<void>;
160
160
  completeDeploymentEndpointConfiguration(): Promise<void>;
161
161
  completePlayConfiguration(): Promise<void>;
162
- filterChangeElementPopup(searchTerm: string): Promise<void>;
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
- // Connector templates now live in the popup's "Reusable assets" tab, not
1132
- // the default "BPMN" one, so they do not render until either that tab is
1133
- // selected or a search is active (search spans every tab and flattens the
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 filterChangeElementPopup(searchTerm) {
1139
- // Two independent paths put the template in the DOM, and neither is
1140
- // reliable alone: selecting the tab renders the template entries
1141
- // directly, while searching spans every tab and flattens the result
1142
- // list. Run 32243588212 showed the search registering (the tab strip
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
- const hasSearch = await this.changeElementSearchInput
1153
- .isVisible({ timeout: 5000 })
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
- if (!hasSearch) {
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
- await this.changeElementSearchInput.click();
1159
- await this.changeElementSearchInput.fill('');
1160
- await this.changeElementSearchInput.pressSequentially(searchTerm, {
1161
- delay: 50,
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 clickWebhookMessageStartEventConnectorOption() {
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 === 0) {
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
- // A reload drops the selection, so re-select the start event before
1183
- // reopening the popup -- the context pad only renders for a selected
1184
- // element and "Change element" would otherwise never appear.
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 new Promise((resolve) => setTimeout(resolve, 10000));
1211
+ await (0, sleep_1.sleep)(10000);
1198
1212
  }
1199
1213
  }
1200
- throw new Error(`Failed to click the button after ${maxRetries} attempts.`);
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
- const max = 3;
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 });
@@ -263,9 +263,15 @@ class OCTenantPage {
263
263
  await this.roleIdSearchBox.click();
264
264
  }
265
265
  async fillRoleIdSearchBox(roleId, roleIdentifier) {
266
- await this.roleIdSearchBox.fill(roleId);
267
266
  const result = this.page.getByText(roleIdentifier).first();
268
- await (0, test_1.expect)(result).toBeVisible({ timeout: 15000 });
267
+ // The dropdown matches against the roles fetched with an empty search
268
+ // filter, so clearing the box re-triggers that fetch before retyping.
269
+ await (0, test_1.expect)(async () => {
270
+ await this.roleIdSearchBox.fill('');
271
+ await this.page.waitForLoadState('networkidle');
272
+ await this.roleIdSearchBox.fill(roleId);
273
+ await (0, test_1.expect)(result).toBeVisible({ timeout: 15000 });
274
+ }).toPass({ timeout: 90000, intervals: [2000] });
269
275
  await result.click();
270
276
  }
271
277
  async clickAssignRoleSubButton() {
@@ -279,6 +285,7 @@ class OCTenantPage {
279
285
  return;
280
286
  }
281
287
  await this.clickAssignRoleButton();
288
+ await this.page.waitForLoadState('networkidle');
282
289
  await this.clickRoleIdSearchBox();
283
290
  await this.fillRoleIdSearchBox(roleId, roleIdentifier);
284
291
  await this.clickAssignRoleSubButton();
@@ -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.filterChangeElementPopup('Webhook Message Start Event Connector');
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
- await modelerCreatePage.filterChangeElementPopup('Webhook Intermediate Event Connector');
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
  });
@@ -6,6 +6,12 @@ async function findLocatorWithPagination(page, locator, options) {
6
6
  const maxPages = 10;
7
7
  const paginationButton = page.getByRole('button', { name: 'Next page' });
8
8
  const backupPaginationButton = page.getByRole('button', { name: 'Load more' });
9
+ // The design-system DataTable the Admin UI is migrating onto labels its
10
+ // forward control just "Next", unlike Carbon's "Next page". Scope it to the
11
+ // pagination bar so an unrelated wizard "Next" can never be clicked.
12
+ const dataTablePaginationButton = page
13
+ .locator('[data-slot="data-table-pagination"]')
14
+ .getByRole('button', { name: 'Next', exact: true });
9
15
  const startTime = Date.now();
10
16
  for (let pageNum = 0; pageNum < maxPages; pageNum++) {
11
17
  if (Date.now() - startTime > timeout) {
@@ -20,7 +26,10 @@ async function findLocatorWithPagination(page, locator, options) {
20
26
  console.log(`Found locator "${locator}" on page ${pageNum + 1}`);
21
27
  return true;
22
28
  }
23
- const nextButton = paginationButton.or(backupPaginationButton).last();
29
+ const nextButton = paginationButton
30
+ .or(backupPaginationButton)
31
+ .or(dataTablePaginationButton)
32
+ .last();
24
33
  const isVisible = await nextButton
25
34
  .isVisible({ timeout: 1000 })
26
35
  .catch(() => false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1026",
3
+ "version": "0.0.1028",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",