@camunda/e2e-test-suite 0.0.1602 → 0.0.1603

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.
@@ -225,7 +225,24 @@ class BrowseAllConnectorMarketplacePage {
225
225
  if (exactVisible) {
226
226
  return exactCard;
227
227
  }
228
- await (0, test_1.expect)(relatedCard, `Browse All returned no Marketplace connector matching "${connectorName}".`).not.toHaveCount(0, { timeout: 105000 });
228
+ // When the Marketplace listing request fails (e.g. Web Modeler's
229
+ // /api/external/marketplace/connectors proxy answers 500), Browse All
230
+ // shows a "Couldn't load Marketplace connectors" alert and never
231
+ // re-requests on its own. Click its "Try again" button while polling so
232
+ // a short upstream blip self-heals; a sustained outage still fails here.
233
+ const marketplaceLoadRetryButton = this.page
234
+ .getByRole('alert')
235
+ .filter({ hasText: /couldn.t load marketplace connectors/i })
236
+ .getByRole('button', { name: 'Try again' });
237
+ let marketplaceLoadRetries = 0;
238
+ await (0, test_1.expect)(async () => {
239
+ if ((await relatedCard.count()) === 0 &&
240
+ (await marketplaceLoadRetryButton.isVisible())) {
241
+ marketplaceLoadRetries++;
242
+ await marketplaceLoadRetryButton.click({ timeout: 10000 });
243
+ }
244
+ (0, test_1.expect)(await relatedCard.count(), `Browse All returned no Marketplace connector matching "${connectorName}" (Marketplace load retried ${marketplaceLoadRetries}x).`).toBeGreaterThan(0);
245
+ }).toPass({ timeout: 105000, intervals: [2000, 5000, 10000] });
229
246
  if (await exactCard.isVisible()) {
230
247
  return exactCard;
231
248
  }
@@ -38,6 +38,7 @@ declare class IdentityTenantPage {
38
38
  clickRemoveButton(): Promise<void>;
39
39
  createTenant(tenantName: string): Promise<void>;
40
40
  clickTenant(tenantName: string): Promise<void>;
41
+ private isTenantDetailsOpen;
41
42
  private isRowVisible;
42
43
  assignUserToTenant(tenantName: string, user: string, userEmail: string): Promise<void>;
43
44
  removeUserFromTenant(tenantName: string): Promise<void>;
@@ -158,6 +158,13 @@ class IdentityTenantPage {
158
158
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
159
159
  try {
160
160
  await this.page.waitForLoadState('domcontentloaded').catch(() => { });
161
+ // A failed assignUserToTenant attempt reloads the tenant *details*
162
+ // page, which has no tenants list. Its "Name | Default" row matches
163
+ // this.row() but has no clickable list cell, so every retry timed
164
+ // out. If the tenant is already open, there is nothing to click.
165
+ if (await this.isTenantDetailsOpen(tenantName)) {
166
+ return;
167
+ }
161
168
  const tenantRow = this.row(tenantName);
162
169
  await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, tenantRow);
163
170
  await tenantRow.locator('td').first().click({ timeout: 30000 });
@@ -174,6 +181,14 @@ class IdentityTenantPage {
174
181
  }
175
182
  }
176
183
  }
184
+ async isTenantDetailsOpen(tenantName) {
185
+ const heading = this.page.getByRole('heading', {
186
+ level: 1,
187
+ name: tenantName,
188
+ exact: true,
189
+ });
190
+ return (await heading.isVisible()) && (await this.usersTab.isVisible());
191
+ }
177
192
  async isRowVisible(name) {
178
193
  await (0, sleep_1.sleep)(1000);
179
194
  if (await this.row(name).isVisible()) {
@@ -984,10 +984,29 @@ class ModelerHomePage {
984
984
  await this.clickWorkspaceBreadcrumb();
985
985
  return;
986
986
  }
987
- await this.page.goto(this.processApplicationUrl, {
988
- waitUntil: 'domcontentloaded',
989
- });
990
- await this.processApplicationPage.waitForProcessApplication();
987
+ // A goto onto the deep link can strand Hub on its app-shell "Loading"
988
+ // spinner: when the silent Keycloak refresh (/modeler/login ->
989
+ // /modeler/login-callback) stalls, the header logo points at
990
+ // /modeler/login and the container never mounts, however long we wait.
991
+ // Re-navigating to the same absolute URL restarts that login flow (a
992
+ // page.reload() would not -- see clickDiagramTypeDropdown()), so retry
993
+ // the goto instead of spending one 90s budget on a dead shell.
994
+ const maxAttempts = 3;
995
+ for (let attempt = 1;; attempt++) {
996
+ await this.page.goto(this.processApplicationUrl, {
997
+ waitUntil: 'domcontentloaded',
998
+ });
999
+ try {
1000
+ await this.processApplicationPage.waitForProcessApplication(60000);
1001
+ break;
1002
+ }
1003
+ catch (error) {
1004
+ if (attempt >= maxAttempts) {
1005
+ throw error;
1006
+ }
1007
+ console.log(`returnToResourceContainer: process application not rendered after attempt ${attempt}, re-navigating...`);
1008
+ }
1009
+ }
991
1010
  await this.clickMessageBanner();
992
1011
  }
993
1012
  // Single seam for "give me an empty BPMN diagram in this test's container".
@@ -38,6 +38,7 @@ declare class IdentityTenantPage {
38
38
  clickRemoveButton(): Promise<void>;
39
39
  createTenant(tenantName: string): Promise<void>;
40
40
  clickTenant(tenantName: string): Promise<void>;
41
+ private isTenantDetailsOpen;
41
42
  private isRowVisible;
42
43
  assignUserToTenant(tenantName: string, user: string, userEmail: string): Promise<void>;
43
44
  removeUserFromTenant(tenantName: string): Promise<void>;
@@ -158,6 +158,13 @@ class IdentityTenantPage {
158
158
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
159
159
  try {
160
160
  await this.page.waitForLoadState('domcontentloaded').catch(() => { });
161
+ // A failed assignUserToTenant attempt reloads the tenant *details*
162
+ // page, which has no tenants list. Its "Name | Default" row matches
163
+ // this.row() but has no clickable list cell, so every retry timed
164
+ // out. If the tenant is already open, there is nothing to click.
165
+ if (await this.isTenantDetailsOpen(tenantName)) {
166
+ return;
167
+ }
161
168
  const tenantRow = this.row(tenantName);
162
169
  await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, tenantRow);
163
170
  await tenantRow.locator('td').first().click({ timeout: 30000 });
@@ -174,6 +181,14 @@ class IdentityTenantPage {
174
181
  }
175
182
  }
176
183
  }
184
+ async isTenantDetailsOpen(tenantName) {
185
+ const heading = this.page.getByRole('heading', {
186
+ level: 1,
187
+ name: tenantName,
188
+ exact: true,
189
+ });
190
+ return (await heading.isVisible()) && (await this.usersTab.isVisible());
191
+ }
177
192
  async isRowVisible(name) {
178
193
  await (0, sleep_1.sleep)(1000);
179
194
  if (await this.row(name).isVisible()) {
@@ -93,20 +93,20 @@ class ConnectorTemplatePage {
93
93
  // Replace the whole content of the open template file. Web Modeler's Monaco
94
94
  // editor uses the EditContext API, under which keyboard.insertText of a
95
95
  // multi-line string is re-indented line by line (and can end up as invalid
96
- // JSON, which disables Publish). Pasting is applied verbatim, so the new
97
- // JSON goes through the clipboard instead.
96
+ // JSON, which disables Publish). Pasting is applied verbatim, but a
97
+ // ControlOrMeta+V key press never triggers a native paste in headless
98
+ // Chromium (the nightly showed the editor unchanged after it). So the JSON
99
+ // is delivered as a `paste` ClipboardEvent dispatched on the element Monaco
100
+ // focused, whose handler reads the text from `clipboardData`.
98
101
  async replaceTemplateJson(json, expectedText) {
99
102
  const editor = this.page.locator('.monaco-editor').first();
100
103
  await (0, test_1.expect)(editor).toBeVisible({ timeout: 90000 });
101
- await this.page
102
- .context()
103
- .grantPermissions(['clipboard-read', 'clipboard-write']);
104
- await this.page.evaluate(async (text) => await navigator.clipboard.writeText(text), json);
105
- // Click inside the visible text area, just right of the line numbers:
106
- // clicking the centre of `.view-lines` can land far right on a long line
107
- // and miss the editor, so the shortcuts below would act on the page
108
- // instead. Monaco marks the editor `focused` once it owns the keyboard.
109
104
  await (0, test_1.expect)(async () => {
105
+ // Click inside the visible text area, just right of the line numbers:
106
+ // clicking the centre of `.view-lines` can land far right on a long
107
+ // line and miss the editor, so the shortcuts below would act on the
108
+ // page instead. Monaco marks the editor `focused` once it owns the
109
+ // keyboard.
110
110
  const box = await editor.boundingBox();
111
111
  if (!box) {
112
112
  throw new Error('Template editor has no bounding box');
@@ -116,13 +116,27 @@ class ConnectorTemplatePage {
116
116
  timeout: 10000,
117
117
  });
118
118
  await (0, test_1.expect)(editor).toHaveClass(/\bfocused\b/, { timeout: 5000 });
119
- }).toPass({ timeout: 60000 });
120
- await this.page.keyboard.press('ControlOrMeta+A');
121
- await this.page.keyboard.press('ControlOrMeta+V');
122
- await this.page.keyboard.press('ControlOrMeta+Home');
123
- await (0, test_1.expect)(editor.locator('.view-lines')).toContainText(expectedText, {
124
- timeout: 30000,
125
- });
119
+ await this.page.keyboard.press('ControlOrMeta+A');
120
+ const pasted = await editor.evaluate((root, text) => {
121
+ const target = document.activeElement;
122
+ if (!target || !root.contains(target)) {
123
+ return false;
124
+ }
125
+ const clipboardData = new DataTransfer();
126
+ clipboardData.setData('text/plain', text);
127
+ target.dispatchEvent(new ClipboardEvent('paste', {
128
+ clipboardData,
129
+ bubbles: true,
130
+ cancelable: true,
131
+ }));
132
+ return true;
133
+ }, json);
134
+ (0, test_1.expect)(pasted, 'Monaco lost focus before the paste').toBeTruthy();
135
+ await this.page.keyboard.press('ControlOrMeta+Home');
136
+ await (0, test_1.expect)(editor.locator('.view-lines')).toContainText(expectedText, {
137
+ timeout: 10000,
138
+ });
139
+ }).toPass({ timeout: 90000 });
126
140
  await (0, test_1.expect)(this.page.getByText('Your JSON is invalid')).toBeHidden({
127
141
  timeout: 30000,
128
142
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1602",
3
+ "version": "0.0.1603",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",