@camunda/e2e-test-suite 0.0.1271 → 0.0.1273

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.
Files changed (61) hide show
  1. package/dist/pages/8.10/ClusterSecretsPage.js +17 -1
  2. package/dist/pages/8.9/AppsPage.js +10 -1
  3. package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
  4. package/dist/pages/8.9/ClusterDetailsPage.js +66 -1
  5. package/dist/pages/8.9/ClusterPage.js +11 -2
  6. package/dist/pages/8.9/ClusterSecretsPage.js +19 -4
  7. package/dist/pages/8.9/ConsoleOrganizationPage.js +50 -17
  8. package/dist/pages/8.9/FormJsPage.js +23 -0
  9. package/dist/pages/8.9/ModelerCreatePage.d.ts +3 -0
  10. package/dist/pages/8.9/ModelerCreatePage.js +108 -7
  11. package/dist/pages/8.9/ModelerHomePage.d.ts +8 -0
  12. package/dist/pages/8.9/ModelerHomePage.js +116 -7
  13. package/dist/pages/8.9/ProcessApplicationPage.d.ts +1 -0
  14. package/dist/pages/8.9/ProcessApplicationPage.js +96 -0
  15. package/dist/pages/8.9/SettingsPage.js +12 -2
  16. package/dist/pages/BrowseAllConnectorMarketplacePage.d.ts +1 -0
  17. package/dist/pages/BrowseAllConnectorMarketplacePage.js +26 -14
  18. package/dist/pages/SM-8.9/ClusterPage.js +5 -1
  19. package/dist/resources/Basic_Auth_REST_Connector.bpmn +2 -2
  20. package/dist/resources/ConnectorsBasicTest.bpmn +6 -6
  21. package/dist/resources/REST_Connector_Bearer_Auth_Process.bpmn +2 -2
  22. package/dist/resources/REST_Connector_No_Auth_Process.bpmn +1 -1
  23. package/dist/test-setup.d.ts +0 -1
  24. package/dist/test-setup.js +7 -29
  25. package/dist/tests/8.10/connectors-user-flows.spec.js +4 -7
  26. package/dist/tests/8.10/saas-migration-path-user-flows.spec.js +1 -2
  27. package/dist/tests/8.10/smoke-tests.spec.js +2 -3
  28. package/dist/tests/8.7/connectors-user-flows.spec.js +10 -14
  29. package/dist/tests/8.7/saas-migration-path-user-flows.spec.js +1 -2
  30. package/dist/tests/8.7/smoke-tests.spec.js +3 -3
  31. package/dist/tests/8.8/connectors-user-flows.spec.js +5 -7
  32. package/dist/tests/8.8/saas-migration-path-user-flows.spec.js +1 -2
  33. package/dist/tests/8.8/smoke-tests.spec.js +3 -3
  34. package/dist/tests/8.9/connectors-user-flows.spec.js +5 -7
  35. package/dist/tests/8.9/console-user-flows.spec.js +3 -0
  36. package/dist/tests/8.9/saas-migration-path-user-flows.spec.js +1 -2
  37. package/dist/tests/8.9/smoke-tests.spec.js +3 -3
  38. package/dist/tests/SM-8.10/connectors-user-flows.spec.js +6 -9
  39. package/dist/tests/SM-8.10/mt-enabled-user-flows.spec.js +6 -6
  40. package/dist/tests/SM-8.10/smoke-tests.spec.js +2 -1
  41. package/dist/tests/SM-8.7/connectors-user-flows.spec.js +4 -4
  42. package/dist/tests/SM-8.7/mt-enabled-user-flows.spec.js +5 -4
  43. package/dist/tests/SM-8.7/smoke-tests.spec.js +3 -2
  44. package/dist/tests/SM-8.8/connectors-user-flows.spec.js +4 -4
  45. package/dist/tests/SM-8.8/mt-enabled-user-flows.spec.js +5 -4
  46. package/dist/tests/SM-8.8/smoke-tests.spec.js +2 -1
  47. package/dist/tests/SM-8.9/connectors-user-flows.spec.js +6 -6
  48. package/dist/tests/SM-8.9/mt-enabled-user-flows.spec.js +5 -4
  49. package/dist/tests/SM-8.9/smoke-tests.spec.js +2 -1
  50. package/dist/tests/c8Run-8.10/connectors-user-flows.spec.js +2 -2
  51. package/dist/tests/c8Run-8.7/connectors-user-flows.spec.js +5 -8
  52. package/dist/tests/c8Run-8.8/connectors-user-flows.spec.js +5 -8
  53. package/dist/tests/c8Run-8.9/connectors-user-flows.spec.js +5 -8
  54. package/dist/utils/connectorSecrets.js +2 -1
  55. package/dist/utils/constants.d.ts +2 -0
  56. package/dist/utils/constants.js +20 -1
  57. package/package.json +1 -1
  58. package/resources/Basic_Auth_REST_Connector.bpmn +2 -2
  59. package/resources/ConnectorsBasicTest.bpmn +6 -6
  60. package/resources/REST_Connector_Bearer_Auth_Process.bpmn +2 -2
  61. package/resources/REST_Connector_No_Auth_Process.bpmn +1 -1
@@ -7,6 +7,7 @@ const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocat
7
7
  const randomName_1 = require("../../utils/randomName");
8
8
  const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
9
9
  const sessionRecovery_1 = require("../../utils/sessionRecovery");
10
+ const modelerCluster_1 = require("../../utils/modelerCluster");
10
11
  class ModelerHomePage {
11
12
  defaultFolderName = 'Cross Component Test Project';
12
13
  // The default cluster test-setup.spec.ts's "Create Default Cluster" test
@@ -52,6 +53,8 @@ class ModelerHomePage {
52
53
  messageBanner;
53
54
  idpTemplateOption;
54
55
  idpEmptyStateButton;
56
+ idpProjectsTab;
57
+ createIdpProjectButton;
55
58
  idpApplicationNameInput;
56
59
  createIdpApplicationButton;
57
60
  dialog;
@@ -60,6 +63,11 @@ class ModelerHomePage {
60
63
  blockingDialogDismiss;
61
64
  optionsButton;
62
65
  formNameBreadcrumb;
66
+ formBreadcrumbButton;
67
+ formActionsButton;
68
+ renameFormMenuItem;
69
+ renameInput;
70
+ renameSubmitButton;
63
71
  constructor(page) {
64
72
  this.page = page;
65
73
  this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
@@ -113,7 +121,12 @@ class ModelerHomePage {
113
121
  });
114
122
  this.projectNameInput = page.locator('[data-test="editable-input"]');
115
123
  this.formNameInput = page.locator('[data-test="editable-input"]');
116
- this.idpApplicationNameInput = page.getByPlaceholder('New IDP Application');
124
+ // Hub's workspace projects page renamed IDP applications to "IDP projects"
125
+ // (camunda-hub CreateIdpApplicationModal.common.tsx).
126
+ this.idpApplicationNameInput = page
127
+ .getByPlaceholder('New IDP Application')
128
+ .or(page.getByPlaceholder('New IDP project'))
129
+ .first();
117
130
  this.createIdpApplicationButton = page.getByRole('button', {
118
131
  name: 'Create',
119
132
  exact: true,
@@ -130,6 +143,13 @@ class ModelerHomePage {
130
143
  this.idpEmptyStateButton = page
131
144
  .getByRole('button', { name: 'Create IDP application' })
132
145
  .first();
146
+ // Hub's workspace projects page (camunda-hub WorkspaceProjects.tsx) has no
147
+ // create menu at the root: IDP projects live on their own tab, created
148
+ // through its "Create IDP project" split button (header or empty state).
149
+ this.idpProjectsTab = page.locator('[data-test="tab-idp-applications"]');
150
+ this.createIdpProjectButton = page
151
+ .getByRole('button', { name: 'Create IDP project', exact: true })
152
+ .first();
133
153
  this.diagramTypeDropdown = page.locator('[data-test="diagram-dropdown"]');
134
154
  // Matched on the test id alone: hub's design-system variant of the create
135
155
  // menu renders "BPMN diagram" (lowercase d), so a text filter breaks the
@@ -192,9 +212,31 @@ class ModelerHomePage {
192
212
  .getByRole('button', { name: /^(Got it|Close|Dismiss)/i })
193
213
  .first();
194
214
  this.optionsButton = page.getByRole('button', { name: 'Options' });
195
- this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
215
+ // The legacy breadcrumb shows the name directly; Hub's newer nav
216
+ // (isNavV2) embeds it in the current crumb's kebab ("<name> actions").
217
+ this.formNameBreadcrumb = (formName) => page
218
+ .locator('button[data-test="breadcrumb-form"]', {
196
219
  hasText: formName,
197
- });
220
+ })
221
+ .or(page.getByRole('button', { name: `${formName} actions` }))
222
+ .first();
223
+ this.formBreadcrumbButton = page.locator('button[data-test="breadcrumb-form"]');
224
+ // Nav-v2 moves Rename/Duplicate/Delete behind a kebab next to the current
225
+ // (last) crumb (camunda-hub use-file-page-actions.ts); same locator as
226
+ // camunda-hub's own e2e BasePage.breadcrumbActionsButton.
227
+ this.formActionsButton = page
228
+ .getByRole('navigation', { name: 'Context navigation' })
229
+ .or(page.getByRole('navigation', { name: 'Breadcrumb', exact: true }))
230
+ .getByRole('button', { name: / actions$/ })
231
+ .last();
232
+ this.renameFormMenuItem = page
233
+ .locator('[data-test="rename-form"]')
234
+ .or(page.getByRole('menuitem', { name: 'Rename', exact: true }))
235
+ .first();
236
+ // Nav-v2's Rename opens a modal dialog (camunda-hub RenameDialogDs.tsx)
237
+ // instead of the legacy inline breadcrumb editor.
238
+ this.renameInput = page.locator('[data-test="rename-input"]');
239
+ this.renameSubmitButton = page.locator('[data-test="rename-submit"]');
198
240
  }
199
241
  // An announcement dialog renders over the projects list and swallows every
200
242
  // click on it: Playwright reports the project link as visible, enabled and
@@ -361,10 +403,19 @@ class ModelerHomePage {
361
403
  // clickCreateProcessApplication, which already covers this for the sibling
362
404
  // resource type.
363
405
  async startIdpApplicationCreation() {
364
- const rootLoaded = await this.becameVisible(this.idpEmptyStateButton.or(this.diagramTypeDropdown).first(), 60000);
406
+ const rootLoaded = await this.becameVisible(this.idpEmptyStateButton
407
+ .or(this.diagramTypeDropdown)
408
+ .or(this.idpProjectsTab)
409
+ .first(), 60000);
365
410
  if (!rootLoaded) {
366
411
  await this.page.reload();
367
412
  }
413
+ if (await this.becameVisible(this.idpProjectsTab, 5000)) {
414
+ await this.idpProjectsTab.click({ timeout: 30000 });
415
+ await (0, test_1.expect)(this.createIdpProjectButton).toBeVisible({ timeout: 60000 });
416
+ await this.createIdpProjectButton.click({ timeout: 30000 });
417
+ return;
418
+ }
368
419
  if (await this.becameVisible(this.idpEmptyStateButton, 5000)) {
369
420
  await this.idpEmptyStateButton.click({ timeout: 60000 });
370
421
  return;
@@ -381,10 +432,61 @@ class ModelerHomePage {
381
432
  async clickFormOption() {
382
433
  await this.formTemplateOption.click();
383
434
  }
435
+ // The legacy shell opens a fresh form with its breadcrumb name in inline-edit
436
+ // mode. Nav-v2 has no inline editing: the form stays "New form" until renamed
437
+ // through the crumb's "<name> actions" kebab and the shared rename dialog.
438
+ // Retried as a unit, and confirmed on the crumb, because a re-render
439
+ // (post-create refetch, form store load) can drop the editor mid-fill.
384
440
  async enterFormName(name) {
385
- await this.formNameInput.click({ timeout: 60000 });
386
- await this.formNameInput.fill(name);
387
- await this.formNameInput.press('Enter');
441
+ const maxAttempts = 3;
442
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
443
+ try {
444
+ if (await this.formNameBreadcrumb(name).isVisible()) {
445
+ return;
446
+ }
447
+ const editorOpen = await this.becameVisible(this.formNameInput.or(this.renameInput).first(), 15000);
448
+ if (!editorOpen) {
449
+ await this.openFormRenameEditor();
450
+ }
451
+ if (await this.renameInput.isVisible().catch(() => false)) {
452
+ await this.renameInput.fill(name, { timeout: 30000 });
453
+ await (0, test_1.expect)(this.renameInput).toHaveValue(name, { timeout: 5000 });
454
+ await this.renameSubmitButton.click({ timeout: 10000 });
455
+ await (0, test_1.expect)(this.renameInput).toBeHidden({ timeout: 30000 });
456
+ }
457
+ else {
458
+ await this.formNameInput.click({ timeout: 30000 });
459
+ await this.formNameInput.fill(name);
460
+ await this.formNameInput.press('Enter');
461
+ }
462
+ await (0, test_1.expect)(this.formNameBreadcrumb(name)).toBeVisible({
463
+ timeout: 30000,
464
+ });
465
+ return;
466
+ }
467
+ catch (error) {
468
+ if (attempt === maxAttempts) {
469
+ throw error;
470
+ }
471
+ console.warn(`Form rename to "${name}" did not commit ` +
472
+ `(attempt ${attempt}/${maxAttempts}): ${error}`);
473
+ }
474
+ }
475
+ }
476
+ async openFormRenameEditor() {
477
+ await (0, test_1.expect)(this.formBreadcrumbButton.or(this.formActionsButton).first()).toBeVisible({ timeout: 60000 });
478
+ if (await this.formActionsButton.isVisible().catch(() => false)) {
479
+ // Until the new form has loaded, nav-v2 keeps showing the previous
480
+ // page's crumbs (camunda-hub NavProvider), so the last kebab can still
481
+ // belong to the parent container. A fresh form is named "New form";
482
+ // wait for its own crumb before renaming anything.
483
+ await (0, test_1.expect)(this.formActionsButton).toHaveAccessibleName('New form actions', { timeout: 60000 });
484
+ await this.formActionsButton.click({ timeout: 30000 });
485
+ }
486
+ else {
487
+ await this.formBreadcrumbButton.click({ timeout: 60000 });
488
+ }
489
+ await this.renameFormMenuItem.click({ timeout: 30000 });
388
490
  }
389
491
  // Kept under its original name because it is what the existing call sites
390
492
  // already mean: "go back to where my resources live". That is now the
@@ -711,6 +813,13 @@ class ModelerHomePage {
711
813
  async selectCluster(clusterName) {
712
814
  //Select the correct cluster if multiple exist
713
815
  const baseClusterName = clusterName.split('(')[0].trim();
816
+ // With Hub's Environments feature on (camunda-hub#29210) the IDP create
817
+ // dialog picks an Environment row instead of a cluster toggle.
818
+ const hasEnvironmentRow = await this.becameVisible(this.dialog.locator('[data-test^="environment-row-"]').first(), 10000);
819
+ if (hasEnvironmentRow) {
820
+ await (0, modelerCluster_1.selectDeployEnvironment)(this.dialog, baseClusterName);
821
+ return;
822
+ }
714
823
  const cluster = this.dialog.getByText(new RegExp(`^${baseClusterName}`));
715
824
  await (0, test_1.expect)(cluster).toBeVisible({
716
825
  timeout: 30000,
@@ -24,6 +24,7 @@ declare class ProcessApplicationPage {
24
24
  rememberResourceContainer(url: string): void;
25
25
  forgetStageCluster(): void;
26
26
  ensureStageCluster(clusterName: string): Promise<boolean>;
27
+ private ensureWorkspaceEnvironment;
27
28
  private openStageDefinition;
28
29
  openByName(name: string): Promise<void>;
29
30
  }
@@ -266,7 +266,19 @@ class ProcessApplicationPage {
266
266
  // suite is. Re-navigating is the recovery; waiting longer on a page that
267
267
  // has already gone wrong is not, which is why the inner timeouts stay
268
268
  // short. A genuinely unbindable cluster still fails, with the same error.
269
+ //
270
+ // Under Hub workspaces (/workspaces/<ws>/projects/<id>) there is no
271
+ // /connected-clusters route -- it renders "No match for the current
272
+ // location". With Hub's Environments feature on (camunda-hub#29210),
273
+ // WorkspaceProjectDetail hides the Connected clusters card altogether and
274
+ // the deploy dialog offers the environments assigned to the parent
275
+ // workspace instead, so the binding is made there.
276
+ const workspaceId = paUrl.match(/\/workspaces\/([^/]+)\/projects\//)?.[1];
269
277
  await (0, test_1.expect)(async () => {
278
+ if (workspaceId) {
279
+ await this.ensureWorkspaceEnvironment(paUrl, workspaceId, clusterName);
280
+ return;
281
+ }
270
282
  await this.page.goto(`${paUrl}/connected-clusters`, {
271
283
  waitUntil: 'domcontentloaded',
272
284
  });
@@ -277,6 +289,90 @@ class ProcessApplicationPage {
277
289
  await this.page.goto(returnTo, { waitUntil: 'domcontentloaded' });
278
290
  return true;
279
291
  }
292
+ // Assigns the Environment backed by clusterName to the workspace, from
293
+ // workspace management's Environments tab (camunda-hub
294
+ // WorkspaceEnvironmentsTab). Without the Environments feature the project
295
+ // page still carries the Connected clusters card, whose "Configure" button
296
+ // opens the Define stages dialog -- that path is taken instead.
297
+ //
298
+ // Save replaces the workspace's whole assignment with the picker's
299
+ // selection, and parallel workers share this workspace, so only this
300
+ // cluster's toggle is touched and the result re-read afterwards. A
301
+ // concurrent save that drops it anyway surfaces as a deploy dialog without
302
+ // the cluster, which forgets the binding and comes back here (see
303
+ // forgetStageCluster).
304
+ async ensureWorkspaceEnvironment(paUrl, workspaceId, clusterName) {
305
+ await this.page.goto(paUrl, { waitUntil: 'domcontentloaded' });
306
+ await this.waitForProcessApplication();
307
+ const configure = this.page.getByRole('button', {
308
+ name: 'Configure',
309
+ exact: true,
310
+ });
311
+ // isVisible() is a point-in-time check: the Connected clusters card can
312
+ // still be mounting right after waitForProcessApplication resolves, so an
313
+ // instant read here races the card and can wrongly fall through to the
314
+ // (possibly unavailable) Environments tab below. Give it the same short
315
+ // wait the analogous hasEnvironmentRow checks elsewhere in this PR use.
316
+ const configureAppeared = await configure
317
+ .waitFor({ state: 'visible', timeout: 10000 })
318
+ .then(() => true)
319
+ .catch(() => false);
320
+ if (configureAppeared) {
321
+ await configure.click({ timeout: 30000 });
322
+ await this.defineDevelopmentStage(clusterName);
323
+ return;
324
+ }
325
+ const manageUrl = `${new URL(paUrl).origin}/workspaces/${workspaceId}/manage?tab=environments`;
326
+ await this.page.goto(manageUrl, { waitUntil: 'domcontentloaded' });
327
+ const heading = this.page.locator('[data-test="manage-environments-heading"]');
328
+ if (!(await heading
329
+ .waitFor({ state: 'visible', timeout: 60000 })
330
+ .then(() => true)
331
+ .catch(() => false))) {
332
+ throw new Error(`Could not bind "${clusterName}" as a deploy target: the project page ` +
333
+ 'has no Connected clusters "Configure" control, and the workspace ' +
334
+ `Environments tab did not render at ${manageUrl}.`);
335
+ }
336
+ const editButton = this.page.locator('[data-test="edit-environments-button"]');
337
+ await (0, test_1.expect)(editButton).toBeEnabled({ timeout: 60000 });
338
+ await editButton.click({ timeout: 30000 });
339
+ const editor = this.page.getByRole('group', {
340
+ name: 'Edit assigned environments',
341
+ });
342
+ await (0, test_1.expect)(editor).toBeVisible({ timeout: 30000 });
343
+ const search = editor.locator('[data-test="picker-search"]');
344
+ if (await search.isVisible().catch(() => false)) {
345
+ await search.fill(clusterName);
346
+ }
347
+ // Picker card toggles are named "Select <environment> (<cluster>)" and
348
+ // flip to "Selected ..." with aria-pressed once picked.
349
+ const escapedClusterName = clusterName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
350
+ const toggle = editor
351
+ .locator('[data-test="picker-environment"]')
352
+ .getByRole('button', {
353
+ name: new RegExp(`^Select(ed)? .*\\(${escapedClusterName}\\)$`),
354
+ })
355
+ .first();
356
+ await (0, test_1.expect)(toggle).toBeVisible({ timeout: 60000 });
357
+ if ((await toggle.getAttribute('aria-pressed')) === 'true') {
358
+ await this.page
359
+ .getByRole('button', { name: 'Cancel', exact: true })
360
+ .click({ timeout: 30000 });
361
+ await (0, test_1.expect)(editor).toBeHidden({ timeout: 30000 });
362
+ return;
363
+ }
364
+ await toggle.click({ timeout: 30000 });
365
+ await (0, test_1.expect)(toggle).toHaveAttribute('aria-pressed', 'true');
366
+ await this.page
367
+ .getByRole('button', { name: 'Save', exact: true })
368
+ .click({ timeout: 30000 });
369
+ await (0, test_1.expect)(editor).toBeHidden({ timeout: 60000 });
370
+ await (0, test_1.expect)(this.page
371
+ .getByRole('list', { name: 'Assigned environments' })
372
+ .getByRole('listitem')
373
+ .filter({ hasText: clusterName })
374
+ .first()).toBeVisible({ timeout: 60000 });
375
+ }
280
376
  // The /connected-clusters deep link opens the Define stages dialog by itself
281
377
  // while the application has no stage bound. Once one is bound -- a test that
282
378
  // deploys to a second cluster -- it has to be reopened from the page, so try
@@ -7,8 +7,18 @@ class SettingsPage {
7
7
  logoutButton;
8
8
  constructor(page) {
9
9
  this.page = page;
10
- this.openSettingsButton = page.getByLabel('Open Settings');
11
- this.logoutButton = page.getByRole('button', { name: 'Log out' });
10
+ // The Camunda Hub shell replaces Console's "Open Settings" header button
11
+ // with the design system's UserMenu, whose trigger carries
12
+ // data-slot="user-menu-trigger" and whose logout entry is a "Log out"
13
+ // menuitem (camunda-hub NavShellDs.common.tsx, nav-shell-ds.spec.saas.tsx).
14
+ this.openSettingsButton = page
15
+ .getByLabel('Open Settings')
16
+ .or(page.locator('[data-slot="user-menu-trigger"]'))
17
+ .first();
18
+ this.logoutButton = page
19
+ .getByRole('button', { name: 'Log out' })
20
+ .or(page.getByRole('menuitem', { name: 'Log out', exact: true }))
21
+ .first();
12
22
  }
13
23
  async clickOpenSettingsButton(retries = 3) {
14
24
  for (let i = 0; i < retries; i++) {
@@ -11,6 +11,7 @@ declare class BrowseAllConnectorMarketplacePage {
11
11
  isOpen(): Promise<boolean>;
12
12
  waitForConnectorSearchResult(connectorName?: string): Promise<Locator>;
13
13
  importConnectorToProject(connectorName?: string): Promise<void>;
14
+ private waitForAnyVisible;
14
15
  private completeImportReviewIfNecessary;
15
16
  private findConnectorCard;
16
17
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BrowseAllConnectorMarketplacePage = void 0;
4
+ const test_1 = require("@playwright/test");
4
5
  class BrowseAllConnectorMarketplacePage {
5
6
  page;
6
7
  searchForConnectorTextbox;
@@ -52,9 +53,8 @@ class BrowseAllConnectorMarketplacePage {
52
53
  const retryButton = this.marketplaceDetailFooter.getByRole('button', {
53
54
  name: 'Try again',
54
55
  });
55
- const detailState = addButton.or(addedStatus).or(retryButton);
56
56
  for (let attempt = 1; attempt <= 3; attempt++) {
57
- await detailState.waitFor({ state: 'visible', timeout: 120000 });
57
+ await this.waitForAnyVisible([addButton, addedStatus, retryButton], 120000);
58
58
  if (await addedStatus.isVisible()) {
59
59
  break;
60
60
  }
@@ -73,20 +73,35 @@ class BrowseAllConnectorMarketplacePage {
73
73
  throw new Error('Browse All could not import the Marketplace connector after 3 attempts.');
74
74
  }
75
75
  }
76
- await this.availableElementsHeading
77
- .or(addedStatus)
78
- .waitFor({ state: 'visible', timeout: 120000 });
76
+ await this.waitForAnyVisible([this.availableElementsHeading, addedStatus], 120000);
79
77
  await browseAllDialog
80
78
  .getByRole('button', { name: 'Close' })
81
79
  .click({ timeout: 60000 });
82
80
  await browseAllDialog.waitFor({ state: 'hidden', timeout: 60000 });
83
81
  }
82
+ // Waits until any one of `locators` is visible.
83
+ //
84
+ // `Locator.or()` cannot do this: the union it builds is strict, so the
85
+ // moment two of the states are on screen together `waitFor` throws
86
+ // "strict mode violation ... resolved to 2 elements" rather than
87
+ // resolving. The marketplace shows exactly that pair once an import
88
+ // finishes -- the "Available elements" heading and the "Added to this
89
+ // project" status -- which failed every REST connector flow that imports
90
+ // its connector from Browse All. Polling each state on its own keeps the
91
+ // "whichever comes first" semantics these waits need.
92
+ async waitForAnyVisible(locators, timeout) {
93
+ await (0, test_1.expect)(async () => {
94
+ const visible = await Promise.all(locators.map((locator) => locator.isVisible()));
95
+ (0, test_1.expect)(visible).toContain(true);
96
+ }).toPass({ timeout, intervals: [1000, 2000, 5000] });
97
+ }
84
98
  async completeImportReviewIfNecessary(retryButton, addedStatus) {
85
- await this.importModal
86
- .or(this.availableElementsHeading)
87
- .or(addedStatus)
88
- .or(retryButton)
89
- .waitFor({ state: 'visible', timeout: 120000 });
99
+ await this.waitForAnyVisible([
100
+ this.importModal,
101
+ this.availableElementsHeading,
102
+ addedStatus,
103
+ retryButton,
104
+ ], 120000);
90
105
  if (await retryButton.isVisible()) {
91
106
  return false;
92
107
  }
@@ -106,10 +121,7 @@ class BrowseAllConnectorMarketplacePage {
106
121
  await confirmButton.click({ timeout: 60000 });
107
122
  }
108
123
  await this.importModal.waitFor({ state: 'hidden', timeout: 120000 });
109
- await this.availableElementsHeading
110
- .or(addedStatus)
111
- .or(retryButton)
112
- .waitFor({ state: 'visible', timeout: 120000 });
124
+ await this.waitForAnyVisible([this.availableElementsHeading, addedStatus, retryButton], 120000);
113
125
  return !(await retryButton.isVisible());
114
126
  }
115
127
  async findConnectorCard(connectorName) {
@@ -22,7 +22,11 @@ class ClusterPage {
22
22
  });
23
23
  }
24
24
  async assertCustomTagsAreVisible(tags) {
25
- await this.page.waitForLoadState('networkidle');
25
+ // No page.waitForLoadState('networkidle') here: the Console clusters page
26
+ // emits continuous polling/SSE traffic, so networkidle never fires and the
27
+ // wait times out on every attempt. The toBeVisible auto-wait below already
28
+ // covers the readiness window this assertion needs. SM-8.8 and SM-8.10
29
+ // dropped the same call for the same reason.
26
30
  for (const tag of tags) {
27
31
  await (0, test_1.expect)(this.clustersList.getByText(tag, { exact: true })).toBeVisible({
28
32
  timeout: 60000,
@@ -15,13 +15,13 @@
15
15
  <zeebe:input source="username" target="authentication.username" />
16
16
  <zeebe:input source="password" target="authentication.password" />
17
17
  <zeebe:input source="GET" target="method" />
18
- <zeebe:input source="https://camunda.proxy.beeceptor.com/pre-prod/basic-auth-test" target="url" />
18
+ <zeebe:input source="https://dummyjson.com/http/200/Awesome!" target="url" />
19
19
  <zeebe:input source="20" target="connectionTimeoutInSeconds" />
20
20
  <zeebe:input source="20" target="readTimeoutInSeconds" />
21
21
  </zeebe:ioMapping>
22
22
  <zeebe:taskHeaders>
23
23
  <zeebe:header key="resultVariable" value="result" />
24
- <zeebe:header key="resultExpression" value="={message:response.body.status}" />
24
+ <zeebe:header key="resultExpression" value="={message:response.body.message}" />
25
25
  <zeebe:header key="errorExpression" />
26
26
  <zeebe:header key="retryBackoff" value="PT0S" />
27
27
  </zeebe:taskHeaders>
@@ -1041,13 +1041,13 @@
1041
1041
  <zeebe:properties>
1042
1042
  <zeebe:property name="inbound.type" value="io.camunda:http-polling:1" />
1043
1043
  <zeebe:property name="authentication.type" value="oauth-client-credentials-flow" />
1044
- <zeebe:property name="authentication.oauthTokenEndpoint" value="https://camunda.proxy.beeceptor.com/pre-prod/oauth/getToken" />
1045
- <zeebe:property name="authentication.clientId" value="clientID" />
1046
- <zeebe:property name="authentication.clientSecret" value="clientSecret" />
1047
- <zeebe:property name="authentication.clientAuthentication" value="basicAuthHeader" />
1048
- <zeebe:property name="authentication.scopes" value="read:clients" />
1044
+ <zeebe:property name="authentication.oauthTokenEndpoint" value="=tokenEndpoint" />
1045
+ <zeebe:property name="authentication.clientId" value="=zeebeClientId" />
1046
+ <zeebe:property name="authentication.clientSecret" value="=zeebeSecret" />
1047
+ <zeebe:property name="authentication.clientAuthentication" value="credentialsBody" />
1048
+ <zeebe:property name="authentication.scopes" />
1049
1049
  <zeebe:property name="method" value="GET" />
1050
- <zeebe:property name="url" value="https://camunda.proxy.beeceptor.com/pre-prod/oauth/response" />
1050
+ <zeebe:property name="url" value="https://dummyjson.com/http/200/Awesome!" />
1051
1051
  <zeebe:property name="httpRequestInterval" value="PT15S" />
1052
1052
  <zeebe:property name="processPollingInterval" value="PT5S" />
1053
1053
  <zeebe:property name="connectionTimeoutInSeconds" value="20" />
@@ -12,12 +12,12 @@
12
12
  <zeebe:input source="bearer" target="authentication.type" />
13
13
  <zeebe:input source="thisisabearertoken" target="authentication.token" />
14
14
  <zeebe:input source="POST" target="method" />
15
- <zeebe:input source="https://camunda.proxy.beeceptor.com/pre-prod/bearer-auth-test" target="url" />
15
+ <zeebe:input source="https://dummyjson.com/http/200/Awesome!" target="url" />
16
16
  <zeebe:input source="20" target="connectionTimeoutInSeconds" />
17
17
  <zeebe:input source="20" target="readTimeoutInSeconds" />
18
18
  </zeebe:ioMapping>
19
19
  <zeebe:taskHeaders>
20
- <zeebe:header key="resultExpression" value="={message:response.body.status}" />
20
+ <zeebe:header key="resultExpression" value="={message:response.body.message}" />
21
21
  <zeebe:header key="retryBackoff" value="PT0S" />
22
22
  </zeebe:taskHeaders>
23
23
  </bpmn:extensionElements>
@@ -11,7 +11,7 @@
11
11
  <zeebe:ioMapping>
12
12
  <zeebe:input source="noAuth" target="authentication.type" />
13
13
  <zeebe:input source="GET" target="method" />
14
- <zeebe:input source="https://camunda.proxy.beeceptor.com/pre-prod/no-auth-test" target="url" />
14
+ <zeebe:input source="https://dummyjson.com/http/200/Awesome!" target="url" />
15
15
  <zeebe:input source="20" target="connectionTimeoutInSeconds" />
16
16
  <zeebe:input source="20" target="readTimeoutInSeconds" />
17
17
  </zeebe:ioMapping>
@@ -3,4 +3,3 @@ export declare function captureScreenshot(page: Page, testInfo: TestInfo): Promi
3
3
  export declare function captureFailureVideo(page: Page, testInfo: TestInfo): Promise<void>;
4
4
  export declare function generateRandomStringAsync(length: number): Promise<string>;
5
5
  export declare function performBasicAuthPostRequest(requestUrl: string, username: string, password: string): Promise<void>;
6
- export declare function performBearerTokenAuthPostRequest(requestUrl: string, bearerToken: string): Promise<void>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.performBearerTokenAuthPostRequest = exports.performBasicAuthPostRequest = exports.generateRandomStringAsync = exports.captureFailureVideo = exports.captureScreenshot = void 0;
6
+ exports.performBasicAuthPostRequest = exports.generateRandomStringAsync = exports.captureFailureVideo = exports.captureScreenshot = void 0;
7
7
  const test_1 = require("@playwright/test");
8
8
  const crypto_1 = require("crypto");
9
9
  const axios_1 = __importDefault(require("axios"));
@@ -128,37 +128,15 @@ function generateRandomStringAsync(length) {
128
128
  });
129
129
  }
130
130
  exports.generateRandomStringAsync = generateRandomStringAsync;
131
+ // Fails the calling test when the REST mock the connector is about to call is
132
+ // unreachable, so the flow reports the mock rather than an unexplained Operate
133
+ // incident minutes later. The credentials go in the axios config, not in the
134
+ // second argument: that argument is the request body, so the previous version
135
+ // posted `{auth: {...}}` as JSON and sent no Authorization header at all.
131
136
  async function performBasicAuthPostRequest(requestUrl, username, password) {
132
137
  await (0, test_1.expect)(async () => {
133
- const authResponse = await axios_1.default.post(requestUrl, {
134
- auth: {
135
- username: username,
136
- password: password,
137
- },
138
- });
138
+ const authResponse = await axios_1.default.post(requestUrl, {}, { auth: { username: username, password: password } });
139
139
  (0, test_1.expect)(authResponse.status).toBe(200);
140
140
  }).toPass(constants_1.defaultAssertionOptions);
141
141
  }
142
142
  exports.performBasicAuthPostRequest = performBasicAuthPostRequest;
143
- async function performBearerTokenAuthPostRequest(requestUrl, bearerToken) {
144
- try {
145
- const authToken = bearerToken;
146
- const apiUrl = requestUrl;
147
- const response = await axios_1.default.post(apiUrl, {
148
- headers: {
149
- Authorization: `Bearer ${authToken}`,
150
- },
151
- });
152
- if (response.status === 200) {
153
- console.log('Bearer Token API request successful.');
154
- console.log('Response Data:', response.data);
155
- }
156
- else {
157
- console.error('Bearer Token API request failed.');
158
- }
159
- }
160
- catch (error) {
161
- console.error('Bearer Token API request error:', error);
162
- }
163
- }
164
- exports.performBearerTokenAuthPostRequest = performBearerTokenAuthPostRequest;
@@ -25,8 +25,7 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
25
25
  await (0, _setup_1.captureScreenshot)(page, testInfo);
26
26
  await (0, _setup_1.captureFailureVideo)(page, testInfo);
27
27
  });
28
- // Skipped due to bug #3673: https://github.com/camunda/c8-cross-component-e2e-tests/issues/3673
29
- _8_10_1.test.skip('REST Connector No Auth User Flow', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }) => {
28
+ (0, _8_10_1.test)('REST Connector No Auth User Flow', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }) => {
30
29
  _8_10_1.test.slow();
31
30
  const processName = 'REST_Connector_No_Auth_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
32
31
  await _8_10_1.test.step('Navigate to Web Modeler', async () => {
@@ -45,7 +44,7 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
45
44
  await modelerHomePage.clickBpmnTemplateOption();
46
45
  });
47
46
  await _8_10_1.test.step('Create BPMN Diagram with REST Connector and Start Process Instance', async () => {
48
- await (0, UtilitiesPage_2.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, 'https://camunda.proxy.beeceptor.com/pre-prod/no-auth-test', 'noAuth', 'body');
47
+ await (0, UtilitiesPage_2.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, constants_1.REST_MOCK_URL, 'noAuth', 'body');
49
48
  await modelerCreatePage.runProcessInstance(defaultClusterName);
50
49
  });
51
50
  await _8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
@@ -79,8 +78,7 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
79
78
  });
80
79
  });
81
80
  });
82
- // Skipped due to bug #3673: https://github.com/camunda/c8-cross-component-e2e-tests/issues/3673
83
- _8_10_1.test.skip('REST Connector Bearer Token Auth User Flow', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }) => {
81
+ (0, _8_10_1.test)('REST Connector Bearer Token Auth User Flow', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }) => {
84
82
  _8_10_1.test.slow();
85
83
  const processName = 'REST_Connector_Bearer_Auth_Process' +
86
84
  (await (0, _setup_1.generateRandomStringAsync)(3));
@@ -100,9 +98,8 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
100
98
  await modelerHomePage.clickBpmnTemplateOption();
101
99
  });
102
100
  await _8_10_1.test.step('Create BPMN Diagram with REST Connector with Bearer Token Auth and Start Process Instance', async () => {
103
- await (0, UtilitiesPage_2.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, 'https://camunda.proxy.beeceptor.com/pre-prod/bearer-auth-test', 'bearer', '{message:response.body.status}');
101
+ await (0, UtilitiesPage_2.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, constants_1.REST_MOCK_URL, 'bearer', '{message:response.body.message}');
104
102
  await modelerCreatePage.runProcessInstance(defaultClusterName);
105
- await (0, _setup_1.performBearerTokenAuthPostRequest)('https://camunda.proxy.beeceptor.com/pre-prod/bearer-auth-test', 'thisisabearertoken');
106
103
  });
107
104
  await _8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
108
105
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
@@ -110,8 +110,7 @@ _8_10_1.test.describe('SaaS Migration Path Tests', () => {
110
110
  await (0, test_1.expect)(page.getByRole('cell', { name: 'Admin' })).toBeVisible();
111
111
  });
112
112
  });
113
- // Skipped due to bug #3673: https://github.com/camunda/c8-cross-component-e2e-tests/issues/3673
114
- _8_10_1.test.skip('Assert Connectors Still Function After SaaS Upgrade @tasklistV2', async ({ operateHomePage, appsPage, operateProcessesPage, operateProcessInstancePage, }) => {
113
+ (0, _8_10_1.test)('Assert Connectors Still Function After SaaS Upgrade @tasklistV2', async ({ operateHomePage, appsPage, operateProcessesPage, operateProcessInstancePage, }) => {
115
114
  _8_10_1.test.slow();
116
115
  // Use the same fixed name that was created in the pre-upgrade smoke tests
117
116
  const processName = 'REST_Connector_Basic_Auth_ProcessUpgradeTest';
@@ -292,8 +292,7 @@ _8_10_1.test.describe('Smoke Tests', () => {
292
292
  await optimizeReportPage.assertUserTaskInstanceCountWithRetry(2, appTab, countRetries, Math.floor(flowBudget(countRetries * constants_1.TIMEOUT.navigation) / countRetries));
293
293
  });
294
294
  });
295
- // Skipped due to bug #3673: https://github.com/camunda/c8-cross-component-e2e-tests/issues/3673
296
- _8_10_1.test.skip('Most Common REST Connector User Flow', async ({ page, loginPage, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }, testInfo) => {
295
+ (0, _8_10_1.test)('Most Common REST Connector User Flow', async ({ page, loginPage, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, }, testInfo) => {
297
296
  _8_10_1.test.slow();
298
297
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
299
298
  const processName = 'REST_Connector_Basic_Auth_Process' + randomString;
@@ -311,7 +310,7 @@ _8_10_1.test.describe('Smoke Tests', () => {
311
310
  await modelerHomePage.clickBpmnTemplateOption();
312
311
  });
313
312
  await _8_10_1.test.step('Create BPMN diagram with REST Connector using Basic Auth', async () => {
314
- await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, 'https://camunda.proxy.beeceptor.com/pre-prod/basic-auth-test', 'basic', '{message:response.body.message}', 'result', { username: 'username', password: 'password' });
313
+ await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, constants_1.REST_MOCK_BASIC_AUTH_URL, 'basic', '{message:response.body.message}', 'result', { username: 'username', password: 'password' });
315
314
  });
316
315
  await _8_10_1.test.step('Deploy and start process instance', async () => {
317
316
  await modelerCreatePage.runProcessInstance(CLUSTER_NAME);