@camunda/e2e-test-suite 0.0.878 → 0.0.880
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.10/ModelerCreatePage.js +16 -13
- 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/tests/SM-8.8/document-handling-user-flows.spec.js +2 -1
- package/package.json +1 -1
|
@@ -103,18 +103,19 @@ class ModelerCreatePage {
|
|
|
103
103
|
this.elemendIdInput = page.getByLabel('ID', { exact: true });
|
|
104
104
|
this.startEventElement = page.locator('.djs-hit').first();
|
|
105
105
|
this.appendPreButton = page.locator('.djs-create-pad-icon > svg').first();
|
|
106
|
-
this.appendTaskButton = page.
|
|
106
|
+
this.appendTaskButton = page.getByRole('button', { name: 'Append task' });
|
|
107
107
|
this.changeTypeButton = page.getByRole('button', {
|
|
108
108
|
name: 'Change element',
|
|
109
109
|
exact: true,
|
|
110
110
|
});
|
|
111
|
-
this.userTaskOption = page
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.
|
|
117
|
-
|
|
111
|
+
this.userTaskOption = page.getByRole('option', { name: 'User task' });
|
|
112
|
+
this.serviceTaskOption = page.getByRole('option', { name: 'Service task' });
|
|
113
|
+
this.appendEndEventButton = page.getByRole('button', {
|
|
114
|
+
name: 'Append end event',
|
|
115
|
+
});
|
|
116
|
+
this.appendGatewayButton = page.getByRole('button', {
|
|
117
|
+
name: 'Append gateway',
|
|
118
|
+
});
|
|
118
119
|
this.parallelGatewayOption = page.getByText('parallel gateway');
|
|
119
120
|
this.dialog = page.getByRole('dialog');
|
|
120
121
|
this.startInstanceMainButton = page.getByRole('button', {
|
|
@@ -147,9 +148,7 @@ class ModelerCreatePage {
|
|
|
147
148
|
.getByRole('dialog')
|
|
148
149
|
.getByRole('button', { name: 'Deploy' });
|
|
149
150
|
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
150
|
-
this.restConnectorOption = page.
|
|
151
|
-
name: 'REST Outbound Connector',
|
|
152
|
-
});
|
|
151
|
+
this.restConnectorOption = page.locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
|
|
153
152
|
this.changeElementSearchInput = page.locator('.djs-popup-search input');
|
|
154
153
|
this.marketPlaceButton = page.getByTitle('Browse Marketplace for more Connectors');
|
|
155
154
|
this.clientIdTextbox = page.getByLabel('Client ID');
|
|
@@ -179,9 +178,13 @@ class ModelerCreatePage {
|
|
|
179
178
|
this.secondPlacedElement = page
|
|
180
179
|
.locator('[class= "djs-element djs-shape"]')
|
|
181
180
|
.last();
|
|
182
|
-
this.connectToOtherElementButton = page.
|
|
181
|
+
this.connectToOtherElementButton = page.getByRole('button', {
|
|
182
|
+
name: 'Connect to other element',
|
|
183
|
+
});
|
|
183
184
|
this.canvas = page.locator('rect').nth(1);
|
|
184
|
-
this.intermediateBoundaryEvent = page.
|
|
185
|
+
this.intermediateBoundaryEvent = page.getByRole('button', {
|
|
186
|
+
name: 'Append intermediate/boundary',
|
|
187
|
+
});
|
|
185
188
|
this.intermediateWebhookConnectorOption = page.getByRole('listitem', {
|
|
186
189
|
name: 'Webhook Intermediate Event Connector',
|
|
187
190
|
});
|
|
@@ -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);
|
|
@@ -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 ||
|