@camunda/e2e-test-suite 0.0.1094 → 0.0.1095

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.
@@ -7,6 +7,8 @@ declare class Authorization {
7
7
  protected readonly createAuthorizationOwnerTypeCombobox: Locator;
8
8
  protected readonly createAuthorizationOwnerTypeOption: (name: string) => Locator;
9
9
  protected readonly createAuthorizationOwnerComboBox: Locator;
10
+ protected readonly createAuthorizationOwnerSearchInput: Locator;
11
+ protected readonly createAuthorizationOwnerLegacyComboBox: Locator;
10
12
  protected readonly createAuthorizationOwnerOption: (name: string) => Locator;
11
13
  protected readonly createAuthorizationResourceTypeDropdown: Locator;
12
14
  protected readonly createAuthorizationResourceTypeOption: (name: string) => Locator;
@@ -12,6 +12,8 @@ class Authorization {
12
12
  createAuthorizationOwnerTypeCombobox;
13
13
  createAuthorizationOwnerTypeOption;
14
14
  createAuthorizationOwnerComboBox;
15
+ createAuthorizationOwnerSearchInput;
16
+ createAuthorizationOwnerLegacyComboBox;
15
17
  createAuthorizationOwnerOption;
16
18
  createAuthorizationResourceTypeDropdown;
17
19
  createAuthorizationResourceTypeOption;
@@ -40,17 +42,30 @@ class Authorization {
40
42
  name,
41
43
  exact: true,
42
44
  });
43
- // The Owner field is no longer a Carbon ComboBox labelled "Owner".
44
- // Identity replaced it with an entity-search dropdown
45
+ // The Owner field has two shapes depending on the build under test.
46
+ //
47
+ // Since camunda/camunda#57931 Identity renders an entity-search dropdown
45
48
  // (identity/client/src/components/form/EntitySearchSingleSelect.tsx ->
46
- // EntitySearchDropdown -> DropdownSearch), which renders a bare
47
- // `FormLabel` plus a Carbon `Search` input carrying only the
48
- // "Search by owner ID" placeholder — nothing in the modal exposes the
49
- // accessible name "Owner" any more, so the old combobox click waited out
50
- // its full timeout. Anchor on the placeholder, which is what the
51
- // component actually renders for every searchable owner type.
52
- this.createAuthorizationOwnerComboBox =
49
+ // EntitySearchDropdown -> DropdownSearch): a bare `FormLabel` plus a
50
+ // Carbon `Search` input carrying only the "Search by owner ID"
51
+ // placeholder, with nothing exposing the accessible name "Owner".
52
+ //
53
+ // Builds that predate that change — notably released alpha generations
54
+ // such as 8.10.0-alpha5, tagged 2026-08-31 before the stable/8.10
55
+ // backport (#61570) landed on 2026-09-01 — still render the older Carbon
56
+ // `ComboBox` (owner-selection/OwnerSelection.tsx) whose `titleText` is
57
+ // "Owner". Match either so the suite runs against both.
58
+ this.createAuthorizationOwnerSearchInput =
53
59
  this.createAuthorizationModal.getByPlaceholder('Search by owner ID');
60
+ this.createAuthorizationOwnerLegacyComboBox =
61
+ this.createAuthorizationModal.getByRole('combobox', {
62
+ name: 'Owner',
63
+ exact: true,
64
+ });
65
+ this.createAuthorizationOwnerComboBox =
66
+ this.createAuthorizationOwnerSearchInput
67
+ .or(this.createAuthorizationOwnerLegacyComboBox)
68
+ .first();
54
69
  this.createAuthorizationOwnerOption = (name) => this.createAuthorizationModal.getByRole('option', {
55
70
  name,
56
71
  exact: true,
@@ -96,14 +111,23 @@ class Authorization {
96
111
  await (0, test_1.expect)(this.createAuthorizationModal).toBeVisible();
97
112
  await this.createAuthorizationOwnerTypeCombobox.click();
98
113
  await this.createAuthorizationOwnerTypeOption(authorization.ownerType).click();
99
- // Owner type selection re-renders the owner field via a query-driven
100
- // component swap, which can lag well past 30s under CI load — the
101
- // product team's own e2e suite hit the same widget and had to raise an
102
- // analogous wait from 20s to 60s for the same reason (camunda/camunda#61353).
114
+ // Owner type selection re-renders the owner field, and on the
115
+ // entity-search build that swap is query-driven, so it can lag well past
116
+ // 30s under CI load — the product team's own e2e suite raised an
117
+ // analogous wait on the same widget from 20s to 60s (camunda/camunda#61353).
103
118
  await (0, test_1.expect)(this.createAuthorizationOwnerComboBox).toBeVisible({
104
119
  timeout: 60000,
105
120
  });
106
- await this.createAuthorizationOwnerComboBox.fill(authorization.ownerId);
121
+ const usesEntitySearch = await this.createAuthorizationOwnerSearchInput.isVisible();
122
+ const ownerField = usesEntitySearch
123
+ ? this.createAuthorizationOwnerSearchInput
124
+ : this.createAuthorizationOwnerLegacyComboBox;
125
+ if (!usesEntitySearch) {
126
+ // The legacy Carbon ComboBox renders its menu only once opened; the
127
+ // entity-search input renders results off the typed value alone.
128
+ await ownerField.click();
129
+ }
130
+ await ownerField.fill(authorization.ownerId);
107
131
  // The dropdown renders results only while the search box holds text, and
108
132
  // the list is fetched from the server on a debounce rather than filtered
109
133
  // client-side, so the matching entry appears a beat later. Clicking the
@@ -117,13 +141,17 @@ class Authorization {
117
141
  .first();
118
142
  await (0, test_1.expect)(ownerMenuItem).toBeVisible({ timeout: 60000 });
119
143
  await ownerMenuItem.click({ timeout: 20000 });
120
- // A committed selection swaps the search box for a filter Tag
121
- // (EntitySearchSingleSelect renders one or the other), so the box going
122
- // away is how we know the owner actually landed on the form rather than
123
- // the click being swallowed.
124
- await (0, test_1.expect)(this.createAuthorizationOwnerComboBox).toBeHidden({
125
- timeout: 15000,
126
- });
144
+ // On the entity-search build a committed selection swaps the search box
145
+ // for a filter Tag (EntitySearchSingleSelect renders one or the other), so
146
+ // the box going away is how we know the owner actually landed on the form
147
+ // rather than the click being swallowed. The legacy ComboBox keeps its
148
+ // input and shows the selection as the input value, so this check only
149
+ // applies to the entity-search shape.
150
+ if (usesEntitySearch) {
151
+ await (0, test_1.expect)(this.createAuthorizationOwnerSearchInput).toBeHidden({
152
+ timeout: 15000,
153
+ });
154
+ }
127
155
  await this.selectResourceTypeFromDialog(authorization.resourceType);
128
156
  await this.createAuthorizationResourceIdField.fill(authorization.resourceId);
129
157
  for (const permission of authorization.accessPermissions) {
@@ -21,7 +21,7 @@ declare class Authorization {
21
21
  protected readonly deleteAuthorizationModalDeleteButton: Locator;
22
22
  protected readonly tableFilter: Locator;
23
23
  constructor(page: Page);
24
- protected selectResourceType(resourceType: string): Promise<void>;
24
+ protected selectResourceType(resourceType: string, timeout?: number): Promise<void>;
25
25
  private selectAuthorizationOwner;
26
26
  createAuthorization(authorization: {
27
27
  ownerType: string;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Authorization = void 0;
4
4
  const test_1 = require("@playwright/test");
5
- const UtilitiesPage_1 = require("../SM-8.10/UtilitiesPage");
6
5
  const findLocatorWithPagination_1 = require("../../utils/assertionHelpers/findLocatorWithPagination");
7
6
  const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
8
7
  const sleep_1 = require("../../utils/sleep");
@@ -41,10 +40,17 @@ class Authorization {
41
40
  this.createAuthorizationModal.getByRole('combobox', {
42
41
  name: 'Owner type',
43
42
  });
44
- this.createAuthorizationOwnerTypeOption = (name) => this.createAuthorizationModal.getByRole('option', {
43
+ // The design-system Select portals its listbox to the end of <body>, so
44
+ // the options are siblings of the dialog rather than descendants, and
45
+ // while the listbox is open Radix marks everything outside it aria-hidden
46
+ // - which takes the dialog itself out of the accessibility tree. Query the
47
+ // options from the page root so both placements resolve.
48
+ this.createAuthorizationOwnerTypeOption = (name) => this.page
49
+ .getByRole('option', {
45
50
  name,
46
51
  exact: true,
47
- });
52
+ })
53
+ .first();
48
54
  this.createAuthorizationOwnerComboBox =
49
55
  this.createAuthorizationModal.getByRole('combobox', {
50
56
  name: 'Owner',
@@ -57,32 +63,38 @@ class Authorization {
57
63
  // Every owner type now renders a server-backed search dropdown instead of
58
64
  // the combobox that listed all owners up front. The dropdown is a Carbon
59
65
  // Search inside a ListBox, so it has no combobox role; its results are
60
- // list-box menu items that carry the owner ID as their title.
66
+ // list-box menu items that carry the owner ID as their title. The
67
+ // design-system build renders the same field as a command palette whose
68
+ // results are role=option items in a popover portalled out of the dialog.
61
69
  this.createAuthorizationOwnerSearchInput =
62
70
  this.createAuthorizationModal.getByPlaceholder('Search by owner ID');
63
- this.createAuthorizationOwnerSearchOption = (name) => this.createAuthorizationModal
64
- .locator('.cds--list-box__menu-item')
71
+ this.createAuthorizationOwnerSearchOption = (name) => this.page
72
+ .locator('.cds--list-box__menu-item, [role="option"]')
65
73
  .filter({ hasText: name })
66
74
  .first();
67
75
  this.createAuthorizationResourceTypeDropdown =
68
76
  this.createAuthorizationModal.getByRole('combobox', {
69
77
  name: 'Resource type',
70
78
  });
71
- this.createAuthorizationResourceTypeOption = (name) => this.createAuthorizationModal.getByRole('option', {
79
+ this.createAuthorizationResourceTypeOption = (name) => this.page
80
+ .getByRole('option', {
72
81
  name,
73
82
  exact: true,
74
- });
83
+ })
84
+ .first();
75
85
  this.createAuthorizationResourceIdField =
76
86
  this.createAuthorizationModal.getByRole('textbox', {
77
87
  name: 'Resource ID',
78
88
  });
79
- this.createAuthorizationAccessPermission = (name) => this.createAuthorizationModal.locator(`.cds--checkbox-wrapper label[for="${name}"]`);
89
+ this.createAuthorizationAccessPermission = (name) => this.createAuthorizationModal.locator(`label[for="${name}"]`);
80
90
  this.createAuthorizationSubmitButton =
81
91
  this.createAuthorizationModal.getByRole('button', {
82
92
  name: 'Create authorization',
83
93
  });
84
94
  this.authorizationsList = page.getByRole('table');
85
- this.deleteAuthorizationButton = (name) => this.authorizationsList.getByRole('row', { name }).getByLabel('Delete');
95
+ this.deleteAuthorizationButton = (name) => this.authorizationsList
96
+ .getByRole('row', { name })
97
+ .getByRole('button', { name: 'Delete' });
86
98
  this.deleteAuthorizationModal = page.getByRole('dialog', {
87
99
  name: /delete authorization/i,
88
100
  });
@@ -103,12 +115,12 @@ class Authorization {
103
115
  // "Authorization type" filter combobox (identity/client ListV2.tsx, gated on
104
116
  // isNewDesignSystemEnabled). Select the resource type through whichever this
105
117
  // build renders: click the tab, or open the combobox and pick the option.
106
- async selectResourceType(resourceType) {
118
+ async selectResourceType(resourceType, timeout = 60000) {
107
119
  const tab = this.selectResourceTypeTab(resourceType);
108
120
  const filterCombobox = this.page.getByRole('combobox', {
109
121
  name: 'Authorization type',
110
122
  });
111
- await (0, test_1.expect)(tab.or(filterCombobox)).toBeVisible({ timeout: 60000 });
123
+ await (0, test_1.expect)(tab.or(filterCombobox)).toBeVisible({ timeout });
112
124
  if (await tab.isVisible()) {
113
125
  await tab.click();
114
126
  return;
@@ -116,7 +128,7 @@ class Authorization {
116
128
  await filterCombobox.click();
117
129
  await this.page
118
130
  .getByRole('option', { name: resourceType, exact: true })
119
- .click({ timeout: 30000 });
131
+ .click({ timeout: Math.min(timeout, 30000) });
120
132
  }
121
133
  // Released 8.10 alpha images still render the Carbon combobox that lists
122
134
  // every owner, so accept whichever owner field this build shows.
@@ -164,7 +176,7 @@ class Authorization {
164
176
  for (const permission of authorization.accessPermissions) {
165
177
  await (0, sleep_1.sleep)(200);
166
178
  const permissionLabel = this.createAuthorizationAccessPermission(permission);
167
- const permissionInput = this.createAuthorizationModal.locator(`input[id="${permission}"]`);
179
+ const permissionInput = this.createAuthorizationModal.locator(`input[id="${permission}"], [role="checkbox"][id="${permission}"]`);
168
180
  await (0, test_1.expect)(permissionLabel).toBeVisible();
169
181
  if (!(await permissionInput.isChecked())) {
170
182
  await permissionLabel.scrollIntoViewIfNeeded();
@@ -188,7 +200,7 @@ class Authorization {
188
200
  }
189
201
  }
190
202
  await (0, submitModalWithRetry_1.submitModalWithRetry)(this.createAuthorizationSubmitButton, this.createAuthorizationModal);
191
- await this.selectResourceTypeTab(authorization.resourceType).click();
203
+ await this.selectResourceType(authorization.resourceType);
192
204
  const item = this.page
193
205
  .getByRole('row')
194
206
  .filter({
@@ -215,9 +227,7 @@ class Authorization {
215
227
  break;
216
228
  }
217
229
  try {
218
- const resourceTab = this.selectResourceTypeTab(authorization.resourceType);
219
- await (0, test_1.expect)(resourceTab).toBeVisible({ timeout: budget(30000) });
220
- await resourceTab.click({ timeout: budget(30000) });
230
+ await this.selectResourceType(authorization.resourceType, budget(30000));
221
231
  for (const filterValue of [authorization.ownerId, '']) {
222
232
  await this.filterAuthorizationsByOwner(filterValue);
223
233
  const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, item, {
@@ -268,11 +278,29 @@ class Authorization {
268
278
  await (0, test_1.expect)(this.deleteAuthorizationModal).toBeVisible();
269
279
  await this.deleteAuthorizationModalDeleteButton.click();
270
280
  await (0, test_1.expect)(this.deleteAuthorizationModal).not.toBeVisible();
271
- await (0, UtilitiesPage_1.assertLocatorVisibleWithRetry)(this.page, this.authorizationsList
272
- .getByRole('cell', {
273
- name: resourceId,
274
- })
275
- .first(), resourceId, 5000, true, 3, this.selectResourceTypeTab(resourceType));
281
+ // Re-selecting the resource type takes two interactions on the
282
+ // design-system build, so this cannot go through the shared
283
+ // reload-click-assert helper, which only accepts a single click locator.
284
+ const deletedCell = this.authorizationsList
285
+ .getByRole('cell', { name: resourceId })
286
+ .first();
287
+ let lastError;
288
+ for (let attempt = 0; attempt < 3; attempt++) {
289
+ await this.page.reload();
290
+ await this.page.waitForLoadState('domcontentloaded');
291
+ await this.selectResourceType(resourceType);
292
+ try {
293
+ await (0, test_1.expect)(deletedCell).not.toBeVisible({ timeout: 5000 });
294
+ return;
295
+ }
296
+ catch (error) {
297
+ lastError = error;
298
+ console.warn(`Attempt ${attempt + 1} still listed the ${resourceType} ` +
299
+ `authorization for "${resourceId}". Retrying...`);
300
+ }
301
+ }
302
+ throw new Error(`Authorization for resource "${resourceId}" is still listed: ` +
303
+ String(lastError));
276
304
  }
277
305
  }
278
306
  exports.Authorization = Authorization;
@@ -71,7 +71,16 @@ class OCIdentityRolesPage {
71
71
  // options are not inside assignMappingModal's DOM subtree - query them from
72
72
  // the page. Each option repeats the id as both title and subtitle, so match
73
73
  // on contained text rather than the accessible name.
74
- this.assignMappingRuleOption = (name) => page.getByRole('option').filter({ hasText: name }).first();
74
+ //
75
+ // Builds predating the design-system migration (released alpha
76
+ // generations such as 8.10.0-alpha5) still render the Carbon
77
+ // `DropdownSearch` list inside the dialog as a role="listbox", with the
78
+ // mapping rule id as the item title - accept either shape.
79
+ this.assignMappingRuleOption = (name) => page
80
+ .getByRole('option')
81
+ .filter({ hasText: name })
82
+ .or(this.assignMappingModal.getByRole('listbox').getByText(name))
83
+ .first();
75
84
  this.assignMappingModalAssignRuleButton = this.assignMappingModal.getByRole('button', {
76
85
  name: 'Assign mapping rule',
77
86
  });
@@ -287,14 +287,37 @@ class OCTenantPage {
287
287
  .filter({ hasText: roleIdentifier })
288
288
  .or(this.assignRoleModal.getByText(roleIdentifier))
289
289
  .first();
290
- // The dropdown matches against the roles fetched with an empty search
291
- // filter, so clearing the box re-triggers that fetch before retyping.
290
+ // The dropdown no longer filters client-side: it queries
291
+ // `/v2/roles/search` with `name: {$like: '*<typed>*'}`, which is
292
+ // case-sensitive, so the role ID ('connectors') never matches the default
293
+ // role's name ('Connectors'). The option row renders the ID followed by
294
+ // the name, so the name is the tail of `roleIdentifier`: search the ID
295
+ // first for the shells that still match on it, then fall back to the name.
296
+ const searchTerms = [roleId];
297
+ const roleName = roleIdentifier.startsWith(roleId)
298
+ ? roleIdentifier.slice(roleId.length)
299
+ : '';
300
+ if (roleName !== '' && roleName !== roleId) {
301
+ searchTerms.push(roleName);
302
+ }
303
+ // The dropdown only queries for a non-empty value, so clearing the box
304
+ // between terms resets it before the next one is typed.
292
305
  await (0, test_1.expect)(async () => {
293
- await this.roleIdSearchBox.fill('');
294
- await this.page.waitForLoadState('networkidle');
295
- await this.roleIdSearchBox.fill(roleId);
296
- await (0, test_1.expect)(result).toBeVisible({ timeout: 15000 });
297
- }).toPass({ timeout: 90000, intervals: [2000] });
306
+ for (const searchTerm of searchTerms) {
307
+ await this.roleIdSearchBox.fill('');
308
+ await this.page.waitForLoadState('networkidle');
309
+ await this.roleIdSearchBox.fill(searchTerm);
310
+ const offered = await result
311
+ .waitFor({ state: 'visible', timeout: 15000 })
312
+ .then(() => true)
313
+ .catch(() => false);
314
+ if (offered) {
315
+ return;
316
+ }
317
+ }
318
+ throw new Error(`Role ${roleIdentifier} was not offered by the assign role search for` +
319
+ ` any of: ${searchTerms.join(', ')}`);
320
+ }).toPass({ timeout: 120000, intervals: [2000] });
298
321
  await result.click();
299
322
  }
300
323
  async clickAssignRoleSubButton() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1094",
3
+ "version": "0.0.1095",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",