@camunda/e2e-test-suite 0.0.907 → 0.0.908
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.
|
@@ -33,12 +33,14 @@ declare class ClusterSecretsPage {
|
|
|
33
33
|
name: string;
|
|
34
34
|
value: string;
|
|
35
35
|
}[]): Promise<void>;
|
|
36
|
+
reopenConnectorSecretsTab(cluster: string): Promise<void>;
|
|
37
|
+
assertSecretsToolbarVisible(cluster: string): Promise<void>;
|
|
36
38
|
assertSecretsOrCreate(cluster: string, secrets: {
|
|
37
39
|
name: string;
|
|
38
40
|
value: string;
|
|
39
41
|
}[]): Promise<void>;
|
|
40
|
-
clickImportButton(): Promise<void>;
|
|
41
|
-
bulkImportSecrets(secrets: {
|
|
42
|
+
clickImportButton(cluster: string): Promise<void>;
|
|
43
|
+
bulkImportSecrets(cluster: string, secrets: {
|
|
42
44
|
name: string;
|
|
43
45
|
value: string;
|
|
44
46
|
}[]): Promise<void>;
|
|
@@ -169,9 +169,33 @@ class ClusterSecretsPage {
|
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
171
|
async createSetOfSecrets(cluster, secrets) {
|
|
172
|
-
await this.bulkImportSecrets(secrets);
|
|
172
|
+
await this.bulkImportSecrets(cluster, secrets);
|
|
173
173
|
await this.assertSecretsOrCreate(cluster, secrets);
|
|
174
174
|
}
|
|
175
|
+
// The Connector Secrets panel renders "Oops ... something went wrong" as soon
|
|
176
|
+
// as one of its Console API calls is rejected (the org-wide rate limit answers
|
|
177
|
+
// 429 under the nightly's worker count), and reloading from that state can
|
|
178
|
+
// land on the Console SPA's global "An error has occurred" page, which never
|
|
179
|
+
// renders the secrets toolbar again. Re-enter the tab from the Clusters list
|
|
180
|
+
// instead of reloading in place, so recovery does not depend on the broken
|
|
181
|
+
// page's own client-side state.
|
|
182
|
+
async reopenConnectorSecretsTab(cluster) {
|
|
183
|
+
await this.page.goto('/');
|
|
184
|
+
await this.clickClusterBanner();
|
|
185
|
+
await this.clickCluster(cluster);
|
|
186
|
+
await (0, test_1.expect)(this.clusterSecretTab).toBeVisible({ timeout: 30000 });
|
|
187
|
+
await this.clusterSecretTab.click({ timeout: 30000 });
|
|
188
|
+
}
|
|
189
|
+
async assertSecretsToolbarVisible(cluster) {
|
|
190
|
+
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.createNewSecretButton, {
|
|
191
|
+
visibilityTimeout: 60000,
|
|
192
|
+
totalTimeout: 300000,
|
|
193
|
+
maxRetries: 3,
|
|
194
|
+
postAction: async () => {
|
|
195
|
+
await this.reopenConnectorSecretsTab(cluster);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
175
199
|
async assertSecretsOrCreate(cluster, secrets) {
|
|
176
200
|
for (const secret of secrets) {
|
|
177
201
|
try {
|
|
@@ -189,19 +213,29 @@ class ClusterSecretsPage {
|
|
|
189
213
|
}
|
|
190
214
|
}
|
|
191
215
|
}
|
|
192
|
-
async clickImportButton() {
|
|
193
|
-
|
|
216
|
+
async clickImportButton(cluster) {
|
|
217
|
+
// Same broken-panel state as assertSecretsToolbarVisible guards against:
|
|
218
|
+
// when the secrets panel failed to load, Import is simply absent, and only
|
|
219
|
+
// re-entering the tab brings it back.
|
|
220
|
+
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.importButton, {
|
|
221
|
+
visibilityTimeout: 30000,
|
|
222
|
+
totalTimeout: 180000,
|
|
223
|
+
maxRetries: 3,
|
|
224
|
+
postAction: async () => {
|
|
225
|
+
await this.reopenConnectorSecretsTab(cluster);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
194
228
|
await this.importButton.click({ timeout: 30000 });
|
|
195
229
|
}
|
|
196
|
-
async bulkImportSecrets(secrets) {
|
|
230
|
+
async bulkImportSecrets(cluster, secrets) {
|
|
197
231
|
const mapped = secrets
|
|
198
232
|
.map((secret) => `${secret.name}=${secret.value}`)
|
|
199
233
|
.join('\n');
|
|
200
|
-
await this.clickImportButton();
|
|
234
|
+
await this.clickImportButton(cluster);
|
|
201
235
|
if (!(await this.dialog.isVisible())) {
|
|
202
236
|
await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.fromFileButton, {
|
|
203
237
|
postAction: async () => {
|
|
204
|
-
await this.clickImportButton();
|
|
238
|
+
await this.clickImportButton(cluster);
|
|
205
239
|
},
|
|
206
240
|
});
|
|
207
241
|
await this.fromFileButton.click();
|
|
@@ -229,9 +263,11 @@ class ClusterSecretsPage {
|
|
|
229
263
|
await this.page.reload();
|
|
230
264
|
await (0, sleep_1.sleep)(5000);
|
|
231
265
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
266
|
+
// A single reload is not enough when the panel broke because the Console API
|
|
267
|
+
// rate-limited it: the reload itself can land on the SPA's global error page
|
|
268
|
+
// (observed in run 30971320305 for both cluster setups), so keep re-entering
|
|
269
|
+
// the tab until the toolbar is genuinely rendered.
|
|
270
|
+
await this.assertSecretsToolbarVisible(cluster);
|
|
235
271
|
await this.page.reload();
|
|
236
272
|
await (0, sleep_1.sleep)(5000);
|
|
237
273
|
}
|
|
@@ -55,19 +55,54 @@ async function getUserToken(page) {
|
|
|
55
55
|
}
|
|
56
56
|
return token;
|
|
57
57
|
}
|
|
58
|
+
// Console's API sits behind an Envoy local rate limit, which rejects bursts
|
|
59
|
+
// with HTTP 429 "local_rate_limited". The nightly drives a single org from many
|
|
60
|
+
// workers (both Tasklist projects' setups plus one project-folder test per test
|
|
61
|
+
// user), so a cluster call can be rejected while nothing is actually wrong.
|
|
62
|
+
// Retry those — and gateway 5xx — with backoff instead of failing setup on the
|
|
63
|
+
// first rejection.
|
|
64
|
+
const TRANSIENT_API_STATUSES = [429, 500, 502, 503, 504];
|
|
65
|
+
const API_MAX_ATTEMPTS = 5;
|
|
66
|
+
async function clusterApiRequest(page, method, url, data) {
|
|
67
|
+
let response;
|
|
68
|
+
for (let attempt = 1; attempt <= API_MAX_ATTEMPTS; attempt++) {
|
|
69
|
+
const token = await getUserToken(page);
|
|
70
|
+
response = await page.request.fetch(url, {
|
|
71
|
+
method,
|
|
72
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
73
|
+
...(data === undefined ? {} : { data }),
|
|
74
|
+
});
|
|
75
|
+
if (!TRANSIENT_API_STATUSES.includes(response.status()))
|
|
76
|
+
return response;
|
|
77
|
+
console.log(`Console API ${method} ${url} returned HTTP ${response.status()} ` +
|
|
78
|
+
`(attempt ${attempt}/${API_MAX_ATTEMPTS}), retrying`);
|
|
79
|
+
if (attempt < API_MAX_ATTEMPTS)
|
|
80
|
+
await (0, sleep_1.sleep)(attempt * 10000);
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
58
84
|
async function findClusterByName(page, clusterName) {
|
|
59
|
-
const token = await getUserToken(page);
|
|
60
85
|
const orgId = getOrgId();
|
|
61
|
-
const response = await page
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
const response = await clusterApiRequest(page, 'GET', `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`);
|
|
87
|
+
// Never report "not found" for a lookup that did not actually complete.
|
|
88
|
+
// ensureClusterViaApi treats undefined as "no such cluster yet" and creates
|
|
89
|
+
// one, so swallowing a rate-limited/failed listing here makes it create a
|
|
90
|
+
// second cluster with the same name — orphaning the one the sibling project's
|
|
91
|
+
// setup is already using (and, when the create is rate-limited too, failing
|
|
92
|
+
// the whole setup with a misleading "Failed to create cluster" error). Only a
|
|
93
|
+
// successful, parseable listing can prove a cluster is absent.
|
|
94
|
+
if (response.status() !== 200) {
|
|
95
|
+
throw new Error(`Failed to list clusters of org ${orgId}: ` +
|
|
96
|
+
`HTTP ${response.status()} - ${await response.text()}`);
|
|
97
|
+
}
|
|
64
98
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
99
|
let data;
|
|
66
100
|
try {
|
|
67
101
|
data = await response.json();
|
|
68
102
|
}
|
|
69
103
|
catch {
|
|
70
|
-
|
|
104
|
+
throw new Error(`Cluster listing of org ${orgId} was not valid JSON: ` +
|
|
105
|
+
`${await response.text()}`);
|
|
71
106
|
}
|
|
72
107
|
const clusters = Array.isArray(data)
|
|
73
108
|
? data
|
|
@@ -199,7 +234,6 @@ function requestedGenerationName() {
|
|
|
199
234
|
return clusterVersion;
|
|
200
235
|
}
|
|
201
236
|
async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
202
|
-
const token = await getUserToken(page);
|
|
203
237
|
const orgId = getOrgId();
|
|
204
238
|
// Some trigger flows (e.g. playwright_saas_pr_trigger_monorepo.yml) create a
|
|
205
239
|
// throwaway cluster generation per run, built from that specific commit's
|
|
@@ -234,24 +268,21 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
|
234
268
|
generationId = resolveGenerationId();
|
|
235
269
|
}
|
|
236
270
|
const k8sContextId = region.toUpperCase() === 'AWS' ? AWS_REGION_ID : GCP_REGION_ID;
|
|
237
|
-
const createResponse = await page
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// v2-enabled can never be brought back to v1 for the rest of its life.
|
|
253
|
-
tasklistV2Enabled: false,
|
|
254
|
-
},
|
|
271
|
+
const createResponse = await clusterApiRequest(page, 'POST', `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`, {
|
|
272
|
+
name: clusterName,
|
|
273
|
+
planTypeId: PLAN_TYPE_ID,
|
|
274
|
+
channelId,
|
|
275
|
+
generationId,
|
|
276
|
+
k8sContextId,
|
|
277
|
+
autoUpdate: true,
|
|
278
|
+
stageLabel: 'dev',
|
|
279
|
+
// Upgrading a cluster to Tasklist v2 is a one-way, irreversible switch,
|
|
280
|
+
// and v2 cannot run job-worker-based user tasks, draft variables, or
|
|
281
|
+
// public start forms -- all of which the @tasklistV1-tagged suites
|
|
282
|
+
// exercise. Explicitly request v1 rather than relying on whatever the
|
|
283
|
+
// Console API defaults new clusters to, since a cluster that ends up
|
|
284
|
+
// v2-enabled can never be brought back to v1 for the rest of its life.
|
|
285
|
+
tasklistV2Enabled: false,
|
|
255
286
|
});
|
|
256
287
|
if (createResponse.status() !== 200 && createResponse.status() !== 201) {
|
|
257
288
|
throw new Error(`Failed to create cluster "${clusterName}": ` +
|