@camunda/e2e-test-suite 0.0.862 → 0.0.864
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.
|
@@ -34,7 +34,7 @@ declare class NavigationPage {
|
|
|
34
34
|
goToOCAdmin(sleepTimeout?: number, credentials?: {
|
|
35
35
|
username: string;
|
|
36
36
|
password: string;
|
|
37
|
-
}): Promise<void>;
|
|
37
|
+
}, maxRetries?: number): Promise<void>;
|
|
38
38
|
goToClusters(sleepTimeout?: number, credentials?: {
|
|
39
39
|
username: string;
|
|
40
40
|
password: string;
|
|
@@ -175,8 +175,8 @@ class NavigationPage {
|
|
|
175
175
|
? lastError
|
|
176
176
|
: new Error(`goToKeycloak failed after ${maxRetries} attempts`);
|
|
177
177
|
}
|
|
178
|
-
async goToOCAdmin(sleepTimeout, credentials) {
|
|
179
|
-
await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/admin`, this.identityPageBanner, sleepTimeout, credentials, env_1.isOpenSearch ? 10 : 5);
|
|
178
|
+
async goToOCAdmin(sleepTimeout, credentials, maxRetries) {
|
|
179
|
+
await this.goTo(`${NORMALIZED_ORCHESTRATION_CONTEXT_PATH}/admin`, this.identityPageBanner, sleepTimeout, credentials, maxRetries ?? (env_1.isOpenSearch ? 10 : 5));
|
|
180
180
|
}
|
|
181
181
|
async goToClusters(sleepTimeout, credentials) {
|
|
182
182
|
await this.goTo(`${MODELER_CONTEXT_PATH}/clusters`, this.modelerPageBanner, sleepTimeout, credentials);
|
|
@@ -128,8 +128,16 @@ if (process.env.IS_DS === 'true') {
|
|
|
128
128
|
// application can take time to propagate; until it does, /admin
|
|
129
129
|
// redirects to /admin/forbidden and the navigation tabs never render.
|
|
130
130
|
// Re-navigate until the Roles tab is available to ride out that delay.
|
|
131
|
+
//
|
|
132
|
+
// goToOCAdmin's default retry count (5, with growing backoff) assumes
|
|
133
|
+
// a genuine login failure and can alone burn several minutes retrying
|
|
134
|
+
// one navigation attempt -- against a forbidden-redirect that clears
|
|
135
|
+
// the banner too, that single call can exhaust the whole outer
|
|
136
|
+
// toPass budget, leaving it effectively one shot instead of a real
|
|
137
|
+
// retry-until-propagated loop. Pass maxRetries=1 so each outer
|
|
138
|
+
// iteration fails fast and toPass gets many chances over 300s.
|
|
131
139
|
await (0, test_1.expect)(async () => {
|
|
132
|
-
await navigationPage.goToOCAdmin();
|
|
140
|
+
await navigationPage.goToOCAdmin(undefined, undefined, 1);
|
|
133
141
|
await (0, test_1.expect)(ocIdentityHomePage.rolesTab).toBeVisible({
|
|
134
142
|
timeout: 15000,
|
|
135
143
|
});
|
|
@@ -5,6 +5,7 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const sleep_1 = require("./sleep");
|
|
6
6
|
const PLAN_TYPE_ID = '574cde5f-54ae-43c1-851a-e6fb4f03423a';
|
|
7
7
|
const CHANNEL_ID = 'cf355219-cc2d-4266-83e1-2430998ddf30';
|
|
8
|
+
const STABLE_CHANNEL_NAME = 'Stable';
|
|
8
9
|
const GCP_REGION_ID = 'a6fadf1d-b2a0-44ab-b301-2f9aaf6681dc';
|
|
9
10
|
const AWS_REGION_ID = '30fcdd4f-05d7-1c7a-54df-8d9defbd250f';
|
|
10
11
|
const GENERATION_MAP = {
|
|
@@ -83,7 +84,7 @@ async function deleteClusterViaApi(page, clusterName) {
|
|
|
83
84
|
await (0, sleep_1.sleep)(5000);
|
|
84
85
|
}
|
|
85
86
|
exports.deleteClusterViaApi = deleteClusterViaApi;
|
|
86
|
-
|
|
87
|
+
function resolveMinorVersion() {
|
|
87
88
|
// MINOR_VERSION is always a plain "8.x" string in every trigger flow
|
|
88
89
|
// (run-tests-saas extracts it from the human-readable version input;
|
|
89
90
|
// run-tests-smoke-int sets it straight from c8Version). CLUSTER_VERSION is
|
|
@@ -91,34 +92,154 @@ async function resolveGenerationId() {
|
|
|
91
92
|
// flow (playwright_saas_pr_trigger_monorepo.yml -> run-tests-smoke-int)
|
|
92
93
|
// deliberately puts a random per-PR cluster *generation name* there
|
|
93
94
|
// (openssl rand -hex 6), a pre-existing convention unrelated to this
|
|
94
|
-
//
|
|
95
|
-
// MINOR_VERSION.
|
|
95
|
+
// lookup. Fall back to it only for callers that don't set MINOR_VERSION.
|
|
96
96
|
const rawVersion = process.env.MINOR_VERSION || process.env.CLUSTER_VERSION || '';
|
|
97
|
-
|
|
97
|
+
return rawVersion.match(/^(\d+\.\d+)/)?.[1] ?? rawVersion;
|
|
98
|
+
}
|
|
99
|
+
function resolveGenerationId() {
|
|
100
|
+
const minorVersion = resolveMinorVersion();
|
|
98
101
|
const generationId = GENERATION_MAP[minorVersion];
|
|
99
102
|
if (generationId)
|
|
100
103
|
return generationId;
|
|
101
|
-
throw new Error(`No generation UUID found for MINOR_VERSION/CLUSTER_VERSION
|
|
104
|
+
throw new Error(`No generation UUID found for MINOR_VERSION/CLUSTER_VERSION=` +
|
|
105
|
+
`"${process.env.MINOR_VERSION || process.env.CLUSTER_VERSION || ''}" ` +
|
|
102
106
|
`(resolved minor: "${minorVersion}"). ` +
|
|
103
107
|
`Known versions: ${Object.keys(GENERATION_MAP).join(', ')}`);
|
|
104
108
|
}
|
|
109
|
+
// Query the same cluster-parameters endpoint the Console create-cluster dialog
|
|
110
|
+
// uses. Its channels expose their selectable generations under
|
|
111
|
+
// `allowedGenerations`.
|
|
112
|
+
async function fetchClusterChannels(page) {
|
|
113
|
+
const token = await getUserToken(page);
|
|
114
|
+
const orgId = getOrgId();
|
|
115
|
+
const response = await page.request.get(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/parameters`, { headers: { Authorization: `Bearer ${token}` } });
|
|
116
|
+
if (response.status() !== 200) {
|
|
117
|
+
throw new Error(`Failed to fetch cluster parameters: ` +
|
|
118
|
+
`HTTP ${response.status()} - ${await response.text()}`);
|
|
119
|
+
}
|
|
120
|
+
const params = (await response.json());
|
|
121
|
+
return params.channels ?? [];
|
|
122
|
+
}
|
|
123
|
+
// Pick the latest released generation for a minor version from the Stable
|
|
124
|
+
// channel. Migration source clusters must start on a real released generation
|
|
125
|
+
// (e.g. "Camunda 8.7+gen33"), not the "8.x Cross Component Test Suite" snapshot
|
|
126
|
+
// in GENERATION_MAP -- upgrading a snapshot is not a representative path.
|
|
127
|
+
function pickLatestStableTarget(channels, minorVersion) {
|
|
128
|
+
const stable = channels.find((channel) => channel.name === STABLE_CHANNEL_NAME);
|
|
129
|
+
if (!stable) {
|
|
130
|
+
throw new Error(`No "${STABLE_CHANNEL_NAME}" channel found in cluster parameters`);
|
|
131
|
+
}
|
|
132
|
+
const escaped = minorVersion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
+
// Released generations are named "Camunda <minor>+gen<N>"; pick the highest N.
|
|
134
|
+
const genPattern = new RegExp(`^Camunda ${escaped}\\+gen(\\d+)`);
|
|
135
|
+
let best;
|
|
136
|
+
for (const generation of stable.allowedGenerations ?? []) {
|
|
137
|
+
const match = generation.name.match(genPattern);
|
|
138
|
+
if (!match)
|
|
139
|
+
continue;
|
|
140
|
+
const gen = Number(match[1]);
|
|
141
|
+
if (!best || gen > best.gen)
|
|
142
|
+
best = { uuid: generation.uuid, gen };
|
|
143
|
+
}
|
|
144
|
+
if (!best) {
|
|
145
|
+
throw new Error(`No stable "Camunda ${minorVersion}+gen<N>" generation found in the ` +
|
|
146
|
+
`"${STABLE_CHANNEL_NAME}" channel`);
|
|
147
|
+
}
|
|
148
|
+
console.log(`Resolved latest stable generation for Camunda ${minorVersion}: ` +
|
|
149
|
+
`gen${best.gen} (${best.uuid})`);
|
|
150
|
+
return { channelId: stable.uuid, generationId: best.uuid };
|
|
151
|
+
}
|
|
152
|
+
// Resolve an explicitly requested generation (by display name) to its channel
|
|
153
|
+
// and UUID, so a provided Source Generation input is honored as-is.
|
|
154
|
+
function pickGenerationByName(channels, generationName) {
|
|
155
|
+
for (const channel of channels) {
|
|
156
|
+
const match = (channel.allowedGenerations ?? []).find((generation) => generation.name === generationName);
|
|
157
|
+
if (match) {
|
|
158
|
+
console.log(`Resolved source generation "${generationName}" from channel ` +
|
|
159
|
+
`"${channel.name}" (${match.uuid})`);
|
|
160
|
+
return { channelId: channel.uuid, generationId: match.uuid };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`Source generation "${generationName}" not found in any channel's ` +
|
|
164
|
+
`allowed generations`);
|
|
165
|
+
}
|
|
166
|
+
// Migration source cluster target: honor an explicit Source Generation (passed
|
|
167
|
+
// via CLUSTER_VERSION) when set; otherwise auto-select the latest stable
|
|
168
|
+
// generation for the source minor version.
|
|
169
|
+
async function resolveMigrationTarget(page) {
|
|
170
|
+
const channels = await fetchClusterChannels(page);
|
|
171
|
+
const sourceGeneration = (process.env.CLUSTER_VERSION ?? '').trim();
|
|
172
|
+
if (sourceGeneration) {
|
|
173
|
+
return pickGenerationByName(channels, sourceGeneration);
|
|
174
|
+
}
|
|
175
|
+
return pickLatestStableTarget(channels, resolveMinorVersion());
|
|
176
|
+
}
|
|
177
|
+
// Snapshot generations mapped in GENERATION_MAP are named
|
|
178
|
+
// "<minor> Cross Component Test Suite" (see .github/c8_versions.json). The
|
|
179
|
+
// regular SaaS nightly passes that name through CLUSTER_VERSION and must keep
|
|
180
|
+
// resolving via the fixed GENERATION_MAP UUID, so it is excluded below.
|
|
181
|
+
const SNAPSHOT_GENERATION_SUFFIX = 'Cross Component Test Suite';
|
|
182
|
+
// The on-demand SaaS manual workflow lets the operator pick an exact cluster
|
|
183
|
+
// generation -- any Console generation name ("Camunda 8.10+gen21", a Release
|
|
184
|
+
// Test name, "my-new-generation", ...) -- and passes it through CLUSTER_VERSION.
|
|
185
|
+
// Treat any such explicit name as a request to resolve that generation live
|
|
186
|
+
// across all channels (pickGenerationByName), instead of the version-keyed
|
|
187
|
+
// GENERATION_MAP snapshot. Two values are NOT explicit generation names and
|
|
188
|
+
// stay on the snapshot lookup: a bare "8.x" minor, and the nightly snapshot
|
|
189
|
+
// name. The monorepo PR-trigger flow sets CLUSTER_GENERATION_ID, which wins
|
|
190
|
+
// earlier, so its random per-PR CLUSTER_VERSION never reaches here.
|
|
191
|
+
function requestedGenerationName() {
|
|
192
|
+
const clusterVersion = (process.env.CLUSTER_VERSION ?? '').trim();
|
|
193
|
+
if (!clusterVersion)
|
|
194
|
+
return undefined;
|
|
195
|
+
if (/^\d+\.\d+$/.test(clusterVersion))
|
|
196
|
+
return undefined;
|
|
197
|
+
if (clusterVersion.endsWith(SNAPSHOT_GENERATION_SUFFIX))
|
|
198
|
+
return undefined;
|
|
199
|
+
return clusterVersion;
|
|
200
|
+
}
|
|
105
201
|
async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
106
202
|
const token = await getUserToken(page);
|
|
107
203
|
const orgId = getOrgId();
|
|
108
204
|
// Some trigger flows (e.g. playwright_saas_pr_trigger_monorepo.yml) create a
|
|
109
205
|
// throwaway cluster generation per run, built from that specific commit's
|
|
110
206
|
// component images, and pass its real UUID through as CLUSTER_GENERATION_ID.
|
|
111
|
-
// That must win over
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
207
|
+
// That must win over any other lookup -- falling back to a fixed generation
|
|
208
|
+
// would silently test the wrong (stale) images instead of failing loudly.
|
|
209
|
+
//
|
|
210
|
+
// Migration flows (IS_MIGRATION) create the SOURCE cluster and must start it
|
|
211
|
+
// on the latest released generation from the Stable channel, resolved live,
|
|
212
|
+
// rather than the "8.x Cross Component Test Suite" snapshot in GENERATION_MAP.
|
|
213
|
+
//
|
|
214
|
+
// On-demand runs (playwright_saas_manual.yml) pass an explicit generation
|
|
215
|
+
// name via CLUSTER_VERSION; resolve it live across all channels so the
|
|
216
|
+
// operator's chosen generation is used instead of the version-keyed snapshot.
|
|
217
|
+
let channelId = CHANNEL_ID;
|
|
218
|
+
let generationId;
|
|
219
|
+
const requestedGeneration = requestedGenerationName();
|
|
220
|
+
if (process.env.CLUSTER_GENERATION_ID) {
|
|
221
|
+
generationId = process.env.CLUSTER_GENERATION_ID;
|
|
222
|
+
}
|
|
223
|
+
else if (process.env.IS_MIGRATION === 'true') {
|
|
224
|
+
const migrationTarget = await resolveMigrationTarget(page);
|
|
225
|
+
channelId = migrationTarget.channelId;
|
|
226
|
+
generationId = migrationTarget.generationId;
|
|
227
|
+
}
|
|
228
|
+
else if (requestedGeneration) {
|
|
229
|
+
const target = pickGenerationByName(await fetchClusterChannels(page), requestedGeneration);
|
|
230
|
+
channelId = target.channelId;
|
|
231
|
+
generationId = target.generationId;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
generationId = resolveGenerationId();
|
|
235
|
+
}
|
|
115
236
|
const k8sContextId = region.toUpperCase() === 'AWS' ? AWS_REGION_ID : GCP_REGION_ID;
|
|
116
237
|
const createResponse = await page.request.post(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`, {
|
|
117
238
|
headers: { Authorization: `Bearer ${token}` },
|
|
118
239
|
data: {
|
|
119
240
|
name: clusterName,
|
|
120
241
|
planTypeId: PLAN_TYPE_ID,
|
|
121
|
-
channelId
|
|
242
|
+
channelId,
|
|
122
243
|
generationId,
|
|
123
244
|
k8sContextId,
|
|
124
245
|
autoUpdate: true,
|