@camunda/e2e-test-suite 0.0.879 → 0.0.881
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/SM-8.8/ConnectorMarketplacePage.d.ts +4 -0
- package/dist/pages/SM-8.8/ConnectorMarketplacePage.js +57 -7
- package/dist/pages/SM-8.8/ModelerHomePage.d.ts +1 -0
- package/dist/pages/SM-8.8/ModelerHomePage.js +46 -7
- package/dist/pages/SM-8.8/UtilitiesPage.js +6 -35
- package/dist/pages/SM-8.9/ConnectorMarketplacePage.d.ts +1 -0
- package/dist/pages/SM-8.9/ConnectorMarketplacePage.js +22 -7
- package/dist/tests/SM-8.8/document-handling-user-flows.spec.js +2 -1
- package/dist/tests/SM-8.9/document-handling-user-flows.spec.js +2 -1
- package/package.json +1 -1
|
@@ -4,7 +4,10 @@ declare class ConnectorMarketplacePage {
|
|
|
4
4
|
readonly searchForConnectorTextbox: Locator;
|
|
5
5
|
readonly downloadToProjectButton: Locator;
|
|
6
6
|
readonly replaceResourceButton: Locator;
|
|
7
|
+
readonly addToProjectButton: Locator;
|
|
7
8
|
readonly cancelButton: Locator;
|
|
9
|
+
readonly backToMarketplaceButton: Locator;
|
|
10
|
+
readonly downloadErrorMessage: Locator;
|
|
8
11
|
constructor(page: Page);
|
|
9
12
|
clickSearchForConnectorTextbox(): Promise<void>;
|
|
10
13
|
fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
|
|
@@ -12,5 +15,6 @@ declare class ConnectorMarketplacePage {
|
|
|
12
15
|
clickCancelButton(): Promise<void>;
|
|
13
16
|
clickReplaceResourceButton(): Promise<void>;
|
|
14
17
|
downloadConnectorToProject(): Promise<void>;
|
|
18
|
+
searchAndDownloadConnector(connectorName: string, maxRetries?: number): Promise<void>;
|
|
15
19
|
}
|
|
16
20
|
export { ConnectorMarketplacePage };
|
|
@@ -7,7 +7,10 @@ class ConnectorMarketplacePage {
|
|
|
7
7
|
searchForConnectorTextbox;
|
|
8
8
|
downloadToProjectButton;
|
|
9
9
|
replaceResourceButton;
|
|
10
|
+
addToProjectButton;
|
|
10
11
|
cancelButton;
|
|
12
|
+
backToMarketplaceButton;
|
|
13
|
+
downloadErrorMessage;
|
|
11
14
|
constructor(page) {
|
|
12
15
|
this.page = page;
|
|
13
16
|
this.searchForConnectorTextbox = page.getByPlaceholder('Search for a connector');
|
|
@@ -18,6 +21,15 @@ class ConnectorMarketplacePage {
|
|
|
18
21
|
this.replaceResourceButton = page.getByRole('button', {
|
|
19
22
|
name: 'Replace resource',
|
|
20
23
|
});
|
|
24
|
+
this.addToProjectButton = page.getByRole('button', {
|
|
25
|
+
name: 'Add to project',
|
|
26
|
+
});
|
|
27
|
+
this.backToMarketplaceButton = page.getByRole('button', {
|
|
28
|
+
name: 'Back to Marketplace',
|
|
29
|
+
});
|
|
30
|
+
this.downloadErrorMessage = page
|
|
31
|
+
.getByText('Download Error')
|
|
32
|
+
.or(page.getByText('We are sorry, an error occurred'));
|
|
21
33
|
}
|
|
22
34
|
async clickSearchForConnectorTextbox() {
|
|
23
35
|
await this.searchForConnectorTextbox.click({ timeout: 60000 });
|
|
@@ -35,17 +47,55 @@ class ConnectorMarketplacePage {
|
|
|
35
47
|
async clickReplaceResourceButton() {
|
|
36
48
|
await this.replaceResourceButton.click({ timeout: 30000 });
|
|
37
49
|
}
|
|
50
|
+
// The import dialog confirms with either "Add to project" (the resource is new
|
|
51
|
+
// to the project) or "Replace resource" (a file with the same name already
|
|
52
|
+
// exists); on a backend failure it renders a download-error panel with neither
|
|
53
|
+
// button. Probe which one rendered and click only that one - issuing both
|
|
54
|
+
// clicks through Promise.race also fired the Cancel click, so the import was
|
|
55
|
+
// abandoned and the connector template never landed in the project.
|
|
38
56
|
async downloadConnectorToProject() {
|
|
39
57
|
await this.clickDownloadToProjectButton();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
const importConfirmButton = this.addToProjectButton.or(this.replaceResourceButton);
|
|
59
|
+
const settled = await importConfirmButton
|
|
60
|
+
.or(this.downloadErrorMessage)
|
|
61
|
+
.first()
|
|
62
|
+
.waitFor({ state: 'visible', timeout: 20000 })
|
|
63
|
+
.then(() => true)
|
|
64
|
+
.catch(() => false);
|
|
65
|
+
if (settled && (await importConfirmButton.first().isVisible())) {
|
|
66
|
+
await importConfirmButton.first().click({ timeout: 30000 });
|
|
45
67
|
}
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
}
|
|
69
|
+
// The marketplace backend transiently fails in two ways: the search returns an
|
|
70
|
+
// error instead of results, or the resource download from the external
|
|
71
|
+
// marketplace host ends in a download-error panel. Either way no template
|
|
72
|
+
// lands in the project, so re-issue the search and download (clearing the
|
|
73
|
+
// field first) with escalating backoff until one attempt completes cleanly.
|
|
74
|
+
async searchAndDownloadConnector(connectorName, maxRetries = 6) {
|
|
75
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
76
|
+
if (await this.backToMarketplaceButton.isVisible()) {
|
|
77
|
+
await this.backToMarketplaceButton.click({ timeout: 30000 });
|
|
78
|
+
}
|
|
79
|
+
await this.clickSearchForConnectorTextbox();
|
|
80
|
+
await this.searchForConnectorTextbox.fill('', { timeout: 60000 });
|
|
81
|
+
await this.fillSearchForConnectorTextbox(connectorName);
|
|
82
|
+
const isDownloadable = await this.downloadToProjectButton
|
|
83
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
84
|
+
.then(() => true)
|
|
85
|
+
.catch(() => false);
|
|
86
|
+
if (isDownloadable) {
|
|
87
|
+
await this.downloadConnectorToProject();
|
|
88
|
+
if (!(await this.downloadErrorMessage.first().isVisible())) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (attempt < maxRetries) {
|
|
93
|
+
await (0, sleep_1.sleep)(attempt * 5000);
|
|
94
|
+
}
|
|
48
95
|
}
|
|
96
|
+
throw new Error(`Connector "${connectorName}" could not be downloaded from the ` +
|
|
97
|
+
`marketplace after ${maxRetries} attempts; the marketplace search or ` +
|
|
98
|
+
`download API is returning errors.`);
|
|
49
99
|
}
|
|
50
100
|
}
|
|
51
101
|
exports.ConnectorMarketplacePage = ConnectorMarketplacePage;
|
|
@@ -31,6 +31,7 @@ declare class ModelerHomePage {
|
|
|
31
31
|
constructor(page: Page);
|
|
32
32
|
clickCreateNewProjectButton(): Promise<void>;
|
|
33
33
|
enterNewProjectName(name: string): Promise<void>;
|
|
34
|
+
waitForModelerReady(timeoutMs?: number): Promise<void>;
|
|
34
35
|
createCrossComponentProjectFolder(): Promise<void>;
|
|
35
36
|
clickCrossComponentProjectFolder(): Promise<void>;
|
|
36
37
|
clickProcessDiagram(name: string): Promise<void>;
|
|
@@ -5,6 +5,7 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
|
|
7
7
|
const LoginPage_1 = require("./LoginPage");
|
|
8
|
+
const NavigationPage_1 = require("./NavigationPage");
|
|
8
9
|
class ModelerHomePage {
|
|
9
10
|
page;
|
|
10
11
|
defaultFolderName = 'Cross Component Test Project';
|
|
@@ -106,15 +107,53 @@ class ModelerHomePage {
|
|
|
106
107
|
await this.projectNameInput.fill(name);
|
|
107
108
|
await this.projectNameInput.press('Enter');
|
|
108
109
|
}
|
|
110
|
+
// goToModeler() gates on the nav banner, but that banner also renders on the
|
|
111
|
+
// unauthenticated `/modeler/login` page — so navigation can settle on a shell
|
|
112
|
+
// whose project list never loads. Gate on an actionable element (the "New
|
|
113
|
+
// project" button or an existing project folder) instead.
|
|
114
|
+
async waitForModelerReady(timeoutMs = 120000) {
|
|
115
|
+
// .first(): when the project folder already exists on the shared cluster
|
|
116
|
+
// both elements match, and waitFor() requires a single element.
|
|
117
|
+
await this.createNewProjectButton
|
|
118
|
+
.or(this.crossComponentProjectFolder)
|
|
119
|
+
.first()
|
|
120
|
+
.waitFor({ state: 'visible', timeout: timeoutMs });
|
|
121
|
+
}
|
|
109
122
|
async createCrossComponentProjectFolder() {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
const maxAttempts = 3;
|
|
124
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
125
|
+
try {
|
|
126
|
+
await this.waitForModelerReady();
|
|
127
|
+
await this.clickMessageBanner();
|
|
128
|
+
if (await this.crossComponentProjectFolder.isVisible()) {
|
|
129
|
+
console.log('Cross Component Project folder already exists. Clicking into it');
|
|
130
|
+
await this.clickCrossComponentProjectFolder();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await this.clickCreateNewProjectButton();
|
|
134
|
+
await this.enterNewProjectName(this.defaultFolderName);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (attempt < maxAttempts - 1) {
|
|
139
|
+
console.log(`createCrossComponentProjectFolder attempt ${attempt + 1} failed. Re-authenticating and retrying...`);
|
|
140
|
+
// The Modeler SPA requests the OIDC discovery document once per load;
|
|
141
|
+
// when Identity answers 503 it stops at a spinner and never retries,
|
|
142
|
+
// so waiting longer on the same page cannot help. A bare reload can
|
|
143
|
+
// land back on the empty login form without re-authenticating, so
|
|
144
|
+
// re-run the full navigation+login flow.
|
|
145
|
+
try {
|
|
146
|
+
await new NavigationPage_1.NavigationPage(this.page).goToModeler();
|
|
147
|
+
}
|
|
148
|
+
catch (navError) {
|
|
149
|
+
console.log(`Re-navigation to Modeler failed: ${navError}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
115
156
|
}
|
|
116
|
-
await this.clickCreateNewProjectButton();
|
|
117
|
-
await this.enterNewProjectName(this.defaultFolderName);
|
|
118
157
|
}
|
|
119
158
|
async clickCrossComponentProjectFolder() {
|
|
120
159
|
let attempts = 0;
|
|
@@ -443,42 +443,13 @@ async function modelAndRunConnectorsDocHandlingDiagram(modelerCreatePage, modele
|
|
|
443
443
|
await (0, sleep_1.sleep)(10000);
|
|
444
444
|
await modelerCreatePage.clickFirstPlaceRESTConnector();
|
|
445
445
|
await modelerCreatePage.clickChangeTypeButton();
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
// The change-element popup virtualizes its list: connector templates
|
|
453
|
-
// render below the built-in BPMN tasks and are not in the queryable DOM
|
|
454
|
-
// slice until the list is filtered. Type into the popup search box (the
|
|
455
|
-
// same `.djs-popup-search input` selector the bpmn-js/hub e2e tests use)
|
|
456
|
-
// so the REST Outbound Connector entry is rendered before asserting on it.
|
|
457
|
-
// Wait for the search input to render before filling: a bare isVisible()
|
|
458
|
-
// check races the popup re-render and silently skips the fill, leaving the
|
|
459
|
-
// list unfiltered so the off-screen connector entry never appears.
|
|
460
|
-
await (0, test_1.expect)(modelerCreatePage.changeElementSearchInput).toBeVisible({
|
|
461
|
-
timeout: 30000,
|
|
462
|
-
});
|
|
463
|
-
await modelerCreatePage.changeElementSearchInput.fill('REST Outbound');
|
|
464
|
-
await (0, test_1.expect)(modelerCreatePage.restConnectorOption).toBeVisible({
|
|
465
|
-
timeout: 15000,
|
|
466
|
-
});
|
|
467
|
-
await modelerCreatePage.restConnectorOption.scrollIntoViewIfNeeded();
|
|
468
|
-
await modelerCreatePage.restConnectorOption.click({
|
|
469
|
-
timeout: 60000,
|
|
470
|
-
force: true,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
catch {
|
|
474
|
-
await modelerCreatePage.clickMarketPlaceButton();
|
|
475
|
-
await connectorMarketplacePage.clickSearchForConnectorTextbox();
|
|
476
|
-
await connectorMarketplacePage.fillSearchForConnectorTextbox('REST Connector');
|
|
477
|
-
await connectorMarketplacePage.downloadConnectorToProject();
|
|
478
|
-
}
|
|
479
|
-
await (0, sleep_1.sleep)(5000);
|
|
446
|
+
// SM Web Modeler ships no bundled marketplace connectors, so the
|
|
447
|
+
// change-element popup never offers the REST Outbound Connector and the
|
|
448
|
+
// template has to come from the marketplace.
|
|
449
|
+
await modelerCreatePage.clickMarketPlaceButton();
|
|
450
|
+
await connectorMarketplacePage.searchAndDownloadConnector('REST Connector');
|
|
480
451
|
await page.reload();
|
|
481
|
-
await
|
|
452
|
+
await page.waitForLoadState('networkidle');
|
|
482
453
|
await modelerCreatePage.fillStartEventVariablesForDocHandling(zeebeUrl, zeebeClientId, zeebeClientSecret);
|
|
483
454
|
await modelerCreatePage.setOAuthTokenEndpoint(page.locator('.djs-hit').first(), endpoint);
|
|
484
455
|
await (0, sleep_1.sleep)(2000);
|
|
@@ -4,6 +4,7 @@ declare class ConnectorMarketplacePage {
|
|
|
4
4
|
readonly searchForConnectorTextbox: Locator;
|
|
5
5
|
readonly downloadToProjectButton: Locator;
|
|
6
6
|
readonly replaceResourceButton: Locator;
|
|
7
|
+
readonly addToProjectButton: Locator;
|
|
7
8
|
readonly cancelButton: Locator;
|
|
8
9
|
constructor(page: Page);
|
|
9
10
|
clickSearchForConnectorTextbox(): Promise<void>;
|
|
@@ -7,6 +7,7 @@ class ConnectorMarketplacePage {
|
|
|
7
7
|
searchForConnectorTextbox;
|
|
8
8
|
downloadToProjectButton;
|
|
9
9
|
replaceResourceButton;
|
|
10
|
+
addToProjectButton;
|
|
10
11
|
cancelButton;
|
|
11
12
|
constructor(page) {
|
|
12
13
|
this.page = page;
|
|
@@ -18,6 +19,9 @@ class ConnectorMarketplacePage {
|
|
|
18
19
|
this.replaceResourceButton = page.getByRole('button', {
|
|
19
20
|
name: 'Replace resource',
|
|
20
21
|
});
|
|
22
|
+
this.addToProjectButton = page.getByRole('button', {
|
|
23
|
+
name: 'Add to project',
|
|
24
|
+
});
|
|
21
25
|
}
|
|
22
26
|
async clickSearchForConnectorTextbox() {
|
|
23
27
|
await this.searchForConnectorTextbox.click({ timeout: 60000 });
|
|
@@ -36,17 +40,28 @@ class ConnectorMarketplacePage {
|
|
|
36
40
|
async clickReplaceResourceButton() {
|
|
37
41
|
await this.replaceResourceButton.click({ timeout: 30000 });
|
|
38
42
|
}
|
|
43
|
+
// Hub only opens its ImportModal when the download needs confirmation, and the
|
|
44
|
+
// primary button depends on project state: "Add to project" when the template
|
|
45
|
+
// is not in the project yet, "Replace resource" when one already exists.
|
|
46
|
+
// "Cancel" aborts the import, so the primary button is chosen explicitly --
|
|
47
|
+
// racing the two clicks lets Cancel win and silently drops the template.
|
|
39
48
|
async downloadConnectorToProject() {
|
|
40
49
|
await this.clickDownloadToProjectButton();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const primaryButton = this.addToProjectButton.or(this.replaceResourceButton);
|
|
51
|
+
const modalOpened = await primaryButton
|
|
52
|
+
.first()
|
|
53
|
+
.waitFor({ state: 'visible', timeout: 20000 })
|
|
54
|
+
.then(() => true)
|
|
55
|
+
.catch(() => false);
|
|
56
|
+
if (!modalOpened) {
|
|
48
57
|
return;
|
|
49
58
|
}
|
|
59
|
+
if (await this.addToProjectButton.isVisible()) {
|
|
60
|
+
await this.addToProjectButton.click({ timeout: 20000 });
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
await this.replaceResourceButton.click({ timeout: 20000 });
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
// The marketplace search backend can transiently return HTTP 504
|
|
52
67
|
// ("upstream request timeout"), which leaves the modal in an error or
|
|
@@ -114,7 +114,8 @@ if (process.env.IS_DS === 'true') {
|
|
|
114
114
|
await operateProcessInstancePage.assertProcessVariableContainsText('Upload_Files', 'aws');
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
|
-
|
|
117
|
+
// Skipped due to bug #8162: https://github.com/camunda/connectors/issues/8162
|
|
118
|
+
SM_8_8_1.test.skip('Document Handling Connectors User Flow - AWS @tasklistV2', async ({ page, navigationPage, modelerHomePage, modelerCreatePage, operateHomePage, operateProcessesPage, connectorMarketplacePage, operateProcessInstancePage, ocIdentityHomePage, ocIdentityRolesPage, }) => {
|
|
118
119
|
const processName = 'Document_Handling_Connectors_User_Flow_AWS_Process' +
|
|
119
120
|
(await (0, _setup_1.generateRandomStringAsync)(3));
|
|
120
121
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL ||
|
|
@@ -116,7 +116,8 @@ if (process.env.IS_DS === 'true') {
|
|
|
116
116
|
await operateProcessInstancePage.assertProcessVariableContainsText('Upload_Files', 'aws');
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
|
-
|
|
119
|
+
// Skipped due to bug #8162: https://github.com/camunda/connectors/issues/8162
|
|
120
|
+
SM_8_9_1.test.skip('Document Handling Connectors User Flow - AWS @tasklistV2', async ({ page, navigationPage, modelerHomePage, modelerCreatePage, operateHomePage, operateProcessesPage, connectorMarketplacePage, operateProcessInstancePage, ocIdentityHomePage, ocIdentityRolesPage, }) => {
|
|
120
121
|
const processName = 'Document_Handling_Connectors_User_Flow_AWS_Process' +
|
|
121
122
|
(await (0, _setup_1.generateRandomStringAsync)(3));
|
|
122
123
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL ||
|