@camunda/e2e-test-suite 0.0.1234 → 0.0.1235

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.
@@ -12,6 +12,7 @@ declare class OCIdentityHomePage {
12
12
  readonly licenseKeyTagCommercial: Locator;
13
13
  readonly noPermissionsMessage: Locator;
14
14
  constructor(page: Page);
15
+ private openTab;
15
16
  clickAuthorizationsTab(): Promise<void>;
16
17
  clickRolesTab(): Promise<void>;
17
18
  clickGroupsTab(): Promise<void>;
@@ -73,21 +73,35 @@ class OCIdentityHomePage {
73
73
  this.licenseKeyTagCommercial = page.getByText('Commercial license');
74
74
  this.noPermissionsMessage = page.getByText('You don’t have access to this component');
75
75
  }
76
+ // Every caller arrives here from somewhere else in Admin -- a role detail
77
+ // page that was just reloaded, a freshly dismissed modal -- and the default
78
+ // retry gives the sidebar three 10s looks with no recovery between them.
79
+ // That is the whole budget a hand-off has if the SPA is still re-rendering
80
+ // its shell, and when it runs out the run reports "Failed to assert
81
+ // visibility locator ... 'Authorizations' ... after 3 attempts" for a nav
82
+ // that is permanently part of the page. Look longer, and reload between
83
+ // attempts so a shell that never finished painting gets another chance
84
+ // instead of being polled in the same broken state three times.
85
+ async openTab(tab) {
86
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, tab, {
87
+ visibilityTimeout: 20000,
88
+ postAction: async () => {
89
+ await this.page.reload();
90
+ },
91
+ });
92
+ await tab.click({ timeout: 30000 });
93
+ }
76
94
  async clickAuthorizationsTab() {
77
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.authorizationsTab);
78
- await this.authorizationsTab.click({ timeout: 30000 });
95
+ await this.openTab(this.authorizationsTab);
79
96
  }
80
97
  async clickRolesTab() {
81
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.rolesTab);
82
- await this.rolesTab.click({ timeout: 30000 });
98
+ await this.openTab(this.rolesTab);
83
99
  }
84
100
  async clickGroupsTab() {
85
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.groupsTab);
86
- await this.groupsTab.click({ timeout: 30000 });
101
+ await this.openTab(this.groupsTab);
87
102
  }
88
103
  async clickClusterVariablesTab() {
89
- await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.clusterVariablesTab);
90
- await this.clusterVariablesTab.click({ timeout: 30000 });
104
+ await this.openTab(this.clusterVariablesTab);
91
105
  }
92
106
  }
93
107
  exports.OCIdentityHomePage = OCIdentityHomePage;
@@ -25,7 +25,9 @@ declare class OCIdentityRolesPage {
25
25
  readonly assignUserModal: Locator;
26
26
  readonly assignUserModalInputField: Locator;
27
27
  readonly assignUserModalAssignButton: Locator;
28
+ readonly roleSearchBox: Locator;
28
29
  constructor(page: Page);
30
+ private searchForRole;
29
31
  clickMappingRulesTab(): Promise<void>;
30
32
  clickRole(roleID: string): Promise<void>;
31
33
  assignMappingRuleToRole(roleId: string, mappingId: string): Promise<void>;
@@ -30,6 +30,7 @@ class OCIdentityRolesPage {
30
30
  assignUserModal;
31
31
  assignUserModalInputField;
32
32
  assignUserModalAssignButton;
33
+ roleSearchBox;
33
34
  constructor(page) {
34
35
  this.page = page;
35
36
  this.rolesList = page.getByRole('table');
@@ -105,6 +106,25 @@ class OCIdentityRolesPage {
105
106
  this.assignUserModalAssignButton = this.assignUserModal.getByRole('button', {
106
107
  name: /assign user/i,
107
108
  });
109
+ // Both role-list shapes render this filter with the same placeholder:
110
+ // camunda/camunda identity/client/src/pages/roles/List.tsx (Carbon
111
+ // TableToolbarSearch) and ListV2.tsx (design-system SearchInput) each pass
112
+ // `searchPlaceholder={t("searchByRoleId")}`. Only V2 mirrors it into an
113
+ // aria-label, so match on the placeholder to cover both.
114
+ this.roleSearchBox = page.getByPlaceholder('Search by role ID');
115
+ }
116
+ // The roles list is server-side paginated at 10 rows per page, and these
117
+ // long-lived RBA clusters accumulate a role per test run -- the org that
118
+ // failed was already at "Page 6 of 6". A freshly created role sorts to the
119
+ // end, so `findLocatorWithPagination` has to walk every page one request at
120
+ // a time and exhausts its 30s budget before it arrives, reporting a role
121
+ // that is plainly on screen as missing. Both list shapes pass
122
+ // `searchKey="roleId"` with the default `eq` operator, so filtering queries
123
+ // the backend for that exact role and puts it on page 1.
124
+ async searchForRole(roleId) {
125
+ await (0, test_1.expect)(this.roleSearchBox).toBeVisible({ timeout: 60000 });
126
+ await this.roleSearchBox.fill(roleId, { timeout: 30000 });
127
+ await (0, test_1.expect)(this.roleSearchBox).toHaveValue(roleId, { timeout: 30000 });
108
128
  }
109
129
  async clickMappingRulesTab() {
110
130
  await (0, test_1.expect)(this.selectResourceTypeTab('Mapping Rules')).toBeVisible({
@@ -113,6 +133,7 @@ class OCIdentityRolesPage {
113
133
  await this.selectResourceTypeTab('Mapping Rules').click();
114
134
  }
115
135
  async clickRole(roleID) {
136
+ await this.searchForRole(roleID);
116
137
  await (0, test_1.expect)(this.roleCell(roleID).first()).toBeVisible({ timeout: 60000 });
117
138
  await this.roleCell(roleID).first().click();
118
139
  }
@@ -149,11 +170,21 @@ class OCIdentityRolesPage {
149
170
  await this.createNameField.fill(role.name);
150
171
  await this.createRoleModalCreateButton.click();
151
172
  await (0, test_1.expect)(this.createRoleModal).not.toBeVisible({ timeout: 30000 });
152
- await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, this.rolesList
173
+ await this.searchForRole(role.id);
174
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.rolesList
153
175
  .getByRole('cell', {
154
176
  name: role.name,
155
177
  })
156
- .first());
178
+ .first(), {
179
+ visibilityTimeout: 20000,
180
+ postAction: async () => {
181
+ // The filter is component state, not a URL param, so a reload drops
182
+ // it -- re-apply it or the retry falls back to the page walk this
183
+ // whole approach exists to avoid.
184
+ await this.page.reload();
185
+ await this.searchForRole(role.id);
186
+ },
187
+ });
157
188
  }
158
189
  async assignMappingToRole(role, mappingRuleId) {
159
190
  try {
@@ -169,8 +200,12 @@ class OCIdentityRolesPage {
169
200
  await (0, test_1.expect)(this.rowValue(mappingRuleId)).toContainText(`${mappingRuleId}`);
170
201
  }
171
202
  async assignUserToRole(role, email) {
203
+ // Filter before probing for the role: unfiltered, this only ever sees the
204
+ // ten rows of page 1, so an existing role further down the list reads as
205
+ // missing and we try to create it a second time.
206
+ await this.searchForRole(role);
172
207
  try {
173
- await (0, test_1.expect)(this.page.getByText(role).first()).toBeVisible({
208
+ await (0, test_1.expect)(this.roleCell(role).first()).toBeVisible({
174
209
  timeout: 10000,
175
210
  });
176
211
  }
@@ -186,6 +221,7 @@ class OCIdentityRolesPage {
186
221
  });
187
222
  }
188
223
  async deleteRole(name) {
224
+ await this.searchForRole(name);
189
225
  const locator = this.deleteRoleButton(name);
190
226
  await (0, expectLocatorWithPagination_1.expectLocatorWithPagination)(this.page, locator);
191
227
  await locator.click();
@@ -17,7 +17,7 @@ declare class TaskPanelPage {
17
17
  private openFilterControls;
18
18
  private collapseFilterPanel;
19
19
  private static appliedFilter;
20
- taskCount(name: string): Promise<number>;
20
+ taskCount(name: string, expectedCount?: number): Promise<number>;
21
21
  clickTasksTab(): Promise<void>;
22
22
  scrollToLastTask(name: string): Promise<void>;
23
23
  scrollToFirstTask(name: string): Promise<void>;
@@ -190,9 +190,37 @@ class TaskPanelPage {
190
190
  const param = filterParam[option] ?? null;
191
191
  return (url) => url.searchParams.get('filter') === param;
192
192
  }
193
- async taskCount(name) {
194
- await (0, sleep_1.sleep)(20000);
195
- return this.availableTasks.getByText(name, { exact: true }).count();
193
+ // `expectedCount` turns the single fixed-delay sample into a poll. Tasklist
194
+ // only lists a task once the exporter has indexed the instance, and on these
195
+ // long-lived SaaS RBA clusters that routinely lags past the 20s wait -- the
196
+ // count then comes back one short and the caller's `toBe(before + 1)` fails
197
+ // on a task that does arrive moments later. Callers that have no expectation
198
+ // yet (the baseline read taken before starting a process) still get the
199
+ // fixed wait. The count returned is always the real one, so a task that
200
+ // genuinely never appears still fails the caller's assertion.
201
+ async taskCount(name, expectedCount) {
202
+ const tasks = this.availableTasks.getByText(name, { exact: true });
203
+ if (expectedCount === undefined) {
204
+ await (0, sleep_1.sleep)(20000);
205
+ return tasks.count();
206
+ }
207
+ // Re-reading the same rendered list only finds the new task if the panel
208
+ // refetches while we wait, and the sample that failed sat on 1 of an
209
+ // expected 2 for the whole budget -- the instance was started, the
210
+ // exporter published its task afterwards, and the view never asked again.
211
+ // Reload between samples so each one is an actual query rather than a
212
+ // re-count of the DOM we already had. The count returned is still the
213
+ // real one, so a task that genuinely never arrives still fails the
214
+ // caller's assertion.
215
+ const deadline = Date.now() + 180000;
216
+ let count = await tasks.count();
217
+ while (count !== expectedCount && Date.now() < deadline) {
218
+ await (0, sleep_1.sleep)(5000);
219
+ await this.page.reload();
220
+ await (0, test_1.expect)(this.availableTasks).toBeVisible({ timeout: 60000 });
221
+ count = await tasks.count();
222
+ }
223
+ return count;
196
224
  }
197
225
  async clickTasksTab() {
198
226
  await (0, test_1.expect)(this.tasksTab).toBeVisible({ timeout: 60000 });
@@ -39,10 +39,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
39
39
  const process1 = processName + '1';
40
40
  const process2 = processName + '2';
41
41
  const role = `rbaNoPermission${randomString}`;
42
- const testUser = {
43
- username: process.env.C8_USERNAME_TEST_4,
44
- password: process.env.C8_PASSWORD_TEST_4,
45
- };
42
+ const testUser = (0, users_1.requireTestUser)('C8_USERNAME_TEST_4');
46
43
  await _8_10_1.test.step('Navigate to OC Identity and Assign User To A Role', async () => {
47
44
  await appsPage.clickCamundaApps();
48
45
  await appsPage.clickAdmin(clusterName);
@@ -149,10 +146,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
149
146
  const process2 = processName + '2';
150
147
  const role1 = `rbaOnePerm${randomString}1`;
151
148
  const role2 = `rbaOnePerm${randomString}2`;
152
- const testUser = {
153
- username: process.env.C8_USERNAME_TEST_6,
154
- password: process.env.C8_PASSWORD_TEST_6,
155
- };
149
+ const testUser = (0, users_1.requireTestUser)('C8_USERNAME_TEST_6');
156
150
  await _8_10_1.test.step('Navigate to OC Identity and Assign User To Two Roles', async () => {
157
151
  await appsPage.clickCamundaApps();
158
152
  await appsPage.clickAdmin(clusterName);
@@ -365,7 +359,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
365
359
  const firstProcessTaskCount = await taskPanelPage.taskCount(process1);
366
360
  await taskPanelPage.clickProcessesTab();
367
361
  await taskProcessesPage.startProcess(process1);
368
- const updatedFirstProcessTaskCount = await taskPanelPage.taskCount(process1);
362
+ const updatedFirstProcessTaskCount = await taskPanelPage.taskCount(process1, firstProcessTaskCount + 1);
369
363
  (0, test_1.expect)(updatedFirstProcessTaskCount).toBe(firstProcessTaskCount + 1);
370
364
  });
371
365
  await _8_10_1.test.step('Assert Second Process Can Be Started From Tasklist', async () => {
@@ -373,7 +367,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
373
367
  const secondProcessTaskCount = await taskPanelPage.taskCount(process2);
374
368
  await taskPanelPage.clickProcessesTab();
375
369
  await taskProcessesPage.startProcess(process2);
376
- const updatedSecondProcessTaskCount = await taskPanelPage.taskCount(process2);
370
+ const updatedSecondProcessTaskCount = await taskPanelPage.taskCount(process2, secondProcessTaskCount + 1);
377
371
  (0, test_1.expect)(updatedSecondProcessTaskCount).toBe(secondProcessTaskCount + 1);
378
372
  });
379
373
  await _8_10_1.test.step('Navigate to Operate and Assert Both Processes Are Accessible', async () => {
@@ -395,10 +389,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
395
389
  const role = `rbaSelectedProcessesPermission${randomString}`;
396
390
  const process1 = processName + '1';
397
391
  const process2 = processName + '2';
398
- const testUser = {
399
- username: process.env.C8_USERNAME_TEST_5,
400
- password: process.env.C8_PASSWORD_TEST_5,
401
- };
392
+ const testUser = (0, users_1.requireTestUser)('C8_USERNAME_TEST_5');
402
393
  await _8_10_1.test.step('Navigate to OC Identity and Assign User To A Role', async () => {
403
394
  await appsPage.clickCamundaApps();
404
395
  await appsPage.clickAdmin(clusterName);
@@ -470,7 +461,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
470
461
  const firstProcessTaskCount = await taskPanelPage.taskCount(process1);
471
462
  await taskPanelPage.clickProcessesTab();
472
463
  await taskProcessesPage.startProcess(process1);
473
- const updatedFirstProcessTaskCount = await taskPanelPage.taskCount(process1);
464
+ const updatedFirstProcessTaskCount = await taskPanelPage.taskCount(process1, firstProcessTaskCount + 1);
474
465
  (0, test_1.expect)(updatedFirstProcessTaskCount).toBe(firstProcessTaskCount + 1);
475
466
  });
476
467
  await _8_10_1.test.step('Assert Second Process Can Be Started From Tasklist', async () => {
@@ -478,7 +469,7 @@ _8_10_1.test.describe.parallel('RBA Enabled User Flows Test @tasklistV2', () =>
478
469
  const secondProcessTaskCount = await taskPanelPage.taskCount(process2);
479
470
  await taskPanelPage.clickProcessesTab();
480
471
  await taskProcessesPage.startProcess(process2);
481
- const updatedSecondProcessTaskCount = await taskPanelPage.taskCount(process2);
472
+ const updatedSecondProcessTaskCount = await taskPanelPage.taskCount(process2, secondProcessTaskCount + 1);
482
473
  (0, test_1.expect)(updatedSecondProcessTaskCount).toBe(secondProcessTaskCount + 1);
483
474
  });
484
475
  await _8_10_1.test.step('Navigate to Operate and Assert Both Processes Are Accessible', async () => {
@@ -100,3 +100,7 @@ export declare function getTestUsers(): ({
100
100
  username: string;
101
101
  password: string;
102
102
  })[];
103
+ export declare function requireTestUser(usernameVar: string): {
104
+ username: string;
105
+ password: string;
106
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTestUsers = exports.getTestUser = exports.smokeTestUsers = exports.testUsers = void 0;
3
+ exports.requireTestUser = exports.getTestUsers = exports.getTestUser = exports.smokeTestUsers = exports.testUsers = void 0;
4
4
  exports.testUsers = {
5
5
  mainUser: {
6
6
  username: process.env.C8_USERNAME,
@@ -115,3 +115,37 @@ function getTestUsers() {
115
115
  return [exports.testUsers.mainUser];
116
116
  }
117
117
  exports.getTestUsers = getTestUsers;
118
+ // Specs that exercise a *restricted* user reach for a numbered credential
119
+ // slot directly rather than through getTestUser. The SaaS organisation a run
120
+ // gets is sized by grepping the spec for the slots it names
121
+ // (scripts/flakiness-saas-create-org.sh), so when a slot is missed the
122
+ // variable is simply absent -- and `process.env.C8_USERNAME_TEST_4!` then
123
+ // hands `undefined` to whatever consumes it next. Run 35494241466 spent 150
124
+ // of its 152 repeats that way: roughly twenty minutes each to reach the
125
+ // assign-user modal and die on "locator.fill: value: expected string, got
126
+ // undefined", an error that names neither the slot nor the org that lacks it.
127
+ //
128
+ // Resolve the pair here instead, so a slot the org was never given is
129
+ // reported in one second and by name. Callers pass the username variable as
130
+ // a literal so the org-sizing grep above still sees it in the spec.
131
+ //
132
+ // There is deliberately no fallback to mainUser of the kind getTestUser has:
133
+ // mainUser is the organisation owner, and an RBA flow that runs its
134
+ // "restricted" user as the owner would assert nothing.
135
+ function requireTestUser(usernameVar) {
136
+ const passwordVar = usernameVar.replace('USERNAME', 'PASSWORD');
137
+ const username = process.env[usernameVar];
138
+ const password = process.env[passwordVar];
139
+ const missing = [
140
+ username ? null : usernameVar,
141
+ password ? null : passwordVar,
142
+ ].filter(Boolean);
143
+ if (missing.length > 0) {
144
+ throw new Error(`Test user credentials are not set: ${missing.join(', ')}. The ` +
145
+ 'organisation for this run was created without that user slot -- ' +
146
+ 'see scripts/flakiness-saas-create-org.sh, which sizes the org from ' +
147
+ 'the slots the spec names.');
148
+ }
149
+ return { username: username, password: password };
150
+ }
151
+ exports.requireTestUser = requireTestUser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1234",
3
+ "version": "0.0.1235",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",