@camunda/e2e-test-suite 0.0.1030 → 0.0.1031

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.
@@ -40,11 +40,17 @@ class Authorization {
40
40
  name,
41
41
  exact: true,
42
42
  });
43
+ // The Owner field is no longer a Carbon ComboBox labelled "Owner".
44
+ // Identity replaced it with an entity-search dropdown
45
+ // (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.
43
52
  this.createAuthorizationOwnerComboBox =
44
- this.createAuthorizationModal.getByRole('combobox', {
45
- name: 'Owner',
46
- exact: true,
47
- });
53
+ this.createAuthorizationModal.getByPlaceholder('Search by owner ID');
48
54
  this.createAuthorizationOwnerOption = (name) => this.createAuthorizationModal.getByRole('option', {
49
55
  name,
50
56
  exact: true,
@@ -90,33 +96,30 @@ class Authorization {
90
96
  await (0, test_1.expect)(this.createAuthorizationModal).toBeVisible();
91
97
  await this.createAuthorizationOwnerTypeCombobox.click();
92
98
  await this.createAuthorizationOwnerTypeOption(authorization.ownerType).click();
93
- await this.createAuthorizationOwnerComboBox.click();
94
- // Carbon's owner combobox is a Downshift-driven ComboBox. The form's
95
- // Resource type combobox is gated on Downshift's `selectedItem`
96
- // state, NOT on the input's `value` attribute. To commit
97
- // `selectedItem` we must click the active menu item — i.e. the
98
- // element with class `.cds--list-box__menu-item` inside the open
99
- // dropdown.
100
- //
101
- // The previous `getByRole('option', {name, exact: true})` locator
102
- // matched a role=option element but the click did not fire
103
- // Downshift's `onSelectedItemChange` (frame snapshot from run
104
- // 26632778543 shows the input value populated but Resource type
105
- // still disabled). Scoping to the Carbon menu-item class — the same
106
- // approach used in the product repo's qa-suite
107
- // (camunda/camunda qa/c8-orchestration-cluster-e2e-test-suite/
108
- // pages/IdentityAuthorizationsPage.ts selectAuthorizationOwner) —
109
- // selects the actual Downshift menu item and fires the handler.
110
- const ownerSearchInput = this.createAuthorizationModal.getByPlaceholder('Search by owner ID');
111
- if (await ownerSearchInput.isVisible({ timeout: 5000 }).catch(() => false)) {
112
- await ownerSearchInput.fill(authorization.ownerId);
113
- }
99
+ await (0, test_1.expect)(this.createAuthorizationOwnerComboBox).toBeVisible({
100
+ timeout: 30000,
101
+ });
102
+ await this.createAuthorizationOwnerComboBox.fill(authorization.ownerId);
103
+ // The dropdown renders results only while the search box holds text, and
104
+ // the list is fetched from the server on a debounce rather than filtered
105
+ // client-side, so the matching entry appears a beat later. Clicking the
106
+ // Carbon menu item is what commits the selection: DropdownSearch wires
107
+ // `onSelect` to `ListBox.MenuItem`'s onClick
108
+ // (identity/client/src/components/form/DropdownSearch.tsx), and that is
109
+ // the only path that promotes an entry into the form's owner value.
114
110
  const ownerMenuItem = this.createAuthorizationModal
115
111
  .locator('.cds--list-box__menu-item')
116
112
  .filter({ hasText: authorization.ownerId })
117
113
  .first();
118
114
  await (0, test_1.expect)(ownerMenuItem).toBeVisible({ timeout: 60000 });
119
115
  await ownerMenuItem.click({ timeout: 20000 });
116
+ // A committed selection swaps the search box for a filter Tag
117
+ // (EntitySearchSingleSelect renders one or the other), so the box going
118
+ // away is how we know the owner actually landed on the form rather than
119
+ // the click being swallowed.
120
+ await (0, test_1.expect)(this.createAuthorizationOwnerComboBox).toBeHidden({
121
+ timeout: 15000,
122
+ });
120
123
  await this.selectResourceTypeFromDialog(authorization.resourceType);
121
124
  await this.createAuthorizationResourceIdField.fill(authorization.resourceId);
122
125
  for (const permission of authorization.accessPermissions) {
@@ -739,8 +739,13 @@ class ModelerCreatePage {
739
739
  async fillNamedInput(name) {
740
740
  await this.nameInput.fill(name, { timeout: 60000 });
741
741
  }
742
+ // 10s was too tight for the first click after the editor opens: the button
743
+ // is already resolved, visible and stable, and the click then waits out its
744
+ // whole budget on the actionability check while the bpmn-js canvas is still
745
+ // settling ("done scrolling" is the last line before the timeout). Every
746
+ // other breadcrumb interaction in this file budgets 30-60s; match them.
742
747
  async clickDiagramBreadcrumb() {
743
- await this.diagramBreadcrumb.click({ timeout: 10000 });
748
+ await this.diagramBreadcrumb.click({ timeout: 60000 });
744
749
  }
745
750
  // Opening the breadcrumb menu leaves the pointer on the breadcrumb button,
746
751
  // and hub renders that button's truncated-name tooltip on top of the menu,
@@ -105,7 +105,20 @@ class ModelerHomePage {
105
105
  this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
106
106
  this.messageBanner = page.locator('[data-test="close-top-banner"]');
107
107
  this.dialog = page.getByRole('dialog');
108
- this.closeButton = page.getByRole('button', { name: 'Got it - Dismiss' });
108
+ // Both announcement modals hub stacks over the projects list dismiss
109
+ // through a "Got it…" footer button: the What's New modal's reads
110
+ // "Got it - Dismiss", and the migration notice added in camunda-hub
111
+ // #27819 ("Your projects have a new structure") reads just "Got it".
112
+ //
113
+ // Matched as a visible button rather than by role/name: the What's New
114
+ // modal stays mounted with `open={false}` after it is closed, so its
115
+ // dismiss button remains in the DOM but hidden, and an `.or(...).first()`
116
+ // union would resolve to that hidden button in DOM order and report
117
+ // "nothing to dismiss" while the migration notice is still covering the
118
+ // projects list.
119
+ this.closeButton = page
120
+ .locator('button:visible', { hasText: /^Got it/ })
121
+ .first();
109
122
  this.optionsButton = page.getByRole('button', { name: 'Options' });
110
123
  this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
111
124
  hasText: formName,
@@ -120,6 +133,12 @@ class ModelerHomePage {
120
133
  this.renameMenuItem = page.getByRole('menuitem', { name: 'Rename' });
121
134
  }
122
135
  async dismissOverlays() {
136
+ // Before the generic pass, for the same reason
137
+ // clickCrossComponentProjectFolder dismisses the dialog first: the loop
138
+ // below closes whichever overlay it finds and moves on, so a single pass
139
+ // leaves a stacked announcement dialog open, and every caller that relies
140
+ // on dismissOverlays() to clear the page then clicks into it.
141
+ await this.dismissBlockingDialog();
123
142
  for (const locator of [
124
143
  this.closeButton,
125
144
  this.page.getByRole('button', { name: 'Close' }).first(),
@@ -163,6 +182,12 @@ class ModelerHomePage {
163
182
  if (await committedProjectName.first().isVisible()) {
164
183
  return;
165
184
  }
185
+ // The rename runs on a freshly created project, which is exactly when an
186
+ // announcement dialog mounts on top of it: it takes focus, the inline
187
+ // editor unmounts, and the probe below then reads "closed" and sends the
188
+ // recovery down a path that cannot reach an editor the dialog is
189
+ // covering.
190
+ await this.dismissBlockingDialog();
166
191
  // Bounded, not instantaneous: the editor can still be mounting, and
167
192
  // reading that as "closed" would send the recovery to the projects-list
168
193
  // branch while the browser sits on the project page, where the row it
@@ -413,12 +438,18 @@ class ModelerHomePage {
413
438
  await this.page.getByText('Home', { exact: true }).first().click();
414
439
  }
415
440
  }
441
+ // An announcement dialog covering the Modeler header makes every click on the
442
+ // org button bounce off the modal layer ("intercepts pointer events") until
443
+ // the timeout, so dismiss it before each attempt instead of waiting out a
444
+ // fixed sleep and hoping the header is clear.
416
445
  async clickOpenOrganizationsButton() {
417
- await (0, sleep_1.sleep)(5000);
418
- await (0, test_1.expect)(this.openOrganizationsButton).toBeVisible({
419
- timeout: 10000,
446
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.openOrganizationsButton, {
447
+ preAction: async () => {
448
+ await this.dismissBlockingDialog();
449
+ },
450
+ totalTimeout: 120000,
451
+ visibilityTimeout: 30000,
420
452
  });
421
- await this.openOrganizationsButton.click({ timeout: 30000 });
422
453
  }
423
454
  async createForm(formName) {
424
455
  await this.clickDiagramTypeDropdown();
@@ -199,29 +199,31 @@ class ProcessApplicationPage {
199
199
  // Worth the noise: an unbound cluster otherwise surfaces as a deploy dialog
200
200
  // that silently offers nothing, which says nothing about its own cause.
201
201
  async openStageDefinition() {
202
- const opened = await this.defineStagesDialog
203
- .waitFor({ state: 'visible', timeout: 15000 })
204
- .then(() => true)
205
- .catch(() => false);
206
- if (opened) {
207
- return;
208
- }
209
202
  // Page-scoped .first(): hub renders one such control on this page, and a
210
203
  // second placement must not turn every rebinding test into a strict-mode
211
204
  // violation.
212
205
  const reopen = this.page
213
206
  .getByRole('button', { name: /define stages|edit stages|configure/i })
214
207
  .first();
215
- const canReopen = await reopen
216
- .waitFor({ state: 'visible', timeout: 10000 })
217
- .then(() => true)
218
- .catch(() => false);
219
- if (!canReopen) {
220
- throw new Error('Could not open the "Define stages" dialog on /connected-clusters: it ' +
221
- 'did not open by itself and no "Define stages" / "Edit stages" / ' +
222
- '"Configure" control was found.');
223
- }
224
- await reopen.click({ timeout: 30000 });
208
+ // Retried as a whole, because the two ways in race each other. On a slow
209
+ // load the deep link's own auto-open lands AFTER the first wait below has
210
+ // given up; the dialog then sits on top of the reopen control, so the
211
+ // fallback click waits out its timeout on an intercepted pointer while the
212
+ // dialog it was meant to open is already on screen. Re-probing for the
213
+ // dialog at the top of every attempt settles that: whichever route got it
214
+ // there, the next pass sees it and returns.
215
+ await (0, test_1.expect)(async () => {
216
+ const opened = await this.defineStagesDialog
217
+ .waitFor({ state: 'visible', timeout: 15000 })
218
+ .then(() => true)
219
+ .catch(() => false);
220
+ if (opened) {
221
+ return;
222
+ }
223
+ await reopen.waitFor({ state: 'visible', timeout: 10000 });
224
+ await reopen.click({ timeout: 15000 });
225
+ await (0, test_1.expect)(this.defineStagesDialog).toBeVisible({ timeout: 15000 });
226
+ }).toPass({ timeout: 150000 });
225
227
  }
226
228
  async openByName(name) {
227
229
  const processApplication = this.page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1030",
3
+ "version": "0.0.1031",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",