@camunda/e2e-test-suite 0.0.1440 → 0.0.1441
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.7/ClusterDetailsPage.js +14 -0
- package/dist/pages/8.7/ClusterSecretsPage.d.ts +1 -1
- package/dist/pages/8.7/ClusterSecretsPage.js +30 -11
- package/dist/pages/8.7/ConnectorMarketplacePage.d.ts +2 -0
- package/dist/pages/8.7/ConnectorMarketplacePage.js +28 -17
- package/dist/tests/8.7/connectors-user-flows.spec.js +22 -8
- package/package.json +1 -1
|
@@ -458,6 +458,20 @@ class ClusterDetailsPage {
|
|
|
458
458
|
return variables;
|
|
459
459
|
}
|
|
460
460
|
async createAPIClient(name) {
|
|
461
|
+
// The clients table paginates 10 rows per page in no particular order, so
|
|
462
|
+
// once an org holds more than 10 clients the new one is usually not on
|
|
463
|
+
// page 1 and the scopes assertion below never finds its row. Filter the
|
|
464
|
+
// table by the client name up front (the search box only renders once
|
|
465
|
+
// the cluster has at least one client).
|
|
466
|
+
const clientSearch = this.page.getByRole('searchbox', { name: 'Search' });
|
|
467
|
+
await this.createFirstClientButton
|
|
468
|
+
.or(this.createNewClientButton)
|
|
469
|
+
.first()
|
|
470
|
+
.waitFor({ state: 'visible', timeout: 60000 })
|
|
471
|
+
.catch(() => { });
|
|
472
|
+
if (await clientSearch.isVisible().catch(() => false)) {
|
|
473
|
+
await clientSearch.fill(name);
|
|
474
|
+
}
|
|
461
475
|
await this.clickCreateClientButton();
|
|
462
476
|
await (0, test_1.expect)(this.createClientCredentialsDialog).toBeVisible();
|
|
463
477
|
await this.fillAPIClientName(name);
|
|
@@ -15,7 +15,7 @@ declare class ClusterSecretsPage {
|
|
|
15
15
|
readonly clusterSecretTab: Locator;
|
|
16
16
|
readonly confirmDeleteInput: Locator;
|
|
17
17
|
constructor(page: Page);
|
|
18
|
-
deleteConnectorSecretsIfExist(): Promise<void>;
|
|
18
|
+
deleteConnectorSecretsIfExist(keys?: string[]): Promise<void>;
|
|
19
19
|
private dismissOpenModal;
|
|
20
20
|
clickCreateNewSecretButton(clusterName: string): Promise<void>;
|
|
21
21
|
clickClusterBanner(): Promise<void>;
|
|
@@ -31,12 +31,8 @@ class ClusterSecretsPage {
|
|
|
31
31
|
this.connectorSecretsTable = page.getByRole('table', {
|
|
32
32
|
name: 'Connector secrets',
|
|
33
33
|
});
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// "Row actions" (data-slot="data-table-actions-trigger"), with no
|
|
37
|
-
// "Options" cell -- so the old locator found nothing, the pre-clean
|
|
38
|
-
// returned early, and re-creating an existing secret failed with
|
|
39
|
-
// "Secret names must be unique".
|
|
34
|
+
// Console's secrets table moved off Carbon: the per-row overflow menu is
|
|
35
|
+
// labelled "Row actions" instead of living in an "Options" cell.
|
|
40
36
|
this.optionsButton = page
|
|
41
37
|
.getByRole('cell', { name: 'Options' })
|
|
42
38
|
.getByRole('button')
|
|
@@ -81,7 +77,10 @@ class ClusterSecretsPage {
|
|
|
81
77
|
name: 'Type the word DELETE to confirm',
|
|
82
78
|
});
|
|
83
79
|
}
|
|
84
|
-
|
|
80
|
+
// Pass `keys` to delete only those secrets. Without it every deletable
|
|
81
|
+
// secret on the cluster is removed, which also wipes the secrets a
|
|
82
|
+
// concurrent run of a secrets flow on the same cluster has just created.
|
|
83
|
+
async deleteConnectorSecretsIfExist(keys) {
|
|
85
84
|
try {
|
|
86
85
|
await (0, test_1.expect)(this.optionsButton.first()).toBeVisible({
|
|
87
86
|
timeout: 10000,
|
|
@@ -90,9 +89,17 @@ class ClusterSecretsPage {
|
|
|
90
89
|
catch (error) {
|
|
91
90
|
return; //No Connector Secrets found in the list
|
|
92
91
|
}
|
|
92
|
+
const secretRows = keys
|
|
93
|
+
? this.page.getByRole('row').filter({
|
|
94
|
+
has: this.page.getByRole('cell', {
|
|
95
|
+
name: new RegExp(`^(${keys.join('|')})$`),
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
: this.page.getByRole('row');
|
|
99
|
+
const rowOptionsButtons = secretRows.locator(this.optionsButton);
|
|
93
100
|
try {
|
|
94
101
|
for (let sweep = 0; sweep < 20; sweep++) {
|
|
95
|
-
const options = await
|
|
102
|
+
const options = await rowOptionsButtons.all();
|
|
96
103
|
if (options.length === 0) {
|
|
97
104
|
break;
|
|
98
105
|
}
|
|
@@ -129,8 +136,7 @@ class ClusterSecretsPage {
|
|
|
129
136
|
// buttons at once (strict mode violation) while asserting something that
|
|
130
137
|
// is no longer true. The post-condition this helper actually owns is
|
|
131
138
|
// that nothing *deletable* is left behind.
|
|
132
|
-
const deletableSecretRows =
|
|
133
|
-
.getByRole('row')
|
|
139
|
+
const deletableSecretRows = secretRows
|
|
134
140
|
.filter({ has: this.optionsButton })
|
|
135
141
|
.filter({ hasNotText: 'CAMUNDA_PROVIDED_' });
|
|
136
142
|
await (0, test_1.expect)(deletableSecretRows).toHaveCount(0, { timeout: 30000 });
|
|
@@ -148,7 +154,8 @@ class ClusterSecretsPage {
|
|
|
148
154
|
// Closes whichever Carbon modal is currently on screen, and waits for it to
|
|
149
155
|
// actually leave the DOM rather than assuming a fixed delay.
|
|
150
156
|
async dismissOpenModal() {
|
|
151
|
-
|
|
157
|
+
// Carbon modals (.cds--modal) and the Radix dialogs Console renders now.
|
|
158
|
+
const openModal = this.page.locator('.cds--modal.is-visible, [role="dialog"][data-state="open"]');
|
|
152
159
|
if ((await openModal.count().catch(() => 0)) === 0) {
|
|
153
160
|
return;
|
|
154
161
|
}
|
|
@@ -201,6 +208,18 @@ class ClusterSecretsPage {
|
|
|
201
208
|
await (0, test_1.expect)(this.page.getByText('Creating...')).not.toBeVisible({
|
|
202
209
|
timeout: 60000,
|
|
203
210
|
});
|
|
211
|
+
// Console can answer a create right after deleteConnectorSecretsIfExist()
|
|
212
|
+
// with "Secret names must be unique". The dialog then stays open and its
|
|
213
|
+
// overlay blocks every later click, so resubmit until the key is accepted
|
|
214
|
+
// and the dialog closes. This cannot resolve a key that a concurrent run
|
|
215
|
+
// of this flow on the same cluster really does hold.
|
|
216
|
+
const uniqueNameError = this.dialog.getByText('Secret names must be unique');
|
|
217
|
+
await (0, test_1.expect)(async () => {
|
|
218
|
+
if (await uniqueNameError.isVisible()) {
|
|
219
|
+
await this.clickCreateButton();
|
|
220
|
+
}
|
|
221
|
+
await (0, test_1.expect)(this.dialog).toBeHidden({ timeout: 5000 });
|
|
222
|
+
}).toPass({ timeout: 120000, intervals: [5000] });
|
|
204
223
|
}
|
|
205
224
|
async clickKeyInput() {
|
|
206
225
|
await this.keyInput.click();
|
|
@@ -10,6 +10,8 @@ declare class ConnectorMarketplacePage {
|
|
|
10
10
|
readonly tryAgainButton: Locator;
|
|
11
11
|
readonly checkFailedAlert: Locator;
|
|
12
12
|
readonly closeButton: Locator;
|
|
13
|
+
readonly addRemainingElementsButton: Locator;
|
|
14
|
+
readonly alreadyAddedStatus: Locator;
|
|
13
15
|
readonly browseAllMarketplaceDetailsButton: Locator;
|
|
14
16
|
constructor(page: Page);
|
|
15
17
|
clickSearchForConnectorTextbox(): Promise<void>;
|
|
@@ -13,6 +13,8 @@ class ConnectorMarketplacePage {
|
|
|
13
13
|
tryAgainButton;
|
|
14
14
|
checkFailedAlert;
|
|
15
15
|
closeButton;
|
|
16
|
+
addRemainingElementsButton;
|
|
17
|
+
alreadyAddedStatus;
|
|
16
18
|
browseAllMarketplaceDetailsButton;
|
|
17
19
|
constructor(page) {
|
|
18
20
|
this.page = page;
|
|
@@ -40,11 +42,16 @@ class ConnectorMarketplacePage {
|
|
|
40
42
|
name: 'Add to project',
|
|
41
43
|
});
|
|
42
44
|
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
43
|
-
this.tryAgainButton = page.getByRole('button', { name: 'Try again' });
|
|
44
45
|
this.checkFailedAlert = page
|
|
45
46
|
.getByText("Couldn't check this connector")
|
|
46
47
|
.first();
|
|
47
48
|
this.closeButton = page.getByRole('button', { name: 'Close' });
|
|
49
|
+
const marketplaceDetailFooter = page.locator('[data-test="marketplace-detail-footer"]');
|
|
50
|
+
this.addRemainingElementsButton = marketplaceDetailFooter.getByRole('button', { name: 'Add remaining elements' });
|
|
51
|
+
this.tryAgainButton = page.getByRole('button', { name: 'Try again' });
|
|
52
|
+
this.alreadyAddedStatus = marketplaceDetailFooter
|
|
53
|
+
.getByRole('status')
|
|
54
|
+
.filter({ hasText: 'Added to this project' });
|
|
48
55
|
}
|
|
49
56
|
async clickSearchForConnectorTextbox() {
|
|
50
57
|
await this.searchForConnectorTextbox.click({ timeout: 60000 });
|
|
@@ -79,24 +86,18 @@ class ConnectorMarketplacePage {
|
|
|
79
86
|
// immediate isVisible() probes below, which would just repeat the same
|
|
80
87
|
// race this wait exists to close.
|
|
81
88
|
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// Hub retries a transient (5xx) check once by itself before it shows
|
|
90
|
-
// "Try again" (use-marketplace-connector-inspection.ts), and the gateway
|
|
91
|
-
// only answers a hung metadata request with a 504 after ~60s -- so
|
|
92
|
-
// "Try again" first renders ~121s after the click. A 120s wait expired
|
|
93
|
-
// ~1s before that and never reached the retry below; allow both attempts.
|
|
94
|
-
// A "Try again" check keeps the button on screen (loading) and hides the
|
|
95
|
-
// "Couldn't check" alert until it fails again, so wait on that alert
|
|
96
|
-
// rather than the button to retry as soon as a check has failed.
|
|
89
|
+
// Browse all's details panel (camunda-hub MarketplaceConnectorDetails)
|
|
90
|
+
// renders no action button at all once the connector is already in the
|
|
91
|
+
// project -- only an "Added to this project" footer status. Every run of
|
|
92
|
+
// these flows shares the Cross Component Test Project, so after the first
|
|
93
|
+
// run adds REST Connector the old wait for a button always timed out. A
|
|
94
|
+
// partially added connector offers "Add remaining elements", and a failed
|
|
95
|
+
// project check offers "Try again" -- click it and wait again.
|
|
97
96
|
const outcome = this.addToProjectButton
|
|
97
|
+
.or(this.addRemainingElementsButton)
|
|
98
98
|
.or(this.replaceResourceButton)
|
|
99
99
|
.or(this.cancelButton)
|
|
100
|
+
.or(this.alreadyAddedStatus)
|
|
100
101
|
.first();
|
|
101
102
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
102
103
|
await outcome
|
|
@@ -116,15 +117,25 @@ class ConnectorMarketplacePage {
|
|
|
116
117
|
// the old code only handled "Replace resource", so a first-time download
|
|
117
118
|
// (the SaaS nightly always runs in a fresh project) was never confirmed
|
|
118
119
|
// and the connector never reached the diagram. The wait above already
|
|
119
|
-
// confirmed one of the
|
|
120
|
+
// confirmed one of the outcomes is visible, so these are immediate checks.
|
|
120
121
|
if (await this.addToProjectButton.isVisible()) {
|
|
121
122
|
await this.addToProjectButton.click({ timeout: 10000 });
|
|
122
123
|
imported = true;
|
|
123
124
|
}
|
|
125
|
+
else if (await this.addRemainingElementsButton.isVisible()) {
|
|
126
|
+
await this.addRemainingElementsButton.click({ timeout: 10000 });
|
|
127
|
+
imported = true;
|
|
128
|
+
}
|
|
124
129
|
else if (await this.replaceResourceButton.isVisible()) {
|
|
125
130
|
await this.replaceResourceButton.click({ timeout: 10000 });
|
|
126
131
|
imported = true;
|
|
127
132
|
}
|
|
133
|
+
else if (await this.alreadyAddedStatus.isVisible()) {
|
|
134
|
+
// Nothing to import; close the dialog so it does not cover the canvas.
|
|
135
|
+
if (await this.closeButton.isVisible()) {
|
|
136
|
+
await this.closeButton.click({ timeout: 10000 });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
128
139
|
else if (await this.cancelButton.isVisible()) {
|
|
129
140
|
await this.cancelButton.click({ timeout: 10000 });
|
|
130
141
|
}
|
|
@@ -163,27 +163,38 @@ _8_7_1.test.describe('Connectors User Flow Tests', () => {
|
|
|
163
163
|
(0, _8_7_1.test)('Connector Secrets User Flow - GCP', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, connectorSettingsPage, clusterPage, clusterSecretsPage, }) => {
|
|
164
164
|
_8_7_1.test.slow();
|
|
165
165
|
const processName = 'REST_Connector_Process' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
166
|
+
// Per-run secret keys: concurrent runs of this flow share "Test Cluster",
|
|
167
|
+
// so fixed keys made one run's create fail with "Secret names must be
|
|
168
|
+
// unique" and its cleanup delete the secrets another run's instance was
|
|
169
|
+
// about to resolve.
|
|
170
|
+
const secretSuffix = await (0, _setup_1.generateRandomStringAsync)(6);
|
|
171
|
+
const endpointUrlKey = `endpoint_url_${secretSuffix}`;
|
|
172
|
+
const usernameKey = `username_${secretSuffix}`;
|
|
173
|
+
const passwordKey = `password_${secretSuffix}`;
|
|
166
174
|
await _8_7_1.test.step('Navigate to Cluster Secrets', async () => {
|
|
167
175
|
await homePage.clickClusters();
|
|
168
176
|
await clusterPage.clickClusterLink(defaultClusterName);
|
|
169
177
|
await clusterPage.clickConnectorSecretesTab(defaultClusterName);
|
|
170
178
|
});
|
|
171
179
|
await _8_7_1.test.step('Delete Connector Secrets If Exist', async () => {
|
|
172
|
-
await clusterSecretsPage.deleteConnectorSecretsIfExist(
|
|
180
|
+
await clusterSecretsPage.deleteConnectorSecretsIfExist([
|
|
181
|
+
endpointUrlKey,
|
|
182
|
+
usernameKey,
|
|
183
|
+
passwordKey,
|
|
184
|
+
]);
|
|
173
185
|
});
|
|
174
186
|
await _8_7_1.test.step('Create Cluster Secret', async () => {
|
|
175
|
-
await clusterSecretsPage.createNewSecret(defaultClusterName,
|
|
176
|
-
await clusterSecretsPage.createNewSecret(defaultClusterName,
|
|
177
|
-
await clusterSecretsPage.createNewSecret(defaultClusterName,
|
|
187
|
+
await clusterSecretsPage.createNewSecret(defaultClusterName, endpointUrlKey, constants_1.REST_MOCK_BASIC_AUTH_URL);
|
|
188
|
+
await clusterSecretsPage.createNewSecret(defaultClusterName, usernameKey, 'username');
|
|
189
|
+
await clusterSecretsPage.createNewSecret(defaultClusterName, passwordKey, 'password');
|
|
178
190
|
});
|
|
179
191
|
await _8_7_1.test.step('Navigate to Web Modeler', async () => {
|
|
180
192
|
await (0, test_1.expect)(homePage.camundaComponentsButton).toBeVisible({
|
|
181
193
|
timeout: 20000,
|
|
182
194
|
});
|
|
183
195
|
await appsPage.clickCamundaApps();
|
|
184
|
-
// No separate modelerLink check: Hub
|
|
185
|
-
//
|
|
186
|
-
// legacy shell clickModeler() asserts modelerLink itself.
|
|
196
|
+
// No separate modelerLink check: on Hub there is no app-switcher
|
|
197
|
+
// "Modeler" link, and clickModeler() handles both shells.
|
|
187
198
|
await appsPage.clickModeler();
|
|
188
199
|
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
|
|
189
200
|
timeout: 120000,
|
|
@@ -195,7 +206,10 @@ _8_7_1.test.describe('Connectors User Flow Tests', () => {
|
|
|
195
206
|
await modelerHomePage.clickBpmnTemplateOption();
|
|
196
207
|
});
|
|
197
208
|
await _8_7_1.test.step('Create BPMN Diagram with REST Connector using secrets and Start Process Instance', async () => {
|
|
198
|
-
await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName,
|
|
209
|
+
await (0, UtilitiesPage_1.modelRestConnector)(modelerCreatePage, connectorSettingsPage, processName, `secrets.${endpointUrlKey}`, 'basic', '{message:response.body.message}', 'result', {
|
|
210
|
+
username: `secrets.${usernameKey}`,
|
|
211
|
+
password: `secrets.${passwordKey}`,
|
|
212
|
+
});
|
|
199
213
|
await modelerCreatePage.runProcessInstance(defaultClusterName);
|
|
200
214
|
await (0, _setup_1.performBasicAuthPostRequest)(constants_1.REST_MOCK_BASIC_AUTH_URL, 'username', 'password');
|
|
201
215
|
});
|