@camunda/e2e-test-suite 0.0.1043 → 0.0.1045
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/ModelerHomePage.d.ts +1 -0
- package/dist/pages/SM-8.10/ModelerHomePage.js +13 -1
- package/dist/pages/SM-8.10/OCTenantPage.d.ts +2 -0
- package/dist/pages/SM-8.10/OCTenantPage.js +21 -5
- package/dist/pages/SM-8.10/OperateProcessInstancePage.js +2 -2
- package/dist/tests/SM-8.10/mt-enabled-user-flows.spec.js +16 -7
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ declare class ModelerHomePage {
|
|
|
23
23
|
readonly crossComponentProjectFolder: Locator;
|
|
24
24
|
readonly homeBreadcrumb: Locator;
|
|
25
25
|
readonly formNameInput: Locator;
|
|
26
|
+
readonly formEditorCanvas: Locator;
|
|
26
27
|
readonly rows: Locator;
|
|
27
28
|
readonly licenseKeyTagNonProduction: Locator;
|
|
28
29
|
readonly licenseKeyTagNonCommercial: Locator;
|
|
@@ -39,6 +39,7 @@ class ModelerHomePage {
|
|
|
39
39
|
crossComponentProjectFolder;
|
|
40
40
|
homeBreadcrumb;
|
|
41
41
|
formNameInput;
|
|
42
|
+
formEditorCanvas;
|
|
42
43
|
rows;
|
|
43
44
|
licenseKeyTagNonProduction;
|
|
44
45
|
licenseKeyTagNonCommercial;
|
|
@@ -129,6 +130,11 @@ class ModelerHomePage {
|
|
|
129
130
|
.or(page.getByRole('button', { name: 'My organisation' }))
|
|
130
131
|
.first();
|
|
131
132
|
this.formNameInput = page.locator('[data-test="editable-input"]');
|
|
133
|
+
// form-js labels its editor region "Form Definition" on both shells (see
|
|
134
|
+
// FormJsPage.formEditor). Unlike the app header, it mounts only once the
|
|
135
|
+
// editor itself has rendered, which makes it the ready signal enterFormName
|
|
136
|
+
// gates on.
|
|
137
|
+
this.formEditorCanvas = page.getByLabel('Form Definition').first();
|
|
132
138
|
this.rows = page.getByRole('row');
|
|
133
139
|
this.licenseKeyTagNonProduction = page
|
|
134
140
|
.getByText('Non-production license')
|
|
@@ -343,9 +349,15 @@ class ModelerHomePage {
|
|
|
343
349
|
// renamed the PROJECT to the form's name instead of the form. The Carbon
|
|
344
350
|
// shell could not hit this: editable-input existed only inside the editor,
|
|
345
351
|
// so the gate implicitly waited for navigation.
|
|
352
|
+
//
|
|
353
|
+
// waitForURL is not enough on its own: the URL flips to /forms/ before the
|
|
354
|
+
// editor mounts, and the app header -- kebab included -- is already painted
|
|
355
|
+
// by then, still showing the process application as the deepest crumb. The
|
|
356
|
+
// canvas mounts together with the entity crumb, so gate on it rather than
|
|
357
|
+
// on the header, which is present either way.
|
|
346
358
|
await this.page.waitForURL(/\/forms\//, { timeout: 60000 });
|
|
347
359
|
await this.formNameInput
|
|
348
|
-
.or(this.
|
|
360
|
+
.or(this.formEditorCanvas)
|
|
349
361
|
.first()
|
|
350
362
|
.waitFor({ state: 'visible', timeout: 30000 });
|
|
351
363
|
if (await this.formNameInput.isVisible()) {
|
|
@@ -23,6 +23,7 @@ declare class OCTenantPage {
|
|
|
23
23
|
readonly assignRoleButton: Locator;
|
|
24
24
|
readonly roleIdSearchBox: Locator;
|
|
25
25
|
readonly assignRoleSubButton: Locator;
|
|
26
|
+
readonly assignRoleModal: Locator;
|
|
26
27
|
readonly cancelButton: Locator;
|
|
27
28
|
readonly gotItButton: Locator;
|
|
28
29
|
readonly filterTableSearchbox: Locator;
|
|
@@ -64,6 +65,7 @@ declare class OCTenantPage {
|
|
|
64
65
|
clickRoleIdSearchBox(): Promise<void>;
|
|
65
66
|
fillRoleIdSearchBox(roleId: string, roleIdentifier: string): Promise<void>;
|
|
66
67
|
clickAssignRoleSubButton(): Promise<void>;
|
|
68
|
+
private isRoleAssigned;
|
|
67
69
|
assignRoleToTenant(roleId: string, roleIdentifier: string): Promise<void>;
|
|
68
70
|
}
|
|
69
71
|
export { OCTenantPage };
|
|
@@ -27,6 +27,7 @@ class OCTenantPage {
|
|
|
27
27
|
assignRoleButton;
|
|
28
28
|
roleIdSearchBox;
|
|
29
29
|
assignRoleSubButton;
|
|
30
|
+
assignRoleModal;
|
|
30
31
|
cancelButton;
|
|
31
32
|
gotItButton;
|
|
32
33
|
filterTableSearchbox;
|
|
@@ -72,9 +73,10 @@ class OCTenantPage {
|
|
|
72
73
|
this.roleIdSearchBox = page.getByRole('searchbox', {
|
|
73
74
|
name: 'Search by Role ID',
|
|
74
75
|
});
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
this.assignRoleModal = page.getByLabel('Assign role');
|
|
77
|
+
this.assignRoleSubButton = this.assignRoleModal.getByRole('button', {
|
|
78
|
+
name: 'Assign role',
|
|
79
|
+
});
|
|
78
80
|
this.cancelButton = page
|
|
79
81
|
.getByRole('dialog')
|
|
80
82
|
.getByRole('button', { name: 'Cancel' });
|
|
@@ -263,7 +265,10 @@ class OCTenantPage {
|
|
|
263
265
|
await this.roleIdSearchBox.click();
|
|
264
266
|
}
|
|
265
267
|
async fillRoleIdSearchBox(roleId, roleIdentifier) {
|
|
266
|
-
|
|
268
|
+
// Scoped to the modal: the tenant's own roles table sits behind it and
|
|
269
|
+
// carries the same role text, so an unscoped match resolves to that row
|
|
270
|
+
// and its click is intercepted by the modal overlay.
|
|
271
|
+
const result = this.assignRoleModal.getByText(roleIdentifier).first();
|
|
267
272
|
// The dropdown matches against the roles fetched with an empty search
|
|
268
273
|
// filter, so clearing the box re-triggers that fetch before retyping.
|
|
269
274
|
await (0, test_1.expect)(async () => {
|
|
@@ -277,10 +282,21 @@ class OCTenantPage {
|
|
|
277
282
|
async clickAssignRoleSubButton() {
|
|
278
283
|
await this.assignRoleSubButton.click();
|
|
279
284
|
}
|
|
285
|
+
async isRoleAssigned(roleId) {
|
|
286
|
+
return this.row(roleId)
|
|
287
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
288
|
+
.then(() => true)
|
|
289
|
+
.catch(() => false);
|
|
290
|
+
}
|
|
280
291
|
async assignRoleToTenant(roleId, roleIdentifier) {
|
|
281
292
|
await this.clickRolesTab();
|
|
282
293
|
await this.page.waitForLoadState('networkidle');
|
|
283
|
-
|
|
294
|
+
// The roles table renders from its own fetch, which `networkidle` can
|
|
295
|
+
// resolve ahead of, so an immediate `isVisible()` reads the table as empty
|
|
296
|
+
// and reopens the modal for a role that is already assigned. The assign
|
|
297
|
+
// dropdown never offers an already-assigned role, so that attempt can
|
|
298
|
+
// never converge.
|
|
299
|
+
if (await this.isRoleAssigned(roleId)) {
|
|
284
300
|
console.log(`Role ${roleId} is already assigned to tenant`);
|
|
285
301
|
return;
|
|
286
302
|
}
|
|
@@ -47,14 +47,14 @@ class OperateProcessInstancePage {
|
|
|
47
47
|
return; // Exit the function if the expectation is met
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
|
-
// If the
|
|
50
|
+
// If the completed icon isn't found, reload the page and try again
|
|
51
51
|
retryCount++;
|
|
52
52
|
console.log(`Attempt ${retryCount} failed. Retrying...`);
|
|
53
53
|
await this.page.reload();
|
|
54
54
|
await this.page.waitForLoadState('domcontentloaded');
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
throw new Error(`
|
|
57
|
+
throw new Error(`Completed icon not visible after ${maxRetries} attempts.`);
|
|
58
58
|
}
|
|
59
59
|
async activeIconAssertion() {
|
|
60
60
|
let retryCount = 0;
|
|
@@ -449,19 +449,28 @@ if (process.env.IS_MT === 'true') {
|
|
|
449
449
|
await modelerCreatePage.runProcessInstance('', tenantName2);
|
|
450
450
|
});
|
|
451
451
|
await SM_8_10_1.test.step('View Process Instances in Operate, and Assert Process Complete in Operate with Correct Tenant', async () => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
// Re-entered through `goToOperate` on every attempt: this flow reaches
|
|
453
|
+
// Operate ~10 minutes in, and when the session's token refresh fails
|
|
454
|
+
// the instance route answers with an error document instead of the app
|
|
455
|
+
// shell. The reload loop inside `completedIconAssertion` only
|
|
456
|
+
// re-requests that same document, so the session has to be
|
|
457
|
+
// re-established before the icon can be asserted at all.
|
|
458
|
+
const openCompletedInstance = async (processName) => {
|
|
459
|
+
await (0, test_1.expect)(async () => {
|
|
460
|
+
await navigationPage.goToOperate();
|
|
461
|
+
await operateHomePage.clickProcessesTab();
|
|
462
|
+
await operateProcessesPage.clickProcessInstanceLink(processName, 'completed');
|
|
463
|
+
await operateProcessInstancePage.completedIconAssertion();
|
|
464
|
+
}).toPass({ timeout: 600000, intervals: [5000] });
|
|
465
|
+
};
|
|
466
|
+
await openCompletedInstance(processName1);
|
|
456
467
|
await (0, expectTextWithRetry_1.expectTextWithRetry)(page, '"Awesome!"', {
|
|
457
468
|
postAction: async () => {
|
|
458
469
|
await page.reload();
|
|
459
470
|
},
|
|
460
471
|
});
|
|
461
472
|
await operateProcessInstancePage.tenantNameAssertion(tenantName1);
|
|
462
|
-
await
|
|
463
|
-
await operateProcessesPage.clickProcessInstanceLink(processName2, 'completed');
|
|
464
|
-
await operateProcessInstancePage.completedIconAssertion();
|
|
473
|
+
await openCompletedInstance(processName2);
|
|
465
474
|
await (0, expectTextWithRetry_1.expectTextWithRetry)(page, '"Awesome!"', {
|
|
466
475
|
postAction: async () => {
|
|
467
476
|
await page.reload();
|