@camunda/e2e-test-suite 0.0.1030 → 0.0.1032

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
@@ -32,6 +32,7 @@ declare class ModelerHomePage {
32
32
  readonly formNameBreadcrumb: (formName: string) => Locator;
33
33
  readonly buttonSkipCustomization: Locator;
34
34
  constructor(page: Page);
35
+ dismissBlockingDialog(): Promise<void>;
35
36
  dismissOverlays(): Promise<void>;
36
37
  clickCreateNewProjectButton(): Promise<void>;
37
38
  enterNewProjectName(name: string): Promise<void>;
@@ -96,7 +96,19 @@ class ModelerHomePage {
96
96
  this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
97
97
  this.messageBanner = page.locator('[data-test="close-top-banner"]');
98
98
  this.dialog = page.getByRole('dialog');
99
- this.closeButton = page.getByRole('button', { name: 'Got it - Dismiss' });
99
+ // Both announcement modals hub stacks over the projects list dismiss
100
+ // through a "Got it…" footer button: the What's New modal's reads
101
+ // "Got it - Dismiss", and the migration notice ("Your projects have a new
102
+ // structure") reads just "Got it".
103
+ //
104
+ // Matched as a visible button rather than by role/name: the What's New
105
+ // modal stays mounted with `open={false}` after it is closed, so its
106
+ // dismiss button remains in the DOM but hidden, and a role/name match
107
+ // would resolve both and fail strict mode -- reporting "nothing to
108
+ // dismiss" while the migration notice is still covering the projects list.
109
+ this.closeButton = page
110
+ .locator('button:visible', { hasText: /^Got it/ })
111
+ .first();
100
112
  this.optionsButton = page.getByRole('button', { name: 'Options' });
101
113
  this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
102
114
  hasText: formName,
@@ -105,7 +117,39 @@ class ModelerHomePage {
105
117
  name: 'Skip customization',
106
118
  });
107
119
  }
120
+ // An announcement dialog renders over the projects list and swallows every
121
+ // click on it: Playwright reports the project link as visible, enabled and
122
+ // stable, then times out with the modal overlay "intercepts pointer events".
123
+ // Pressing Escape blindly does not help -- Carbon only closes the dialog when
124
+ // focus is inside it, and focus sits on the body after a page load -- so
125
+ // close it via its own dismiss button and confirm it is gone.
126
+ //
127
+ // The dialog is mounted a beat after the projects list, so let the page
128
+ // settle before looking for it, and re-probe after each close: hub can stack
129
+ // the What's New modal and the migration notice, and closing one reveals the
130
+ // next.
131
+ async dismissBlockingDialog() {
132
+ await this.page
133
+ .waitForLoadState('networkidle', { timeout: 15000 })
134
+ .catch(() => false);
135
+ for (let attempt = 0; attempt < 3; attempt++) {
136
+ const isOpen = await this.closeButton
137
+ .waitFor({ state: 'visible', timeout: 10000 })
138
+ .then(() => true)
139
+ .catch(() => false);
140
+ if (!isOpen)
141
+ return;
142
+ await this.closeButton.click({ timeout: 5000 }).catch(() => { });
143
+ await this.closeButton
144
+ .waitFor({ state: 'hidden', timeout: 5000 })
145
+ .catch(() => false);
146
+ }
147
+ }
108
148
  async dismissOverlays() {
149
+ // Before the generic pass: clickMessageBanner and the loop below resolve
150
+ // `.or(...).first()` unions in DOM order, where the top banner comes first,
151
+ // so on their own they close the banner and leave the dialog open.
152
+ await this.dismissBlockingDialog();
109
153
  for (const locator of [
110
154
  this.closeButton,
111
155
  this.page.getByRole('button', { name: 'Close' }).first(),
@@ -147,6 +191,11 @@ class ModelerHomePage {
147
191
  if (await committedProjectName.first().isVisible()) {
148
192
  return;
149
193
  }
194
+ // The rename runs on a freshly created project, which is exactly when an
195
+ // announcement dialog mounts on top of it: it takes focus, the inline
196
+ // editor unmounts, and the click below then waits out its whole timeout
197
+ // on an element that never comes back.
198
+ await this.dismissOverlays();
150
199
  await this.projectNameInput.first().click({ timeout: 15000 });
151
200
  await this.projectNameInput.first().fill(name);
152
201
  // Confirm the value actually landed in the edit field before committing;
@@ -397,12 +446,17 @@ class ModelerHomePage {
397
446
  .catch(() => { });
398
447
  }
399
448
  }
449
+ // An announcement dialog covering the Modeler header makes every click on the
450
+ // org button bounce off the modal layer ("intercepts pointer events") until
451
+ // the timeout, so dismiss overlays before each attempt.
400
452
  async clickOpenOrganizationsButton() {
401
- await (0, sleep_1.sleep)(5000);
402
- await (0, test_1.expect)(this.openOrganizationsButton).toBeVisible({
403
- timeout: 10000,
453
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.openOrganizationsButton, {
454
+ preAction: async () => {
455
+ await this.dismissOverlays();
456
+ },
457
+ totalTimeout: 120000,
458
+ visibilityTimeout: 30000,
404
459
  });
405
- await this.openOrganizationsButton.click({ timeout: 30000 });
406
460
  }
407
461
  async createForm(formName) {
408
462
  const maxRetries = 3;
@@ -429,24 +483,19 @@ class ModelerHomePage {
429
483
  async clickCrossComponentProjectFolder() {
430
484
  await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.crossComponentProjectFolder, {
431
485
  visibilityTimeout: 60000,
432
- totalTimeout: 180000,
486
+ // Widened from 180s: dismissing the announcement dialog costs up to ~25s
487
+ // of settle-and-probe per attempt, which at 180s left room for barely one
488
+ // retry after an intercepted click had already burnt its 60s.
489
+ totalTimeout: 240000,
433
490
  maxRetries: 5,
434
491
  preAction: async () => {
435
- // A Carbon modal (`.cds--modal.is-visible`) sometimes covers the
436
- // project folder and intercepts pointer events; clickMessageBanner
437
- // only handles a single banner/close button so it doesn't dismiss
438
- // the modal. Escape it explicitly first — same pattern 8.7 already
439
- // uses for the same symptom.
440
- try {
441
- const blockingModal = this.page.locator('.cds--modal.is-visible');
442
- if ((await blockingModal.count()) > 0) {
443
- await this.page.keyboard.press('Escape');
444
- await (0, sleep_1.sleep)(1000);
445
- }
446
- }
447
- catch {
448
- // No blocking modal to dismiss
449
- }
492
+ // Dismiss the dialog BEFORE the banner: the Modeler home page can
493
+ // stack both at once, and clickMessageBanner resolves an
494
+ // `.or(...).first()` union in DOM order — the top banner comes first,
495
+ // so on its own it closes the banner and leaves the dialog open. The
496
+ // previous press-Escape-on-any-visible-modal handling could not close
497
+ // it at all: the migration notice ignores Escape.
498
+ await this.dismissBlockingDialog();
450
499
  await this.clickMessageBanner();
451
500
  },
452
501
  postAction: async () => {
@@ -31,6 +31,7 @@ declare class ModelerHomePage {
31
31
  readonly optionsButton: Locator;
32
32
  readonly formNameBreadcrumb: (formName: string) => Locator;
33
33
  constructor(page: Page);
34
+ dismissBlockingDialog(): Promise<void>;
34
35
  dismissOverlays(): Promise<void>;
35
36
  clickCreateNewProjectButton(): Promise<void>;
36
37
  enterNewProjectName(name: string): Promise<void>;
@@ -100,13 +100,58 @@ class ModelerHomePage {
100
100
  this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
101
101
  this.messageBanner = page.locator('[data-test="close-top-banner"]');
102
102
  this.dialog = page.getByRole('dialog');
103
- this.closeButton = page.getByRole('button', { name: 'Got it - Dismiss' });
103
+ // Both announcement modals hub stacks over the projects list dismiss
104
+ // through a "Got it…" footer button: the What's New modal's reads
105
+ // "Got it - Dismiss", and the migration notice ("Your projects have a new
106
+ // structure") reads just "Got it".
107
+ //
108
+ // Matched as a visible button rather than by role/name: the What's New
109
+ // modal stays mounted with `open={false}` after it is closed, so its
110
+ // dismiss button remains in the DOM but hidden, and a role/name match
111
+ // would resolve both and fail strict mode -- reporting "nothing to
112
+ // dismiss" while the migration notice is still covering the projects list.
113
+ this.closeButton = page
114
+ .locator('button:visible', { hasText: /^Got it/ })
115
+ .first();
104
116
  this.optionsButton = page.getByRole('button', { name: 'Options' });
105
117
  this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
106
118
  hasText: formName,
107
119
  });
108
120
  }
121
+ // An announcement dialog renders over the projects list and swallows every
122
+ // click on it: Playwright reports the project link as visible, enabled and
123
+ // stable, then times out with the modal overlay "intercepts pointer events".
124
+ // Pressing Escape blindly does not help -- Carbon only closes the dialog when
125
+ // focus is inside it, and focus sits on the body after a page load -- so
126
+ // close it via its own dismiss button and confirm it is gone.
127
+ //
128
+ // The dialog is mounted a beat after the projects list, so let the page
129
+ // settle before looking for it, and re-probe after each close: hub can stack
130
+ // the What's New modal and the migration notice, and closing one reveals the
131
+ // next.
132
+ async dismissBlockingDialog() {
133
+ await this.page
134
+ .waitForLoadState('networkidle', { timeout: 15000 })
135
+ .catch(() => false);
136
+ for (let attempt = 0; attempt < 3; attempt++) {
137
+ const isOpen = await this.closeButton
138
+ .waitFor({ state: 'visible', timeout: 10000 })
139
+ .then(() => true)
140
+ .catch(() => false);
141
+ if (!isOpen)
142
+ return;
143
+ await this.closeButton.click({ timeout: 5000 }).catch(() => { });
144
+ await this.closeButton
145
+ .waitFor({ state: 'hidden', timeout: 5000 })
146
+ .catch(() => false);
147
+ }
148
+ }
109
149
  async dismissOverlays() {
150
+ // Before the generic pass: the loop below closes whichever overlay it finds
151
+ // and moves on, so a single pass leaves a stacked announcement dialog open,
152
+ // and every caller that relies on dismissOverlays() to clear the page then
153
+ // clicks into it.
154
+ await this.dismissBlockingDialog();
110
155
  for (const locator of [
111
156
  this.closeButton,
112
157
  this.page.getByRole('button', { name: 'Close' }).first(),
@@ -141,6 +186,11 @@ class ModelerHomePage {
141
186
  if (await this.page.getByTitle(name, { exact: true }).isVisible()) {
142
187
  return;
143
188
  }
189
+ // The rename runs on a freshly created project, which is exactly when an
190
+ // announcement dialog mounts on top of it: it takes focus, the inline
191
+ // editor unmounts, and the click below then waits out its whole timeout
192
+ // on an element that never comes back.
193
+ await this.dismissBlockingDialog();
144
194
  await this.projectNameInput.click({ timeout: 60000 });
145
195
  await this.projectNameInput.fill(name);
146
196
  await this.projectNameInput.press('Enter');
@@ -342,12 +392,18 @@ class ModelerHomePage {
342
392
  await this.page.getByText('Home', { exact: true }).first().click();
343
393
  }
344
394
  }
395
+ // An announcement dialog covering the Modeler header makes every click on the
396
+ // org button bounce off the modal layer ("intercepts pointer events") until
397
+ // the timeout, so dismiss it before each attempt instead of waiting out a
398
+ // fixed sleep and hoping the header is clear.
345
399
  async clickOpenOrganizationsButton() {
346
- await (0, sleep_1.sleep)(5000);
347
- await (0, test_1.expect)(this.openOrganizationsButton).toBeVisible({
348
- timeout: 10000,
400
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.openOrganizationsButton, {
401
+ preAction: async () => {
402
+ await this.dismissBlockingDialog();
403
+ },
404
+ totalTimeout: 120000,
405
+ visibilityTimeout: 30000,
349
406
  });
350
- await this.openOrganizationsButton.click({ timeout: 30000 });
351
407
  }
352
408
  async createForm(formName) {
353
409
  await this.clickDiagramTypeDropdown();
@@ -361,26 +417,19 @@ class ModelerHomePage {
361
417
  async clickCrossComponentProjectFolder() {
362
418
  await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.crossComponentProjectFolder, {
363
419
  visibilityTimeout: 60000,
364
- totalTimeout: 180000,
420
+ // Widened from 180s: dismissing the announcement dialog costs up to ~25s
421
+ // of settle-and-probe per attempt, which at 180s left room for barely one
422
+ // retry after an intercepted click had already burnt its 60s.
423
+ totalTimeout: 240000,
365
424
  maxRetries: 5,
366
425
  preAction: async () => {
367
- // A Carbon modal (`.cds--modal.is-visible`) sometimes covers the
368
- // project folder and intercepts pointer events. Escape it explicitly
369
- // first — same pattern 8.7 already uses for the same symptom.
370
- try {
371
- const blockingModal = this.page.locator('.cds--modal.is-visible');
372
- if ((await blockingModal.count()) > 0) {
373
- await this.page.keyboard.press('Escape');
374
- await (0, sleep_1.sleep)(1000);
375
- }
376
- }
377
- catch {
378
- // No blocking modal to dismiss
379
- }
380
426
  // dismissOverlays, not clickMessageBanner: the latter resolves
381
427
  // `messageBanner.or(closeButton).first()`, so with a top banner on
382
428
  // screen it always picks the banner and leaves any dialog in place.
383
- // dismissOverlays walks every overlay control in turn instead.
429
+ // dismissOverlays closes the announcement dialog first, then walks
430
+ // every other overlay control in turn. The previous
431
+ // press-Escape-on-any-visible-modal handling could not close the
432
+ // migration notice at all: it ignores Escape.
384
433
  await this.dismissOverlays();
385
434
  },
386
435
  postAction: async () => {
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.1032",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",