@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.
@@ -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
- await this.clickCreateClientButton();
489
- await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible({
490
- timeout: 30000,
491
- });
492
- await this.fillAPIClientName(name);
493
- await this.checkOrchestrationClusterCheckbox();
494
- await this.checkOptimizeCheckbox();
495
- await this.checkSecretsCheckbox();
496
- await this.clickCreateButton();
497
- // The credentials dialog only renders once POST .../clients resolves, and
498
- // that call can take well over 20s on a freshly created cluster.
499
- await (0, test_1.expect)(this.clientCredentialsDialog).toBeVisible({
500
- timeout: 60000,
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
- await this.clickCreateNewSecretButton(cluster);
157
- await this.clickKeyInput();
158
- await this.fillKeyInput(key);
159
- await (0, sleep_1.sleep)(1000);
160
- await this.clickValueInput();
161
- await this.fillValueInput(value);
162
- await (0, sleep_1.sleep)(1000);
163
- await this.clickCreateButton();
164
- await (0, test_1.expect)(this.page.getByText('Creating...')).not.toBeVisible({
165
- timeout: 60000,
166
- });
167
- await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({
168
- timeout: 60000,
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
- // Public start forms are a v1-only feature; the shared "Test Cluster"
478
- // used by the rest of this file is deliberately kept on v1 (via
479
- // createClusterViaApi's tasklistV2Enabled: false) so job-worker-based
480
- // user tasks keep working there. This assertion needs the opposite --
481
- // a genuinely v2-upgraded backend -- so it gets its own dedicated
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
- async function createClusterViaApi(page, clusterName, region = 'GCP') {
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
- // 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,
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 orgId=${orgId} name="${clusterName}"`);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.916",
3
+ "version": "0.0.918",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",