@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.
@@ -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.moreActionsButton)
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.assignRoleSubButton = page
76
- .getByLabel('Assign role')
77
- .getByRole('button', { name: 'Assign role' });
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
- const result = this.page.getByText(roleIdentifier).first();
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
- if (await this.row(roleId).isVisible()) {
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 active icon isn't found, reload the page and try again
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(`Active icon not visible after ${maxRetries} attempts.`);
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
- await navigationPage.goToOperate();
453
- await operateHomePage.clickProcessesTab();
454
- await operateProcessesPage.clickProcessInstanceLink(processName1, 'completed');
455
- await operateProcessInstancePage.completedIconAssertion();
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 operateHomePage.clickProcessesTab();
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1043",
3
+ "version": "0.0.1045",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",