@camunda/e2e-test-suite 0.0.1031 → 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.
@@ -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.1031",
3
+ "version": "0.0.1032",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",