@camunda/e2e-test-suite 0.0.1044 → 0.0.1046

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.
@@ -870,11 +870,23 @@ class ModelerCreatePage {
870
870
  // the kebab once they render, so a mouse click lands on the tag instead.
871
871
  // Open the menu from the keyboard -- same control, reached the way a
872
872
  // keyboard user reaches it, and unaffected by what paints over it.
873
+ //
874
+ // The header also keeps settling after the editor paints -- license tags
875
+ // mount into that same ml-auto sibling and the breadcrumb label swaps to
876
+ // the persisted diagram name -- and either re-render unmounts an already
877
+ // open Radix menu, so re-open it on each attempt instead of clicking into
878
+ // a menu that may already be gone.
873
879
  const diagramActions = this.moreActionsButton.last();
874
- await diagramActions.focus();
875
- await diagramActions.press('Enter');
876
- await this.renameMenuItem.click();
877
- await (0, test_1.expect)(this.renameInput).toBeVisible({ timeout: 10000 });
880
+ await (0, test_1.expect)(async () => {
881
+ if (await this.renameMenuItem.isVisible()) {
882
+ await this.page.keyboard.press('Escape');
883
+ await (0, test_1.expect)(this.renameMenuItem).toBeHidden({ timeout: 5000 });
884
+ }
885
+ await diagramActions.focus();
886
+ await diagramActions.press('Enter');
887
+ await this.renameMenuItem.click({ timeout: 15000 });
888
+ await (0, test_1.expect)(this.renameInput).toBeVisible({ timeout: 10000 });
889
+ }).toPass({ timeout: 120000, intervals: [1000, 2000, 5000] });
878
890
  await this.renameInput.fill(name);
879
891
  await this.renameSubmitButton.click();
880
892
  await (0, test_1.expect)(this.renameInput).toBeHidden({ timeout: 10000 });
@@ -386,6 +386,23 @@ class ModelerHomePage {
386
386
  // open would otherwise have the next Enter close it again.
387
387
  async renameViaMoreActions(name) {
388
388
  const entityActions = this.moreActionsButton.last();
389
+ // The header keeps settling after the editor paints: the license tags
390
+ // mount into the ml-auto sibling and the breadcrumb label swaps from its
391
+ // placeholder to the persisted entity name. Either re-render shifts and
392
+ // then unmounts the open Radix menu ("element is not stable", then
393
+ // "element was detached from the DOM"), and the menu item never comes
394
+ // back because the menu itself closed. Re-open the menu on each attempt
395
+ // rather than clicking into one that may already be gone.
396
+ await (0, test_1.expect)(async () => {
397
+ if (await this.renameMenuItem.isVisible()) {
398
+ await this.page.keyboard.press('Escape');
399
+ await (0, test_1.expect)(this.renameMenuItem).toBeHidden({ timeout: 5000 });
400
+ }
401
+ await entityActions.focus();
402
+ await entityActions.press('Enter');
403
+ await this.renameMenuItem.click({ timeout: 15000 });
404
+ await (0, test_1.expect)(this.renameInput).toBeVisible({ timeout: 10000 });
405
+ }).toPass({ timeout: 120000, intervals: [1000, 2000, 5000] });
389
406
  await (0, test_1.expect)(async () => {
390
407
  await this.page.keyboard.press('Escape');
391
408
  await entityActions.focus();
@@ -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.1044",
3
+ "version": "0.0.1046",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",