@camunda/e2e-test-suite 0.0.1440 → 0.0.1442

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.
@@ -458,6 +458,20 @@ class ClusterDetailsPage {
458
458
  return variables;
459
459
  }
460
460
  async createAPIClient(name) {
461
+ // The clients table paginates 10 rows per page in no particular order, so
462
+ // once an org holds more than 10 clients the new one is usually not on
463
+ // page 1 and the scopes assertion below never finds its row. Filter the
464
+ // table by the client name up front (the search box only renders once
465
+ // the cluster has at least one client).
466
+ const clientSearch = this.page.getByRole('searchbox', { name: 'Search' });
467
+ await this.createFirstClientButton
468
+ .or(this.createNewClientButton)
469
+ .first()
470
+ .waitFor({ state: 'visible', timeout: 60000 })
471
+ .catch(() => { });
472
+ if (await clientSearch.isVisible().catch(() => false)) {
473
+ await clientSearch.fill(name);
474
+ }
461
475
  await this.clickCreateClientButton();
462
476
  await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible();
463
477
  await this.fillAPIClientName(name);
@@ -15,7 +15,7 @@ declare class ClusterSecretsPage {
15
15
  readonly clusterSecretTab: Locator;
16
16
  readonly confirmDeleteInput: Locator;
17
17
  constructor(page: Page);
18
- deleteConnectorSecretsIfExist(): Promise<void>;
18
+ deleteConnectorSecretsIfExist(keys?: string[]): Promise<void>;
19
19
  private dismissOpenModal;
20
20
  clickCreateNewSecretButton(clusterName: string): Promise<void>;
21
21
  clickClusterBanner(): Promise<void>;
@@ -31,12 +31,8 @@ class ClusterSecretsPage {
31
31
  this.connectorSecretsTable = page.getByRole('table', {
32
32
  name: 'Connector secrets',
33
33
  });
34
- // Legacy Carbon table: an "Options" cell holding the kebab. Hub's
35
- // design-system data table instead labels each row's kebab
36
- // "Row actions" (data-slot="data-table-actions-trigger"), with no
37
- // "Options" cell -- so the old locator found nothing, the pre-clean
38
- // returned early, and re-creating an existing secret failed with
39
- // "Secret names must be unique".
34
+ // Console's secrets table moved off Carbon: the per-row overflow menu is
35
+ // labelled "Row actions" instead of living in an "Options" cell.
40
36
  this.optionsButton = page
41
37
  .getByRole('cell', { name: 'Options' })
42
38
  .getByRole('button')
@@ -81,7 +77,10 @@ class ClusterSecretsPage {
81
77
  name: 'Type the word DELETE to confirm',
82
78
  });
83
79
  }
84
- async deleteConnectorSecretsIfExist() {
80
+ // Pass `keys` to delete only those secrets. Without it every deletable
81
+ // secret on the cluster is removed, which also wipes the secrets a
82
+ // concurrent run of a secrets flow on the same cluster has just created.
83
+ async deleteConnectorSecretsIfExist(keys) {
85
84
  try {
86
85
  await (0, test_1.expect)(this.optionsButton.first()).toBeVisible({
87
86
  timeout: 10000,
@@ -90,9 +89,17 @@ class ClusterSecretsPage {
90
89
  catch (error) {
91
90
  return; //No Connector Secrets found in the list
92
91
  }
92
+ const secretRows = keys
93
+ ? this.page.getByRole('row').filter({
94
+ has: this.page.getByRole('cell', {
95
+ name: new RegExp(`^(${keys.join('|')})$`),
96
+ }),
97
+ })
98
+ : this.page.getByRole('row');
99
+ const rowOptionsButtons = secretRows.locator(this.optionsButton);
93
100
  try {
94
101
  for (let sweep = 0; sweep < 20; sweep++) {
95
- const options = await this.optionsButton.all();
102
+ const options = await rowOptionsButtons.all();
96
103
  if (options.length === 0) {
97
104
  break;
98
105
  }
@@ -129,8 +136,7 @@ class ClusterSecretsPage {
129
136
  // buttons at once (strict mode violation) while asserting something that
130
137
  // is no longer true. The post-condition this helper actually owns is
131
138
  // that nothing *deletable* is left behind.
132
- const deletableSecretRows = this.page
133
- .getByRole('row')
139
+ const deletableSecretRows = secretRows
134
140
  .filter({ has: this.optionsButton })
135
141
  .filter({ hasNotText: 'CAMUNDA_PROVIDED_' });
136
142
  await (0, test_1.expect)(deletableSecretRows).toHaveCount(0, { timeout: 30000 });
@@ -148,7 +154,8 @@ class ClusterSecretsPage {
148
154
  // Closes whichever Carbon modal is currently on screen, and waits for it to
149
155
  // actually leave the DOM rather than assuming a fixed delay.
150
156
  async dismissOpenModal() {
151
- const openModal = this.page.locator('.cds--modal.is-visible');
157
+ // Carbon modals (.cds--modal) and the Radix dialogs Console renders now.
158
+ const openModal = this.page.locator('.cds--modal.is-visible, [role="dialog"][data-state="open"]');
152
159
  if ((await openModal.count().catch(() => 0)) === 0) {
153
160
  return;
154
161
  }
@@ -201,6 +208,18 @@ class ClusterSecretsPage {
201
208
  await (0, test_1.expect)(this.page.getByText('Creating...')).not.toBeVisible({
202
209
  timeout: 60000,
203
210
  });
211
+ // Console can answer a create right after deleteConnectorSecretsIfExist()
212
+ // with "Secret names must be unique". The dialog then stays open and its
213
+ // overlay blocks every later click, so resubmit until the key is accepted
214
+ // and the dialog closes. This cannot resolve a key that a concurrent run
215
+ // of this flow on the same cluster really does hold.
216
+ const uniqueNameError = this.dialog.getByText('Secret names must be unique');
217
+ await (0, test_1.expect)(async () => {
218
+ if (await uniqueNameError.isVisible()) {
219
+ await this.clickCreateButton();
220
+ }
221
+ await (0, test_1.expect)(this.dialog).toBeHidden({ timeout: 5000 });
222
+ }).toPass({ timeout: 120000, intervals: [5000] });
204
223
  }
205
224
  async clickKeyInput() {
206
225
  await this.keyInput.click();
@@ -10,6 +10,8 @@ declare class ConnectorMarketplacePage {
10
10
  readonly tryAgainButton: Locator;
11
11
  readonly checkFailedAlert: Locator;
12
12
  readonly closeButton: Locator;
13
+ readonly addRemainingElementsButton: Locator;
14
+ readonly alreadyAddedStatus: Locator;
13
15
  readonly browseAllMarketplaceDetailsButton: Locator;
14
16
  constructor(page: Page);
15
17
  clickSearchForConnectorTextbox(): Promise<void>;
@@ -13,6 +13,8 @@ class ConnectorMarketplacePage {
13
13
  tryAgainButton;
14
14
  checkFailedAlert;
15
15
  closeButton;
16
+ addRemainingElementsButton;
17
+ alreadyAddedStatus;
16
18
  browseAllMarketplaceDetailsButton;
17
19
  constructor(page) {
18
20
  this.page = page;
@@ -40,11 +42,16 @@ class ConnectorMarketplacePage {
40
42
  name: 'Add to project',
41
43
  });
42
44
  this.cancelButton = page.getByRole('button', { name: 'Cancel' });
43
- this.tryAgainButton = page.getByRole('button', { name: 'Try again' });
44
45
  this.checkFailedAlert = page
45
46
  .getByText("Couldn't check this connector")
46
47
  .first();
47
48
  this.closeButton = page.getByRole('button', { name: 'Close' });
49
+ const marketplaceDetailFooter = page.locator('[data-test="marketplace-detail-footer"]');
50
+ this.addRemainingElementsButton = marketplaceDetailFooter.getByRole('button', { name: 'Add remaining elements' });
51
+ this.tryAgainButton = page.getByRole('button', { name: 'Try again' });
52
+ this.alreadyAddedStatus = marketplaceDetailFooter
53
+ .getByRole('status')
54
+ .filter({ hasText: 'Added to this project' });
48
55
  }
49
56
  async clickSearchForConnectorTextbox() {
50
57
  await this.searchForConnectorTextbox.click({ timeout: 60000 });
@@ -79,24 +86,18 @@ class ConnectorMarketplacePage {
79
86
  // immediate isVisible() probes below, which would just repeat the same
80
87
  // race this wait exists to close.
81
88
  //
82
- // The Browse all review step first "checks this project" (camunda-hub
83
- // marketplace-connector-detail-state.ts), which waits on the project's
84
- // element-template metadata request -- nightly and live traces show that
85
- // request still pending 30s in. When the check fails the footer offers
86
- // "Try again" (RETRY_INSPECTION) instead of "Add to project", so give the
87
- // check longer and re-run it on failure rather than timing out.
88
- //
89
- // Hub retries a transient (5xx) check once by itself before it shows
90
- // "Try again" (use-marketplace-connector-inspection.ts), and the gateway
91
- // only answers a hung metadata request with a 504 after ~60s -- so
92
- // "Try again" first renders ~121s after the click. A 120s wait expired
93
- // ~1s before that and never reached the retry below; allow both attempts.
94
- // A "Try again" check keeps the button on screen (loading) and hides the
95
- // "Couldn't check" alert until it fails again, so wait on that alert
96
- // rather than the button to retry as soon as a check has failed.
89
+ // Browse all's details panel (camunda-hub MarketplaceConnectorDetails)
90
+ // renders no action button at all once the connector is already in the
91
+ // project -- only an "Added to this project" footer status. Every run of
92
+ // these flows shares the Cross Component Test Project, so after the first
93
+ // run adds REST Connector the old wait for a button always timed out. A
94
+ // partially added connector offers "Add remaining elements", and a failed
95
+ // project check offers "Try again" -- click it and wait again.
97
96
  const outcome = this.addToProjectButton
97
+ .or(this.addRemainingElementsButton)
98
98
  .or(this.replaceResourceButton)
99
99
  .or(this.cancelButton)
100
+ .or(this.alreadyAddedStatus)
100
101
  .first();
101
102
  for (let attempt = 0; attempt < 4; attempt++) {
102
103
  await outcome
@@ -116,15 +117,25 @@ class ConnectorMarketplacePage {
116
117
  // the old code only handled "Replace resource", so a first-time download
117
118
  // (the SaaS nightly always runs in a fresh project) was never confirmed
118
119
  // and the connector never reached the diagram. The wait above already
119
- // confirmed one of the three is visible, so these are immediate checks.
120
+ // confirmed one of the outcomes is visible, so these are immediate checks.
120
121
  if (await this.addToProjectButton.isVisible()) {
121
122
  await this.addToProjectButton.click({ timeout: 10000 });
122
123
  imported = true;
123
124
  }
125
+ else if (await this.addRemainingElementsButton.isVisible()) {
126
+ await this.addRemainingElementsButton.click({ timeout: 10000 });
127
+ imported = true;
128
+ }
124
129
  else if (await this.replaceResourceButton.isVisible()) {
125
130
  await this.replaceResourceButton.click({ timeout: 10000 });
126
131
  imported = true;
127
132
  }
133
+ else if (await this.alreadyAddedStatus.isVisible()) {
134
+ // Nothing to import; close the dialog so it does not cover the canvas.
135
+ if (await this.closeButton.isVisible()) {
136
+ await this.closeButton.click({ timeout: 10000 });
137
+ }
138
+ }
128
139
  else if (await this.cancelButton.isVisible()) {
129
140
  await this.cancelButton.click({ timeout: 10000 });
130
141
  }
@@ -163,27 +163,38 @@ _8_7_1.test.describe('Connectors User Flow Tests', () => {
163
163
  (0, _8_7_1.test)('Connector Secrets User Flow - GCP', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, clusterPage, clusterSecretsPage, }) => {
164
164
  _8_7_1.test.slow();
165
165
  const processName = 'REST_Connector_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
166
+ // Per-run secret keys: concurrent runs of this flow share "Test Cluster",
167
+ // so fixed keys made one run's create fail with "Secret names must be
168
+ // unique" and its cleanup delete the secrets another run's instance was
169
+ // about to resolve.
170
+ const secretSuffix = await (0, _setup_1.generateRandomStringAsync)(6);
171
+ const endpointUrlKey = `endpoint_url_${secretSuffix}`;
172
+ const usernameKey = `username_${secretSuffix}`;
173
+ const passwordKey = `password_${secretSuffix}`;
166
174
  await _8_7_1.test.step('Navigate to Cluster Secrets', async () => {
167
175
  await homePage.clickClusters();
168
176
  await clusterPage.clickClusterLink(defaultClusterName);
169
177
  await clusterPage.clickConnectorSecretesTab(defaultClusterName);
170
178
  });
171
179
  await _8_7_1.test.step('Delete Connector Secrets If Exist', async () => {
172
- await clusterSecretsPage.deleteConnectorSecretsIfExist();
180
+ await clusterSecretsPage.deleteConnectorSecretsIfExist([
181
+ endpointUrlKey,
182
+ usernameKey,
183
+ passwordKey,
184
+ ]);
173
185
  });
174
186
  await _8_7_1.test.step('Create Cluster Secret', async () => {
175
- await clusterSecretsPage.createNewSecret(defaultClusterName, 'endpoint_url', constants_1.REST_MOCK_BASIC_AUTH_URL);
176
- await clusterSecretsPage.createNewSecret(defaultClusterName, 'username', 'username');
177
- await clusterSecretsPage.createNewSecret(defaultClusterName, 'password', 'password');
187
+ await clusterSecretsPage.createNewSecret(defaultClusterName, endpointUrlKey, constants_1.REST_MOCK_BASIC_AUTH_URL);
188
+ await clusterSecretsPage.createNewSecret(defaultClusterName, usernameKey, 'username');
189
+ await clusterSecretsPage.createNewSecret(defaultClusterName, passwordKey, 'password');
178
190
  });
179
191
  await _8_7_1.test.step('Navigate to Web Modeler', async () => {
180
192
  await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
181
193
  timeout: 20000,
182
194
  });
183
195
  await appsPage.clickCamundaApps();
184
- // No separate modelerLink check: Hub has no app-switcher "Modeler" link
185
- // (clickModeler() goes through its home link instead), and on the
186
- // legacy shell clickModeler() asserts modelerLink itself.
196
+ // No separate modelerLink check: on Hub there is no app-switcher
197
+ // "Modeler" link, and clickModeler() handles both shells.
187
198
  await appsPage.clickModeler();
188
199
  await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
189
200
  timeout: 120000,
@@ -195,7 +206,10 @@ _8_7_1.test.describe('Connectors User Flow Tests', () => {
195
206
  await modelerHomePage.clickBpmnTemplateOption();
196
207
  });
197
208
  await _8_7_1.test.step('Create BPMN Diagram with REST Connector using secrets and Start Process Instance', async () => {
198
- await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, 'secrets.endpoint_url', 'basic', '{message:response.body.message}', 'result', { username: 'secrets.username', password: 'secrets.password' });
209
+ await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, `secrets.${endpointUrlKey}`, 'basic', '{message:response.body.message}', 'result', {
210
+ username: `secrets.${usernameKey}`,
211
+ password: `secrets.${passwordKey}`,
212
+ });
199
213
  await modelerCreatePage.runProcessInstance(defaultClusterName);
200
214
  await (0, _setup_1.performBasicAuthPostRequest)(constants_1.REST_MOCK_BASIC_AUTH_URL, 'username', 'password');
201
215
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1440",
3
+ "version": "0.0.1442",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",