@camunda/e2e-test-suite 0.0.1443 → 0.0.1445
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/.github/c8_versions.json +1 -0
- package/README.md +2 -0
- package/dist/fixtures/SM-8.11.d.ts +77 -0
- package/dist/fixtures/SM-8.11.js +176 -0
- package/dist/pages/SM-8.11/Authorization.d.ts +39 -0
- package/dist/pages/SM-8.11/Authorization.js +349 -0
- package/dist/pages/SM-8.11/ClusterDetailsPage.d.ts +8 -0
- package/dist/pages/SM-8.11/ClusterDetailsPage.js +65 -0
- package/dist/pages/SM-8.11/ClusterPage.d.ts +11 -0
- package/dist/pages/SM-8.11/ClusterPage.js +37 -0
- package/dist/pages/SM-8.11/ConnectorMarketplacePage.d.ts +27 -0
- package/dist/pages/SM-8.11/ConnectorMarketplacePage.js +187 -0
- package/dist/pages/SM-8.11/ConnectorSettingsPage.d.ts +41 -0
- package/dist/pages/SM-8.11/ConnectorSettingsPage.js +153 -0
- package/dist/pages/SM-8.11/ConnectorTemplatePage.d.ts +11 -0
- package/dist/pages/SM-8.11/ConnectorTemplatePage.js +34 -0
- package/dist/pages/SM-8.11/FormJsPage.d.ts +33 -0
- package/dist/pages/SM-8.11/FormJsPage.js +109 -0
- package/dist/pages/SM-8.11/HomePage.d.ts +20 -0
- package/dist/pages/SM-8.11/HomePage.js +49 -0
- package/dist/pages/SM-8.11/IdentityTenantPage.d.ts +53 -0
- package/dist/pages/SM-8.11/IdentityTenantPage.js +264 -0
- package/dist/pages/SM-8.11/IdentityUsersPage.d.ts +92 -0
- package/dist/pages/SM-8.11/IdentityUsersPage.js +485 -0
- package/dist/pages/SM-8.11/KeycloakAdminPage.d.ts +17 -0
- package/dist/pages/SM-8.11/KeycloakAdminPage.js +78 -0
- package/dist/pages/SM-8.11/KeycloakLoginPage.d.ts +12 -0
- package/dist/pages/SM-8.11/KeycloakLoginPage.js +32 -0
- package/dist/pages/SM-8.11/KeycloakUtils.d.ts +17 -0
- package/dist/pages/SM-8.11/KeycloakUtils.js +94 -0
- package/dist/pages/SM-8.11/LoginPage.d.ts +21 -0
- package/dist/pages/SM-8.11/LoginPage.js +112 -0
- package/dist/pages/SM-8.11/ManagementIdentityPage.d.ts +44 -0
- package/dist/pages/SM-8.11/ManagementIdentityPage.js +176 -0
- package/dist/pages/SM-8.11/ModelerCreatePage.d.ts +238 -0
- package/dist/pages/SM-8.11/ModelerCreatePage.js +1950 -0
- package/dist/pages/SM-8.11/ModelerHomePage.d.ts +97 -0
- package/dist/pages/SM-8.11/ModelerHomePage.js +956 -0
- package/dist/pages/SM-8.11/NavigationPage.d.ts +58 -0
- package/dist/pages/SM-8.11/NavigationPage.js +389 -0
- package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.d.ts +10 -0
- package/dist/pages/SM-8.11/OCIdentityAuthorizationsPage.js +22 -0
- package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.d.ts +22 -0
- package/dist/pages/SM-8.11/OCIdentityClusterVariablesPage.js +120 -0
- package/dist/pages/SM-8.11/OCIdentityGroupsPage.d.ts +39 -0
- package/dist/pages/SM-8.11/OCIdentityGroupsPage.js +154 -0
- package/dist/pages/SM-8.11/OCIdentityHomePage.d.ts +23 -0
- package/dist/pages/SM-8.11/OCIdentityHomePage.js +115 -0
- package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.d.ts +23 -0
- package/dist/pages/SM-8.11/OCIdentityMappingRulesPage.js +116 -0
- package/dist/pages/SM-8.11/OCIdentityRolesPage.d.ts +61 -0
- package/dist/pages/SM-8.11/OCIdentityRolesPage.js +433 -0
- package/dist/pages/SM-8.11/OCTenantPage.d.ts +73 -0
- package/dist/pages/SM-8.11/OCTenantPage.js +399 -0
- package/dist/pages/SM-8.11/OperateHomePage.d.ts +26 -0
- package/dist/pages/SM-8.11/OperateHomePage.js +139 -0
- package/dist/pages/SM-8.11/OperateProcessInstancePage.d.ts +30 -0
- package/dist/pages/SM-8.11/OperateProcessInstancePage.js +276 -0
- package/dist/pages/SM-8.11/OperateProcessesPage.d.ts +38 -0
- package/dist/pages/SM-8.11/OperateProcessesPage.js +383 -0
- package/dist/pages/SM-8.11/OptimizeCollectionsPage.d.ts +17 -0
- package/dist/pages/SM-8.11/OptimizeCollectionsPage.js +62 -0
- package/dist/pages/SM-8.11/OptimizeDashboardPage.d.ts +14 -0
- package/dist/pages/SM-8.11/OptimizeDashboardPage.js +63 -0
- package/dist/pages/SM-8.11/OptimizeHomePage.d.ts +19 -0
- package/dist/pages/SM-8.11/OptimizeHomePage.js +76 -0
- package/dist/pages/SM-8.11/OptimizeReportPage.d.ts +50 -0
- package/dist/pages/SM-8.11/OptimizeReportPage.js +209 -0
- package/dist/pages/SM-8.11/PlayPage.d.ts +47 -0
- package/dist/pages/SM-8.11/PlayPage.js +389 -0
- package/dist/pages/SM-8.11/ProcessApplicationPage.d.ts +35 -0
- package/dist/pages/SM-8.11/ProcessApplicationPage.js +340 -0
- package/dist/pages/SM-8.11/SettingsPage.d.ts +12 -0
- package/dist/pages/SM-8.11/SettingsPage.js +34 -0
- package/dist/pages/SM-8.11/TaskDetailsPage.d.ts +70 -0
- package/dist/pages/SM-8.11/TaskDetailsPage.js +273 -0
- package/dist/pages/SM-8.11/TaskPanelPage.d.ts +29 -0
- package/dist/pages/SM-8.11/TaskPanelPage.js +320 -0
- package/dist/pages/SM-8.11/TaskProcessesPage.d.ts +15 -0
- package/dist/pages/SM-8.11/TaskProcessesPage.js +77 -0
- package/dist/pages/SM-8.11/UtilitiesPage.d.ts +38 -0
- package/dist/pages/SM-8.11/UtilitiesPage.js +573 -0
- package/dist/pages/SM-8.11/optimizeReportUtils.d.ts +6 -0
- package/dist/pages/SM-8.11/optimizeReportUtils.js +77 -0
- package/dist/tests/SM-8.11/cluster-management-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/cluster-management-user-flows.spec.js +48 -0
- package/dist/tests/SM-8.11/cluster-variables.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/cluster-variables.spec.js +226 -0
- package/dist/tests/SM-8.11/connectors-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/connectors-user-flows.spec.js +307 -0
- package/dist/tests/SM-8.11/document-handling-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/document-handling-user-flows.spec.js +174 -0
- package/dist/tests/SM-8.11/hto-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/hto-user-flows.spec.js +410 -0
- package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/hub-web-modeler-smoke.spec.js +24 -0
- package/dist/tests/SM-8.11/identity-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/identity-user-flows.spec.js +220 -0
- package/dist/tests/SM-8.11/license-key-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/license-key-user-flows.spec.js +461 -0
- package/dist/tests/SM-8.11/login.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/login.spec.js +24 -0
- package/dist/tests/SM-8.11/migration-path-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/migration-path-user-flows.spec.js +263 -0
- package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/mt-enabled-user-flows.spec.js +607 -0
- package/dist/tests/SM-8.11/optimize-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/optimize-user-flows.spec.js +505 -0
- package/dist/tests/SM-8.11/play.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/play.spec.js +144 -0
- package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/rba-enabled-user-flows.spec.js +712 -0
- package/dist/tests/SM-8.11/smoke-tests.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/smoke-tests.spec.js +167 -0
- package/dist/tests/SM-8.11/test-setup.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/test-setup.spec.js +24 -0
- package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/topology-orchestration-smoke.spec.js +58 -0
- package/dist/tests/SM-8.11/web-modeler-user-flows.spec.d.ts +1 -0
- package/dist/tests/SM-8.11/web-modeler-user-flows.spec.js +223 -0
- package/package.json +1 -1
- package/playwright.config.ts +21 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class ClusterDetailsPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly clusterDetailsHeading: Locator;
|
|
5
|
+
constructor(page: Page);
|
|
6
|
+
assertCustomPropertyIsNavigated(linkTitle: string, linkName: string, link: string | RegExp): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export { ClusterDetailsPage };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClusterDetailsPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
class ClusterDetailsPage {
|
|
6
|
+
page;
|
|
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;
|
|
12
|
+
constructor(page) {
|
|
13
|
+
this.page = page;
|
|
14
|
+
this.clusterDetailsHeading = this.page.getByRole('heading', {
|
|
15
|
+
name: 'Cluster details',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async assertCustomPropertyIsNavigated(linkTitle, linkName, link) {
|
|
19
|
+
const context = this.page.context();
|
|
20
|
+
const matchesLink = (url) => typeof link === 'string'
|
|
21
|
+
? url.href.startsWith(link)
|
|
22
|
+
: link.test(url.href);
|
|
23
|
+
// Serve a stub document only when the external site itself is
|
|
24
|
+
// unreachable, so the popup still commits the URL Console navigated to.
|
|
25
|
+
const handler = async (route) => {
|
|
26
|
+
if (route.request().resourceType() !== 'document') {
|
|
27
|
+
await route.continue();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const response = await route.fetch({ timeout: 30000 });
|
|
32
|
+
await route.fulfill({ response });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const url = route.request().url();
|
|
36
|
+
console.warn(`External link ${url} unreachable, stubbing: ${error}`);
|
|
37
|
+
await route.fulfill({
|
|
38
|
+
status: 200,
|
|
39
|
+
contentType: 'text/html',
|
|
40
|
+
body: '<html><body></body></html>',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
await context.route(matchesLink, handler);
|
|
45
|
+
try {
|
|
46
|
+
const pageTabPromise = this.page.waitForEvent('popup', {
|
|
47
|
+
timeout: 60000,
|
|
48
|
+
});
|
|
49
|
+
// Each custom property is a <dt>/<dd> row (direct child of the details
|
|
50
|
+
// card's <dl>), keyed by the property's own description — not by a
|
|
51
|
+
// generic "Link 1"/"Link 2" row title, which this DOM never had.
|
|
52
|
+
await this.page
|
|
53
|
+
.locator('dl > div')
|
|
54
|
+
.filter({ has: this.page.locator('dt', { hasText: linkTitle }) })
|
|
55
|
+
.getByRole('link', { name: linkName, exact: true })
|
|
56
|
+
.click({ timeout: 60000 });
|
|
57
|
+
const camundaPage = await pageTabPromise;
|
|
58
|
+
await (0, test_1.expect)(camundaPage).toHaveURL(link, { timeout: 60000 });
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await context.unroute(matchesLink, handler);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ClusterDetailsPage = ClusterDetailsPage;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class ClusterPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly clustersList: Locator;
|
|
5
|
+
readonly defaultClusterName: string;
|
|
6
|
+
readonly cluster: Locator;
|
|
7
|
+
constructor(page: Page);
|
|
8
|
+
clickClusterLink(): Promise<void>;
|
|
9
|
+
assertCustomTagsAreVisible(tags: string[]): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export { ClusterPage };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClusterPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
class ClusterPage {
|
|
6
|
+
page;
|
|
7
|
+
clustersList;
|
|
8
|
+
defaultClusterName = process.env.CLUSTER_NAME || 'integration-zeebe';
|
|
9
|
+
cluster;
|
|
10
|
+
constructor(page) {
|
|
11
|
+
this.page = page;
|
|
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"]');
|
|
15
|
+
this.cluster = this.clustersList.getByText(this.defaultClusterName).first();
|
|
16
|
+
}
|
|
17
|
+
async clickClusterLink() {
|
|
18
|
+
await (0, test_1.expect)(this.cluster).toBeVisible({ timeout: 60000 });
|
|
19
|
+
await this.cluster.click({ timeout: 30000 });
|
|
20
|
+
await (0, test_1.expect)(this.page.getByText('Cluster details').first()).toBeVisible({
|
|
21
|
+
timeout: 10000,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async assertCustomTagsAreVisible(tags) {
|
|
25
|
+
// The previous implementation called page.waitForLoadState('networkidle')
|
|
26
|
+
// here. The Console clusters page emits continuous polling/SSE traffic,
|
|
27
|
+
// so networkidle never fires — the test would hang for 12 min then
|
|
28
|
+
// retry, costing ~24 min total. The toBeVisible auto-waits below
|
|
29
|
+
// already cover the readiness window we actually need.
|
|
30
|
+
for (const tag of tags) {
|
|
31
|
+
await (0, test_1.expect)(this.page.getByText(tag, { exact: true }).first()).toBeVisible({
|
|
32
|
+
timeout: 60000,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.ClusterPage = ClusterPage;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class ConnectorMarketplacePage {
|
|
3
|
+
private page;
|
|
4
|
+
private browseAllConnectorMarketplacePage;
|
|
5
|
+
private connectorName?;
|
|
6
|
+
private legacyMarketplaceDialog;
|
|
7
|
+
readonly searchForConnectorTextbox: Locator;
|
|
8
|
+
readonly downloadToProjectButton: Locator;
|
|
9
|
+
readonly addToProjectButton: Locator;
|
|
10
|
+
readonly replaceResourceButton: Locator;
|
|
11
|
+
readonly saveAsCopyButton: Locator;
|
|
12
|
+
readonly cancelButton: Locator;
|
|
13
|
+
readonly marketplaceErrorMessage: Locator;
|
|
14
|
+
readonly noResultsMessage: Locator;
|
|
15
|
+
constructor(page: Page);
|
|
16
|
+
clickSearchForConnectorTextbox(): Promise<void>;
|
|
17
|
+
fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
|
|
18
|
+
clickDownloadToProjectButton(): Promise<void>;
|
|
19
|
+
clickCancelButton(): Promise<void>;
|
|
20
|
+
clickReplaceResourceButton(): Promise<void>;
|
|
21
|
+
waitForConnectorSearchResults(): Promise<void>;
|
|
22
|
+
downloadConnectorToProject(): Promise<void>;
|
|
23
|
+
private confirmImport;
|
|
24
|
+
private waitForImportToSettle;
|
|
25
|
+
private getLegacyDownloadButton;
|
|
26
|
+
}
|
|
27
|
+
export { ConnectorMarketplacePage };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorMarketplacePage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const BrowseAllConnectorMarketplacePage_1 = require("../BrowseAllConnectorMarketplacePage");
|
|
6
|
+
const sleep_1 = require("../../utils/sleep");
|
|
7
|
+
class ConnectorMarketplacePage {
|
|
8
|
+
page;
|
|
9
|
+
browseAllConnectorMarketplacePage;
|
|
10
|
+
connectorName;
|
|
11
|
+
legacyMarketplaceDialog;
|
|
12
|
+
searchForConnectorTextbox;
|
|
13
|
+
downloadToProjectButton;
|
|
14
|
+
addToProjectButton;
|
|
15
|
+
replaceResourceButton;
|
|
16
|
+
saveAsCopyButton;
|
|
17
|
+
cancelButton;
|
|
18
|
+
marketplaceErrorMessage;
|
|
19
|
+
noResultsMessage;
|
|
20
|
+
constructor(page) {
|
|
21
|
+
this.page = page;
|
|
22
|
+
this.browseAllConnectorMarketplacePage =
|
|
23
|
+
new BrowseAllConnectorMarketplacePage_1.BrowseAllConnectorMarketplacePage(page);
|
|
24
|
+
this.searchForConnectorTextbox =
|
|
25
|
+
this.browseAllConnectorMarketplacePage.searchForConnectorTextbox
|
|
26
|
+
.or(page.getByPlaceholder('Search for a connector'))
|
|
27
|
+
.first();
|
|
28
|
+
this.legacyMarketplaceDialog = page
|
|
29
|
+
.getByRole('dialog', { name: 'Browse Marketplace connectors' })
|
|
30
|
+
.or(page.getByRole('dialog', { name: 'Discover Connectors Dialog' }))
|
|
31
|
+
.first();
|
|
32
|
+
this.downloadToProjectButton = page
|
|
33
|
+
.getByRole('button', { name: 'Download to project' })
|
|
34
|
+
.first();
|
|
35
|
+
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
36
|
+
this.addToProjectButton = page.getByRole('button', {
|
|
37
|
+
name: 'Add to project',
|
|
38
|
+
});
|
|
39
|
+
this.replaceResourceButton = page.getByRole('button', {
|
|
40
|
+
name: 'Replace resource',
|
|
41
|
+
});
|
|
42
|
+
this.saveAsCopyButton = page.getByRole('button', { name: 'Save as copy' });
|
|
43
|
+
this.marketplaceErrorMessage = page.getByText('an error occurred, please try again later');
|
|
44
|
+
this.noResultsMessage = page.getByText('find a match for your search phrase');
|
|
45
|
+
}
|
|
46
|
+
async clickSearchForConnectorTextbox() {
|
|
47
|
+
await this.searchForConnectorTextbox.click({ timeout: 60000 });
|
|
48
|
+
await (0, sleep_1.sleep)(2000);
|
|
49
|
+
}
|
|
50
|
+
async fillSearchForConnectorTextbox(connectorName) {
|
|
51
|
+
this.connectorName = connectorName;
|
|
52
|
+
await this.searchForConnectorTextbox.fill(connectorName, { timeout: 60000 });
|
|
53
|
+
await (0, sleep_1.sleep)(2000);
|
|
54
|
+
}
|
|
55
|
+
async clickDownloadToProjectButton() {
|
|
56
|
+
await this.getLegacyDownloadButton().click({ timeout: 60000 });
|
|
57
|
+
}
|
|
58
|
+
async clickCancelButton() {
|
|
59
|
+
await this.cancelButton.click({ timeout: 30000 });
|
|
60
|
+
}
|
|
61
|
+
async clickReplaceResourceButton() {
|
|
62
|
+
await this.replaceResourceButton.click({ timeout: 30000 });
|
|
63
|
+
}
|
|
64
|
+
async waitForConnectorSearchResults() {
|
|
65
|
+
if (await this.browseAllConnectorMarketplacePage.isOpen()) {
|
|
66
|
+
await this.browseAllConnectorMarketplacePage.waitForConnectorSearchResult(this.connectorName);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const MAX_RETRIES = 4;
|
|
70
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
71
|
+
const downloadButton = this.getLegacyDownloadButton();
|
|
72
|
+
await (0, test_1.expect)(downloadButton
|
|
73
|
+
.or(this.marketplaceErrorMessage)
|
|
74
|
+
.or(this.noResultsMessage)).toBeVisible({ timeout: 60000 });
|
|
75
|
+
if (await downloadButton.isVisible()) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(`Marketplace search attempt ${attempt} of ${MAX_RETRIES} returned an error or no results. Retrying search...`);
|
|
79
|
+
if (attempt < MAX_RETRIES) {
|
|
80
|
+
// The marketplace search API returns transient 5xx. Back off for
|
|
81
|
+
// growing intervals before re-issuing the query so a short outage
|
|
82
|
+
// self-heals instead of burning all attempts inside a few seconds.
|
|
83
|
+
await (0, sleep_1.sleep)(attempt * 5000);
|
|
84
|
+
const searchText = await this.searchForConnectorTextbox.inputValue();
|
|
85
|
+
await this.searchForConnectorTextbox.clear();
|
|
86
|
+
await (0, sleep_1.sleep)(2000);
|
|
87
|
+
await this.searchForConnectorTextbox.fill(searchText);
|
|
88
|
+
await (0, sleep_1.sleep)(2000);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new Error('Marketplace connector search failed after retries due to persistent error or no matching results');
|
|
92
|
+
}
|
|
93
|
+
async downloadConnectorToProject() {
|
|
94
|
+
if (await this.browseAllConnectorMarketplacePage.isOpen()) {
|
|
95
|
+
await this.browseAllConnectorMarketplacePage.importConnectorToProject(this.connectorName);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
await this.clickDownloadToProjectButton();
|
|
99
|
+
if (await this.confirmImport()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// The modal is up but rendered none of the known import actions. Dismiss it
|
|
103
|
+
// so it does not block the next click on the diagram, then fail here:
|
|
104
|
+
// continuing would leave the diagram's connector template unresolved
|
|
105
|
+
// ("Template: Not found") and only surface 60s later as a missing
|
|
106
|
+
// properties-panel group, pointing at the wrong step.
|
|
107
|
+
if (await this.cancelButton.isVisible().catch(() => false)) {
|
|
108
|
+
await this.cancelButton.click({ timeout: 20000 });
|
|
109
|
+
throw new Error('Connector import modal offered no import action ("Add to project", "Replace resource" or "Save as copy"); the import was abandoned.');
|
|
110
|
+
}
|
|
111
|
+
// Hub imported without asking for confirmation, so the write is in flight
|
|
112
|
+
// here too.
|
|
113
|
+
await this.waitForImportToSettle();
|
|
114
|
+
}
|
|
115
|
+
// Hub's import modal labels its primary button after the project state:
|
|
116
|
+
// "Add to project" when the template is new to the project, "Replace
|
|
117
|
+
// resource" when the same template is already there, "Save as copy" on a
|
|
118
|
+
// conflicting version. Probe them in order and click the one actually
|
|
119
|
+
// rendered; clicking "Cancel" instead abandons the import and leaves the
|
|
120
|
+
// diagram's connector template unresolved ("Template: Not found"), which
|
|
121
|
+
// only surfaces much later as a missing properties-panel group.
|
|
122
|
+
async confirmImport() {
|
|
123
|
+
const importButtons = [
|
|
124
|
+
this.addToProjectButton,
|
|
125
|
+
this.replaceResourceButton,
|
|
126
|
+
this.saveAsCopyButton,
|
|
127
|
+
];
|
|
128
|
+
// Carbon animates the modal in, so a click issued mid-transition is
|
|
129
|
+
// rejected ("element is not stable") and by the next Playwright retry the
|
|
130
|
+
// button can already be gone; keep probing the labels until one click
|
|
131
|
+
// actually lands instead of burning the whole budget on a single button.
|
|
132
|
+
const deadline = Date.now() + 45000;
|
|
133
|
+
do {
|
|
134
|
+
for (const button of importButtons) {
|
|
135
|
+
if (!(await button.isVisible().catch(() => false))) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Register the waiter before the click: the write can be acknowledged
|
|
139
|
+
// before the modal finishes unmounting.
|
|
140
|
+
const templateWritten = this.page
|
|
141
|
+
.waitForResponse((response) => response.request().method() === 'POST' &&
|
|
142
|
+
response.url().includes('/internal/files'), { timeout: 30000 })
|
|
143
|
+
.catch(() => null);
|
|
144
|
+
const clicked = await button
|
|
145
|
+
.click({ timeout: 5000 })
|
|
146
|
+
.then(() => true)
|
|
147
|
+
.catch(() => false);
|
|
148
|
+
if (!clicked) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
await (0, test_1.expect)(button).toBeHidden({ timeout: 60000 });
|
|
152
|
+
const written = await templateWritten;
|
|
153
|
+
// A rejected write leaves the project without the template just like
|
|
154
|
+
// an aborted one, so surface the status here instead of letting it
|
|
155
|
+
// resurface 60s later as a missing "Authentication" group in
|
|
156
|
+
// ModelerCreatePage.
|
|
157
|
+
if (written && !written.ok()) {
|
|
158
|
+
throw new Error(`Connector template import failed: writing it into the project returned HTTP ${written.status()}.`);
|
|
159
|
+
}
|
|
160
|
+
await this.waitForImportToSettle();
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
await (0, sleep_1.sleep)(1000);
|
|
164
|
+
} while (Date.now() < deadline);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// Hub unmounts the ImportModal as soon as the click is handled, but writes the
|
|
168
|
+
// template into the project with a POST that is still in flight at that point.
|
|
169
|
+
// Returning right after the button hides let the caller's `page.reload()`
|
|
170
|
+
// abort that write ~20ms later, leaving the project's connector-template list
|
|
171
|
+
// empty and every templated task on the diagram at "Template: Not found" --
|
|
172
|
+
// the properties panel then never renders the "Authentication" group.
|
|
173
|
+
async waitForImportToSettle() {
|
|
174
|
+
await this.page.waitForLoadState('networkidle').catch(() => { });
|
|
175
|
+
}
|
|
176
|
+
getLegacyDownloadButton() {
|
|
177
|
+
if (!this.connectorName) {
|
|
178
|
+
return this.downloadToProjectButton;
|
|
179
|
+
}
|
|
180
|
+
return this.legacyMarketplaceDialog
|
|
181
|
+
.getByText(this.connectorName, { exact: true })
|
|
182
|
+
.first()
|
|
183
|
+
.locator('xpath=ancestor::*[.//button[normalize-space()="Download to project"]][1]')
|
|
184
|
+
.getByRole('button', { name: 'Download to project' });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.ConnectorMarketplacePage = ConnectorMarketplacePage;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class ConnectorSettingsPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly urlInput: Locator;
|
|
5
|
+
readonly resultExpressionInput: Locator;
|
|
6
|
+
readonly authenticationTypeDropdown: Locator;
|
|
7
|
+
readonly usernameInput: Locator;
|
|
8
|
+
readonly passwordInput: Locator;
|
|
9
|
+
readonly methodTypeDropdown: Locator;
|
|
10
|
+
readonly resultVariableInput: Locator;
|
|
11
|
+
readonly bearerTokenInput: Locator;
|
|
12
|
+
readonly authenticationTab: Locator;
|
|
13
|
+
readonly outputMappingTab: Locator;
|
|
14
|
+
readonly httpEndpointTab: Locator;
|
|
15
|
+
readonly correlationTab: Locator;
|
|
16
|
+
readonly inputTab: Locator;
|
|
17
|
+
readonly webhookConfigurationTab: Locator;
|
|
18
|
+
constructor(page: Page);
|
|
19
|
+
clickUrlInput(): Promise<void>;
|
|
20
|
+
fillUrlInput(url: string): Promise<void>;
|
|
21
|
+
clickResultExpressionInput(): Promise<void>;
|
|
22
|
+
fillResultExpressionInput(resultExpression: string): Promise<void>;
|
|
23
|
+
selectAuthenticationType(authentication: string): Promise<void>;
|
|
24
|
+
clickUsernameInput(): Promise<void>;
|
|
25
|
+
fillUsernameInput(username: string): Promise<void>;
|
|
26
|
+
clickPasswordInput(): Promise<void>;
|
|
27
|
+
fillPasswordInput(password: string): Promise<void>;
|
|
28
|
+
selectMethodType(method: string): Promise<void>;
|
|
29
|
+
clickResultVariableInput(): Promise<void>;
|
|
30
|
+
fillResultVariableInput(variable: string): Promise<void>;
|
|
31
|
+
clickBearerTokenInput(): Promise<void>;
|
|
32
|
+
fillBearerTokenInput(token: string): Promise<void>;
|
|
33
|
+
private clickTabWithRetry;
|
|
34
|
+
clickAuthenticationTab(): Promise<void>;
|
|
35
|
+
clickOutputMappingTab(): Promise<void>;
|
|
36
|
+
clickHTTPEndpointTab(): Promise<void>;
|
|
37
|
+
clickCorrelationTab(): Promise<void>;
|
|
38
|
+
clickInputTab(): Promise<void>;
|
|
39
|
+
clickWebhookConfigurationTab(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export { ConnectorSettingsPage };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorSettingsPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
class ConnectorSettingsPage {
|
|
6
|
+
page;
|
|
7
|
+
urlInput;
|
|
8
|
+
resultExpressionInput;
|
|
9
|
+
authenticationTypeDropdown;
|
|
10
|
+
usernameInput;
|
|
11
|
+
passwordInput;
|
|
12
|
+
methodTypeDropdown;
|
|
13
|
+
resultVariableInput;
|
|
14
|
+
bearerTokenInput;
|
|
15
|
+
authenticationTab;
|
|
16
|
+
outputMappingTab;
|
|
17
|
+
httpEndpointTab;
|
|
18
|
+
correlationTab;
|
|
19
|
+
inputTab;
|
|
20
|
+
webhookConfigurationTab;
|
|
21
|
+
constructor(page) {
|
|
22
|
+
this.page = page;
|
|
23
|
+
this.urlInput = page.getByLabel('URL');
|
|
24
|
+
this.resultExpressionInput = page.getByLabel('Result expression');
|
|
25
|
+
this.authenticationTypeDropdown = page.getByLabel('Type');
|
|
26
|
+
this.usernameInput = page.getByLabel('Username');
|
|
27
|
+
this.passwordInput = page.getByLabel('Password', { exact: true });
|
|
28
|
+
this.methodTypeDropdown = page.getByLabel('Method');
|
|
29
|
+
this.resultVariableInput = page.getByLabel('Result variable');
|
|
30
|
+
this.bearerTokenInput = page.getByLabel('Bearer token');
|
|
31
|
+
this.authenticationTab = page
|
|
32
|
+
.locator('div')
|
|
33
|
+
.filter({ hasText: /^Authentication$/ })
|
|
34
|
+
.first();
|
|
35
|
+
this.outputMappingTab = page
|
|
36
|
+
.locator('div')
|
|
37
|
+
.filter({ hasText: /^Output mapping$/ })
|
|
38
|
+
.first();
|
|
39
|
+
this.httpEndpointTab = page
|
|
40
|
+
.locator('div')
|
|
41
|
+
.filter({ hasText: /^HTTP endpoint$/ })
|
|
42
|
+
.first();
|
|
43
|
+
this.correlationTab = page
|
|
44
|
+
.locator('div')
|
|
45
|
+
.filter({ hasText: /^Correlation$/ })
|
|
46
|
+
.first();
|
|
47
|
+
this.inputTab = page
|
|
48
|
+
.locator('div')
|
|
49
|
+
.filter({ hasText: /^Input$/ })
|
|
50
|
+
.first();
|
|
51
|
+
this.webhookConfigurationTab = page
|
|
52
|
+
.locator('div')
|
|
53
|
+
.filter({ hasText: /^Webhook configuration$/ })
|
|
54
|
+
.first();
|
|
55
|
+
}
|
|
56
|
+
async clickUrlInput() {
|
|
57
|
+
try {
|
|
58
|
+
await this.urlInput.click({ timeout: 60000 });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
await this.httpEndpointTab.click();
|
|
62
|
+
await this.urlInput.click({ timeout: 60000 });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async fillUrlInput(url) {
|
|
66
|
+
await this.urlInput.fill(url);
|
|
67
|
+
await (0, test_1.expect)(this.page.getByText('URL must not be empty.')).not.toBeVisible({ timeout: 30000 });
|
|
68
|
+
}
|
|
69
|
+
async clickResultExpressionInput() {
|
|
70
|
+
try {
|
|
71
|
+
await this.resultExpressionInput.click();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
await this.clickOutputMappingTab();
|
|
75
|
+
await this.resultExpressionInput.click();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async fillResultExpressionInput(resultExpression) {
|
|
79
|
+
await this.resultExpressionInput.fill(resultExpression);
|
|
80
|
+
}
|
|
81
|
+
async selectAuthenticationType(authentication) {
|
|
82
|
+
await this.authenticationTypeDropdown.selectOption(authentication);
|
|
83
|
+
}
|
|
84
|
+
async clickUsernameInput() {
|
|
85
|
+
await this.usernameInput.click({ timeout: 30000 });
|
|
86
|
+
}
|
|
87
|
+
async fillUsernameInput(username) {
|
|
88
|
+
await this.usernameInput.fill(username, { timeout: 30000 });
|
|
89
|
+
await (0, test_1.expect)(this.page.getByText('Username must not be empty.')).not.toBeVisible({ timeout: 30000 });
|
|
90
|
+
}
|
|
91
|
+
async clickPasswordInput() {
|
|
92
|
+
await this.passwordInput.click();
|
|
93
|
+
}
|
|
94
|
+
async fillPasswordInput(password) {
|
|
95
|
+
await this.passwordInput.fill(password);
|
|
96
|
+
await (0, test_1.expect)(this.page.getByText('Password must not be empty.')).not.toBeVisible({ timeout: 30000 });
|
|
97
|
+
}
|
|
98
|
+
async selectMethodType(method) {
|
|
99
|
+
await this.methodTypeDropdown.selectOption(method, { timeout: 60000 });
|
|
100
|
+
}
|
|
101
|
+
async clickResultVariableInput() {
|
|
102
|
+
await this.resultVariableInput.click();
|
|
103
|
+
}
|
|
104
|
+
async fillResultVariableInput(variable) {
|
|
105
|
+
await this.resultVariableInput.fill(variable);
|
|
106
|
+
}
|
|
107
|
+
async clickBearerTokenInput() {
|
|
108
|
+
await this.bearerTokenInput.click({ timeout: 30000 });
|
|
109
|
+
}
|
|
110
|
+
async fillBearerTokenInput(token) {
|
|
111
|
+
await this.bearerTokenInput.fill(token);
|
|
112
|
+
await (0, test_1.expect)(this.page.getByText('Bearer token must not be empty.')).not.toBeVisible({ timeout: 30000 });
|
|
113
|
+
}
|
|
114
|
+
// Connector settings tabs can take several seconds to render on slower clusters
|
|
115
|
+
// after the element type is changed. Each clickXxxTab method retries with a
|
|
116
|
+
// reload-free wait so a single slow render doesn't cause a hard timeout failure.
|
|
117
|
+
async clickTabWithRetry(tab, tabName, maxAttempts = 3) {
|
|
118
|
+
let lastError;
|
|
119
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
120
|
+
try {
|
|
121
|
+
await (0, test_1.expect)(tab).toBeVisible({ timeout: 30000 });
|
|
122
|
+
await tab.click({ timeout: 30000 });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
lastError = err;
|
|
127
|
+
console.warn(`clickTabWithRetry: "${tabName}" attempt ${attempt + 1}/${maxAttempts} failed: ${err instanceof Error ? err.message : err}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw lastError instanceof Error
|
|
131
|
+
? lastError
|
|
132
|
+
: new Error(`Tab "${tabName}" not clickable after ${maxAttempts} attempts`);
|
|
133
|
+
}
|
|
134
|
+
async clickAuthenticationTab() {
|
|
135
|
+
await this.clickTabWithRetry(this.authenticationTab, 'Authentication');
|
|
136
|
+
}
|
|
137
|
+
async clickOutputMappingTab() {
|
|
138
|
+
await this.clickTabWithRetry(this.outputMappingTab, 'Output mapping');
|
|
139
|
+
}
|
|
140
|
+
async clickHTTPEndpointTab() {
|
|
141
|
+
await this.clickTabWithRetry(this.httpEndpointTab, 'HTTP endpoint');
|
|
142
|
+
}
|
|
143
|
+
async clickCorrelationTab() {
|
|
144
|
+
await this.clickTabWithRetry(this.correlationTab, 'Correlation');
|
|
145
|
+
}
|
|
146
|
+
async clickInputTab() {
|
|
147
|
+
await this.clickTabWithRetry(this.inputTab, 'Input');
|
|
148
|
+
}
|
|
149
|
+
async clickWebhookConfigurationTab() {
|
|
150
|
+
await this.clickTabWithRetry(this.webhookConfigurationTab, 'Webhook configuration');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.ConnectorSettingsPage = ConnectorSettingsPage;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class ConnectorTemplatePage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly publishToOrgButton: Locator;
|
|
5
|
+
readonly publishButton: Locator;
|
|
6
|
+
constructor(page: Page);
|
|
7
|
+
clickPublishToOrgButton(): Promise<void>;
|
|
8
|
+
clickPublishButton(): Promise<void>;
|
|
9
|
+
publishConnector(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export { ConnectorTemplatePage };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectorTemplatePage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
class ConnectorTemplatePage {
|
|
6
|
+
page;
|
|
7
|
+
publishToOrgButton;
|
|
8
|
+
publishButton;
|
|
9
|
+
constructor(page) {
|
|
10
|
+
this.page = page;
|
|
11
|
+
this.publishButton = page.getByRole('button', {
|
|
12
|
+
name: 'Publish',
|
|
13
|
+
exact: true,
|
|
14
|
+
});
|
|
15
|
+
this.publishToOrgButton = page.locator('[data-test="publish-to-org"]');
|
|
16
|
+
}
|
|
17
|
+
async clickPublishToOrgButton() {
|
|
18
|
+
await this.publishToOrgButton.click({ timeout: 90000 });
|
|
19
|
+
}
|
|
20
|
+
async clickPublishButton() {
|
|
21
|
+
await this.publishButton.click({ timeout: 90000 });
|
|
22
|
+
}
|
|
23
|
+
async publishConnector() {
|
|
24
|
+
try {
|
|
25
|
+
await (0, test_1.expect)(this.publishToOrgButton).toBeVisible({ timeout: 15000 });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
await this.clickPublishToOrgButton();
|
|
31
|
+
await this.clickPublishButton();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.ConnectorTemplatePage = ConnectorTemplatePage;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Page, Locator } from '@playwright/test';
|
|
2
|
+
declare class FormJsPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly aiFormGeneratorButton: Locator;
|
|
5
|
+
readonly formRequestInput: Locator;
|
|
6
|
+
readonly generateFormButton: Locator;
|
|
7
|
+
readonly formEditor: Locator;
|
|
8
|
+
readonly filePicker: Locator;
|
|
9
|
+
readonly generalPanel: Locator;
|
|
10
|
+
readonly keyInput: Locator;
|
|
11
|
+
readonly uploadMultipleFilesToggle: Locator;
|
|
12
|
+
readonly documentReferenceInput: Locator;
|
|
13
|
+
readonly documentPreview: Locator;
|
|
14
|
+
readonly deployDialog: Locator;
|
|
15
|
+
readonly deployFormButton: Locator;
|
|
16
|
+
readonly deployModalSubmitButton: Locator;
|
|
17
|
+
readonly deploySuccessNotification: Locator;
|
|
18
|
+
constructor(page: Page);
|
|
19
|
+
clickAIFormGeneratorButton(): Promise<void>;
|
|
20
|
+
clickFormRequestInput(): Promise<void>;
|
|
21
|
+
fillFormRequestInput(formRequest: string): Promise<void>;
|
|
22
|
+
clickGenerateFormButton(): Promise<void>;
|
|
23
|
+
dragAndDrop(source: Locator, target: Locator): Promise<void>;
|
|
24
|
+
clickGeneralPropertiesPanel(): Promise<void>;
|
|
25
|
+
fillKeyInput(key: string): Promise<void>;
|
|
26
|
+
enableUploadMultipleFiles(): Promise<void>;
|
|
27
|
+
filldocumentReferenceInput(key: string): Promise<void>;
|
|
28
|
+
clickDeployFormButton(): Promise<void>;
|
|
29
|
+
clickDeployModalSubmitButton(): Promise<void>;
|
|
30
|
+
selectOnlyThisResource(): Promise<void>;
|
|
31
|
+
deployForm(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export { FormJsPage };
|