@camunda/e2e-test-suite 0.0.593 → 0.0.595

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.
@@ -5,6 +5,7 @@ declare class ConnectorMarketplacePage {
5
5
  readonly downloadToProjectButton: Locator;
6
6
  readonly replaceResourceButton: Locator;
7
7
  readonly cancelButton: Locator;
8
+ readonly marketplaceErrorLocator: Locator;
8
9
  constructor(page: Page);
9
10
  clickSearchForConnectorTextbox(): Promise<void>;
10
11
  fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
@@ -8,6 +8,7 @@ class ConnectorMarketplacePage {
8
8
  downloadToProjectButton;
9
9
  replaceResourceButton;
10
10
  cancelButton;
11
+ marketplaceErrorLocator;
11
12
  constructor(page) {
12
13
  this.page = page;
13
14
  this.searchForConnectorTextbox = page.getByPlaceholder('Search for a connector');
@@ -18,6 +19,7 @@ class ConnectorMarketplacePage {
18
19
  this.replaceResourceButton = page.getByRole('button', {
19
20
  name: 'Replace resource',
20
21
  });
22
+ this.marketplaceErrorLocator = page.getByText('an error occurred');
21
23
  }
22
24
  async clickSearchForConnectorTextbox() {
23
25
  await this.searchForConnectorTextbox.click({ timeout: 60000 });
@@ -37,7 +39,24 @@ class ConnectorMarketplacePage {
37
39
  await this.replaceResourceButton.click({ timeout: 30000 });
38
40
  }
39
41
  async downloadConnectorToProject() {
40
- await this.clickDownloadToProjectButton();
42
+ // Race the download button against a marketplace API error so we don't
43
+ // block the full 60 s timeout when the marketplace backend is unavailable.
44
+ const downloadOrError = this.downloadToProjectButton.or(this.marketplaceErrorLocator);
45
+ try {
46
+ await downloadOrError.first().waitFor({ state: 'visible', timeout: 60000 });
47
+ }
48
+ catch {
49
+ // Neither appeared — close the dialog and let the caller proceed.
50
+ await this.page.keyboard.press('Escape');
51
+ return;
52
+ }
53
+ if (await this.marketplaceErrorLocator.isVisible()) {
54
+ // Marketplace API error — dismiss the dialog and return gracefully so
55
+ // the caller can still attempt clickWebhookStartEventConnectorOption().
56
+ await this.page.keyboard.press('Escape');
57
+ return;
58
+ }
59
+ await this.downloadToProjectButton.click({ timeout: 10000 });
41
60
  try {
42
61
  await Promise.race([
43
62
  this.replaceResourceButton.click({ timeout: 20000 }),
@@ -19,7 +19,6 @@ declare class PlayPage {
19
19
  readonly loadingInstanceDetailsText: Locator;
20
20
  readonly retryButton: Locator;
21
21
  readonly restartProcess: Locator;
22
- readonly completedMessage: Locator;
23
22
  constructor(page: Page);
24
23
  waitForCompleteJobButtonToBeAvailable(): Promise<void>;
25
24
  clickCompleteJobButton(): Promise<void>;
@@ -4,7 +4,6 @@ exports.PlayPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const sleep_1 = require("../../utils/sleep");
6
6
  const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
7
- const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
8
7
  const env_1 = require("../../utils/env");
9
8
  const maxWaitTimeSeconds = 180000;
10
9
  // OpenSearch indexes process instances more slowly than Elasticsearch, so
@@ -30,7 +29,6 @@ class PlayPage {
30
29
  loadingInstanceDetailsText;
31
30
  retryButton;
32
31
  restartProcess;
33
- completedMessage;
34
32
  constructor(page) {
35
33
  this.page = page;
36
34
  this.completeJobButton = page
@@ -54,7 +52,9 @@ class PlayPage {
54
52
  this.confirmDeleteScenarioButton = page.getByRole('button', {
55
53
  name: 'danger Delete',
56
54
  });
57
- this.viewAllScenariosButton = page.getByText('(View all)').last();
55
+ this.viewAllScenariosButton = page
56
+ .getByTestId('instance-header')
57
+ .getByRole('button', { name: '(View all)' });
58
58
  this.getScenarioRowByName = (scenarioName) => page.locator('tr', { hasText: scenarioName });
59
59
  this.getScenarioRow = (scenarioName) => page.locator('tr', { hasText: scenarioName });
60
60
  this.diagram = page.getByTestId('diagram');
@@ -66,7 +66,6 @@ class PlayPage {
66
66
  this.restartProcess = this.page.getByRole('button', {
67
67
  name: 'Restart process',
68
68
  });
69
- this.completedMessage = this.page.getByText(/completed manually/i);
70
69
  }
71
70
  async waitForCompleteJobButtonToBeAvailable() {
72
71
  await (0, test_1.expect)(this.completeJobButton).toBeVisible({
@@ -163,16 +162,9 @@ class PlayPage {
163
162
  }
164
163
  }
165
164
  async waitForProcessToBeCompleted() {
166
- const incidentText = await this.page.getByText(/incident/i).count();
167
- if (incidentText > 0) {
168
- throw new Error('Process has an incident instead of completing');
169
- }
170
- await (0, test_1.expect)(this.completedMessage.first()).toBeVisible({
165
+ await (0, test_1.expect)(this.page.getByText('Completed')).toBeVisible({
171
166
  timeout: maxWaitTimeSeconds,
172
167
  });
173
- await (0, test_1.expect)(this.completedMessage.first()).not.toBeVisible({
174
- timeout: 60000,
175
- });
176
168
  }
177
169
  async clickSaveScenarioButton() {
178
170
  await this.notifications
@@ -187,6 +179,13 @@ class PlayPage {
187
179
  await (0, test_1.expect)(this.saveScenarioButton).toBeVisible({ timeout: 30000 });
188
180
  await (0, test_1.expect)(this.saveScenarioButton).toBeEnabled({ timeout: 30000 });
189
181
  await this.saveScenarioButton.click({ force: true, timeout: 30000 });
182
+ // Wait for the save-scenario modal to actually open before returning so
183
+ // that enterScenarioName finds the dialog immediately on the first attempt.
184
+ // Trace evidence shows the modal can take up to ~20 s to appear after the
185
+ // button click, so use a 30 s window here.
186
+ await this.saveScenarioModal
187
+ .waitFor({ state: 'visible', timeout: 30000 })
188
+ .catch(() => { });
190
189
  }
191
190
  }
192
191
  async clickViewScenarioButton() {
@@ -195,7 +194,7 @@ class PlayPage {
195
194
  async enterScenarioName(scenarioName) {
196
195
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.dialog, {
197
196
  postAction: async () => {
198
- await this.saveScenarioButton.click({ timeout: 30000 });
197
+ await this.saveScenarioButton.click({ timeout: 5000 }).catch(() => { });
199
198
  },
200
199
  });
201
200
  const input = this.dialog.locator('input#scenario-name');
@@ -207,27 +206,43 @@ class PlayPage {
207
206
  await this.enterScenarioNameInput.fill(newScenarioName);
208
207
  }
209
208
  async clickViewAllScenariosButton() {
210
- // The "(View all)" button is rendered after a save-scenario toast settles.
211
- // Reload + retry to recover when the toast or notification panel covers it.
212
- await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, this.viewAllScenariosButton, {
213
- totalTimeout: 180000,
214
- visibilityTimeout: 30000,
215
- maxRetries: 8,
209
+ // After saving a scenario Play shows an "instance-details" view (Instance
210
+ // History + Variables) with a compact scenario header. The "(View all)"
211
+ // button expands that header into the full scenarios-list view where
212
+ // "Run all scenarios" lives.
213
+ //
214
+ // If the button is not present we are NOT in the full-list view — we are in
215
+ // the instance-details view. Reloading the page resets Play to the
216
+ // scenarios-list view reliably (confirmed by screenshots from past failures).
217
+ await this.notifications
218
+ .first()
219
+ .waitFor({ state: 'hidden', timeout: 30000 })
220
+ .catch(() => { });
221
+ const buttonVisible = await this.viewAllScenariosButton
222
+ .waitFor({ state: 'visible', timeout: 10000 })
223
+ .then(() => true)
224
+ .catch(() => false);
225
+ if (!buttonVisible) {
226
+ // Reload to switch from instance-details view to scenarios-list view.
227
+ await this.page.reload();
228
+ await this.diagram
229
+ .waitFor({ state: 'visible', timeout: 30000 })
230
+ .catch(() => { });
231
+ return;
232
+ }
233
+ // Button found — click it to expand the compact view into the full list.
234
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.viewAllScenariosButton, {
235
+ totalTimeout: 60000,
236
+ visibilityTimeout: 10000,
237
+ maxRetries: 3,
216
238
  preAction: async () => {
217
239
  await this.notifications
218
240
  .first()
219
- .waitFor({ state: 'hidden', timeout: 10000 })
220
- .catch(() => { });
221
- },
222
- postAction: async () => {
223
- // Recovery: reload the page so the Play UI re-fetches scenario state
224
- // and renders the "(View all)" button when multiple scenarios exist.
225
- await this.page.reload();
226
- await this.diagram
227
- .waitFor({ state: 'visible', timeout: 30000 })
241
+ .waitFor({ state: 'hidden', timeout: 30000 })
228
242
  .catch(() => { });
229
243
  },
230
244
  });
245
+ await this.viewAllScenariosButton.click({ force: true, timeout: 10000 });
231
246
  }
232
247
  async confirmSaveScenario() {
233
248
  await this.confirmSaveScenarioButton.click({ timeout: 30000 });
@@ -246,7 +261,46 @@ class PlayPage {
246
261
  await this.confirmDeleteScenarioButton.click();
247
262
  }
248
263
  async clickRunAllScenariosButton() {
249
- await this.runAllScenariosButton.click();
264
+ // Trace evidence: after clicking "(View all)" the Scenarios panel sometimes
265
+ // fails to render (view stays on instance-details) and a stray "Add variable"
266
+ // dialog can appear and block the button. Retry loop:
267
+ // 1. Dismiss any open dialog (Escape).
268
+ // 2. Wait up to 10 s for the button.
269
+ // 3. If still absent, reload — a fresh page load always lands on the
270
+ // Scenarios-list view where the button is immediately available.
271
+ const maxRetries = 3;
272
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
273
+ // Dismiss any blocking dialog (e.g. "Add variable") with Escape.
274
+ const anyDialog = this.page.getByRole('dialog');
275
+ if (await anyDialog
276
+ .first()
277
+ .isVisible()
278
+ .catch(() => false)) {
279
+ await this.page.keyboard.press('Escape');
280
+ await anyDialog
281
+ .first()
282
+ .waitFor({ state: 'hidden', timeout: 5000 })
283
+ .catch(() => { });
284
+ }
285
+ const found = await this.runAllScenariosButton
286
+ .waitFor({ state: 'visible', timeout: 10000 })
287
+ .then(() => true)
288
+ .catch(() => false);
289
+ if (found) {
290
+ await this.runAllScenariosButton.click({ timeout: 15000 });
291
+ return;
292
+ }
293
+ if (attempt < maxRetries) {
294
+ // Scenarios-list view didn't render after (View all) click — reload to
295
+ // reset Play to the default scenarios-list view.
296
+ await this.page.reload();
297
+ await this.diagram
298
+ .waitFor({ state: 'visible', timeout: 30000 })
299
+ .catch(() => { });
300
+ }
301
+ }
302
+ // Final attempt after exhausting retries.
303
+ await this.runAllScenariosButton.click({ timeout: 30000 });
250
304
  }
251
305
  async clickmessagePublishButton() {
252
306
  await this.diagram.waitFor({ state: 'visible', timeout: 30000 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.593",
3
+ "version": "0.0.595",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",