@camunda/e2e-test-suite 0.0.801 → 0.0.803
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(
|
|
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');
|
|
@@ -793,12 +796,35 @@ class ModelerCreatePage {
|
|
|
793
796
|
await this.secondElement.click({ timeout: 60000 });
|
|
794
797
|
await this.clickChangeTypeButton();
|
|
795
798
|
await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
|
|
796
|
-
// Filter the virtualized change-element popup so the option scrolls
|
|
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
|
+
];
|
|
797
808
|
const changeElementSearch = this.page.locator('.djs-popup-search input');
|
|
798
|
-
|
|
799
|
-
.
|
|
800
|
-
.
|
|
801
|
-
|
|
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
|
+
}
|
|
802
828
|
}
|
|
803
829
|
await this.publicHolidayConnectorOption.waitFor({
|
|
804
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(
|
|
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"]');
|
|
@@ -814,12 +817,36 @@ class ModelerCreatePage {
|
|
|
814
817
|
await this.secondElement.click({ timeout: 60000 });
|
|
815
818
|
await this.clickChangeTypeButton();
|
|
816
819
|
await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
|
|
817
|
-
// Filter the virtualized change-element popup so the option scrolls
|
|
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
|
+
];
|
|
818
829
|
const changeElementSearch = this.page.locator('.djs-popup-search input');
|
|
819
|
-
|
|
820
|
-
.
|
|
821
|
-
.
|
|
822
|
-
|
|
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
|
+
}
|
|
823
850
|
}
|
|
824
851
|
// Importing via "Save as copy" can leave both the pre-existing and the
|
|
825
852
|
// newly imported template in the change-element list, so target the
|
|
@@ -95,26 +95,27 @@ class ModelerHomePage {
|
|
|
95
95
|
await this.projectNameInput.fill(name);
|
|
96
96
|
await this.projectNameInput.press('Enter');
|
|
97
97
|
}
|
|
98
|
-
// Wait for the Modeler
|
|
99
|
-
//
|
|
100
|
-
//
|
|
98
|
+
// Wait for the Modeler project workspace to become interactive. The caller
|
|
99
|
+
// arrives here via goToModeler(), which has already loaded and authenticated
|
|
100
|
+
// the app shell (banner visible). The nav banner mounts before the project
|
|
101
|
+
// list, which renders after an API round-trip, so gate on an actionable
|
|
102
|
+
// element — the "Create new project" button or an existing project folder —
|
|
103
|
+
// rather than the banner, which otherwise lets the caller proceed against a
|
|
104
|
+
// half-loaded shell and time out clicking "Create new project".
|
|
105
|
+
//
|
|
106
|
+
// Deliberately does NOT reload: a raw page.reload() drops the authenticated
|
|
107
|
+
// SPA state (goToModeler handles the OAuth redirect flow; reload does not),
|
|
108
|
+
// which lands on a blank page and makes the workspace element unreachable for
|
|
109
|
+
// the rest of the timeout. Transient upstream 50x is already absorbed by
|
|
110
|
+
// goToModeler's gateway-error re-navigation before we get here.
|
|
101
111
|
async waitForModelerReady(timeoutMs = 120000) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// ignore reload errors and keep polling
|
|
114
|
-
}
|
|
115
|
-
await this.page.waitForTimeout(2000);
|
|
116
|
-
}
|
|
117
|
-
throw new Error(`Modeler app shell did not render within ${timeoutMs}ms (banner not found).`);
|
|
112
|
+
// .first(): when the project folder already exists on the shared cluster,
|
|
113
|
+
// both the create-project button and the folder are present, so the .or()
|
|
114
|
+
// resolves to multiple elements — waitFor() requires a single match.
|
|
115
|
+
await this.createNewProjectButton
|
|
116
|
+
.or(this.crossComponentProjectFolder)
|
|
117
|
+
.first()
|
|
118
|
+
.waitFor({ state: 'visible', timeout: timeoutMs });
|
|
118
119
|
}
|
|
119
120
|
async createCrossComponentProjectFolder() {
|
|
120
121
|
// After a fresh user login (e.g. lisa, bart) the Modeler pod may still be
|
package/dist/utils/apiHelpers.js
CHANGED
|
@@ -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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|