@camunda/e2e-test-suite 0.0.1148 → 0.0.1150

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.
@@ -9,6 +9,7 @@ declare class ConnectorMarketplacePage {
9
9
  readonly replaceResourceButton: Locator;
10
10
  readonly saveAsCopyButton: Locator;
11
11
  readonly cancelButton: Locator;
12
+ readonly dialogTitle: Locator;
12
13
  constructor(page: Page);
13
14
  clickSearchForConnectorTextbox(): Promise<void>;
14
15
  fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
@@ -16,6 +17,8 @@ declare class ConnectorMarketplacePage {
16
17
  clickSnackbar(): Promise<void>;
17
18
  clickCloseButton(): Promise<void>;
18
19
  clickReplaceResourceButton(): Promise<void>;
19
- downloadConnectorToProject(): Promise<void>;
20
+ downloadConnectorToProject(): Promise<boolean>;
21
+ private closeMarketplaceDialog;
22
+ searchAndDownloadConnector(connectorName: string): Promise<boolean>;
20
23
  }
21
24
  export { ConnectorMarketplacePage };
@@ -11,8 +11,13 @@ class ConnectorMarketplacePage {
11
11
  replaceResourceButton;
12
12
  saveAsCopyButton;
13
13
  cancelButton;
14
+ dialogTitle;
14
15
  constructor(page) {
15
16
  this.page = page;
17
+ // Title of the marketplace dialog itself — present both while the listing
18
+ // renders and while it is stuck on its loading spinner, so it is the one
19
+ // reliable marker of "the dialog is still up".
20
+ this.dialogTitle = page.getByText('Browse Marketplace connectors');
16
21
  this.searchForConnectorTextbox = page.getByPlaceholder('Search for a connector');
17
22
  this.downloadToProjectButton = page
18
23
  .getByRole('button', { name: 'Download to project' })
@@ -48,38 +53,65 @@ class ConnectorMarketplacePage {
48
53
  }
49
54
  async downloadConnectorToProject() {
50
55
  await this.clickDownloadToProjectButton();
51
- // After clicking "Download to project", Hub opens an ImportModal whose
52
- // primary button depends on project state: "Add to project" (new template),
53
- // "Replace resource" (same template exists), or "Save as copy" (conflict).
54
- // Race all three click calls — Playwright's click() waits for actionability,
55
- // so whichever button Hub actually renders will win the race and be clicked.
56
- // If none appear within 20 s (e.g. the modal never opened), the race rejects
57
- // and the catch path handles any residual Cancel button.
58
- let imported = false;
59
- try {
60
- await Promise.race([
61
- this.addToProjectButton.click({ timeout: 20000 }),
62
- this.replaceResourceButton.click({ timeout: 20000 }),
63
- this.saveAsCopyButton.click({ timeout: 20000 }),
64
- ]);
65
- imported = true;
66
- }
67
- catch (e) {
68
- if (await this.cancelButton.isVisible({ timeout: 3000 }).catch(() => false)) {
69
- await this.cancelButton.click({ timeout: 10000 });
70
- }
71
- }
72
- // Wait for the snackbar that confirms the template was received, then close
73
- // the marketplace dialog so the change-element popup is unblocked before the
74
- // caller selects the newly installed connector.
56
+ // After clicking "Download to project", Hub fetches the connector detail
57
+ // and opens an ImportModal whose primary button depends on project state:
58
+ // "Add to project" (new template), "Replace resource" (same template
59
+ // exists), or "Save as copy" (conflict). Wait once for whichever of the
60
+ // three Hub renders, then click that one — never race three clicks, which
61
+ // fires all of them at the same time and hides which button was missing.
62
+ const importButton = this.addToProjectButton
63
+ .or(this.replaceResourceButton)
64
+ .or(this.saveAsCopyButton);
65
+ const imported = await importButton
66
+ .waitFor({ state: 'visible', timeout: 20000 })
67
+ .then(() => true)
68
+ .catch(() => false);
75
69
  if (imported) {
70
+ await importButton.click({ timeout: 20000 });
71
+ // The snackbar confirms Hub received the template. It auto-dismisses, so
72
+ // a miss is not treated as fatal here — the caller proves the template
73
+ // landed by finding the connector in the change-element popup.
76
74
  await this.snackbar
77
75
  .waitFor({ state: 'visible', timeout: 60000 })
78
- .catch(() => { });
79
- if (await this.closeButton.isVisible({ timeout: 5000 }).catch(() => false)) {
80
- await this.closeButton.click({ timeout: 10000 });
81
- }
76
+ .catch(() => false);
82
77
  }
78
+ else if (await this.cancelButton.isVisible({ timeout: 3000 }).catch(() => false)) {
79
+ await this.cancelButton.click({ timeout: 10000 });
80
+ }
81
+ // Always close the marketplace dialog, download or not. When Hub's
82
+ // connector-detail request fails (observed as a 504 on
83
+ // /api/external/marketplace/connectors/<id>) the dialog stays up on its
84
+ // loading spinner forever and swallows every click on the canvas beneath
85
+ // it, so leaving it open turns one failed download into a cascade of
86
+ // 60 s timeouts in the caller.
87
+ await this.closeMarketplaceDialog();
88
+ return imported;
89
+ }
90
+ async closeMarketplaceDialog() {
91
+ if (await this.closeButton.isVisible({ timeout: 5000 }).catch(() => false)) {
92
+ await this.closeButton.click({ timeout: 10000 });
93
+ }
94
+ if (await this.dialogTitle
95
+ .waitFor({ state: 'hidden', timeout: 10000 })
96
+ .then(() => true)
97
+ .catch(() => false)) {
98
+ return;
99
+ }
100
+ // The stuck-spinner state can leave the close control unresponsive;
101
+ // Escape dismisses the dialog just as reliably.
102
+ await this.page.keyboard.press('Escape');
103
+ await this.dialogTitle.waitFor({ state: 'hidden', timeout: 30000 });
104
+ }
105
+ async searchAndDownloadConnector(connectorName) {
106
+ await this.clickSearchForConnectorTextbox();
107
+ await this.fillSearchForConnectorTextbox(connectorName);
108
+ // Wait for the searched connector's card to render before downloading, so
109
+ // the click cannot land on a leftover card from the unfiltered listing.
110
+ await this.page
111
+ .getByText(connectorName, { exact: true })
112
+ .first()
113
+ .waitFor({ state: 'visible', timeout: 60000 });
114
+ return await this.downloadConnectorToProject();
83
115
  }
84
116
  }
85
117
  exports.ConnectorMarketplacePage = ConnectorMarketplacePage;
@@ -185,6 +185,7 @@ declare class ModelerCreatePage {
185
185
  private openAppendCreatePad;
186
186
  clickAppendElementButton(): Promise<void>;
187
187
  clickMarketPlaceButton(): Promise<void>;
188
+ private openChangeElementPopup;
188
189
  clickPublicHolidayConnectorOption(): Promise<void>;
189
190
  clickPublicHolidayYearOption(): Promise<void>;
190
191
  fillPublicHolidayYearOption(year: string): Promise<void>;
@@ -1106,24 +1106,34 @@ class ModelerCreatePage {
1106
1106
  }
1107
1107
  throw new Error(`Failed to click the button after ${maxRetries} attempts.`);
1108
1108
  }
1109
+ async openChangeElementPopup() {
1110
+ await this.clickCanvas();
1111
+ await this.secondElement.click({ timeout: 60000 });
1112
+ await this.clickChangeTypeButton();
1113
+ await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
1114
+ }
1109
1115
  async clickPublicHolidayConnectorOption() {
1110
1116
  const maxRetries = 4;
1111
1117
  for (let retries = 0; retries < maxRetries; retries++) {
1112
1118
  try {
1113
- if (retries === 2) {
1114
- // Re-download connector on second retry in case the first download failed
1119
+ // Always open the change-type popup before trying to click the option.
1120
+ // This has to come first: the marketplace button only exists inside
1121
+ // that popup, so the re-download recovery below can only work once the
1122
+ // popup is open (it used to run before this block, right after a
1123
+ // page.reload(), and so could never find the button).
1124
+ await this.openChangeElementPopup();
1125
+ if (retries > 0) {
1126
+ // The previous attempt did not find the connector in the popup, so
1127
+ // the marketplace download never landed — Hub's connector-detail
1128
+ // request can fail (a 504 on
1129
+ // /api/external/marketplace/connectors/<id> leaves the marketplace
1130
+ // dialog stuck on its spinner and no import modal opens). Re-run the
1131
+ // download from the now-open popup, then reopen the popup.
1115
1132
  await this.clickMarketPlaceButton();
1116
1133
  const connectorMarketplacePage = new ConnectorMarketplacePage_1.ConnectorMarketplacePage(this.page);
1117
- await connectorMarketplacePage.clickSearchForConnectorTextbox();
1118
- await connectorMarketplacePage.fillSearchForConnectorTextbox('Public Holiday Connector');
1119
- await (0, sleep_1.sleep)(10000);
1120
- await connectorMarketplacePage.downloadConnectorToProject();
1134
+ await connectorMarketplacePage.searchAndDownloadConnector('Public Holiday Connector');
1135
+ await this.openChangeElementPopup();
1121
1136
  }
1122
- // Always open the change-type popup before trying to click the option
1123
- await this.clickCanvas();
1124
- await this.secondElement.click({ timeout: 60000 });
1125
- await this.clickChangeTypeButton();
1126
- await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
1127
1137
  // Filter the virtualized change-element popup so the option scrolls
1128
1138
  // into view. Search by each full connector name (the marketplace
1129
1139
  // renamed it "Public Holiday Outbound" -> "Worldwide Public Holiday"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1148",
3
+ "version": "0.0.1150",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",