@camunda/e2e-test-suite 0.0.800 → 0.0.802

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.
@@ -185,9 +185,12 @@ class ModelerCreatePage {
185
185
  this.secondPlacedElement = page.locator('g:nth-child(2) > .djs-element > .djs-hit');
186
186
  this.payloadInput = page.locator('[class="fjs-input"]');
187
187
  this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
188
+ // Match either marketplace display name — the connector was renamed from
189
+ // "Public Holiday Outbound" to "Worldwide Public Holiday"; accepting both
190
+ // keeps the flow green whichever the marketplace currently serves.
188
191
  this.publicHolidayConnectorOption = page
189
192
  .locator('[data-test="modeler"]')
190
- .getByText('Worldwide Public Holiday')
193
+ .getByText(/Worldwide Public Holiday|Public Holiday Outbound/)
191
194
  .first();
192
195
  this.publicHolidayYearOption = page.getByLabel('Year');
193
196
  this.publicHolidayCountryCodeOption = page.getByLabel('Countrycode');
@@ -457,8 +460,13 @@ class ModelerCreatePage {
457
460
  await this.userTaskOption.click();
458
461
  }
459
462
  async clickForm(name) {
463
+ // Baseline upgrade runs use fixed form names, so a Playwright retry re-creates
464
+ // a form with the same name and the picker then lists two identical entries.
465
+ // Scope to the first matching item so the link step stays deterministic on retry
466
+ // instead of failing with a strict-mode violation.
460
467
  await this.page
461
468
  .locator(`[data-test="item-${name}"]`)
469
+ .first()
462
470
  .getByText(name)
463
471
  .click({ timeout: 90000 });
464
472
  }
@@ -788,12 +796,35 @@ class ModelerCreatePage {
788
796
  await this.secondElement.click({ timeout: 60000 });
789
797
  await this.clickChangeTypeButton();
790
798
  await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
791
- // Filter the virtualized change-element popup so the option scrolls into view
799
+ // Filter the virtualized change-element popup so the option scrolls
800
+ // into view. Search by each full connector name (the marketplace
801
+ // renamed it "Public Holiday Outbound" -> "Worldwide Public Holiday"),
802
+ // not the shared "Public Holiday" substring — a generic term could
803
+ // surface a different Public Holiday connector we must not select.
804
+ const connectorNames = [
805
+ 'Worldwide Public Holiday',
806
+ 'Public Holiday Outbound',
807
+ ];
792
808
  const changeElementSearch = this.page.locator('.djs-popup-search input');
793
- if (await changeElementSearch
794
- .isVisible({ timeout: 5000 })
795
- .catch(() => false)) {
796
- await changeElementSearch.pressSequentially('Worldwide Public Holiday', { delay: 50 });
809
+ const searchVisible = await changeElementSearch
810
+ .waitFor({ state: 'visible', timeout: 5000 })
811
+ .then(() => true)
812
+ .catch(() => false);
813
+ if (searchVisible) {
814
+ for (const name of connectorNames) {
815
+ await changeElementSearch.fill('');
816
+ await changeElementSearch.pressSequentially(name, { delay: 50 });
817
+ // waitFor actually blocks up to the timeout; isVisible() would
818
+ // return immediately and miss the option still rendering after
819
+ // the search filter applies.
820
+ const optionVisible = await this.publicHolidayConnectorOption
821
+ .waitFor({ state: 'visible', timeout: 5000 })
822
+ .then(() => true)
823
+ .catch(() => false);
824
+ if (optionVisible) {
825
+ break;
826
+ }
827
+ }
797
828
  }
798
829
  await this.publicHolidayConnectorOption.waitFor({
799
830
  state: 'visible',
@@ -187,9 +187,12 @@ class ModelerCreatePage {
187
187
  this.secondPlacedElement = page.locator('g:nth-child(2) > .djs-element > .djs-hit');
188
188
  this.payloadInput = page.locator('[class="fjs-input"]');
189
189
  this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
190
+ // Match either marketplace display name — the connector was renamed from
191
+ // "Public Holiday Outbound" to "Worldwide Public Holiday"; accepting both
192
+ // keeps the flow green whichever the marketplace currently serves.
190
193
  this.publicHolidayConnectorOption = page
191
194
  .locator('[data-test="modeler"]')
192
- .getByText('Public Holiday Outbound');
195
+ .getByText(/Worldwide Public Holiday|Public Holiday Outbound/);
193
196
  this.publicHolidayYearOption = page.getByLabel('Year');
194
197
  this.publicHolidayCountryCodeOption = page.getByLabel('Countrycode');
195
198
  this.implementationSection = page.locator('[data-group-id="group-userTaskImplementation"]');
@@ -456,8 +459,13 @@ class ModelerCreatePage {
456
459
  await this.userTaskOption.click();
457
460
  }
458
461
  async clickForm(name) {
462
+ // Baseline upgrade runs use fixed form names, so a Playwright retry re-creates
463
+ // a form with the same name and the picker then lists two identical entries.
464
+ // Scope to the first matching item so the link step stays deterministic on retry
465
+ // instead of failing with a strict-mode violation.
459
466
  await this.page
460
467
  .locator(`[data-test="item-${name}"]`)
468
+ .first()
461
469
  .getByText(name)
462
470
  .click({ timeout: 90000 });
463
471
  }
@@ -809,12 +817,36 @@ class ModelerCreatePage {
809
817
  await this.secondElement.click({ timeout: 60000 });
810
818
  await this.clickChangeTypeButton();
811
819
  await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
812
- // Filter the virtualized change-element popup so the option scrolls into view
820
+ // Filter the virtualized change-element popup so the option scrolls
821
+ // into view. Search by each full connector name (the marketplace
822
+ // renamed it "Public Holiday Outbound" -> "Worldwide Public Holiday"),
823
+ // not the shared "Public Holiday" substring — a generic term could
824
+ // surface a different Public Holiday connector we must not select.
825
+ const connectorNames = [
826
+ 'Worldwide Public Holiday',
827
+ 'Public Holiday Outbound',
828
+ ];
813
829
  const changeElementSearch = this.page.locator('.djs-popup-search input');
814
- if (await changeElementSearch
815
- .isVisible({ timeout: 5000 })
816
- .catch(() => false)) {
817
- await changeElementSearch.pressSequentially('Public Holiday Outbound', { delay: 50 });
830
+ const searchVisible = await changeElementSearch
831
+ .waitFor({ state: 'visible', timeout: 5000 })
832
+ .then(() => true)
833
+ .catch(() => false);
834
+ if (searchVisible) {
835
+ for (const name of connectorNames) {
836
+ await changeElementSearch.fill('');
837
+ await changeElementSearch.pressSequentially(name, { delay: 50 });
838
+ // waitFor actually blocks up to the timeout; isVisible() would
839
+ // return immediately and miss the option still rendering after
840
+ // the search filter applies.
841
+ const optionVisible = await this.publicHolidayConnectorOption
842
+ .first()
843
+ .waitFor({ state: 'visible', timeout: 5000 })
844
+ .then(() => true)
845
+ .catch(() => false);
846
+ if (optionVisible) {
847
+ break;
848
+ }
849
+ }
818
850
  }
819
851
  // Importing via "Save as copy" can leave both the pre-existing and the
820
852
  // newly imported template in the change-element list, so target the
@@ -183,9 +183,13 @@ class ModelerCreatePage {
183
183
  this.secondPlacedElement = page.locator('g:nth-child(2) > .djs-element > .djs-hit');
184
184
  this.payloadInput = page.locator('[class="fjs-input"]');
185
185
  this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
186
+ // Match either marketplace display name — the connector was renamed from
187
+ // "Public Holiday Outbound" to "Worldwide Public Holiday"; accepting both
188
+ // keeps the flow green whichever the marketplace currently serves.
186
189
  this.publicHolidayConnectorOption = page
187
190
  .locator('[data-test="modeler"]')
188
- .getByText('Public Holiday Outbound');
191
+ .getByText(/Worldwide Public Holiday|Public Holiday Outbound/)
192
+ .first();
189
193
  this.publicHolidayYearOption = page.getByLabel('Year');
190
194
  this.publicHolidayCountryCodeOption = page.getByLabel('Countrycode');
191
195
  this.implementationSection = page.locator('[data-group-id="group-userTaskImplementation"]');
@@ -454,8 +458,13 @@ class ModelerCreatePage {
454
458
  await this.userTaskOption.click();
455
459
  }
456
460
  async clickForm(name) {
461
+ // Baseline upgrade runs use fixed form names, so a Playwright retry re-creates
462
+ // a form with the same name and the picker then lists two identical entries.
463
+ // Scope to the first matching item so the link step stays deterministic on retry
464
+ // instead of failing with a strict-mode violation.
457
465
  await this.page
458
466
  .locator(`[data-test="item-${name}"]`)
467
+ .first()
459
468
  .getByText(name)
460
469
  .click({ timeout: 90000 });
461
470
  }
@@ -781,12 +790,35 @@ class ModelerCreatePage {
781
790
  await this.secondElement.click({ timeout: 60000 });
782
791
  await this.clickChangeTypeButton();
783
792
  await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
784
- // Filter the virtualized change-element popup so the option scrolls into view
793
+ // Filter the virtualized change-element popup so the option scrolls
794
+ // into view. Search by each full connector name (the marketplace
795
+ // renamed it "Public Holiday Outbound" -> "Worldwide Public Holiday"),
796
+ // not the shared "Public Holiday" substring — a generic term could
797
+ // surface a different Public Holiday connector we must not select.
798
+ const connectorNames = [
799
+ 'Worldwide Public Holiday',
800
+ 'Public Holiday Outbound',
801
+ ];
785
802
  const changeElementSearch = this.page.locator('.djs-popup-search input');
786
- if (await changeElementSearch
787
- .isVisible({ timeout: 5000 })
788
- .catch(() => false)) {
789
- await changeElementSearch.pressSequentially('Public Holiday Outbound', { delay: 50 });
803
+ const searchVisible = await changeElementSearch
804
+ .waitFor({ state: 'visible', timeout: 5000 })
805
+ .then(() => true)
806
+ .catch(() => false);
807
+ if (searchVisible) {
808
+ for (const name of connectorNames) {
809
+ await changeElementSearch.fill('');
810
+ await changeElementSearch.pressSequentially(name, { delay: 50 });
811
+ // waitFor actually blocks up to the timeout; isVisible() would
812
+ // return immediately and miss the option still rendering after
813
+ // the search filter applies.
814
+ const optionVisible = await this.publicHolidayConnectorOption
815
+ .waitFor({ state: 'visible', timeout: 5000 })
816
+ .then(() => true)
817
+ .catch(() => false);
818
+ if (optionVisible) {
819
+ break;
820
+ }
821
+ }
790
822
  }
791
823
  await this.publicHolidayConnectorOption.waitFor({
792
824
  state: 'visible',
@@ -50,15 +50,15 @@ class OCIdentityClusterVariablesPage {
50
50
  await this.editMenuItem.click();
51
51
  await (0, test_1.expect)(this.variableValueField).toBeVisible();
52
52
  await (0, test_1.expect)(this.monacoEditor).toBeVisible();
53
+ // Monaco's hidden <textarea> only forwards keyboard events to the
54
+ // editor model — `fill()` writes the value but doesn't trigger
55
+ // Monaco's onDidChangeContent, so the modal's JSON validator never
56
+ // re-runs and Save stays disabled. Use real keyboard input instead.
53
57
  await this.monacoEditor.click();
54
- await this.monacoEditorTextArea.clear();
55
- await this.monacoEditorTextArea.fill(newValue);
56
- await this.monacoEditorTextArea.press('Tab');
57
- await test_1.expect
58
- .poll(async () => await this.saveVariableButton.isEnabled(), {
59
- timeout: 30000,
60
- })
61
- .toBe(true);
58
+ await this.page.keyboard.press('ControlOrMeta+A');
59
+ await this.page.keyboard.press('Delete');
60
+ await this.page.keyboard.type(newValue);
61
+ await (0, test_1.expect)(this.saveVariableButton).toBeEnabled();
62
62
  await this.saveVariableButton.click();
63
63
  await (0, test_1.expect)(this.successMessage).toBeVisible();
64
64
  await (0, test_1.expect)(this.editVariableModal).toBeHidden();
@@ -209,24 +209,38 @@ async function authSaasAPI(audience, clusterType) {
209
209
  const creds = getClusterCredentials(clusterType);
210
210
  const tokenAudience = audience ?? creds.tokenAudience;
211
211
  apiRequestContext = await getApiRequestContext();
212
- const tokenResponse = await apiRequestContext.post(creds.authUrl, {
213
- headers: {
214
- 'Content-Type': 'application/json',
215
- },
216
- data: {
217
- audience: tokenAudience,
218
- client_id: creds.clientId,
219
- client_secret: creds.clientSecret,
220
- grant_type: 'client_credentials',
221
- },
222
- });
223
- if (tokenResponse.status() !== 200) {
212
+ // The SaaS token endpoint sits behind Cloudflare, which returns a 1015
213
+ // "You are being rate limited" page when many specs fetch a token at once
214
+ // (each MCP spec authenticates in beforeAll). Retry with exponential
215
+ // backoff so the rate-limit window clears instead of failing the suite.
216
+ const maxAttempts = 5;
217
+ const baseDelayMs = 3000;
218
+ let lastErrorMessage = '';
219
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
220
+ const tokenResponse = await apiRequestContext.post(creds.authUrl, {
221
+ headers: {
222
+ 'Content-Type': 'application/json',
223
+ },
224
+ data: {
225
+ audience: tokenAudience,
226
+ client_id: creds.clientId,
227
+ client_secret: creds.clientSecret,
228
+ grant_type: 'client_credentials',
229
+ },
230
+ });
231
+ if (tokenResponse.status() === 200) {
232
+ const tokenJson = await tokenResponse.json();
233
+ return `Bearer ${tokenJson.access_token}`;
234
+ }
224
235
  const errorBody = await tokenResponse.text();
225
- throw new Error(`Failed to fetch access token. Response: ${errorBody}`);
236
+ lastErrorMessage = `HTTP ${tokenResponse.status()} - ${errorBody.slice(0, 200)}`;
237
+ if (attempt < maxAttempts) {
238
+ console.warn(`authSaasAPI attempt ${attempt}/${maxAttempts} failed to fetch access token (${lastErrorMessage}); retrying after backoff...`);
239
+ await new Promise((r) => setTimeout(r, baseDelayMs * 2 ** (attempt - 1)));
240
+ continue;
241
+ }
226
242
  }
227
- const tokenJson = await tokenResponse.json();
228
- const bearerToken = `Bearer ${tokenJson.access_token}`;
229
- return bearerToken;
243
+ throw new Error(`Failed to fetch access token. Response: ${lastErrorMessage}`);
230
244
  }
231
245
  exports.authSaasAPI = authSaasAPI;
232
246
  async function authSmAPI() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.800",
3
+ "version": "0.0.802",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",