@camunda/e2e-test-suite 0.0.1598 → 0.0.1600

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.
@@ -58,6 +58,7 @@ declare class ConsoleOrganizationPage {
58
58
  clickSettingsTab(): Promise<void>;
59
59
  clickOptInButton(): Promise<void>;
60
60
  enableAlphaFeature(name: string): Promise<void>;
61
+ closeTermsDialog(): Promise<void>;
61
62
  scrollToOptInCheckbox(): Promise<void>;
62
63
  checkOptInCheckbox(): Promise<void>;
63
64
  clickMainUser(): Promise<void>;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConsoleOrganizationPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
- const sleep_1 = require("../../utils/sleep");
6
5
  const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
7
6
  class ConsoleOrganizationPage {
8
7
  page;
@@ -86,7 +85,7 @@ class ConsoleOrganizationPage {
86
85
  this.startProcessIPermissionCheckbox = page.getByRole('checkbox', {
87
86
  name: 'Start process instance',
88
87
  });
89
- this.optInAICheckbox = page.getByLabel('I understand and agree to Terms for AI Usage');
88
+ this.optInAICheckbox = this.dialog.first().getByRole('checkbox');
90
89
  this.rows = this.page.getByRole('row');
91
90
  this.groupsTab = page.getByRole('tab', { name: 'Groups' });
92
91
  this.createGroupButton = page.getByRole('button', {
@@ -283,70 +282,85 @@ class ConsoleOrganizationPage {
283
282
  await this.optInButton.click({ timeout: 60000 });
284
283
  }
285
284
  async enableAlphaFeature(name) {
286
- const maxRetries = 3;
287
- for (let retries = 0; retries < maxRetries; retries++) {
288
- try {
289
- await this.clickSettingsTab();
290
- await this.optInToAlphaFeatures();
291
- await (0, sleep_1.sleep)(30000);
285
+ await this.clickSettingsTab();
286
+ await this.optInToAlphaFeatures();
287
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.rows.first(), {
288
+ visibilityTimeout: 60000,
289
+ totalTimeout: 300000,
290
+ maxRetries: 5,
291
+ postAction: async () => {
292
292
  await this.page.reload();
293
293
  await this.clickSettingsTab();
294
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.rows.first(), {
295
- visibilityTimeout: 15000,
296
- totalTimeout: 90000,
297
- postAction: async () => {
298
- await this.page.reload();
299
- },
300
- });
301
- const alphaFeature = this.rows.filter({ hasText: name }).first();
302
- if ((await alphaFeature.count()) < 1) {
303
- // Returning here used to report the feature as enabled while nothing
304
- // had been enabled at all, so the org silently stayed opted out and
305
- // every dependent spec failed later on a missing UI affordance.
306
- // Throw instead: the surrounding loop reloads and retries, and a
307
- // genuinely absent row fails this step where the cause is visible.
308
- throw new Error(`No alpha feature(${name}) is found.`);
309
- }
310
- //Opt-in The Alpha Feature
311
- const alphaFeatureOptInButton = alphaFeature.getByRole('button', {
312
- name: 'Opt in to enable',
313
- });
314
- if (await alphaFeatureOptInButton.isVisible({ timeout: 10000 })) {
315
- await alphaFeatureOptInButton.click({ timeout: 30000 });
316
- await this.optInAICheckbox.scrollIntoViewIfNeeded();
317
- await this.optInAICheckbox.check({ timeout: 30000 });
318
- await (0, sleep_1.sleep)(3000);
319
- }
320
- //Enable Alpha Feature
321
- const toggleText = await alphaFeature
322
- .locator('[class= "cds--toggle__text"]')
323
- .textContent();
324
- console.info(`Previous feature(${name}) setting is ${toggleText}.`);
325
- if (toggleText == 'Enabled') {
326
- console.log(`Feature ${name} is already enabled.`);
327
- return;
328
- }
329
- if (toggleText == 'Disabled') {
330
- await alphaFeature
331
- .locator('[class= "cds--toggle__switch"]')
332
- .first()
333
- .click();
334
- await (0, sleep_1.sleep)(10000);
335
- }
336
- await (0, test_1.expect)(alphaFeature.locator('[class= "cds--toggle__text"]')).toHaveText('Enabled');
337
- return;
294
+ },
295
+ });
296
+ const alphaFeature = this.rows.filter({ hasText: name }).first();
297
+ if ((await alphaFeature.count()) < 1) {
298
+ throw new Error(`No alpha feature(${name}) is found.`);
299
+ }
300
+ const alphaFeatureOptInButton = alphaFeature.getByRole('button', {
301
+ name: 'Opt in to enable',
302
+ });
303
+ if (await alphaFeatureOptInButton.isVisible({ timeout: 10000 })) {
304
+ if (await alphaFeatureOptInButton.isEnabled()) {
305
+ await alphaFeatureOptInButton.click({ timeout: 90000 });
306
+ await (0, test_1.expect)(this.dialog.first()).toBeVisible({ timeout: 60000 });
307
+ await this.scrollToOptInCheckbox();
308
+ await this.optInAICheckbox.check({ timeout: 90000 });
309
+ await this.closeTermsDialog();
338
310
  }
339
- catch (error) {
340
- console.error(`Click attempt ${retries + 1} failed: ${error}`);
311
+ }
312
+ const toggle = alphaFeature.getByRole('switch').first();
313
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, toggle, {
314
+ visibilityTimeout: 30000,
315
+ totalTimeout: 180000,
316
+ maxRetries: 5,
317
+ postAction: async () => {
341
318
  await this.page.reload();
342
- await (0, sleep_1.sleep)(10000);
343
- }
319
+ await this.clickSettingsTab();
320
+ },
321
+ });
322
+ if (await toggle.isChecked()) {
323
+ console.log(`Feature ${name} is already enabled.`);
324
+ return;
344
325
  }
345
- throw new Error(`Failed to eanble alpha feature ${name} after ${maxRetries} attempts.`);
326
+ await toggle.click();
327
+ await (0, test_1.expect)(toggle).toBeChecked({ timeout: 60000 });
328
+ }
329
+ async closeTermsDialog() {
330
+ const closeButton = this.dialog
331
+ .first()
332
+ .getByRole('button', { name: 'Close' });
333
+ if (await closeButton.isVisible({ timeout: 10000 }).catch(() => false)) {
334
+ await closeButton.click();
335
+ }
336
+ await (0, test_1.expect)(this.dialog.first()).toBeHidden({ timeout: 60000 });
346
337
  }
347
338
  async scrollToOptInCheckbox() {
348
- await this.page.locator('.ps-contract-scroll').evaluate((el) => {
349
- el.scrollTop = el.scrollHeight;
339
+ const dialog = this.dialog.first();
340
+ await (0, test_1.expect)(dialog).toBeVisible({ timeout: 30000 });
341
+ const contractScroll = this.page.locator('.ps-contract-scroll').first();
342
+ if (await contractScroll.isVisible({ timeout: 5000 }).catch(() => false)) {
343
+ await contractScroll.evaluate((el) => {
344
+ el.scrollTop = el.scrollHeight;
345
+ el.dispatchEvent(new Event('scroll', { bubbles: true }));
346
+ });
347
+ return;
348
+ }
349
+ const box = await dialog.boundingBox();
350
+ if (box) {
351
+ await this.page.mouse.move(box.x + box.width / 2, box.y + box.height * 0.6);
352
+ for (let i = 0; i < 20; i++) {
353
+ await this.page.mouse.wheel(0, 300);
354
+ }
355
+ }
356
+ await dialog.evaluate((dlg) => {
357
+ const all = [dlg, ...Array.from(dlg.querySelectorAll('*'))];
358
+ for (const node of all) {
359
+ if (!(node instanceof HTMLElement))
360
+ continue;
361
+ node.scrollTop = node.scrollHeight;
362
+ node.dispatchEvent(new Event('scroll', { bubbles: true }));
363
+ }
350
364
  });
351
365
  }
352
366
  async checkOptInCheckbox() {
@@ -378,15 +392,17 @@ class ConsoleOrganizationPage {
378
392
  await this.page.reload();
379
393
  },
380
394
  });
381
- try {
382
- await (0, test_1.expect)(this.optInButton).toBeVisible({ timeout: 30000 });
383
- await this.clickOptInButton();
384
- await this.scrollToOptInCheckbox();
385
- await this.checkOptInCheckbox();
386
- }
387
- catch {
388
- await (0, test_1.expect)(this.optInButton).not.toBeVisible();
395
+ if (!(await this.optInButton.isVisible({ timeout: 30000 }).catch(() => false))) {
396
+ return;
389
397
  }
398
+ await this.clickOptInButton();
399
+ await (0, test_1.expect)(this.dialog.first()).toBeVisible({ timeout: 60000 });
400
+ await this.scrollToOptInCheckbox();
401
+ await this.optInCheckbox.check({ timeout: 90000 });
402
+ await this.closeTermsDialog();
403
+ await this.page.reload();
404
+ await this.clickSettingsTab();
405
+ await (0, test_1.expect)(this.optInButton).not.toBeVisible({ timeout: 60000 });
390
406
  }
391
407
  async clickGroupsTab() {
392
408
  await this.groupsTab.click({ timeout: 60000 });
@@ -302,8 +302,17 @@ class ModelerCreatePage {
302
302
  this.processIdInput = page.getByLabel('ID', { exact: true });
303
303
  this.payloadNameInput = page.getByLabel('Full Name');
304
304
  this.payloadDateInput = page.getByLabel('Date of Birth');
305
- this.payloadCheckboxInput = this.form.getByLabel('Agree');
306
- this.payloadNumberInput = this.form.getByLabel('Count');
305
+ // Scoped to the deploy/run dialog and matched by role, not through the
306
+ // exact-class form container: the start form renders inside the dialog
307
+ // and `[class="fjs-container"]` only matches when that element carries
308
+ // no other class, so an extra class on the form root silently empties
309
+ // every locator built on it.
310
+ this.payloadCheckboxInput = this.dialog
311
+ .first()
312
+ .getByRole('checkbox', { name: 'Agree' });
313
+ this.payloadNumberInput = this.dialog
314
+ .first()
315
+ .getByRole('textbox', { name: 'Count' });
307
316
  this.formPropertySection = page.locator('[data-group-id="group-form"]');
308
317
  this.formLinkingTypeDropdown = page.getByLabel('Type');
309
318
  this.formLinkTypeOptions = page.locator('#bio-properties-panel-formType');
@@ -296,6 +296,14 @@ class ClusterPage {
296
296
  await this.selectSubRegion(region);
297
297
  await this.clickClusterOption(process.env.CLUSTER_VERSION);
298
298
  await this.determineTasklistAPI(tasklistV1Api);
299
+ // The form re-renders when its session/parameters refresh lands after the
300
+ // name was typed, which clears the name: Create then only shows "Please
301
+ // enter a name for the cluster" and no cluster is ever requested. Re-fill
302
+ // it right before submitting.
303
+ if ((await this.clusterNameInput.inputValue()) !== name) {
304
+ await this.fillClusterNameInput(name);
305
+ }
306
+ await (0, test_1.expect)(this.clusterNameInput).toHaveValue(name, { timeout: 10000 });
299
307
  await this.clickCreateClusterButton();
300
308
  await this.logClusterCreationInfo(name);
301
309
  await this.clickClustersBreadcrumb(name);
@@ -17,15 +17,25 @@ class ClientCredentialsDetailsPage {
17
17
  await (0, test_1.expect)(this.clientNameHeading(clientName).or(this.operateUrlRow).first()).toBeVisible({ timeout: 60000 });
18
18
  }
19
19
  async getOperateUrl() {
20
- if (await this.operateUrlValue.isVisible({ timeout: 10000 }).catch(() => false)) {
20
+ if (await this.operateUrlValue.isVisible().catch(() => false)) {
21
21
  return await this.operateUrlValue.inputValue();
22
22
  }
23
- const text = await this.page.innerText('body');
24
- const match = text.match(/Operate URL\s*([\w:/.-]+\/?)/i);
25
- if (!match || !match[1]) {
26
- throw new Error('Operate URL not found in client credentials page');
27
- }
28
- return match[1];
23
+ // locator.isVisible() ignores its timeout, so the old probe read the body
24
+ // the instant the client view opened and failed whenever the credentials
25
+ // rendered a moment later. Poll the rendered text until the URL appears.
26
+ const pattern = /Operate URL\s*([\w:/.-]+\/?)/i;
27
+ let operateUrl = '';
28
+ await test_1.expect
29
+ .poll(async () => {
30
+ const text = await this.page.innerText('body').catch(() => '');
31
+ operateUrl = text.match(pattern)?.[1] ?? '';
32
+ return operateUrl;
33
+ }, {
34
+ timeout: 30000,
35
+ message: 'Operate URL not found in client credentials page',
36
+ })
37
+ .not.toBe('');
38
+ return operateUrl;
29
39
  }
30
40
  async goBack() {
31
41
  await this.page.goBack();
@@ -39,6 +39,7 @@ declare class ClusterDetailsPage {
39
39
  readonly createAlertButton: Locator;
40
40
  readonly createNewClientButton: Locator;
41
41
  readonly clientsList: Locator;
42
+ readonly clientsFilterInput: Locator;
42
43
  readonly envVarsTab: Locator;
43
44
  readonly zeebeCheckbox: Locator;
44
45
  readonly apiClientNameTextbox: Locator;
@@ -51,6 +51,7 @@ class ClusterDetailsPage {
51
51
  createAlertButton;
52
52
  createNewClientButton;
53
53
  clientsList;
54
+ clientsFilterInput;
54
55
  envVarsTab;
55
56
  zeebeCheckbox;
56
57
  apiClientNameTextbox;
@@ -194,6 +195,13 @@ class ClusterDetailsPage {
194
195
  name: 'Create new client',
195
196
  });
196
197
  this.clientsList = page.getByRole('row').filter({ hasNotText: 'Scopes' });
198
+ // The clients table paginates and isn't ordered by creation time, so a
199
+ // newly created client can sit on a page other than the one displayed.
200
+ // Filter it first (same locator as pages/8.10/ClusterDetailsPage.ts).
201
+ this.clientsFilterInput = page
202
+ .getByRole('searchbox', { name: 'Filter table' })
203
+ .or(page.getByRole('searchbox', { name: 'Search' }))
204
+ .or(page.getByPlaceholder('Search'));
197
205
  this.envVarsTab = this.clientCredentialsDialog.getByRole('tab', {
198
206
  name: 'Env Vars',
199
207
  });
@@ -313,6 +321,8 @@ class ClusterDetailsPage {
313
321
  const maxAttempts = 3;
314
322
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
315
323
  try {
324
+ await (0, test_1.expect)(this.clientsFilterInput).toBeVisible({ timeout: 60000 });
325
+ await this.clientsFilterInput.fill(name);
316
326
  await (0, test_1.expect)(row).toContainText(scopes, { timeout: 60000 });
317
327
  return;
318
328
  }
@@ -565,6 +575,8 @@ class ClusterDetailsPage {
565
575
  }
566
576
  async searchAndClickClientCredentialsLink(name) {
567
577
  await (0, test_1.expect)(this.clientsList.first()).toBeVisible({ timeout: 60000 });
578
+ await (0, test_1.expect)(this.clientsFilterInput).toBeVisible({ timeout: 60000 });
579
+ await this.clientsFilterInput.fill(name);
568
580
  const clientLink = this.clientCredentialsLink(name);
569
581
  await (0, test_1.expect)(clientLink).toBeVisible({ timeout: 30000 });
570
582
  await clientLink.click();
@@ -823,13 +835,24 @@ class ClusterDetailsPage {
823
835
  return variables;
824
836
  }
825
837
  async createAPIClient(name) {
826
- const maxRetries = 3;
838
+ // Console API requests on SaaS INT fail in bursts (net::ERR_FAILED) that
839
+ // can last a few minutes: GETs recover via the app's own refetch, but
840
+ // the create POST is not retried and surfaces as "fetch error", and the
841
+ // reload between attempts lands the panel with "Create new client"
842
+ // disabled until the cluster queries succeed. Five attempts, each one
843
+ // waiting for the panel to recover first, ride out such a burst.
844
+ const maxRetries = 5;
827
845
  for (let attempt = 0; attempt < maxRetries; attempt++) {
828
846
  // Use a suffixed name on retries so duplicate-name validation never
829
847
  // leaves the Create button permanently disabled. A prior attempt may
830
848
  // have created the client before we could detect the credentials dialog.
831
849
  const attemptName = attempt === 0 ? name : `${name}-r${attempt}`;
832
850
  try {
851
+ // Wait for the clients panel to be usable before clicking: after a
852
+ // failed burst the button renders disabled for tens of seconds, and
853
+ // clicking it only burns clickLocatorWithRetry's attempts.
854
+ await this.dismissOopsIfPresent();
855
+ await (0, test_1.expect)(this.createFirstClientButton.or(this.createNewClientButton)).toBeEnabled({ timeout: 60000 });
833
856
  await this.clickCreateClientButton();
834
857
  await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible({
835
858
  timeout: 30000,
@@ -845,20 +868,22 @@ class ClusterDetailsPage {
845
868
  // Secret will not be shown again.") dialog never appears. Detect
846
869
  // that state and retry instead of burning 60s on a visibility
847
870
  // assertion that will never resolve.
871
+ // locator.isVisible() ignores its timeout and returns immediately,
872
+ // so wait for either outcome explicitly before branching on it.
873
+ const fetchErrorBanner = this.createClientCredentialsDialog.getByText(/fetch error/i);
874
+ await this.clientCredentialsDialog
875
+ .or(fetchErrorBanner)
876
+ .first()
877
+ .waitFor({ state: 'visible', timeout: 30000 })
878
+ .catch(() => false);
848
879
  const credentialsShown = await this.clientCredentialsDialog
849
- .isVisible({ timeout: 30000 })
880
+ .isVisible()
850
881
  .catch(() => false);
851
882
  if (!credentialsShown) {
852
- const stuckCreateDialog = await this.createClientCredentialsDialog
853
- .isVisible({ timeout: 500 })
883
+ const fetchError = await fetchErrorBanner
884
+ .isVisible()
854
885
  .catch(() => false);
855
- const fetchError = stuckCreateDialog
856
- ? await this.createClientCredentialsDialog
857
- .getByText(/fetch error/i)
858
- .isVisible({ timeout: 500 })
859
- .catch(() => false)
860
- : false;
861
- if (stuckCreateDialog && fetchError) {
886
+ if (fetchError) {
862
887
  throw new Error('createAPIClient: create dialog stuck with "fetch error"');
863
888
  }
864
889
  // Not the fetch-error path — fall through to the original
@@ -18,8 +18,7 @@ _8_11_1.test.describe('Cluster Setup Tests', () => {
18
18
  await (0, _setup_1.captureScreenshot)(page, testInfo);
19
19
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
20
20
  });
21
- // Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
22
- _8_11_1.test.skip('Enable Alpha Features', async ({ page, loginPage, consoleOrganizationsPage, homePage, }, testInfo) => {
21
+ (0, _8_11_1.test)('Enable Alpha Features', async ({ page, loginPage, consoleOrganizationsPage, homePage, }, testInfo) => {
23
22
  _8_11_1.test.skip(process.env.IS_PROD === 'true', 'Skipping test because not required on PROD test org');
24
23
  _8_11_1.test.skip(process.env.IS_AG === 'true', 'Skipping test because not required when IS_AG is true');
25
24
  await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
@@ -35,8 +35,7 @@ _8_11_1.test.describe('Web Modeler User Flow Tests', () => {
35
35
  await (0, mailSlurpClient_1.deleteInbox)(inboxes[testInfo.title]);
36
36
  }
37
37
  });
38
- // Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
39
- _8_11_1.test.skip('Form.js Integration with User Task and AI Generated Form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, consoleOrganizationsPage, loginPage, }, testInfo) => {
38
+ (0, _8_11_1.test)('Form.js Integration with User Task and AI Generated Form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, consoleOrganizationsPage, loginPage, }, testInfo) => {
40
39
  _8_11_1.test.slow();
41
40
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
42
41
  const processName = 'User_Task_Process_With_AI_Form' + randomString;
@@ -75,7 +74,7 @@ _8_11_1.test.describe('Web Modeler User Flow Tests', () => {
75
74
  // identity-provider page, and if so signs back in before trying once
76
75
  // more (run 35562939552, "Camunda Modeler" banner absent 90s after 3
77
76
  // such retries).
78
- await (0, UtilitiesPage_1.withSessionRecovery)(page, loginPage, appsPage, modelerHomePage.modelerPageBanner, testUser, (testInfo.workerIndex + 1) * 1000, () => appsPage.openModelerWithRetry(modelerHomePage.modelerPageBanner, 90000, 3, modelerHomePage.createNewProjectButton));
77
+ await (0, UtilitiesPage_1.withSessionRecovery)(page, loginPage, appsPage, modelerHomePage.modelerPageBanner, testUser, (testInfo.workerIndex + 1) * 1000, () => appsPage.openModelerWithRetry(modelerHomePage.modelerPageBanner, 90000, 3, modelerHomePage.createNewProjectButton.or(modelerHomePage.createWorkspaceButton)));
79
78
  });
80
79
  await _8_11_1.test.step('Open Cross Component Test Project', async () => {
81
80
  await modelerHomePage.clickCrossComponentProjectFolder();
@@ -141,8 +140,7 @@ _8_11_1.test.describe('Web Modeler User Flow Tests', () => {
141
140
  await operateTabTaskDetailsPage.clickCompleteTaskButton();
142
141
  });
143
142
  });
144
- // Skipped due to bug #29346: https://github.com/camunda/camunda-hub/issues/29346
145
- _8_11_1.test.skip('Form.js Integration with AI generated Start form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
143
+ (0, _8_11_1.test)('Form.js Integration with AI generated Start form @ai-external', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, formJsPage, }) => {
146
144
  _8_11_1.test.slow();
147
145
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
148
146
  const processName = 'AI-generated-Start-form' + randomString;
@@ -8,6 +8,7 @@ const randomName_1 = require("../../utils/randomName");
8
8
  const fileUpload_1 = require("../../utils/fileUpload");
9
9
  const connectorSecrets_1 = require("../../utils/connectorSecrets");
10
10
  const users_1 = require("../../utils/users");
11
+ const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
11
12
  const testUser = (0, users_1.getTestUser)('twentiethUser');
12
13
  let idpApplicationName;
13
14
  let idpDocumentExtractionTemplateName;
@@ -88,6 +89,13 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
88
89
  await modelerHomePage.clickCrossComponentProjectFolder();
89
90
  });
90
91
  await _8_8_1.test.step('Add an IDP application to the project', async () => {
92
+ // The "Create IDP project" dialog only offers the Environments
93
+ // assigned to the workspace. test-setup's link of the cluster is a
94
+ // no-op when the workspace does not exist yet, so on a fresh org the
95
+ // dialog showed "No deployment environments available" (or only
96
+ // another spec's cluster) on the first attempt. Ensure the link here,
97
+ // as every deploy path in the page objects already does.
98
+ await (0, consoleApiHelpers_1.ensureClusterLinkedToSharedWorkspace)(page, clusterName);
91
99
  // Workspace root only: an IDP application can never be created
92
100
  // inside a process application. The entry point there is the create
93
101
  // menu or the empty state, depending on whether the workspace
@@ -163,6 +171,13 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
163
171
  await modelerHomePage.clickCrossComponentProjectFolder();
164
172
  });
165
173
  await _8_8_1.test.step('Add an IDP application to the project', async () => {
174
+ // The "Create IDP project" dialog only offers the Environments
175
+ // assigned to the workspace. test-setup's link of the cluster is a
176
+ // no-op when the workspace does not exist yet, so on a fresh org the
177
+ // dialog showed "No deployment environments available" (or only
178
+ // another spec's cluster) on the first attempt. Ensure the link here,
179
+ // as every deploy path in the page objects already does.
180
+ await (0, consoleApiHelpers_1.ensureClusterLinkedToSharedWorkspace)(page, clusterName);
166
181
  // Workspace root only: an IDP application can never be created
167
182
  // inside a process application. The entry point there is the create
168
183
  // menu or the empty state, depending on whether the workspace
@@ -57,7 +57,7 @@ _8_9_1.test.describe('Operate access requires authentication @tasklistV2', () =>
57
57
  (0, test_1.expect)([200, 405]).toContain(response.status());
58
58
  });
59
59
  });
60
- (0, _8_9_1.test)('check that POST /v2/user-tasks/search returns 401 without credentials', async ({ homePage, clusterPage, clusterDetailsPage, clientCredentialsDetailsPage, request, }) => {
60
+ (0, _8_9_1.test)('check that POST /v2/user-tasks/search returns 401 without credentials', async ({ page, homePage, clusterPage, clusterDetailsPage, clientCredentialsDetailsPage, request, }) => {
61
61
  clientName = `tasklist-deny-${await (0, _setup_1.generateRandomStringAsync)(5)}`;
62
62
  let operateUrl = '';
63
63
  await _8_9_1.test.step('Add API Client to Cluster', async () => {
@@ -69,8 +69,19 @@ _8_9_1.test.describe('Operate access requires authentication @tasklistV2', () =>
69
69
  await (0, test_1.expect)(clusterDetailsPage.clientsList.filter({ hasText: clientName })).toBeVisible({ timeout: 6000 });
70
70
  });
71
71
  await _8_9_1.test.step('Capture Operate URL (base URL shared with Tasklist v2 endpoint)', async () => {
72
- await clusterDetailsPage.searchAndClickClientCredentialsLink(clientName);
73
- operateUrl = await clientCredentialsDetailsPage.getOperateUrl();
72
+ // A failed Console API request can render the client view as "Oops ...
73
+ // something went wrong." instead of its credentials. The view is not a
74
+ // route, so recover by reloading the API tab and reopening the client.
75
+ let firstAttempt = true;
76
+ await (0, test_1.expect)(async () => {
77
+ if (!firstAttempt) {
78
+ await page.reload({ waitUntil: 'domcontentloaded' });
79
+ await clusterDetailsPage.clickAPITab();
80
+ }
81
+ firstAttempt = false;
82
+ await clusterDetailsPage.searchAndClickClientCredentialsLink(clientName);
83
+ operateUrl = await clientCredentialsDetailsPage.getOperateUrl();
84
+ }).toPass({ timeout: 240000, intervals: [5000] });
74
85
  (0, test_1.expect)(operateUrl).toMatch(/^https?:\/\//);
75
86
  await clientCredentialsDetailsPage.goBack();
76
87
  await clusterDetailsPage.clickAPITab();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1598",
3
+ "version": "0.0.1600",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",