@camunda/e2e-test-suite 0.0.1145 → 0.0.1147

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.
@@ -35,6 +35,7 @@ declare class ModelerCreatePage {
35
35
  readonly cancelButton: Locator;
36
36
  readonly restConnectorOption: Locator;
37
37
  readonly changeElementSearchInput: Locator;
38
+ readonly changeElementResults: Locator;
38
39
  readonly changeElementTabStrip: Locator;
39
40
  readonly reusableAssetsTab: Locator;
40
41
  readonly marketPlaceButton: Locator;
@@ -176,6 +177,8 @@ declare class ModelerCreatePage {
176
177
  expandAssignmentSection(): Promise<void>;
177
178
  clickAppendElementButton(): Promise<void>;
178
179
  clickParallelGatewayOption(): Promise<void>;
180
+ private openReplacePopup;
181
+ private revealReplaceEntry;
179
182
  clickConnectToOtherElementButton(): Promise<void>;
180
183
  clickPriorityInput(): Promise<void>;
181
184
  fillPriorityInput(priority: string): Promise<void>;
@@ -42,6 +42,7 @@ class ModelerCreatePage {
42
42
  cancelButton;
43
43
  restConnectorOption;
44
44
  changeElementSearchInput;
45
+ changeElementResults;
45
46
  changeElementTabStrip;
46
47
  reusableAssetsTab;
47
48
  marketPlaceButton;
@@ -207,6 +208,7 @@ class ModelerCreatePage {
207
208
  .locator('.djs-popup-results')
208
209
  .locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
209
210
  this.changeElementSearchInput = page.locator('.djs-popup-search input');
211
+ this.changeElementResults = page.locator('.djs-popup-results');
210
212
  this.changeElementTabStrip = page.locator('.djs-popup-tabs-container');
211
213
  // The tab renders its label as text content, and the product source
212
214
  // spells it both "Reusable Assets" and "Reusable assets".
@@ -1390,8 +1392,77 @@ class ModelerCreatePage {
1390
1392
  // multiplied the worst-case wait.
1391
1393
  await this.hoverOnLocator(this.appendPreButton);
1392
1394
  }
1395
+ // Owns the whole replace step, popup included: the append leaves the new
1396
+ // (exclusive) gateway selected while the canvas is still panning it into
1397
+ // view, and a forced "Change element" click issued mid-pan lands on empty
1398
+ // canvas -- that clears the selection, closes the context pad, and the
1399
+ // replace popup never opens, so a bare wait on the entry burns its full
1400
+ // timeout with nothing on screen. Pin the shape by id up front so the popup
1401
+ // can be reopened against the same element instead of a positional one.
1393
1402
  async clickParallelGatewayOption() {
1394
- await this.parallelGatewayOption.click({ timeout: 90000 });
1403
+ const selectedId = await this.page
1404
+ .locator('.djs-element.selected')
1405
+ .first()
1406
+ .getAttribute('data-element-id', { timeout: 10000 })
1407
+ .catch(() => null);
1408
+ const element = selectedId
1409
+ ? this.page
1410
+ .locator(`.djs-element[data-element-id="${selectedId}"]`)
1411
+ .first()
1412
+ : this.page.locator('.djs-element.selected').first();
1413
+ const maxRetries = 3;
1414
+ let lastError;
1415
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1416
+ try {
1417
+ if (attempt > 1) {
1418
+ // Drop a popup left open on a stale search filter (or on the wrong
1419
+ // element) so the retry reopens a fresh one for `element`.
1420
+ await this.page.keyboard.press('Escape');
1421
+ }
1422
+ await this.openReplacePopup(element, attempt > 1);
1423
+ await this.revealReplaceEntry(this.parallelGatewayOption, 'Parallel gateway');
1424
+ await this.parallelGatewayOption.click({ timeout: 30000 });
1425
+ // bpmn-js closes the popup once an entry is applied, so a popup still
1426
+ // on screen means the click was swallowed and the element was not
1427
+ // replaced.
1428
+ await (0, test_1.expect)(this.changeElementResults).toBeHidden({ timeout: 30000 });
1429
+ return;
1430
+ }
1431
+ catch (error) {
1432
+ lastError = error;
1433
+ console.error(`Parallel gateway attempt ${attempt} failed: ${error}`);
1434
+ await (0, sleep_1.sleep)(5000);
1435
+ }
1436
+ }
1437
+ throw new Error(`Failed to replace the element with a parallel gateway after ${maxRetries} attempts: ${lastError}`);
1438
+ }
1439
+ async openReplacePopup(element, force) {
1440
+ if (await this.isVisibleWithin(this.changeElementResults, 2000)) {
1441
+ return;
1442
+ }
1443
+ // The context pad only renders for a selected element, so "Change element"
1444
+ // would otherwise never appear.
1445
+ if (!(await this.isVisibleWithin(this.changeTypeButton, 5000))) {
1446
+ await element.click({ force: true, timeout: 30000 });
1447
+ }
1448
+ await this.changeTypeButton.click({ timeout: 60000, force });
1449
+ await (0, test_1.expect)(this.changeElementResults).toBeVisible({ timeout: 30000 });
1450
+ }
1451
+ async revealReplaceEntry(option, searchTerm) {
1452
+ if (await this.isVisibleWithin(option, 5000)) {
1453
+ return;
1454
+ }
1455
+ // The popup lists the project's deployed resources alongside the built-in
1456
+ // replace entries, so the built-in entry can sit below the fold of the
1457
+ // scrolling results list; the popup's own search filters it back into view.
1458
+ if (await this.isVisibleWithin(this.changeElementSearchInput, 5000)) {
1459
+ await this.changeElementSearchInput.click();
1460
+ await this.changeElementSearchInput.fill('');
1461
+ await this.changeElementSearchInput.pressSequentially(searchTerm, {
1462
+ delay: 50,
1463
+ });
1464
+ }
1465
+ await (0, test_1.expect)(option).toBeVisible({ timeout: 30000 });
1395
1466
  }
1396
1467
  async clickConnectToOtherElementButton() {
1397
1468
  await (0, test_1.expect)(this.connectToOtherElementButton).toBeVisible({
@@ -1430,7 +1501,6 @@ class ModelerCreatePage {
1430
1501
  await this.hoverOnLocator(this.appendElementButton);
1431
1502
  }
1432
1503
  await this.clickAppendGatewayButton();
1433
- await this.clickChangeTypeButton();
1434
1504
  await this.clickParallelGatewayOption();
1435
1505
  await this.clickAppendElementButton();
1436
1506
  await this.clickAppendTaskButton();
@@ -1444,7 +1514,6 @@ class ModelerCreatePage {
1444
1514
  await this.assertImplementationOption('zeebeUserTask');
1445
1515
  await this.clickAppendElementButton();
1446
1516
  await this.clickAppendGatewayButton();
1447
- await this.clickChangeTypeButton();
1448
1517
  await this.clickParallelGatewayOption();
1449
1518
  for (let i = 2; i <= numberOfTasks; i++) {
1450
1519
  await this.clickFirstPlacedGateway();
@@ -16,7 +16,64 @@ async function captureScreenshot(page, testInfo) {
16
16
  if (testInfo.status === 'passed' || testInfo.status === 'skipped') {
17
17
  return;
18
18
  }
19
- const screenshotPath = `test-results/screenshots/screenshot-${(0, crypto_1.randomUUID)()}.png`;
19
+ // Capture every open tab, not just the one handed in. Flows that reach
20
+ // Operate/Tasklist through a link (openAppTabWithRetry) assert against a
21
+ // second Page, so capturing only `page` attaches a picture of the tab the
22
+ // test had already left: a doc-handling failure asserting the Operate
23
+ // completed-icon attached the Modeler canvas instead, which is unusable for
24
+ // triage.
25
+ let targets = [page];
26
+ try {
27
+ targets = [
28
+ page,
29
+ ...page
30
+ .context()
31
+ .pages()
32
+ .filter((candidate) => candidate !== page && !candidate.isClosed()),
33
+ ];
34
+ }
35
+ catch (contextError) {
36
+ console.warn(`captureScreenshot: could not enumerate context pages (${contextError}). ` +
37
+ 'Capturing the handed-in page only.');
38
+ }
39
+ let extraTabIndex = 1;
40
+ for (const target of targets) {
41
+ const screenshotPath = `test-results/screenshots/screenshot-${(0, crypto_1.randomUUID)()}.png`;
42
+ const isPrimaryTab = target === page;
43
+ let captured = false;
44
+ if (isPrimaryTab) {
45
+ captured = await screenshotWithViewportFallback(target, screenshotPath);
46
+ }
47
+ else {
48
+ try {
49
+ await target.screenshot({ path: screenshotPath, timeout: 30000 });
50
+ captured = true;
51
+ }
52
+ catch (error) {
53
+ console.warn(`captureScreenshot: extra-tab viewport capture failed (${error}). ` +
54
+ 'Continuing without a screenshot; the test verdict is unaffected.');
55
+ }
56
+ }
57
+ if (!captured) {
58
+ continue;
59
+ }
60
+ const attachmentName = isPrimaryTab
61
+ ? 'screenshot'
62
+ : `screenshot-tab-${extraTabIndex++}`;
63
+ try {
64
+ await testInfo.attach(attachmentName, {
65
+ path: screenshotPath,
66
+ contentType: 'image/png',
67
+ });
68
+ }
69
+ catch (error) {
70
+ console.warn(`captureScreenshot: failed to attach ${attachmentName} (${error}). ` +
71
+ 'Continuing without the attachment; the test verdict is unaffected.');
72
+ }
73
+ }
74
+ }
75
+ exports.captureScreenshot = captureScreenshot;
76
+ async function screenshotWithViewportFallback(page, screenshotPath) {
20
77
  try {
21
78
  await page.screenshot({
22
79
  path: screenshotPath,
@@ -33,15 +90,11 @@ async function captureScreenshot(page, testInfo) {
33
90
  catch (viewportError) {
34
91
  console.warn(`captureScreenshot: viewport capture also failed (${viewportError}). ` +
35
92
  'Continuing without a screenshot; the test verdict is unaffected.');
36
- return;
93
+ return false;
37
94
  }
38
95
  }
39
- await testInfo.attach('screenshot', {
40
- path: screenshotPath,
41
- contentType: 'image/png',
42
- });
96
+ return true;
43
97
  }
44
- exports.captureScreenshot = captureScreenshot;
45
98
  async function captureFailureVideo(page, testInfo) {
46
99
  // Same policy as captureScreenshot above: skip on passed/skipped rather
47
100
  // than requiring literally 'failed', so timed-out/interrupted tests keep
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1145",
3
+ "version": "0.0.1147",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",