@camunda/e2e-test-suite 0.0.1240 → 0.0.1242

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,18 +8,19 @@ declare class NavigationPage {
8
8
  readonly identityPageBanner: Locator;
9
9
  readonly keyboardPageBanner: Locator;
10
10
  readonly managementIdentityPageBanner: Locator;
11
+ private onboardingTourSuppressed;
11
12
  constructor(page: Page);
12
13
  private isGatewayErrorPage;
13
14
  private waitForBanner;
14
15
  goTo(url: string, banner: Locator, sleepTimeout?: number, { username, password, }?: {
15
16
  username?: string;
16
17
  password?: string;
17
- }, maxRetries?: number, beforeBannerCheck?: () => Promise<void>): Promise<void>;
18
+ }, maxRetries?: number): Promise<void>;
18
19
  goToModeler(sleepTimeout?: number, credentials?: {
19
20
  username: string;
20
21
  password: string;
21
22
  }): Promise<void>;
22
- private dismissOnboardingDialog;
23
+ private suppressOnboardingTour;
23
24
  goToTasklist(sleepTimeout?: number, credentials?: {
24
25
  username: string;
25
26
  password: string;
@@ -28,6 +28,7 @@ class NavigationPage {
28
28
  identityPageBanner;
29
29
  keyboardPageBanner;
30
30
  managementIdentityPageBanner;
31
+ onboardingTourSuppressed = false;
31
32
  constructor(page) {
32
33
  this.page = page;
33
34
  // Operate renders the Design System header (AppHeaderV2) whenever the
@@ -112,15 +113,12 @@ class NavigationPage {
112
113
  // that is exactly the reported "Login (/modeler) failed after 8 attempts".
113
114
  // The 60s budget is unchanged: a genuinely slow shell still gets all of it,
114
115
  // and a sustained outage still fails.
115
- async waitForBanner(url, banner, submitted, beforeBannerCheck) {
116
+ async waitForBanner(url, banner, submitted) {
116
117
  const totalTimeout = constants_1._1_SECOND_IN_MS * 60;
117
118
  const deadline = Date.now() + totalTimeout;
118
119
  let lastState = 'no banner';
119
120
  while (Date.now() < deadline) {
120
121
  const round = Math.min(constants_1._1_SECOND_IN_MS * 20, deadline - Date.now());
121
- if (beforeBannerCheck) {
122
- await beforeBannerCheck();
123
- }
124
122
  try {
125
123
  await (0, test_1.expect)(banner).toBeVisible({ timeout: round });
126
124
  return;
@@ -148,7 +146,7 @@ class NavigationPage {
148
146
  }
149
147
  throw new Error(`${url} did not render its banner within ${totalTimeout}ms: ${lastState}`);
150
148
  }
151
- async goTo(url, banner, sleepTimeout, { username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}, maxRetries = 8, beforeBannerCheck) {
149
+ async goTo(url, banner, sleepTimeout, { username = IDENTITY_FIRSTUSER_USERNAME, password = IDENTITY_FIRSTUSER_PASSWORD, } = {}, maxRetries = 8) {
152
150
  const startTime = Date.now();
153
151
  let timeout = constants_1._1_SECOND_IN_MS * 10;
154
152
  if (url === '/modeler') {
@@ -217,7 +215,7 @@ class NavigationPage {
217
215
  else {
218
216
  submitted = await loginPage.login(username, password);
219
217
  }
220
- await this.waitForBanner(url, banner, submitted, beforeBannerCheck);
218
+ await this.waitForBanner(url, banner, submitted);
221
219
  return;
222
220
  }
223
221
  catch (error) {
@@ -264,25 +262,27 @@ class NavigationPage {
264
262
  }
265
263
  }
266
264
  async goToModeler(sleepTimeout, credentials) {
267
- await this.goTo(MODELER_CONTEXT_PATH, this.modelerPageBanner, sleepTimeout, credentials, 8, () => this.dismissOnboardingDialog());
265
+ await this.suppressOnboardingTour();
266
+ await this.goTo(MODELER_CONTEXT_PATH, this.modelerPageBanner, sleepTimeout, credentials, 8);
268
267
  }
269
- // After the Camunda Hub rebrand a first visit to Modeler home greets the user
270
- // with a multi-step "Welcome to Camunda Hub" onboarding dialog. It mounts over
271
- // the home and marks the app shell inert, so the "Camunda Modeler home" banner
272
- // link leaves the accessibility tree until the dialog is dismissed -- gating on
273
- // the banner alone then loops until the whole login budget is spent. Close it
274
- // through the dialog's own control before each banner check so the banner can
275
- // resolve; only unambiguous dismissals are matched (never "Cancel", which is a
276
- // form action on the dialogs other flows open on purpose).
277
- async dismissOnboardingDialog() {
278
- const dismiss = this.page
279
- .locator('[role="dialog"]')
280
- .getByRole('button', { name: /^(Got it|Close|Dismiss)/i })
281
- .first();
282
- await dismiss
283
- .waitFor({ state: 'visible', timeout: 3000 })
284
- .then(() => dismiss.click())
285
- .catch(() => { });
268
+ // Mirrors camunda-hub@452978f: pre-seed the onboarding tour's "seen" localStorage flag
269
+ // (SEEN_KEY_PREFIX in .../tour-dialog/use-onboarding-seen.ts) so it never auto-opens, rather
270
+ // than dismissing it reactively (the two prior fixes here -- both raced the dialog's mount
271
+ // and lost, since the user id needed for the real key isn't known until after that same
272
+ // race). addInitScript avoids the race by running before any page script, matching the
273
+ // flag by prefix since the user id isn't known up front.
274
+ async suppressOnboardingTour() {
275
+ if (this.onboardingTourSuppressed) {
276
+ return;
277
+ }
278
+ this.onboardingTourSuppressed = true;
279
+ await this.page.addInitScript((prefixPattern) => {
280
+ const pattern = new RegExp(prefixPattern);
281
+ const originalGetItem = Storage.prototype.getItem;
282
+ Storage.prototype.getItem = function (key) {
283
+ return pattern.test(key) ? 'true' : originalGetItem.call(this, key);
284
+ };
285
+ }, '^hub\\.onboarding\\..*\\.seen\\.');
286
286
  }
287
287
  async goToTasklist(sleepTimeout, credentials) {
288
288
  await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/tasklist`, this.tasklistPageBanner, sleepTimeout, credentials);
@@ -156,6 +156,12 @@ class ProcessApplicationPage {
156
156
  await (0, test_1.expect)(this.defineStagesDialog).toBeVisible({ timeout: 60000 });
157
157
  await this.developmentClusterSelect.click({ timeout: 30000 });
158
158
  const escapedClusterName = clusterName?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
159
+ // The listbox leads with a "No cluster" entry that unassigns the stage; the
160
+ // placeholder "Select a development cluster" is only the trigger's text, not an
161
+ // option. Excluding just the placeholder leaves "No cluster" first, so an
162
+ // unnamed pick binds no cluster and saves defaultDevClusterId=null -- which
163
+ // makes the later Deploy dialog report "Clusters not configured for stages".
164
+ // Exclude "No cluster" too so the fallback lands on a real cluster.
159
165
  const option = clusterName
160
166
  ? this.page
161
167
  .getByRole('option', { name: new RegExp(`^${escapedClusterName}`) })
@@ -163,6 +169,7 @@ class ProcessApplicationPage {
163
169
  : this.page
164
170
  .getByRole('option')
165
171
  .filter({ hasNotText: 'Select a development cluster' })
172
+ .filter({ hasNotText: 'No cluster' })
166
173
  .first();
167
174
  await option.click({ timeout: 30000 });
168
175
  await this.defineStagesSaveButton.click({ timeout: 30000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1240",
3
+ "version": "0.0.1242",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",