@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.
- package/dist/pages/8.9/AppsPage.js +24 -12
- package/dist/pages/8.9/ClusterDetailsPage.js +8 -1
- package/dist/pages/8.9/ClusterPage.js +22 -20
- package/dist/pages/8.9/HomePage.js +6 -1
- package/dist/pages/8.9/ProcessApplicationPage.js +30 -10
- package/dist/pages/BrowseAllConnectorMarketplacePage.d.ts +17 -0
- package/dist/pages/BrowseAllConnectorMarketplacePage.js +145 -0
- package/dist/pages/SM-8.10/ClusterDetailsPage.d.ts +1 -1
- package/dist/pages/SM-8.10/ClusterDetailsPage.js +13 -6
- package/dist/pages/SM-8.10/ClusterPage.js +3 -1
- package/dist/pages/SM-8.10/ConnectorMarketplacePage.d.ts +2 -0
- package/dist/pages/SM-8.10/ConnectorMarketplacePage.js +16 -1
- package/dist/pages/SM-8.10/ModelerCreatePage.d.ts +4 -1
- package/dist/pages/SM-8.10/ModelerCreatePage.js +78 -51
- package/dist/pages/SM-8.10/ModelerHomePage.d.ts +2 -1
- package/dist/pages/SM-8.10/ModelerHomePage.js +24 -0
- package/dist/pages/SM-8.10/OperateHomePage.js +8 -1
- package/dist/pages/SM-8.10/OptimizeHomePage.js +13 -4
- package/dist/pages/SM-8.10/ProcessApplicationPage.d.ts +10 -5
- package/dist/pages/SM-8.10/ProcessApplicationPage.js +163 -68
- package/dist/pages/SM-8.10/UtilitiesPage.js +6 -6
- package/dist/pages/SM-8.7/NavigationPage.js +2 -1
- package/dist/tests/8.10/connectors-user-flows.spec.js +4 -2
- package/dist/tests/8.10/saas-migration-path-user-flows.spec.js +2 -1
- package/dist/tests/8.10/smoke-tests.spec.js +2 -1
- package/dist/tests/8.7/connectors-user-flows.spec.js +8 -4
- package/dist/tests/8.7/saas-migration-path-user-flows.spec.js +2 -1
- package/dist/tests/8.7/smoke-tests.spec.js +2 -1
- package/dist/tests/8.8/connectors-user-flows.spec.js +4 -2
- package/dist/tests/8.8/saas-migration-path-user-flows.spec.js +2 -1
- package/dist/tests/8.8/smoke-tests.spec.js +2 -1
- package/dist/tests/8.9/connectors-user-flows.spec.js +4 -2
- package/dist/tests/8.9/saas-migration-path-user-flows.spec.js +2 -1
- package/dist/tests/8.9/smoke-tests.spec.js +2 -1
- package/dist/tests/SM-8.10/cluster-management-user-flows.spec.js +13 -6
- package/dist/tests/SM-8.10/connectors-user-flows.spec.js +2 -1
- package/dist/tests/SM-8.10/hub-web-modeler-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.10/hub-web-modeler-smoke.spec.js +24 -0
- package/dist/tests/SM-8.10/mt-enabled-user-flows.spec.js +3 -1
- package/dist/tests/SM-8.10/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.10/topology-orchestration-smoke.spec.js +58 -0
- package/dist/tests/SM-8.7/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.7/topology-orchestration-smoke.spec.js +62 -0
- package/dist/tests/SM-8.8/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.8/topology-orchestration-smoke.spec.js +58 -0
- package/dist/tests/SM-8.9/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.9/topology-orchestration-smoke.spec.js +58 -0
- package/dist/tests/c8Run-8.7/connectors-user-flows.spec.js +6 -3
- package/dist/tests/c8Run-8.8/connectors-user-flows.spec.js +6 -3
- package/dist/tests/c8Run-8.9/connectors-user-flows.spec.js +6 -3
- package/dist/utils/modelerCluster.d.ts +4 -6
- package/dist/utils/modelerCluster.js +55 -32
- package/package.json +1 -1
- 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
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
// carries
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
});
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
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
|
|
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
|
|
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
|
|
56
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
11
|
-
name: '
|
|
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
|
-
.
|
|
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
|
-
|
|
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.
|
|
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>;
|