@camunda/e2e-test-suite 0.0.916 → 0.0.918
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.
- package/dist/pages/8.8/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterDetailsPage.js +52 -14
- package/dist/pages/8.8/ClusterSecretsPage.d.ts +2 -0
- package/dist/pages/8.8/ClusterSecretsPage.js +69 -14
- package/dist/tests/8.8/hto-user-flows.spec.js +5 -6
- package/dist/utils/consoleApiHelpers.d.ts +2 -2
- package/dist/utils/consoleApiHelpers.js +43 -11
- package/package.json +1 -1
|
@@ -79,6 +79,7 @@ declare class ClusterDetailsPage {
|
|
|
79
79
|
createAPIClientAndReturnVariables(name: string, setEnvVariables?: boolean): Promise<{
|
|
80
80
|
[key: string]: string;
|
|
81
81
|
}>;
|
|
82
|
+
private dismissCreateClientDialogIfPresent;
|
|
82
83
|
createAPIClient(name: string): Promise<void>;
|
|
83
84
|
clickEnvVarsButton(): Promise<void>;
|
|
84
85
|
clientCredentialsText(regex: RegExp): Promise<string>;
|
|
@@ -484,21 +484,59 @@ class ClusterDetailsPage {
|
|
|
484
484
|
}
|
|
485
485
|
return variables;
|
|
486
486
|
}
|
|
487
|
+
// A rejected POST .../clients leaves the create dialog open with an inline
|
|
488
|
+
// "Error fetch error" banner instead of closing it. Carbon's modal overlay
|
|
489
|
+
// then intercepts pointer events for the whole page, so nothing else can be
|
|
490
|
+
// clicked until the dialog is gone.
|
|
491
|
+
async dismissCreateClientDialogIfPresent() {
|
|
492
|
+
const isOpen = await this.createClientCredentialsDialog
|
|
493
|
+
.isVisible()
|
|
494
|
+
.catch(() => false);
|
|
495
|
+
if (!isOpen) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
await this.page.keyboard.press('Escape');
|
|
499
|
+
try {
|
|
500
|
+
await (0, test_1.expect)(this.createClientCredentialsDialog).not.toBeVisible({
|
|
501
|
+
timeout: 10000,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
await this.page.reload();
|
|
506
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
507
|
+
await this.clickAPITab();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
487
510
|
async createAPIClient(name) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
511
|
+
const maxAttempts = 3;
|
|
512
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
513
|
+
await this.clickCreateClientButton();
|
|
514
|
+
await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible({
|
|
515
|
+
timeout: 30000,
|
|
516
|
+
});
|
|
517
|
+
await this.fillAPIClientName(name);
|
|
518
|
+
await this.checkOrchestrationClusterCheckbox();
|
|
519
|
+
await this.checkOptimizeCheckbox();
|
|
520
|
+
await this.checkSecretsCheckbox();
|
|
521
|
+
await this.clickCreateButton();
|
|
522
|
+
// The credentials dialog only renders once POST .../clients resolves, and
|
|
523
|
+
// that call can take well over 20s on a freshly created cluster. When the
|
|
524
|
+
// Console API rejects the call instead, the credentials dialog never
|
|
525
|
+
// arrives and the create dialog stays open on its error banner, so close
|
|
526
|
+
// it and retry the create from a clean state rather than leaving the page
|
|
527
|
+
// wedged behind a modal nobody dismissed.
|
|
528
|
+
const created = await this.clientCredentialsDialog
|
|
529
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
530
|
+
.then(() => true)
|
|
531
|
+
.catch(() => false);
|
|
532
|
+
if (created) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
await this.dismissCreateClientDialogIfPresent();
|
|
536
|
+
if (attempt === maxAttempts) {
|
|
537
|
+
throw new Error(`Client credentials dialog never appeared for API client "${name}" after ${maxAttempts} attempts`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
502
540
|
await (0, test_1.expect)(this.clientCredentialsDialog.getByText('The Client Secret will not be shown again.')).toBeVisible({ timeout: 30000 });
|
|
503
541
|
// The clients table behind the dialog re-fetches after the create, and
|
|
504
542
|
// renders a skeleton (no `row` roles at all) until that GET resolves — so
|
|
@@ -21,6 +21,8 @@ declare class ClusterSecretsPage {
|
|
|
21
21
|
constructor(page: Page);
|
|
22
22
|
deleteConnectorSecretsIfExist(): Promise<void>;
|
|
23
23
|
clickCreateNewSecretButton(cluster: string): Promise<void>;
|
|
24
|
+
private dismissOpenDialogIfPresent;
|
|
25
|
+
private secretExists;
|
|
24
26
|
clickClusterBanner(): Promise<void>;
|
|
25
27
|
clickCluster(name: string): Promise<void>;
|
|
26
28
|
clickKeyInput(): Promise<void>;
|
|
@@ -107,7 +107,41 @@ class ClusterSecretsPage {
|
|
|
107
107
|
await this.createNewSecretButton.click({ timeout: 30000 });
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
// A rejected Console API call leaves the create/import modal open with an
|
|
111
|
+
// inline "Error fetch error" banner instead of closing it. Carbon's modal
|
|
112
|
+
// overlay then intercepts pointer events for the whole page, so nothing else
|
|
113
|
+
// can be clicked until the dialog is gone.
|
|
114
|
+
async dismissOpenDialogIfPresent() {
|
|
115
|
+
const isOpen = await this.dialog.isVisible().catch(() => false);
|
|
116
|
+
if (!isOpen) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
await this.page.keyboard.press('Escape');
|
|
120
|
+
try {
|
|
121
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 10000 });
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
await this.page.reload();
|
|
125
|
+
await (0, test_1.expect)(this.dialog).not.toBeVisible({ timeout: 30000 });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async secretExists(name) {
|
|
129
|
+
try {
|
|
130
|
+
await (0, test_1.expect)(this.secretsSearchBox).toBeVisible({ timeout: 30000 });
|
|
131
|
+
await this.secretsSearchBox.click();
|
|
132
|
+
await this.secretsSearchBox.fill(name);
|
|
133
|
+
await (0, test_1.expect)(this.page.getByRole('row', { name: new RegExp(`^${name}\\b`, 'i') })).toBeVisible({ timeout: 10000 });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
110
140
|
async clickClusterBanner() {
|
|
141
|
+
// This is the recovery path taken when a click times out, and a leftover
|
|
142
|
+
// modal overlay is exactly what makes those clicks time out - so the
|
|
143
|
+
// overlay has to be cleared here or recovery can never succeed either.
|
|
144
|
+
await this.dismissOpenDialogIfPresent();
|
|
111
145
|
await (0, test_1.expect)(this.clusterBanner).toBeVisible({ timeout: 30000 });
|
|
112
146
|
await this.clusterBanner.click({ timeout: 60000 });
|
|
113
147
|
}
|
|
@@ -153,20 +187,41 @@ class ClusterSecretsPage {
|
|
|
153
187
|
await this.createButton.click({ timeout: 30000 });
|
|
154
188
|
}
|
|
155
189
|
async createNewSecret(key, value, cluster) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
190
|
+
const maxAttempts = 3;
|
|
191
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
192
|
+
await this.clickCreateNewSecretButton(cluster);
|
|
193
|
+
await this.clickKeyInput();
|
|
194
|
+
await this.fillKeyInput(key);
|
|
195
|
+
await (0, sleep_1.sleep)(1000);
|
|
196
|
+
await this.clickValueInput();
|
|
197
|
+
await this.fillValueInput(value);
|
|
198
|
+
await (0, sleep_1.sleep)(1000);
|
|
199
|
+
await this.clickCreateButton();
|
|
200
|
+
await (0, test_1.expect)(this.page.getByText('Creating...')).not.toBeVisible({
|
|
201
|
+
timeout: 60000,
|
|
202
|
+
});
|
|
203
|
+
// The dialog closing is the only reliable signal that the create actually
|
|
204
|
+
// succeeded: when the Console API rejects the POST the dialog stays open
|
|
205
|
+
// on an inline "Error fetch error" banner, yet createNewSecretButton is
|
|
206
|
+
// still in the DOM behind the overlay so waiting on it passes regardless.
|
|
207
|
+
// Returning from there wedged every later interaction on the page, so
|
|
208
|
+
// dismiss the modal and retry the create instead.
|
|
209
|
+
const closed = await this.dialog
|
|
210
|
+
.waitFor({ state: 'hidden', timeout: 30000 })
|
|
211
|
+
.then(() => true)
|
|
212
|
+
.catch(() => false);
|
|
213
|
+
if (closed) {
|
|
214
|
+
await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({
|
|
215
|
+
timeout: 60000,
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
await this.dismissOpenDialogIfPresent();
|
|
220
|
+
if (await this.secretExists(key)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`Failed to create connector secret "${key}" after ${maxAttempts} attempts`);
|
|
170
225
|
}
|
|
171
226
|
async createSetOfSecrets(cluster, secrets) {
|
|
172
227
|
await this.bulkImportSecrets(cluster, secrets);
|
|
@@ -474,12 +474,11 @@ _8_8_1.test.describe('HTO User Flow Tests', () => {
|
|
|
474
474
|
const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
|
|
475
475
|
const processName = 'User_Task_Process_With_Public_Form' + randomString;
|
|
476
476
|
const formName = 'Public form' + randomString;
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
// cluster rather than sharing one that can never satisfy both.
|
|
477
|
+
// This assertion needs a genuinely v2-upgraded backend, and the switch to
|
|
478
|
+
// v2 is irreversible, so it keeps its own dedicated cluster instead of
|
|
479
|
+
// relying on the mode of the shared "Test Cluster" (which follows the
|
|
480
|
+
// Playwright project the setup ran under -- v1 in the chromium-v1 leg, so
|
|
481
|
+
// that job-worker-based user tasks keep working for the rest of this file).
|
|
483
482
|
const v2ClusterName = 'Public Form V2 Cluster';
|
|
484
483
|
await _8_8_1.test.step('Create Dedicated Tasklist V2 Cluster', async () => {
|
|
485
484
|
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, v2ClusterName);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Page } from '@playwright/test';
|
|
2
2
|
export declare function deleteClusterViaApi(page: Page, clusterName: string): Promise<void>;
|
|
3
|
-
export declare function createClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
|
|
4
|
-
export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string): Promise<string>;
|
|
3
|
+
export declare function createClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
|
|
4
|
+
export declare function ensureClusterViaApi(page: Page, clusterName: string, region?: string, tasklistV2Enabled?: boolean): Promise<string>;
|
|
5
5
|
export declare function ensureTasklistV2Enabled(page: Page, clusterUuid: string): Promise<void>;
|
|
6
6
|
export declare function waitForClusterHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
|
|
7
7
|
export declare function waitForClusterRestartHealthyViaApi(page: Page, clusterUuid: string, timeoutMs?: number): Promise<void>;
|
|
@@ -233,7 +233,33 @@ function requestedGenerationName() {
|
|
|
233
233
|
return undefined;
|
|
234
234
|
return clusterVersion;
|
|
235
235
|
}
|
|
236
|
-
|
|
236
|
+
// Which Tasklist mode a cluster must be created in is decided by the Playwright
|
|
237
|
+
// project the setup ran under, exactly as the UI path already does it in
|
|
238
|
+
// ClusterPage.determineTasklistAPI(): chromium-v1 selects "Tasklist API v1
|
|
239
|
+
// (legacy)", chromium-v2 leaves the v2 default. Mirror that here so the
|
|
240
|
+
// chromium-v2 leg does not end up driving a v1-mode backend.
|
|
241
|
+
//
|
|
242
|
+
// For generations below 8.10 Console turns this into
|
|
243
|
+
// CAMUNDA_TASKLIST_V2_MODE_ENABLED on Tasklist and the gateway, so 8.8/8.9 are
|
|
244
|
+
// the versions where it changes what is under test; 8.10+ is v2-only and
|
|
245
|
+
// ignores the field, and 8.7 predates the mode entirely (TASKLIST_VERSION is
|
|
246
|
+
// unset there, which resolves to v1 as before).
|
|
247
|
+
function resolveTasklistV2Enabled() {
|
|
248
|
+
let projectName = '';
|
|
249
|
+
try {
|
|
250
|
+
projectName = test_1.test.info().project.name;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Called outside a running test (no test.info() available); fall back to
|
|
254
|
+
// the env var the SaaS workflows export from matrix.tasklist_version.
|
|
255
|
+
}
|
|
256
|
+
if (projectName.includes('v1'))
|
|
257
|
+
return false;
|
|
258
|
+
if (projectName.includes('v2'))
|
|
259
|
+
return true;
|
|
260
|
+
return (process.env.TASKLIST_VERSION ?? '').trim().toLowerCase() === 'v2';
|
|
261
|
+
}
|
|
262
|
+
async function createClusterViaApi(page, clusterName, region = 'GCP', tasklistV2Enabled = resolveTasklistV2Enabled()) {
|
|
237
263
|
const orgId = getOrgId();
|
|
238
264
|
// Some trigger flows (e.g. playwright_saas_pr_trigger_monorepo.yml) create a
|
|
239
265
|
// throwaway cluster generation per run, built from that specific commit's
|
|
@@ -276,13 +302,14 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
|
276
302
|
k8sContextId,
|
|
277
303
|
autoUpdate: true,
|
|
278
304
|
stageLabel: 'dev',
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
|
|
305
|
+
// Always sent explicitly rather than omitted: Console defaults an unset
|
|
306
|
+
// field to v2-enabled for any generation >= 8.8, and the switch to v2 is
|
|
307
|
+
// one-way -- a cluster that ends up v2-enabled can never be brought back
|
|
308
|
+
// to v1 for the rest of its life. v2 cannot run job-worker-based user
|
|
309
|
+
// tasks, draft variables, or public start forms, so the v1 leg
|
|
310
|
+
// (@tasklistV1-tagged suites) depends on this being false, and the v2 leg
|
|
311
|
+
// equally depends on it being true.
|
|
312
|
+
tasklistV2Enabled,
|
|
286
313
|
});
|
|
287
314
|
if (createResponse.status() !== 200 && createResponse.status() !== 201) {
|
|
288
315
|
throw new Error(`Failed to create cluster "${clusterName}": ` +
|
|
@@ -300,11 +327,12 @@ async function createClusterViaApi(page, clusterName, region = 'GCP') {
|
|
|
300
327
|
if (!clusterId) {
|
|
301
328
|
throw new Error(`Cluster creation response missing UUID: ${JSON.stringify(result)}`);
|
|
302
329
|
}
|
|
303
|
-
console.log(`Created cluster via API: clusterId=${clusterId} namespace=${clusterId}-zeebe
|
|
330
|
+
console.log(`Created cluster via API: clusterId=${clusterId} namespace=${clusterId}-zeebe ` +
|
|
331
|
+
`orgId=${orgId} name="${clusterName}" tasklistMode=${tasklistV2Enabled ? 'v2' : 'v1'}`);
|
|
304
332
|
return clusterId;
|
|
305
333
|
}
|
|
306
334
|
exports.createClusterViaApi = createClusterViaApi;
|
|
307
|
-
async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
|
|
335
|
+
async function ensureClusterViaApi(page, clusterName, region = 'GCP', tasklistV2Enabled = resolveTasklistV2Enabled()) {
|
|
308
336
|
// Versions with more than one Tasklist-mode Playwright project (e.g. 8.8,
|
|
309
337
|
// 8.9's chromium-v1/chromium-v2) run test-setup.spec.ts once per project,
|
|
310
338
|
// concurrently, against the same org -- and both target a cluster with
|
|
@@ -319,11 +347,15 @@ async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
|
|
|
319
347
|
// setup already created it. Adopt it instead of destroying it -- whichever
|
|
320
348
|
// project's setup gets here first "wins" and creates it, the other one
|
|
321
349
|
// just reuses it.
|
|
350
|
+
//
|
|
351
|
+
// The nightly runs the two Tasklist-mode legs in separate orgs (c8_org_1 for
|
|
352
|
+
// v1, c8_org_2 for v2), so an adopted cluster always carries the mode this
|
|
353
|
+
// leg asked for.
|
|
322
354
|
const existing = await findClusterByName(page, clusterName);
|
|
323
355
|
if (existing) {
|
|
324
356
|
return existing.uuid;
|
|
325
357
|
}
|
|
326
|
-
return createClusterViaApi(page, clusterName, region);
|
|
358
|
+
return createClusterViaApi(page, clusterName, region, tasklistV2Enabled);
|
|
327
359
|
}
|
|
328
360
|
exports.ensureClusterViaApi = ensureClusterViaApi;
|
|
329
361
|
async function ensureTasklistV2Enabled(page, clusterUuid) {
|