@camunda/e2e-test-suite 0.0.1250 → 0.0.1252

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 (54) hide show
  1. package/dist/pages/8.9/AppsPage.js +24 -12
  2. package/dist/pages/8.9/ClusterDetailsPage.js +8 -1
  3. package/dist/pages/8.9/ClusterPage.js +22 -20
  4. package/dist/pages/8.9/HomePage.js +6 -1
  5. package/dist/pages/8.9/ProcessApplicationPage.js +30 -10
  6. package/dist/pages/BrowseAllConnectorMarketplacePage.d.ts +17 -0
  7. package/dist/pages/BrowseAllConnectorMarketplacePage.js +145 -0
  8. package/dist/pages/SM-8.10/ClusterDetailsPage.d.ts +1 -1
  9. package/dist/pages/SM-8.10/ClusterDetailsPage.js +13 -6
  10. package/dist/pages/SM-8.10/ClusterPage.js +3 -1
  11. package/dist/pages/SM-8.10/ConnectorMarketplacePage.d.ts +2 -0
  12. package/dist/pages/SM-8.10/ConnectorMarketplacePage.js +16 -1
  13. package/dist/pages/SM-8.10/ModelerCreatePage.d.ts +4 -1
  14. package/dist/pages/SM-8.10/ModelerCreatePage.js +78 -51
  15. package/dist/pages/SM-8.10/ModelerHomePage.d.ts +2 -1
  16. package/dist/pages/SM-8.10/ModelerHomePage.js +24 -0
  17. package/dist/pages/SM-8.10/OperateHomePage.js +8 -1
  18. package/dist/pages/SM-8.10/OptimizeHomePage.js +13 -4
  19. package/dist/pages/SM-8.10/ProcessApplicationPage.d.ts +10 -5
  20. package/dist/pages/SM-8.10/ProcessApplicationPage.js +163 -68
  21. package/dist/pages/SM-8.10/UtilitiesPage.js +6 -6
  22. package/dist/pages/SM-8.7/NavigationPage.js +2 -1
  23. package/dist/tests/8.10/connectors-user-flows.spec.js +4 -2
  24. package/dist/tests/8.10/saas-migration-path-user-flows.spec.js +2 -1
  25. package/dist/tests/8.10/smoke-tests.spec.js +2 -1
  26. package/dist/tests/8.7/connectors-user-flows.spec.js +8 -4
  27. package/dist/tests/8.7/saas-migration-path-user-flows.spec.js +2 -1
  28. package/dist/tests/8.7/smoke-tests.spec.js +2 -1
  29. package/dist/tests/8.8/connectors-user-flows.spec.js +4 -2
  30. package/dist/tests/8.8/saas-migration-path-user-flows.spec.js +2 -1
  31. package/dist/tests/8.8/smoke-tests.spec.js +2 -1
  32. package/dist/tests/8.9/connectors-user-flows.spec.js +4 -2
  33. package/dist/tests/8.9/saas-migration-path-user-flows.spec.js +2 -1
  34. package/dist/tests/8.9/smoke-tests.spec.js +2 -1
  35. package/dist/tests/SM-8.10/cluster-management-user-flows.spec.js +13 -6
  36. package/dist/tests/SM-8.10/connectors-user-flows.spec.js +2 -1
  37. package/dist/tests/SM-8.10/hub-web-modeler-smoke.spec.d.ts +1 -0
  38. package/dist/tests/SM-8.10/hub-web-modeler-smoke.spec.js +24 -0
  39. package/dist/tests/SM-8.10/mt-enabled-user-flows.spec.js +3 -1
  40. package/dist/tests/SM-8.10/topology-orchestration-smoke.spec.d.ts +1 -0
  41. package/dist/tests/SM-8.10/topology-orchestration-smoke.spec.js +58 -0
  42. package/dist/tests/SM-8.7/topology-orchestration-smoke.spec.d.ts +1 -0
  43. package/dist/tests/SM-8.7/topology-orchestration-smoke.spec.js +62 -0
  44. package/dist/tests/SM-8.8/topology-orchestration-smoke.spec.d.ts +1 -0
  45. package/dist/tests/SM-8.8/topology-orchestration-smoke.spec.js +58 -0
  46. package/dist/tests/SM-8.9/topology-orchestration-smoke.spec.d.ts +1 -0
  47. package/dist/tests/SM-8.9/topology-orchestration-smoke.spec.js +58 -0
  48. package/dist/tests/c8Run-8.7/connectors-user-flows.spec.js +6 -3
  49. package/dist/tests/c8Run-8.8/connectors-user-flows.spec.js +6 -3
  50. package/dist/tests/c8Run-8.9/connectors-user-flows.spec.js +6 -3
  51. package/dist/utils/modelerCluster.d.ts +4 -6
  52. package/dist/utils/modelerCluster.js +55 -32
  53. package/package.json +1 -1
  54. package/playwright.config.ts +18 -5
@@ -93,18 +93,21 @@ class AppsPage {
93
93
  // look up the app link sidebar-wide rather than scoped to a "group"
94
94
  // container that doesn't reliably exist.
95
95
  async clickHubClusterApp(clusterName, appName) {
96
- // Not exact: camunda-hub's own nav-shell-ds.spec.saas.tsx asserts this same
97
- // link with a substring/regex match (`{name: /Operate/}`), not an exact
98
- // string -- and CI traces confirm why: even after the toggle click
99
- // succeeds (no error thrown), an exact-match lookup here still times out
100
- // with "element(s) not found", meaning the rendered link's accessible
101
- // name is not byte-for-byte "Operate"/"Tasklist"/"Optimize" (likely
102
- // additional content from the design-system sidebar item, e.g. an icon
103
- // or badge). The app names are distinct enough that a substring match
104
- // carries no ambiguity risk.
105
- const appLink = this.hubSidebar.getByRole('link', {
106
- name: appName,
107
- });
96
+ // When the sidebar is collapsed (the default on a fresh session), the
97
+ // group toggle opens a Radix popover -- a role="dialog" labelled with the
98
+ // cluster name and portalled OUTSIDE the "Main navigation" <nav> -- so a
99
+ // sidebar-scoped lookup finds nothing there (nightly trace: the toggle
100
+ // flips to aria-expanded="true" and the popover renders "Operate in Test
101
+ // Cluster (opens in a new tab)" et al., yet the nav-scoped link never
102
+ // resolves). Look in that popover first; keep the sidebar-scoped lookup
103
+ // for the expanded-sidebar layout, where the links render inline.
104
+ // Not exact: the rendered accessible name carries more than the bare app
105
+ // name (e.g. "Operate in <cluster> (opens in a new tab)").
106
+ const appLink = this.page
107
+ .getByRole('dialog', { name: clusterName, exact: true })
108
+ .getByRole('link', { name: appName })
109
+ .or(this.hubSidebar.getByRole('link', { name: appName }))
110
+ .first();
108
111
  // Decide whether to expand from clusterName's OWN toggle state, not from
109
112
  // whether "some" appLink is already visible sidebar-wide: in a
110
113
  // multi-cluster org another cluster's group can already be expanded with
@@ -123,6 +126,15 @@ class AppsPage {
123
126
  await collapsedToggle.click({ timeout: 10000 });
124
127
  }
125
128
  await (0, test_1.expect)(appLink).toBeVisible({ timeout: 30000 });
129
+ // Hub's cluster-app links carry target="_blank", so clicking them opens
130
+ // the app in a new tab while every caller keeps asserting on this.page.
131
+ // Follow the href in the current tab instead, matching the same-tab
132
+ // navigation the Console app switcher gave these callers.
133
+ const href = await appLink.getAttribute('href');
134
+ if (href) {
135
+ await this.page.goto(new URL(href, this.page.url()).toString());
136
+ return;
137
+ }
126
138
  await appLink.click({ timeout: 10000 });
127
139
  }
128
140
  async clickModeler() {
@@ -174,7 +174,14 @@ class ClusterDetailsPage {
174
174
  this.orchestrationClusterCheckbox = page
175
175
  .locator('label')
176
176
  .filter({ hasText: /^Orchestration Cluster API$/ });
177
- this.clientCredentialsLink = (clientCredentials) => page.getByRole('cell', { name: clientCredentials }).locator('a');
177
+ // Camunda Hub's data table renders the client name as a link-styled
178
+ // <button data-slot="button"> rather than an <a>. The cell also holds a
179
+ // pointer-events-none row-overlay button with the same accessible name,
180
+ // so match the visible one by its data-slot.
181
+ this.clientCredentialsLink = (clientCredentials) => page
182
+ .getByRole('cell', { name: clientCredentials })
183
+ .locator('a, button[data-slot="button"]')
184
+ .first();
178
185
  this.clientRow = (name) => this.clientsList.filter({ hasText: name });
179
186
  this.clientRowDeleteButton = (name) => this.clientRow(name).getByRole('button', { name: 'Delete' });
180
187
  this.mcpSupportToggle = page.getByRole('switch', {
@@ -76,33 +76,32 @@ class ClusterPage {
76
76
  this.connectorSecretsTab = page.getByRole('tab', {
77
77
  name: 'Connector Secrets',
78
78
  });
79
- this.belgiumRegion = page.locator('label').filter({
79
+ // Camunda Hub's create-cluster form replaced the Carbon tiles (a
80
+ // <label> per option) and tabs with design-system choice cards and toggle
81
+ // groups, all exposed as role="radio" named by their title. Each locator
82
+ // below accepts either shape via this helper.
83
+ const choice = (legacy, name) => legacy.or(page.getByRole('radio', { name, exact: true }));
84
+ this.belgiumRegion = choice(page.locator('label').filter({
80
85
  hasText: 'Chaos, Belgium, Europe (europe-west1)',
81
- });
82
- this.londonRegion = page.locator('label').filter({
83
- hasText: 'London, Europe (europe-west2)',
84
- });
85
- this.ohioRegion = page.locator('label').filter({
86
+ }), 'Chaos, Belgium, Europe (europe-west1)');
87
+ this.londonRegion = choice(page.locator('label').filter({ hasText: 'London, Europe (europe-west2)' }), 'London, Europe (europe-west2)');
88
+ this.ohioRegion = choice(page.locator('label').filter({
86
89
  hasText: 'Ohio, North America (us-east-2)',
87
- });
88
- this.finlandRegion = page.locator('label').filter({
90
+ }), 'Ohio, North America (us-east-2)');
91
+ this.finlandRegion = choice(page.locator('label').filter({
89
92
  hasText: 'Finland, Europe (europe-north1)',
90
- });
93
+ }), 'Finland, Europe (europe-north1)');
91
94
  this.createAClusterButton = page.getByRole('button', {
92
95
  name: 'Create a Cluster',
93
96
  });
94
97
  this.clusterVersionOption = page.getByText(`${process.env.CLUSTER_VERSION}`, { exact: true });
95
98
  this.generationSearchInput = page.getByPlaceholder('Search generations');
96
- this.g3DevClusterType = page.locator('label').filter({
97
- hasText: this.defaultClusterType,
98
- });
99
- this.alphaTab = page.getByRole('tab', { name: 'Alpha' });
100
- this.devTab = page.getByRole('tab', { name: 'Internal Dev' });
101
- this.stableTab = page.getByRole('tab', { name: 'Stable' });
102
- this.qaGenerationsTab = page.getByRole('tab', { name: 'QA Generations' });
103
- this.gcpRegion = page.getByRole('tab', {
104
- name: 'GCP',
105
- });
99
+ this.g3DevClusterType = choice(page.locator('label').filter({ hasText: this.defaultClusterType }), this.defaultClusterType);
100
+ this.alphaTab = choice(page.getByRole('tab', { name: 'Alpha' }), 'Alpha');
101
+ this.devTab = choice(page.getByRole('tab', { name: 'Internal Dev' }), 'Internal Dev');
102
+ this.stableTab = choice(page.getByRole('tab', { name: 'Stable' }), 'Stable');
103
+ this.qaGenerationsTab = choice(page.getByRole('tab', { name: 'QA Generations' }), 'QA Generations');
104
+ this.gcpRegion = choice(page.getByRole('tab', { name: 'GCP' }), 'GCP');
106
105
  this.clustersList = page
107
106
  .getByRole('row')
108
107
  .filter({ hasNotText: 'Generation' }); //Filter out header row
@@ -261,7 +260,10 @@ class ClusterPage {
261
260
  await this.clickClusterNameInput();
262
261
  await this.fillClusterNameInput(name);
263
262
  await this.clickClusterType();
264
- await this.page.getByRole('tab', { name: region }).click();
263
+ await this.page
264
+ .getByRole('tab', { name: region })
265
+ .or(this.page.getByRole('radio', { name: region, exact: true }))
266
+ .click();
265
267
  await this.selectSubRegion(region);
266
268
  await this.clickClusterOption();
267
269
  await this.determineTasklistAPI(tasklistV1Api);
@@ -52,10 +52,15 @@ class HomePage {
52
52
  });
53
53
  this.camundaAppsButton = page.getByLabel('Camunda apps');
54
54
  this.appSwitcher = page.getByLabel('App Switcher');
55
+ // Specs assert this as "the authenticated app shell has rendered" before
56
+ // navigating. Camunda Hub has no app-switcher button at all (apps are
57
+ // reached from the sidebar -- see AppsPage.clickCamundaApps), so on Hub
58
+ // its authenticated header is the equivalent readiness signal.
55
59
  this.camundaComponentsButton = page
56
60
  .getByLabel('Camunda components')
57
61
  .or(this.camundaAppsButton)
58
- .or(this.appSwitcher);
62
+ .or(this.appSwitcher)
63
+ .or(this.hubHeader);
59
64
  this.openOrganizationButton = page.getByLabel('Open Organizations');
60
65
  // "Camunda logo Console" was the old Carbon header's logo link. Camunda
61
66
  // Hub has no equivalent link -- its post-login landing is `hubHeader`
@@ -48,32 +48,52 @@ class ProcessApplicationPage {
48
48
  // .first() defensively only: hub renders exactly one of these today, in the
49
49
  // workspace-root empty state. A future second placement should not turn
50
50
  // every PA-creating test into a strict-mode violation.
51
+ // Camunda Hub renamed the process application to a "project", created from
52
+ // the workspace's "Create project" split button + dialog (camunda-hub
53
+ // CreateSplitButton / CreateProjectModal) -- the same surface
54
+ // pages/SM-8.10/ProcessApplicationPage.ts already handles. exact: the split
55
+ // button's chevron is labelled "Create project options".
51
56
  this.createProcessApplicationButton = page
52
- .getByRole('button', { name: 'Create process application' })
57
+ .getByRole('button', { name: 'Create project', exact: true })
58
+ .or(page.getByRole('button', { name: 'Create process application' }))
53
59
  .first();
54
60
  this.createProcessApplicationOption = page.locator('[data-test="create-process-application"]');
55
- this.creationDialog = page.getByRole('dialog', {
56
- name: 'Create a process application',
57
- });
61
+ this.creationDialog = page
62
+ .getByRole('dialog')
63
+ .filter({ has: page.getByLabel('Project name') })
64
+ .or(page.getByRole('dialog', { name: 'Create a process application' }))
65
+ .first();
58
66
  // Deliberately scoped to the dialog: ModelerCreatePage owns an unscoped
59
67
  // getByLabel('Name', {exact: true}) for the properties panel, which would
60
68
  // resolve to two elements while the creation modal is open.
61
- this.nameInput = this.creationDialog.getByLabel('Name', { exact: true });
62
- this.submitButton = this.creationDialog.getByRole('button', {
63
- name: 'Create',
64
- exact: true,
65
- });
69
+ this.nameInput = this.creationDialog
70
+ .getByLabel('Project name')
71
+ .or(this.creationDialog.getByLabel('Name', { exact: true }))
72
+ .first();
73
+ this.submitButton = this.creationDialog
74
+ .getByRole('button', { name: 'Create project', exact: true })
75
+ .or(this.creationDialog.getByRole('button', { name: 'Create', exact: true }))
76
+ .first();
66
77
  this.cancelButton = this.creationDialog.getByRole('button', {
67
78
  name: 'Cancel',
68
79
  exact: true,
69
80
  });
70
- this.breadcrumb = page.locator('[data-test="breadcrumb-process-application-menu"]');
81
+ // The Hub project detail renders a title-bar hook instead of the
82
+ // process-application breadcrumb menu; either proves we are inside.
83
+ this.breadcrumb = page
84
+ .locator('[data-test="breadcrumb-process-application-menu"]')
85
+ .or(page.locator('[data-test="process-application-title-bar"]'))
86
+ .first();
71
87
  this.folderExplorer = page.getByRole('region', { name: 'Folder explorer' });
72
88
  // The process application page renders more than one diagram-dropdown
73
89
  // (hub's own unit tests reach for it with getAllByTestId), so the dropdown
74
90
  // has to be resolved inside the folder explorer rather than page-wide.
91
+ // The Hub project detail has no "Folder explorer" region and renders its
92
+ // "Create new" menu as [data-test="create-action-dropdown"] (camunda-hub
93
+ // CreateActionDs), so accept that page-wide as well.
75
94
  this.createNewDropdown = this.folderExplorer
76
95
  .locator('[data-test="diagram-dropdown"]')
96
+ .or(page.locator('[data-test="create-action-dropdown"]'))
77
97
  .first();
78
98
  this.inlineNameInput = page.locator('[data-test="editable-input"]');
79
99
  this.defineStagesDialog = page
@@ -0,0 +1,17 @@
1
+ import { Locator, Page } from '@playwright/test';
2
+ declare class BrowseAllConnectorMarketplacePage {
3
+ private page;
4
+ readonly searchForConnectorTextbox: Locator;
5
+ private readonly marketplaceConnectorList;
6
+ private readonly marketplaceConnectorCard;
7
+ private readonly marketplaceDetailFooter;
8
+ private readonly availableElementsHeading;
9
+ private readonly importModal;
10
+ constructor(page: Page);
11
+ isOpen(): Promise<boolean>;
12
+ waitForConnectorSearchResult(connectorName?: string): Promise<Locator>;
13
+ importConnectorToProject(connectorName?: string): Promise<void>;
14
+ private completeImportReviewIfNecessary;
15
+ private findConnectorCard;
16
+ }
17
+ export { BrowseAllConnectorMarketplacePage };
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BrowseAllConnectorMarketplacePage = void 0;
4
+ class BrowseAllConnectorMarketplacePage {
5
+ page;
6
+ searchForConnectorTextbox;
7
+ marketplaceConnectorList;
8
+ marketplaceConnectorCard;
9
+ marketplaceDetailFooter;
10
+ availableElementsHeading;
11
+ importModal;
12
+ constructor(page) {
13
+ this.page = page;
14
+ this.searchForConnectorTextbox = page.getByRole('searchbox', {
15
+ name: 'Search elements and reusable assets',
16
+ });
17
+ this.marketplaceConnectorList = page.getByRole('list', {
18
+ name: 'Marketplace connectors',
19
+ });
20
+ this.marketplaceConnectorCard = this.marketplaceConnectorList
21
+ .getByRole('button', {
22
+ name: /^View details for .+ from Marketplace$/,
23
+ })
24
+ .first();
25
+ this.marketplaceDetailFooter = page.locator('[data-test="marketplace-detail-footer"]');
26
+ this.availableElementsHeading = page.getByRole('heading', {
27
+ name: 'Available elements',
28
+ });
29
+ this.importModal = page.locator('[data-test="import-modal"]');
30
+ }
31
+ async isOpen() {
32
+ return await this.searchForConnectorTextbox.isVisible();
33
+ }
34
+ async waitForConnectorSearchResult(connectorName) {
35
+ await this.marketplaceConnectorCard.waitFor({
36
+ state: 'visible',
37
+ timeout: 120000,
38
+ });
39
+ return await this.findConnectorCard(connectorName);
40
+ }
41
+ async importConnectorToProject(connectorName) {
42
+ const connectorCard = await this.waitForConnectorSearchResult(connectorName);
43
+ await connectorCard.click({ timeout: 60000 });
44
+ const browseAllDialog = this.page.getByRole('dialog').filter({
45
+ has: this.page.getByRole('button', { name: 'Back to all results' }),
46
+ });
47
+ await browseAllDialog.waitFor({ state: 'visible', timeout: 60000 });
48
+ const addButton = this.marketplaceDetailFooter.getByRole('button', {
49
+ name: /^(Add to project|Add remaining elements)$/,
50
+ });
51
+ const addedStatus = this.marketplaceDetailFooter.getByText('Added to this project', { exact: true });
52
+ const retryButton = this.marketplaceDetailFooter.getByRole('button', {
53
+ name: 'Try again',
54
+ });
55
+ const detailState = addButton.or(addedStatus).or(retryButton);
56
+ for (let attempt = 1; attempt <= 3; attempt++) {
57
+ await detailState.waitFor({ state: 'visible', timeout: 120000 });
58
+ if (await addedStatus.isVisible()) {
59
+ break;
60
+ }
61
+ if (await retryButton.isVisible()) {
62
+ if (attempt === 3) {
63
+ throw new Error('Browse All could not inspect the Marketplace connector after 3 attempts.');
64
+ }
65
+ await retryButton.click({ timeout: 60000 });
66
+ continue;
67
+ }
68
+ await addButton.click({ timeout: 60000 });
69
+ if (await this.completeImportReviewIfNecessary(retryButton)) {
70
+ break;
71
+ }
72
+ if (attempt === 3) {
73
+ throw new Error('Browse All could not import the Marketplace connector after 3 attempts.');
74
+ }
75
+ }
76
+ await this.availableElementsHeading.waitFor({
77
+ state: 'visible',
78
+ timeout: 120000,
79
+ });
80
+ await browseAllDialog
81
+ .getByRole('button', { name: 'Close' })
82
+ .click({ timeout: 60000 });
83
+ await browseAllDialog.waitFor({ state: 'hidden', timeout: 60000 });
84
+ }
85
+ async completeImportReviewIfNecessary(retryButton) {
86
+ await this.importModal
87
+ .or(this.availableElementsHeading)
88
+ .or(retryButton)
89
+ .waitFor({ state: 'visible', timeout: 120000 });
90
+ if (await retryButton.isVisible()) {
91
+ return false;
92
+ }
93
+ if (!(await this.importModal.isVisible())) {
94
+ return true;
95
+ }
96
+ const confirmButton = this.importModal
97
+ .getByRole('button', {
98
+ name: /^(Add to project|Replace resource|Save as copy)$/,
99
+ })
100
+ .first();
101
+ const confirmationVisible = await confirmButton
102
+ .waitFor({ state: 'visible', timeout: 10000 })
103
+ .then(() => true)
104
+ .catch(() => false);
105
+ if (confirmationVisible) {
106
+ await confirmButton.click({ timeout: 60000 });
107
+ }
108
+ await this.importModal.waitFor({ state: 'hidden', timeout: 120000 });
109
+ await this.availableElementsHeading
110
+ .or(retryButton)
111
+ .waitFor({ state: 'visible', timeout: 120000 });
112
+ return !(await retryButton.isVisible());
113
+ }
114
+ async findConnectorCard(connectorName) {
115
+ if (!connectorName) {
116
+ return this.marketplaceConnectorCard;
117
+ }
118
+ const exactCard = this.marketplaceConnectorList.getByRole('button', {
119
+ name: `View details for ${connectorName} from Marketplace`,
120
+ exact: true,
121
+ });
122
+ const searchWords = connectorName
123
+ .replace(/\s+connector$/i, '')
124
+ .trim()
125
+ .split(/\s+/)
126
+ .map(escapeRegExp)
127
+ .join('.*');
128
+ const relatedCard = this.marketplaceConnectorList
129
+ .getByRole('button', {
130
+ name: new RegExp(`^View details for .*${searchWords}.* from Marketplace$`, 'i'),
131
+ })
132
+ .first();
133
+ if (await exactCard.isVisible()) {
134
+ return exactCard;
135
+ }
136
+ if (await relatedCard.isVisible()) {
137
+ return relatedCard;
138
+ }
139
+ return this.marketplaceConnectorCard;
140
+ }
141
+ }
142
+ exports.BrowseAllConnectorMarketplacePage = BrowseAllConnectorMarketplacePage;
143
+ function escapeRegExp(value) {
144
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
145
+ }
@@ -1,7 +1,7 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
2
  declare class ClusterDetailsPage {
3
3
  private page;
4
- readonly customPropertiesHeading: Locator;
4
+ readonly clusterDetailsHeading: Locator;
5
5
  constructor(page: Page);
6
6
  assertCustomPropertyIsNavigated(linkTitle: string, linkName: string, link: string | RegExp): Promise<void>;
7
7
  }
@@ -4,21 +4,28 @@ exports.ClusterDetailsPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  class ClusterDetailsPage {
6
6
  page;
7
- customPropertiesHeading;
7
+ // Custom properties no longer have a heading/section of their own — the
8
+ // Environments/Clusters redesign folds them into the "Cluster details"
9
+ // card as rows (camunda-hub's ClusterDetailFields.sm.tsx), so this is the
10
+ // heading that confirms that card — and the properties inside it — rendered.
11
+ clusterDetailsHeading;
8
12
  constructor(page) {
9
13
  this.page = page;
10
- this.customPropertiesHeading = this.page.getByRole('heading', {
11
- name: 'Custom properties',
14
+ this.clusterDetailsHeading = this.page.getByRole('heading', {
15
+ name: 'Cluster details',
12
16
  });
13
17
  }
14
18
  async assertCustomPropertyIsNavigated(linkTitle, linkName, link) {
15
19
  const pageTabPromise = this.page.waitForEvent('popup', {
16
20
  timeout: 60000,
17
21
  });
22
+ // Each custom property is a <dt>/<dd> row (direct child of the details
23
+ // card's <dl>), keyed by the property's own description — not by a
24
+ // generic "Link 1"/"Link 2" row title, which this DOM never had.
18
25
  await this.page
19
- .getByRole('row')
20
- .filter({ hasText: linkTitle })
21
- .getByRole('link', { name: linkName })
26
+ .locator('dl > div')
27
+ .filter({ has: this.page.locator('dt', { hasText: linkTitle }) })
28
+ .getByRole('link', { name: linkName, exact: true })
22
29
  .click({ timeout: 60000 });
23
30
  const camundaPage = await pageTabPromise;
24
31
  await (0, test_1.expect)(camundaPage).toHaveURL(link);
@@ -9,7 +9,9 @@ class ClusterPage {
9
9
  cluster;
10
10
  constructor(page) {
11
11
  this.page = page;
12
- this.clustersList = page.locator('tbody').getByRole('row');
12
+ // The Clusters tab defaults to the card grid (`[data-test="cluster-card"]`),
13
+ // not a table — there is no `tbody` in that view.
14
+ this.clustersList = page.locator('[data-test="cluster-card"]');
13
15
  this.cluster = this.clustersList.getByText(this.defaultClusterName).first();
14
16
  }
15
17
  async clickClusterLink() {
@@ -1,6 +1,8 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
2
  declare class ConnectorMarketplacePage {
3
3
  private page;
4
+ private browseAllConnectorMarketplacePage;
5
+ private connectorName?;
4
6
  readonly searchForConnectorTextbox: Locator;
5
7
  readonly downloadToProjectButton: Locator;
6
8
  readonly addToProjectButton: Locator;
@@ -2,9 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectorMarketplacePage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const BrowseAllConnectorMarketplacePage_1 = require("../BrowseAllConnectorMarketplacePage");
5
6
  const sleep_1 = require("../../utils/sleep");
6
7
  class ConnectorMarketplacePage {
7
8
  page;
9
+ browseAllConnectorMarketplacePage;
10
+ connectorName;
8
11
  searchForConnectorTextbox;
9
12
  downloadToProjectButton;
10
13
  addToProjectButton;
@@ -15,7 +18,10 @@ class ConnectorMarketplacePage {
15
18
  noResultsMessage;
16
19
  constructor(page) {
17
20
  this.page = page;
18
- this.searchForConnectorTextbox = page.getByPlaceholder('Search for a connector');
21
+ this.browseAllConnectorMarketplacePage =
22
+ new BrowseAllConnectorMarketplacePage_1.BrowseAllConnectorMarketplacePage(page);
23
+ this.searchForConnectorTextbox =
24
+ this.browseAllConnectorMarketplacePage.searchForConnectorTextbox.or(page.getByPlaceholder('Search for a connector'));
19
25
  this.downloadToProjectButton = page
20
26
  .getByRole('button', { name: 'Download to project' })
21
27
  .first();
@@ -35,6 +41,7 @@ class ConnectorMarketplacePage {
35
41
  await (0, sleep_1.sleep)(2000);
36
42
  }
37
43
  async fillSearchForConnectorTextbox(connectorName) {
44
+ this.connectorName = connectorName;
38
45
  await this.searchForConnectorTextbox.fill(connectorName, { timeout: 60000 });
39
46
  await (0, sleep_1.sleep)(2000);
40
47
  }
@@ -48,6 +55,10 @@ class ConnectorMarketplacePage {
48
55
  await this.replaceResourceButton.click({ timeout: 30000 });
49
56
  }
50
57
  async waitForConnectorSearchResults() {
58
+ if (await this.browseAllConnectorMarketplacePage.isOpen()) {
59
+ await this.browseAllConnectorMarketplacePage.waitForConnectorSearchResult(this.connectorName);
60
+ return;
61
+ }
51
62
  const MAX_RETRIES = 4;
52
63
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
53
64
  await (0, test_1.expect)(this.downloadToProjectButton
@@ -72,6 +83,10 @@ class ConnectorMarketplacePage {
72
83
  throw new Error('Marketplace connector search failed after retries due to persistent error or no matching results');
73
84
  }
74
85
  async downloadConnectorToProject() {
86
+ if (await this.browseAllConnectorMarketplacePage.isOpen()) {
87
+ await this.browseAllConnectorMarketplacePage.importConnectorToProject(this.connectorName);
88
+ return;
89
+ }
75
90
  await this.clickDownloadToProjectButton();
76
91
  if (await this.confirmImport()) {
77
92
  return;
@@ -2,6 +2,8 @@ import { Page, Locator } from '@playwright/test';
2
2
  declare class ModelerCreatePage {
3
3
  private page;
4
4
  private processApplicationPage;
5
+ private pendingFileSaves;
6
+ private lastFileSaveActivity;
5
7
  readonly generalPanel: Locator;
6
8
  readonly taskDefinitionPanel: Locator;
7
9
  readonly processIdInput: Locator;
@@ -36,7 +38,6 @@ declare class ModelerCreatePage {
36
38
  readonly restConnectorOption: Locator;
37
39
  readonly changeElementSearchInput: Locator;
38
40
  readonly changeElementResults: Locator;
39
- readonly changeElementTabStrip: Locator;
40
41
  readonly reusableAssetsTab: Locator;
41
42
  readonly marketPlaceButton: Locator;
42
43
  readonly clientIdTextbox: Locator;
@@ -106,6 +107,8 @@ declare class ModelerCreatePage {
106
107
  assertThreeElementsVisible(): Promise<void>;
107
108
  clickForm(name: string): Promise<void>;
108
109
  runProcessInstance(variables?: string, tenant?: string, businessId?: string): Promise<void>;
110
+ private static isFileSave;
111
+ private waitForFileSavesToSettle;
109
112
  private suppressTestModeAnnouncement;
110
113
  switchToPlay(): Promise<void>;
111
114
  clickGeneralPropertiesPanel(): Promise<void>;