@camunda/e2e-test-suite 0.0.927 → 0.0.929

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.
@@ -21,6 +21,21 @@ declare class AppsPage {
21
21
  constructor(page: Page);
22
22
  clickCluster(component: Locator, name: string): Promise<void>;
23
23
  clickModeler(): Promise<void>;
24
+ /**
25
+ * Waits for the Modeler app to actually render, and recovers the one landing
26
+ * state a hostname check cannot see: Modeler's OAuth `/login-callback`
27
+ * intermittently answers 503 on SaaS INT, which parks the browser on that
28
+ * URL with an empty document. The hostname is already `modeler.*`, so
29
+ * nothing downstream notices and the caller's banner assertion just waits
30
+ * out its whole timeout on a dead page. Reloading cannot help either — the
31
+ * authorization code in the callback URL is single-use — so re-enter at the
32
+ * Modeler origin root, which restarts the OAuth flow with a fresh code.
33
+ *
34
+ * Rendering is best-effort: some Modeler renders never expose the banner's
35
+ * accessible name when asset requests get 429-throttled, so a non-rendered
36
+ * result is left for the caller to assert on rather than thrown here.
37
+ */
38
+ private waitForModelerToRender;
24
39
  clickTasklist(clusterName: string): Promise<void>;
25
40
  clickIdentity(clusterName: string): Promise<void>;
26
41
  private doClickClusterInOperate;
@@ -69,10 +69,6 @@ class AppsPage {
69
69
  }
70
70
  async clickModeler() {
71
71
  const maxRetries = 5;
72
- const modelerBanner = this.page.getByRole('banner', {
73
- name: 'Camunda Modeler',
74
- });
75
- const modelerDiagramDropdown = this.page.locator('[data-test="diagram-dropdown"]');
76
72
  // If the click is already done and we are sitting on Modeler, return
77
73
  // immediately — observed in failing runs where the banner role lookup
78
74
  // never resolves because partial app.js 429s prevent the accessible
@@ -92,13 +88,10 @@ class AppsPage {
92
88
  await this.page.waitForURL((url) => url.hostname.includes('modeler.'), {
93
89
  timeout: 90000,
94
90
  });
95
- // Best-effort: also wait briefly for the banner or the project
96
- // dropdown so the caller's next interaction has a stable DOM.
97
- // Tolerate this timing out — the URL is already correct.
98
- await Promise.race([
99
- modelerBanner.waitFor({ state: 'visible', timeout: 30000 }),
100
- modelerDiagramDropdown.waitFor({ state: 'visible', timeout: 30000 }),
101
- ]).catch(() => { });
91
+ // Also give the Modeler app a chance to actually render, recovering
92
+ // the one landing state the URL check above cannot see (see
93
+ // waitForModelerToRender).
94
+ await this.waitForModelerToRender();
102
95
  return;
103
96
  }
104
97
  catch (error) {
@@ -119,6 +112,41 @@ class AppsPage {
119
112
  }
120
113
  throw new Error(`Failed to click the modeler link after ${maxRetries} attempts.`);
121
114
  }
115
+ /**
116
+ * Waits for the Modeler app to actually render, and recovers the one landing
117
+ * state a hostname check cannot see: Modeler's OAuth `/login-callback`
118
+ * intermittently answers 503 on SaaS INT, which parks the browser on that
119
+ * URL with an empty document. The hostname is already `modeler.*`, so
120
+ * nothing downstream notices and the caller's banner assertion just waits
121
+ * out its whole timeout on a dead page. Reloading cannot help either — the
122
+ * authorization code in the callback URL is single-use — so re-enter at the
123
+ * Modeler origin root, which restarts the OAuth flow with a fresh code.
124
+ *
125
+ * Rendering is best-effort: some Modeler renders never expose the banner's
126
+ * accessible name when asset requests get 429-throttled, so a non-rendered
127
+ * result is left for the caller to assert on rather than thrown here.
128
+ */
129
+ async waitForModelerToRender(maxAttempts = 2) {
130
+ const modelerBanner = this.page.getByRole('banner', {
131
+ name: 'Camunda Modeler',
132
+ });
133
+ const modelerDiagramDropdown = this.page.locator('[data-test="diagram-dropdown"]');
134
+ const modelerRoot = new URL('/', this.page.url()).toString();
135
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
136
+ const rendered = await Promise.race([
137
+ modelerBanner.waitFor({ state: 'visible', timeout: 30000 }),
138
+ modelerDiagramDropdown.waitFor({ state: 'visible', timeout: 30000 }),
139
+ ])
140
+ .then(() => true)
141
+ .catch(() => false);
142
+ if (rendered || attempt === maxAttempts - 1) {
143
+ return;
144
+ }
145
+ console.warn(`Modeler did not render after landing on ${this.page.url()}; ` +
146
+ `re-entering at ${modelerRoot}`);
147
+ await this.page.goto(modelerRoot, { waitUntil: 'domcontentloaded' });
148
+ }
149
+ }
122
150
  async clickTasklist(clusterName) {
123
151
  const maxRetries = 5;
124
152
  for (let retries = 0; retries < maxRetries; retries++) {
@@ -96,16 +96,16 @@ class ClusterSecretsPage {
96
96
  console.error('Error during Connector Secrets deletion: ', error);
97
97
  }
98
98
  }
99
+ // "Create new secret" is absent whenever the panel rendered its "Oops ...
100
+ // something went wrong" state, so a single re-entry of the tab is not enough
101
+ // when the Console API stays unhealthy for a couple of minutes (run
102
+ // 31453329946: the GET .../connectorSecrets never resolved and the button
103
+ // was still missing after re-entering the tab). Heal through
104
+ // assertSecretsToolbarVisible, which keeps re-entering until the toolbar is
105
+ // genuinely rendered, and only then click.
99
106
  async clickCreateNewSecretButton(cluster) {
100
- try {
101
- await this.createNewSecretButton.click({ timeout: 60000 });
102
- }
103
- catch (error) {
104
- await this.clickClusterBanner();
105
- await this.clickCluster(cluster);
106
- await this.clusterSecretTab.click({ timeout: 30000 });
107
- await this.createNewSecretButton.click({ timeout: 30000 });
108
- }
107
+ await this.assertSecretsToolbarVisible(cluster);
108
+ await this.createNewSecretButton.click({ timeout: 60000 });
109
109
  }
110
110
  // A rejected Console API call leaves the create/import modal open with an
111
111
  // inline "Error fetch error" banner instead of closing it. Carbon's modal
@@ -245,7 +245,7 @@ class ClusterSecretsPage {
245
245
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.createNewSecretButton, {
246
246
  visibilityTimeout: 60000,
247
247
  totalTimeout: 300000,
248
- maxRetries: 3,
248
+ maxRetries: 5,
249
249
  postAction: async () => {
250
250
  await this.reopenConnectorSecretsTab(cluster);
251
251
  },
@@ -274,8 +274,8 @@ class ClusterSecretsPage {
274
274
  // re-entering the tab brings it back.
275
275
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.importButton, {
276
276
  visibilityTimeout: 30000,
277
- totalTimeout: 180000,
278
- maxRetries: 3,
277
+ totalTimeout: 300000,
278
+ maxRetries: 5,
279
279
  postAction: async () => {
280
280
  await this.reopenConnectorSecretsTab(cluster);
281
281
  },
@@ -323,8 +323,15 @@ class ClusterSecretsPage {
323
323
  // (observed in run 30971320305 for both cluster setups), so keep re-entering
324
324
  // the tab until the toolbar is genuinely rendered.
325
325
  await this.assertSecretsToolbarVisible(cluster);
326
+ // The refresh below is what makes the just-imported secrets show up in the
327
+ // table assertSecretsOrCreate searches, but it also re-issues the same
328
+ // .../connectorSecrets request that breaks the panel - in run 31453329946
329
+ // it did exactly that and left assertSecretsOrCreate reading an
330
+ // "Oops ... something went wrong" panel. Re-assert the toolbar after the
331
+ // reload instead of sleeping blindly, so the panel is proven usable (and
332
+ // healed if not) before the caller reads it.
326
333
  await this.page.reload();
327
- await (0, sleep_1.sleep)(5000);
334
+ await this.assertSecretsToolbarVisible(cluster);
328
335
  }
329
336
  }
330
337
  exports.ClusterSecretsPage = ClusterSecretsPage;
@@ -42,6 +42,7 @@ declare class ModelerHomePage {
42
42
  enterFormName(name: string): Promise<void>;
43
43
  clickProjectBreadcrumb(): Promise<void>;
44
44
  clickHomeBreadcrumb(): Promise<void>;
45
+ private dismissBlockingDialogs;
45
46
  clickOpenOrganizationsButton(): Promise<void>;
46
47
  createForm(formName: string): Promise<void>;
47
48
  clickCreateIdpApplicationButton(): Promise<void>;
@@ -198,11 +198,41 @@ class ModelerHomePage {
198
198
  await this.projectBreadcrumb.click({ timeout: 60000 });
199
199
  }
200
200
  async clickHomeBreadcrumb() {
201
- try {
202
- await this.homeBreadcrumb.click();
203
- }
204
- catch (error) {
205
- await this.page.getByText('Home', { exact: true }).first().click();
201
+ // The Console shell's HelpCenter panel ("Your recommendations") renders on
202
+ // top of Web Modeler as a modal dialog that covers the whole page, so the
203
+ // breadcrumb click retries against an element whose pointer events the
204
+ // overlay swallows until the action timeout expires. Dismiss any such
205
+ // overlay before every attempt instead of clicking blindly.
206
+ const homeLink = this.homeBreadcrumb
207
+ .or(this.page.getByText('Home', { exact: true }))
208
+ .first();
209
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, homeLink, {
210
+ visibilityTimeout: 15000,
211
+ totalTimeout: 120000,
212
+ maxRetries: 4,
213
+ preAction: async () => {
214
+ await this.dismissBlockingDialogs();
215
+ },
216
+ postAction: async () => {
217
+ await this.dismissBlockingDialogs();
218
+ },
219
+ });
220
+ }
221
+ async dismissBlockingDialogs() {
222
+ for (const dialog of await this.page.getByRole('dialog').all()) {
223
+ if (!(await dialog.isVisible().catch(() => false))) {
224
+ continue;
225
+ }
226
+ await this.page.keyboard.press('Escape');
227
+ await dialog.waitFor({ state: 'hidden', timeout: 3000 }).catch(() => { });
228
+ if (!(await dialog.isVisible().catch(() => false))) {
229
+ continue;
230
+ }
231
+ const closeButton = dialog.getByRole('button', { name: 'Close' }).first();
232
+ await closeButton
233
+ .waitFor({ state: 'visible', timeout: 3000 })
234
+ .then(() => closeButton.click({ timeout: 5000 }))
235
+ .catch(() => { });
206
236
  }
207
237
  }
208
238
  async clickOpenOrganizationsButton() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.927",
3
+ "version": "0.0.929",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",