@camunda/e2e-test-suite 0.0.1041 → 0.0.1043
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.8/ModelerCreatePage.js +12 -1
- package/dist/pages/8.8/ModelerHomePage.d.ts +2 -0
- package/dist/pages/8.8/ModelerHomePage.js +23 -0
- package/dist/pages/SM-8.10/Authorization.d.ts +2 -0
- package/dist/pages/SM-8.10/Authorization.js +80 -6
- package/dist/pages/SM-8.10/OCIdentityMappingRulesPage.js +8 -3
- package/dist/pages/SM-8.10/OCIdentityRolesPage.js +10 -0
- package/dist/tests/8.8/idp-user-flows.spec.js +10 -8
- package/dist/tests/8.8/optimize-user-flows.spec.js +2 -1
- package/dist/tests/SM-8.10/rba-enabled-user-flows.spec.js +1 -0
- package/package.json +1 -1
|
@@ -903,7 +903,18 @@ class ModelerCreatePage {
|
|
|
903
903
|
await (0, test_1.expect)(this.deploySubButton).toBeVisible({
|
|
904
904
|
timeout: 60000,
|
|
905
905
|
});
|
|
906
|
-
|
|
906
|
+
// The modal renders Deploy enabled while it is still showing
|
|
907
|
+
// "Checking cluster availability", then disables it for
|
|
908
|
+
// ~0.1-2s once the cluster list lands and the deployment
|
|
909
|
+
// policy / linked-form checks run. A forced click inside that
|
|
910
|
+
// window is swallowed by the disabled button -- no deployment
|
|
911
|
+
// request is sent and the dialog never closes. Wait for the
|
|
912
|
+
// button to settle, and click without `force` so Playwright
|
|
913
|
+
// re-checks it is enabled at click time.
|
|
914
|
+
await (0, test_1.expect)(this.deploySubButton).toBeEnabled({
|
|
915
|
+
timeout: 20000,
|
|
916
|
+
});
|
|
917
|
+
await this.deploySubButton.click({ timeout: 20000 });
|
|
907
918
|
return true;
|
|
908
919
|
}
|
|
909
920
|
catch {
|
|
@@ -24,6 +24,7 @@ declare class ModelerHomePage {
|
|
|
24
24
|
readonly uploadFilesButton: Locator;
|
|
25
25
|
readonly messageBanner: Locator;
|
|
26
26
|
readonly idpTemplateOption: Locator;
|
|
27
|
+
readonly idpEmptyStateButton: Locator;
|
|
27
28
|
readonly idpApplicationNameInput: Locator;
|
|
28
29
|
readonly createIdpApplicationButton: Locator;
|
|
29
30
|
readonly dialog: Locator;
|
|
@@ -67,6 +68,7 @@ declare class ModelerHomePage {
|
|
|
67
68
|
clickMessageBanner(): Promise<void>;
|
|
68
69
|
selectCluster(clusterName: string): Promise<void>;
|
|
69
70
|
clickIdpApplicationTemplateOption(): Promise<void>;
|
|
71
|
+
startIdpApplicationCreation(): Promise<void>;
|
|
70
72
|
assertFormBreadcrumbVisible(formName: string): Promise<void>;
|
|
71
73
|
}
|
|
72
74
|
export { ModelerHomePage };
|
|
@@ -43,6 +43,7 @@ class ModelerHomePage {
|
|
|
43
43
|
uploadFilesButton;
|
|
44
44
|
messageBanner;
|
|
45
45
|
idpTemplateOption;
|
|
46
|
+
idpEmptyStateButton;
|
|
46
47
|
idpApplicationNameInput;
|
|
47
48
|
createIdpApplicationButton;
|
|
48
49
|
dialog;
|
|
@@ -74,6 +75,9 @@ class ModelerHomePage {
|
|
|
74
75
|
this.idpTemplateOption = page
|
|
75
76
|
.locator('[data-test="create-idp-application"]')
|
|
76
77
|
.getByText('IDP Application');
|
|
78
|
+
this.idpEmptyStateButton = page.getByRole('button', {
|
|
79
|
+
name: 'Create IDP application',
|
|
80
|
+
});
|
|
77
81
|
this.diagramTypeDropdown = page.locator('[data-test="diagram-dropdown"]');
|
|
78
82
|
// Matched on the test id alone: hub's design-system variant of the create
|
|
79
83
|
// menu renders "BPMN diagram" (lowercase d), so a text filter breaks the
|
|
@@ -605,6 +609,25 @@ class ModelerHomePage {
|
|
|
605
609
|
async clickIdpApplicationTemplateOption() {
|
|
606
610
|
await this.idpTemplateOption.click({ timeout: 120000 });
|
|
607
611
|
}
|
|
612
|
+
// Which control starts an IDP application depends on whether the workspace
|
|
613
|
+
// root already holds a resource: hub's EntityTable renders the toolbar's
|
|
614
|
+
// create menu OR the empty state, never both (same split
|
|
615
|
+
// clickCreateProcessApplication has to straddle). A workspace whose very
|
|
616
|
+
// first resource is the IDP application only ever offers the empty state's
|
|
617
|
+
// "Create IDP application" button, so waiting for the create menu there
|
|
618
|
+
// times out. Race both entry points, then use whichever one rendered.
|
|
619
|
+
async startIdpApplicationCreation() {
|
|
620
|
+
const rootLoaded = await this.becameVisible(this.idpEmptyStateButton.or(this.diagramTypeDropdown).first(), 60000);
|
|
621
|
+
if (!rootLoaded) {
|
|
622
|
+
await this.page.reload();
|
|
623
|
+
}
|
|
624
|
+
if (await this.becameVisible(this.idpEmptyStateButton, 5000)) {
|
|
625
|
+
await this.idpEmptyStateButton.click({ timeout: 60000 });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
await this.clickWorkspaceDiagramTypeDropdown();
|
|
629
|
+
await this.clickIdpApplicationTemplateOption();
|
|
630
|
+
}
|
|
608
631
|
async assertFormBreadcrumbVisible(formName) {
|
|
609
632
|
await (0, test_1.expect)(this.formNameBreadcrumb(formName)).toBeVisible({
|
|
610
633
|
timeout: 120000,
|
|
@@ -29,6 +29,8 @@ declare class Authorization {
|
|
|
29
29
|
resourceId: string;
|
|
30
30
|
accessPermissions: string[];
|
|
31
31
|
}): Promise<void>;
|
|
32
|
+
private expectAuthorizationListed;
|
|
33
|
+
private filterAuthorizationsByOwner;
|
|
32
34
|
deleteAuthorization(resourceId: string, resourceType: string): Promise<void>;
|
|
33
35
|
}
|
|
34
36
|
export { Authorization };
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Authorization = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const UtilitiesPage_1 = require("../SM-8.10/UtilitiesPage");
|
|
6
|
+
const findLocatorWithPagination_1 = require("../../utils/assertionHelpers/findLocatorWithPagination");
|
|
6
7
|
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
7
8
|
const sleep_1 = require("../../utils/sleep");
|
|
8
9
|
class Authorization {
|
|
@@ -89,7 +90,13 @@ class Authorization {
|
|
|
89
90
|
this.deleteAuthorizationModal.getByRole('button', {
|
|
90
91
|
name: /delete authorization/i,
|
|
91
92
|
});
|
|
92
|
-
|
|
93
|
+
// Carbon labels the table toolbar search "Filter table"; the
|
|
94
|
+
// design-system shell the Admin is migrating onto exposes the placeholder
|
|
95
|
+
// as the accessible name instead. Match either.
|
|
96
|
+
this.tableFilter = page
|
|
97
|
+
.getByRole('searchbox', { name: 'Filter table' })
|
|
98
|
+
.or(page.getByRole('searchbox', { name: 'Search by owner ID' }))
|
|
99
|
+
.first();
|
|
93
100
|
}
|
|
94
101
|
// Released 8.10 alpha images still render the Carbon combobox that lists
|
|
95
102
|
// every owner, so accept whichever owner field this build shows.
|
|
@@ -162,14 +169,81 @@ class Authorization {
|
|
|
162
169
|
}
|
|
163
170
|
await (0, submitModalWithRetry_1.submitModalWithRetry)(this.createAuthorizationSubmitButton, this.createAuthorizationModal);
|
|
164
171
|
await this.selectResourceTypeTab(authorization.resourceType).click();
|
|
165
|
-
const item = this.page
|
|
172
|
+
const item = this.page
|
|
173
|
+
.getByRole('row')
|
|
174
|
+
.filter({
|
|
166
175
|
hasText: `${authorization.ownerId}${authorization.resourceId}`,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
await this.
|
|
170
|
-
|
|
176
|
+
})
|
|
177
|
+
.first();
|
|
178
|
+
await this.expectAuthorizationListed(authorization, item);
|
|
179
|
+
}
|
|
180
|
+
// The toolbar search filters by owner ID, so filling it with the resource
|
|
181
|
+
// type narrowed nothing, and the retry helper it fed reloads before it
|
|
182
|
+
// asserts, which dropped the filter entirely. That left the new row to be
|
|
183
|
+
// found on page 1 of the unfiltered table - and the authorizations table
|
|
184
|
+
// paginates, so with the RBA specs creating owners in parallel it is
|
|
185
|
+
// regularly on a later page. Filter by owner, then page through what is
|
|
186
|
+
// left; fall back to the unfiltered table for builds whose search has not
|
|
187
|
+
// caught up with a just-created owner yet.
|
|
188
|
+
async expectAuthorizationListed(authorization, item) {
|
|
189
|
+
const deadline = Date.now() + 180000;
|
|
190
|
+
const budget = (max) => Math.max(5000, Math.min(max, deadline - Date.now()));
|
|
191
|
+
const maxAttempts = 5;
|
|
192
|
+
let lastError;
|
|
193
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
194
|
+
if (attempt > 0 && Date.now() >= deadline) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const resourceTab = this.selectResourceTypeTab(authorization.resourceType);
|
|
199
|
+
await (0, test_1.expect)(resourceTab).toBeVisible({ timeout: budget(30000) });
|
|
200
|
+
await resourceTab.click({ timeout: budget(30000) });
|
|
201
|
+
for (const filterValue of [authorization.ownerId, '']) {
|
|
202
|
+
await this.filterAuthorizationsByOwner(filterValue);
|
|
203
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, item, {
|
|
204
|
+
timeout: budget(filterValue === '' ? 45000 : 15000),
|
|
205
|
+
});
|
|
206
|
+
if (found) {
|
|
207
|
+
await (0, test_1.expect)(item).toBeVisible({ timeout: 5000 });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`No row for owner "${authorization.ownerId}" and resource ` +
|
|
212
|
+
`"${authorization.resourceId}" in the ` +
|
|
213
|
+
`${authorization.resourceType} table`);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
lastError = error;
|
|
217
|
+
if (attempt < maxAttempts - 1) {
|
|
218
|
+
console.warn(`Attempt ${attempt + 1} did not list the ` +
|
|
219
|
+
`${authorization.resourceType} authorization for ` +
|
|
220
|
+
`"${authorization.ownerId}". Retrying...`);
|
|
221
|
+
await this.page.reload();
|
|
222
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Authorization for "${authorization.ownerId}" on ` +
|
|
227
|
+
`"${authorization.resourceId}" was not listed: ${String(lastError)}`);
|
|
228
|
+
}
|
|
229
|
+
async filterAuthorizationsByOwner(ownerId) {
|
|
230
|
+
const hasFilter = await this.tableFilter
|
|
231
|
+
.isVisible({ timeout: 5000 })
|
|
232
|
+
.catch(() => false);
|
|
233
|
+
if (!hasFilter) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
await this.tableFilter.fill(ownerId);
|
|
237
|
+
await (0, sleep_1.sleep)(1000);
|
|
171
238
|
}
|
|
172
239
|
async deleteAuthorization(resourceId, resourceType) {
|
|
240
|
+
// Same paginated table as createAuthorization: the row being deleted is
|
|
241
|
+
// not necessarily on page 1, and the toolbar search cannot narrow by
|
|
242
|
+
// resource ID.
|
|
243
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, this.deleteAuthorizationButton(resourceId), { timeout: 60000 });
|
|
244
|
+
if (!found) {
|
|
245
|
+
throw new Error(`No ${resourceType} authorization row for resource "${resourceId}"`);
|
|
246
|
+
}
|
|
173
247
|
await this.deleteAuthorizationButton(resourceId).click();
|
|
174
248
|
await (0, test_1.expect)(this.deleteAuthorizationModal).toBeVisible();
|
|
175
249
|
await this.deleteAuthorizationModalDeleteButton.click();
|
|
@@ -29,9 +29,14 @@ class OCIdentityMappingRulesPage {
|
|
|
29
29
|
this.deleteMappingRuleDialog = page.getByRole('dialog', {
|
|
30
30
|
name: 'Delete Mapping rule',
|
|
31
31
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// The design-system Admin shell replaced Carbon's table toolbar search
|
|
33
|
+
// (labelled "Filter table") with an input whose aria-label is its
|
|
34
|
+
// placeholder, so match the new shell first and fall back to the legacy
|
|
35
|
+
// one - the same dual-shell pattern the Admin nav locators use.
|
|
36
|
+
this.filterTableSearchbox = page
|
|
37
|
+
.getByRole('searchbox', { name: 'Search by mapping rule ID' })
|
|
38
|
+
.or(page.getByRole('searchbox', { name: 'Filter table' }))
|
|
39
|
+
.first();
|
|
35
40
|
this.deleteMappingRuleButton = this.deleteMappingRuleDialog
|
|
36
41
|
.getByRole('button', {
|
|
37
42
|
name: 'Delete Mapping rule',
|
|
@@ -172,6 +172,16 @@ class OCIdentityRolesPage {
|
|
|
172
172
|
async deleteRole(name, options) {
|
|
173
173
|
const { throwError = true } = options || {};
|
|
174
174
|
try {
|
|
175
|
+
// The roles table paginates at 10 rows and the RBA specs run in
|
|
176
|
+
// parallel against a cluster that already ships six built-in roles, so
|
|
177
|
+
// a role created by this test is regularly on page 2 by the time it is
|
|
178
|
+
// deleted. Without walking the pages the row - and therefore its Delete
|
|
179
|
+
// button - simply never resolves, which is what the 10s click timeout
|
|
180
|
+
// reported.
|
|
181
|
+
const found = await (0, findLocatorWithPagination_1.findLocatorWithPagination)(this.page, this.deleteRoleButton(name), { timeout: 30000 });
|
|
182
|
+
if (!found) {
|
|
183
|
+
throw new Error(`Role "${name}" was not found on any page of the roles table`);
|
|
184
|
+
}
|
|
175
185
|
await this.deleteRoleButton(name).click();
|
|
176
186
|
await (0, test_1.expect)(this.deleteRoleModal).toBeVisible();
|
|
177
187
|
await this.deleteRoleModalDeleteButton.click();
|
|
@@ -87,10 +87,11 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
|
|
|
87
87
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
88
88
|
});
|
|
89
89
|
await _8_8_1.test.step('Add an IDP application to the project', async () => {
|
|
90
|
-
// Workspace
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
// Workspace root only: an IDP application can never be created
|
|
91
|
+
// inside a process application. The entry point there is the create
|
|
92
|
+
// menu or the empty state, depending on whether the workspace
|
|
93
|
+
// already holds a resource.
|
|
94
|
+
await modelerHomePage.startIdpApplicationCreation();
|
|
94
95
|
});
|
|
95
96
|
await _8_8_1.test.step('Creates a document extraction template', async () => {
|
|
96
97
|
await modelerHomePage.enterIdpApplicationName(idpApplicationName);
|
|
@@ -161,10 +162,11 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
|
|
|
161
162
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
162
163
|
});
|
|
163
164
|
await _8_8_1.test.step('Add an IDP application to the project', async () => {
|
|
164
|
-
// Workspace
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
// Workspace root only: an IDP application can never be created
|
|
166
|
+
// inside a process application. The entry point there is the create
|
|
167
|
+
// menu or the empty state, depending on whether the workspace
|
|
168
|
+
// already holds a resource.
|
|
169
|
+
await modelerHomePage.startIdpApplicationCreation();
|
|
168
170
|
});
|
|
169
171
|
await _8_8_1.test.step('Creates a document extraction template', async () => {
|
|
170
172
|
await modelerHomePage.enterIdpApplicationName(idpApplicationName);
|
|
@@ -203,7 +203,8 @@ _8_8_1.test.describe('Optimize User Flow Tests', () => {
|
|
|
203
203
|
await (0, test_1.expect)(newOperateTab.getByText('Displaying data from 1 instance.')).toBeVisible({ timeout: 90000 });
|
|
204
204
|
});
|
|
205
205
|
});
|
|
206
|
-
|
|
206
|
+
// Skipped due to bug #60153: https://github.com/camunda/camunda/issues/60153
|
|
207
|
+
_8_8_1.test.skip('Process Import Flow @tasklistV1', async ({ page, homePage, modelerHomePage, appsPage, modelerCreatePage, }) => {
|
|
207
208
|
_8_8_1.test.slow();
|
|
208
209
|
const processName = 'Process Import Flow' + (await (0, _setup_1.generateRandomStringAsync)(6));
|
|
209
210
|
await _8_8_1.test.step('Navigate to Web Modeler', async () => {
|
|
@@ -25,6 +25,7 @@ if (node_process_1.default.env.IS_RBA === 'true') {
|
|
|
25
25
|
await navigationPage.goToOCAdmin();
|
|
26
26
|
const ids = idsToBeDeleted[testInfo.title];
|
|
27
27
|
if (ids['mappingIds'].length > 0) {
|
|
28
|
+
await ocIdentityHomePage.clickMappingRulesTab();
|
|
28
29
|
for (const id of ids['mappingIds']) {
|
|
29
30
|
await ocIdentityMappingRulesPage.deleteMappingRule(id);
|
|
30
31
|
}
|