@camunda/e2e-test-suite 0.0.1602 → 0.0.1603
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/BrowseAllConnectorMarketplacePage.js +18 -1
- package/dist/pages/SM-8.10/IdentityTenantPage.d.ts +1 -0
- package/dist/pages/SM-8.10/IdentityTenantPage.js +15 -0
- package/dist/pages/SM-8.10/ModelerHomePage.js +23 -4
- package/dist/pages/SM-8.11/IdentityTenantPage.d.ts +1 -0
- package/dist/pages/SM-8.11/IdentityTenantPage.js +15 -0
- package/dist/pages/SM-8.9/ConnectorTemplatePage.js +31 -17
- package/package.json +1 -1
|
@@ -225,7 +225,24 @@ class BrowseAllConnectorMarketplacePage {
|
|
|
225
225
|
if (exactVisible) {
|
|
226
226
|
return exactCard;
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
// When the Marketplace listing request fails (e.g. Web Modeler's
|
|
229
|
+
// /api/external/marketplace/connectors proxy answers 500), Browse All
|
|
230
|
+
// shows a "Couldn't load Marketplace connectors" alert and never
|
|
231
|
+
// re-requests on its own. Click its "Try again" button while polling so
|
|
232
|
+
// a short upstream blip self-heals; a sustained outage still fails here.
|
|
233
|
+
const marketplaceLoadRetryButton = this.page
|
|
234
|
+
.getByRole('alert')
|
|
235
|
+
.filter({ hasText: /couldn.t load marketplace connectors/i })
|
|
236
|
+
.getByRole('button', { name: 'Try again' });
|
|
237
|
+
let marketplaceLoadRetries = 0;
|
|
238
|
+
await (0, test_1.expect)(async () => {
|
|
239
|
+
if ((await relatedCard.count()) === 0 &&
|
|
240
|
+
(await marketplaceLoadRetryButton.isVisible())) {
|
|
241
|
+
marketplaceLoadRetries++;
|
|
242
|
+
await marketplaceLoadRetryButton.click({ timeout: 10000 });
|
|
243
|
+
}
|
|
244
|
+
(0, test_1.expect)(await relatedCard.count(), `Browse All returned no Marketplace connector matching "${connectorName}" (Marketplace load retried ${marketplaceLoadRetries}x).`).toBeGreaterThan(0);
|
|
245
|
+
}).toPass({ timeout: 105000, intervals: [2000, 5000, 10000] });
|
|
229
246
|
if (await exactCard.isVisible()) {
|
|
230
247
|
return exactCard;
|
|
231
248
|
}
|
|
@@ -38,6 +38,7 @@ declare class IdentityTenantPage {
|
|
|
38
38
|
clickRemoveButton(): Promise<void>;
|
|
39
39
|
createTenant(tenantName: string): Promise<void>;
|
|
40
40
|
clickTenant(tenantName: string): Promise<void>;
|
|
41
|
+
private isTenantDetailsOpen;
|
|
41
42
|
private isRowVisible;
|
|
42
43
|
assignUserToTenant(tenantName: string, user: string, userEmail: string): Promise<void>;
|
|
43
44
|
removeUserFromTenant(tenantName: string): Promise<void>;
|
|
@@ -158,6 +158,13 @@ class IdentityTenantPage {
|
|
|
158
158
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
159
159
|
try {
|
|
160
160
|
await this.page.waitForLoadState('domcontentloaded').catch(() => { });
|
|
161
|
+
// A failed assignUserToTenant attempt reloads the tenant *details*
|
|
162
|
+
// page, which has no tenants list. Its "Name | Default" row matches
|
|
163
|
+
// this.row() but has no clickable list cell, so every retry timed
|
|
164
|
+
// out. If the tenant is already open, there is nothing to click.
|
|
165
|
+
if (await this.isTenantDetailsOpen(tenantName)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
161
168
|
const tenantRow = this.row(tenantName);
|
|
162
169
|
await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, tenantRow);
|
|
163
170
|
await tenantRow.locator('td').first().click({ timeout: 30000 });
|
|
@@ -174,6 +181,14 @@ class IdentityTenantPage {
|
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
}
|
|
184
|
+
async isTenantDetailsOpen(tenantName) {
|
|
185
|
+
const heading = this.page.getByRole('heading', {
|
|
186
|
+
level: 1,
|
|
187
|
+
name: tenantName,
|
|
188
|
+
exact: true,
|
|
189
|
+
});
|
|
190
|
+
return (await heading.isVisible()) && (await this.usersTab.isVisible());
|
|
191
|
+
}
|
|
177
192
|
async isRowVisible(name) {
|
|
178
193
|
await (0, sleep_1.sleep)(1000);
|
|
179
194
|
if (await this.row(name).isVisible()) {
|
|
@@ -984,10 +984,29 @@ class ModelerHomePage {
|
|
|
984
984
|
await this.clickWorkspaceBreadcrumb();
|
|
985
985
|
return;
|
|
986
986
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
987
|
+
// A goto onto the deep link can strand Hub on its app-shell "Loading"
|
|
988
|
+
// spinner: when the silent Keycloak refresh (/modeler/login ->
|
|
989
|
+
// /modeler/login-callback) stalls, the header logo points at
|
|
990
|
+
// /modeler/login and the container never mounts, however long we wait.
|
|
991
|
+
// Re-navigating to the same absolute URL restarts that login flow (a
|
|
992
|
+
// page.reload() would not -- see clickDiagramTypeDropdown()), so retry
|
|
993
|
+
// the goto instead of spending one 90s budget on a dead shell.
|
|
994
|
+
const maxAttempts = 3;
|
|
995
|
+
for (let attempt = 1;; attempt++) {
|
|
996
|
+
await this.page.goto(this.processApplicationUrl, {
|
|
997
|
+
waitUntil: 'domcontentloaded',
|
|
998
|
+
});
|
|
999
|
+
try {
|
|
1000
|
+
await this.processApplicationPage.waitForProcessApplication(60000);
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
if (attempt >= maxAttempts) {
|
|
1005
|
+
throw error;
|
|
1006
|
+
}
|
|
1007
|
+
console.log(`returnToResourceContainer: process application not rendered after attempt ${attempt}, re-navigating...`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
991
1010
|
await this.clickMessageBanner();
|
|
992
1011
|
}
|
|
993
1012
|
// Single seam for "give me an empty BPMN diagram in this test's container".
|
|
@@ -38,6 +38,7 @@ declare class IdentityTenantPage {
|
|
|
38
38
|
clickRemoveButton(): Promise<void>;
|
|
39
39
|
createTenant(tenantName: string): Promise<void>;
|
|
40
40
|
clickTenant(tenantName: string): Promise<void>;
|
|
41
|
+
private isTenantDetailsOpen;
|
|
41
42
|
private isRowVisible;
|
|
42
43
|
assignUserToTenant(tenantName: string, user: string, userEmail: string): Promise<void>;
|
|
43
44
|
removeUserFromTenant(tenantName: string): Promise<void>;
|
|
@@ -158,6 +158,13 @@ class IdentityTenantPage {
|
|
|
158
158
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
159
159
|
try {
|
|
160
160
|
await this.page.waitForLoadState('domcontentloaded').catch(() => { });
|
|
161
|
+
// A failed assignUserToTenant attempt reloads the tenant *details*
|
|
162
|
+
// page, which has no tenants list. Its "Name | Default" row matches
|
|
163
|
+
// this.row() but has no clickable list cell, so every retry timed
|
|
164
|
+
// out. If the tenant is already open, there is nothing to click.
|
|
165
|
+
if (await this.isTenantDetailsOpen(tenantName)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
161
168
|
const tenantRow = this.row(tenantName);
|
|
162
169
|
await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, tenantRow);
|
|
163
170
|
await tenantRow.locator('td').first().click({ timeout: 30000 });
|
|
@@ -174,6 +181,14 @@ class IdentityTenantPage {
|
|
|
174
181
|
}
|
|
175
182
|
}
|
|
176
183
|
}
|
|
184
|
+
async isTenantDetailsOpen(tenantName) {
|
|
185
|
+
const heading = this.page.getByRole('heading', {
|
|
186
|
+
level: 1,
|
|
187
|
+
name: tenantName,
|
|
188
|
+
exact: true,
|
|
189
|
+
});
|
|
190
|
+
return (await heading.isVisible()) && (await this.usersTab.isVisible());
|
|
191
|
+
}
|
|
177
192
|
async isRowVisible(name) {
|
|
178
193
|
await (0, sleep_1.sleep)(1000);
|
|
179
194
|
if (await this.row(name).isVisible()) {
|
|
@@ -93,20 +93,20 @@ class ConnectorTemplatePage {
|
|
|
93
93
|
// Replace the whole content of the open template file. Web Modeler's Monaco
|
|
94
94
|
// editor uses the EditContext API, under which keyboard.insertText of a
|
|
95
95
|
// multi-line string is re-indented line by line (and can end up as invalid
|
|
96
|
-
// JSON, which disables Publish). Pasting is applied verbatim,
|
|
97
|
-
//
|
|
96
|
+
// JSON, which disables Publish). Pasting is applied verbatim, but a
|
|
97
|
+
// ControlOrMeta+V key press never triggers a native paste in headless
|
|
98
|
+
// Chromium (the nightly showed the editor unchanged after it). So the JSON
|
|
99
|
+
// is delivered as a `paste` ClipboardEvent dispatched on the element Monaco
|
|
100
|
+
// focused, whose handler reads the text from `clipboardData`.
|
|
98
101
|
async replaceTemplateJson(json, expectedText) {
|
|
99
102
|
const editor = this.page.locator('.monaco-editor').first();
|
|
100
103
|
await (0, test_1.expect)(editor).toBeVisible({ timeout: 90000 });
|
|
101
|
-
await this.page
|
|
102
|
-
.context()
|
|
103
|
-
.grantPermissions(['clipboard-read', 'clipboard-write']);
|
|
104
|
-
await this.page.evaluate(async (text) => await navigator.clipboard.writeText(text), json);
|
|
105
|
-
// Click inside the visible text area, just right of the line numbers:
|
|
106
|
-
// clicking the centre of `.view-lines` can land far right on a long line
|
|
107
|
-
// and miss the editor, so the shortcuts below would act on the page
|
|
108
|
-
// instead. Monaco marks the editor `focused` once it owns the keyboard.
|
|
109
104
|
await (0, test_1.expect)(async () => {
|
|
105
|
+
// Click inside the visible text area, just right of the line numbers:
|
|
106
|
+
// clicking the centre of `.view-lines` can land far right on a long
|
|
107
|
+
// line and miss the editor, so the shortcuts below would act on the
|
|
108
|
+
// page instead. Monaco marks the editor `focused` once it owns the
|
|
109
|
+
// keyboard.
|
|
110
110
|
const box = await editor.boundingBox();
|
|
111
111
|
if (!box) {
|
|
112
112
|
throw new Error('Template editor has no bounding box');
|
|
@@ -116,13 +116,27 @@ class ConnectorTemplatePage {
|
|
|
116
116
|
timeout: 10000,
|
|
117
117
|
});
|
|
118
118
|
await (0, test_1.expect)(editor).toHaveClass(/\bfocused\b/, { timeout: 5000 });
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
119
|
+
await this.page.keyboard.press('ControlOrMeta+A');
|
|
120
|
+
const pasted = await editor.evaluate((root, text) => {
|
|
121
|
+
const target = document.activeElement;
|
|
122
|
+
if (!target || !root.contains(target)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const clipboardData = new DataTransfer();
|
|
126
|
+
clipboardData.setData('text/plain', text);
|
|
127
|
+
target.dispatchEvent(new ClipboardEvent('paste', {
|
|
128
|
+
clipboardData,
|
|
129
|
+
bubbles: true,
|
|
130
|
+
cancelable: true,
|
|
131
|
+
}));
|
|
132
|
+
return true;
|
|
133
|
+
}, json);
|
|
134
|
+
(0, test_1.expect)(pasted, 'Monaco lost focus before the paste').toBeTruthy();
|
|
135
|
+
await this.page.keyboard.press('ControlOrMeta+Home');
|
|
136
|
+
await (0, test_1.expect)(editor.locator('.view-lines')).toContainText(expectedText, {
|
|
137
|
+
timeout: 10000,
|
|
138
|
+
});
|
|
139
|
+
}).toPass({ timeout: 90000 });
|
|
126
140
|
await (0, test_1.expect)(this.page.getByText('Your JSON is invalid')).toBeHidden({
|
|
127
141
|
timeout: 30000,
|
|
128
142
|
});
|