@camunda/e2e-test-suite 0.0.1234 → 0.0.1236

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 });
@@ -6,6 +6,8 @@ declare class OperateHomePage {
6
6
  readonly informationDialog: Locator;
7
7
  readonly editVariableButton: Locator;
8
8
  readonly variableValueInput: Locator;
9
+ readonly variableValueEditor: Locator;
10
+ readonly variableValueContent: Locator;
9
11
  readonly saveVariableButton: Locator;
10
12
  readonly editVariableSpinner: Locator;
11
13
  constructor(page: Page);
@@ -9,6 +9,8 @@ class OperateHomePage {
9
9
  informationDialog;
10
10
  editVariableButton;
11
11
  variableValueInput;
12
+ variableValueEditor;
13
+ variableValueContent;
12
14
  saveVariableButton;
13
15
  editVariableSpinner;
14
16
  constructor(page) {
@@ -28,6 +30,21 @@ class OperateHomePage {
28
30
  });
29
31
  this.editVariableButton = page.getByTestId('edit-variable-button');
30
32
  this.variableValueInput = page.getByTestId('edit-variable-value');
33
+ // "The row is in write mode." InlineJsonEditor renders a CodeMirror
34
+ // instance carrying this test id only on its editable branch; the
35
+ // read-only branch renders ReadOnlyEditor instead, with no CodeMirror
36
+ // at all. The product's own suite resolves the editor the same way --
37
+ // see camunda/camunda
38
+ // qa/c8-orchestration-cluster-e2e-test-suite/pages/OperateHomePage.ts.
39
+ this.variableValueEditor = page.getByTestId('code-mirror-editor');
40
+ // The editable node *inside* that container. CodeMirror puts its
41
+ // contenteditable on `.cm-content`, so that -- not the container -- is
42
+ // what takes focus and what holds the text. Asserting focus on the
43
+ // container reports `inactive` even when the caret is correctly placed,
44
+ // because clicking the container focuses its contenteditable child.
45
+ this.variableValueContent = this.variableValueEditor
46
+ .locator('.cm-content')
47
+ .first();
31
48
  // Carbon icon-only buttons expose their accessible name via aria-labelledby
32
49
  // (tooltip text "Save") which overrides aria-label for computed accessible name.
33
50
  // Use a CSS attribute selector to match the raw aria-label attribute directly.
@@ -56,8 +73,34 @@ class OperateHomePage {
56
73
  throw new Error(`Failed to click the processes tab after ${maxRetries} attempts.`);
57
74
  }
58
75
  async clickEditVariableButton(variableName) {
59
- const editVariableButton = 'variable-' + variableName;
60
- await this.page.getByTestId(editVariableButton).getByLabel('Edit').click();
76
+ const editVariableButton = this.page
77
+ .getByTestId('variable-' + variableName)
78
+ .getByLabel('Edit');
79
+ await (0, test_1.expect)(editVariableButton).toBeVisible({ timeout: 30000 });
80
+ // The caller reaches this straight out of activeIconAssertion, which
81
+ // reloads the page while it polls for the status icon, so the variables
82
+ // panel is usually still mounting when the click lands. A click during
83
+ // that render resolves without raising and without effect: the row stays
84
+ // read-only, InlineJsonEditor keeps rendering ReadOnlyEditor, and the
85
+ // editor the next step needs is never created. Retry until write mode is
86
+ // actually active rather than asserting on the editor afterwards and
87
+ // reporting the dropped click as a missing element.
88
+ //
89
+ // Only click while the row is still read-only. Entering write mode
90
+ // replaces the Edit control, so once the click has taken, clicking again
91
+ // just waits out its timeout against an element that is gone or still
92
+ // being replaced -- which is what the retry did on the slower macOS and
93
+ // Windows runners, where the editor needs longer to mount than one
94
+ // iteration allowed: it re-clicked, exhausted the budget, and raised the
95
+ // click timeout instead of the state the row was actually in. Probing
96
+ // first keeps the retry for a genuinely dropped click and turns the slow
97
+ // case into what it should be, a wait.
98
+ await (0, test_1.expect)(async () => {
99
+ if (await editVariableButton.isVisible()) {
100
+ await editVariableButton.click({ timeout: 10000 });
101
+ }
102
+ await (0, test_1.expect)(this.variableValueEditor).toBeVisible({ timeout: 15000 });
103
+ }).toPass({ timeout: 90000 });
61
104
  }
62
105
  async clickVariableValueInput() {
63
106
  await this.variableValueInput.click();
@@ -69,15 +112,18 @@ class OperateHomePage {
69
112
  // Operate migrated this editor from Monaco to CodeMirror
70
113
  // (@uiw/react-codemirror; see camunda/camunda#62782) -- the editable
71
114
  // region is now a div.cm-content, not .monaco-editor, and it no longer
72
- // exposes a Monaco-style .cursor element. clickEditVariableButton still
73
- // triggers autoFocus, so wait for the content element to actually have
74
- // focus (:focus) before sending events, same intent as the old cursor
75
- // check. Scoped to the variable row to avoid matching other editors.
76
- const variableRow = this.page.getByTestId('variable-testVariable');
77
- await variableRow
78
- .locator('.cm-content:focus')
79
- .first()
80
- .waitFor({ state: 'visible', timeout: 30000 });
115
+ // exposes a Monaco-style .cursor element.
116
+ //
117
+ // clickEditVariableButton has already established that the editor exists
118
+ // -- it retries the Edit click until it does -- so this only has to put
119
+ // the caret in it. Do not wait on focus arriving by itself: autoFocus
120
+ // fires when the editor mounts, which is before this runs, so a wait for
121
+ // `:focus` can only ever observe focus that has already been placed
122
+ // elsewhere. Click to focus, then confirm. Both act on the
123
+ // contenteditable rather than its container, which cannot hold focus.
124
+ await (0, test_1.expect)(this.variableValueContent).toBeVisible({ timeout: 30000 });
125
+ await this.variableValueContent.click();
126
+ await (0, test_1.expect)(this.variableValueContent).toBeFocused({ timeout: 10000 });
81
127
  // Insert the COMPLETE JSON value, both quotes included. Monaco used to
82
128
  // auto-close a lone opening quote, so the old recipe inserted only
83
129
  // `"value`; CodeMirror's closeBrackets extension ignores insertions
@@ -97,7 +143,9 @@ class OperateHomePage {
97
143
  await this.page.keyboard.press('Backspace');
98
144
  const jsonValue = JSON.stringify(newValue);
99
145
  await this.page.keyboard.insertText(jsonValue);
100
- await (0, test_1.expect)(variableRow.locator('.cm-content').first()).toContainText(jsonValue, { timeout: 10000 });
146
+ await (0, test_1.expect)(this.variableValueContent).toContainText(jsonValue, {
147
+ timeout: 10000,
148
+ });
101
149
  }
102
150
  async clickSaveVariableButton() {
103
151
  await this.saveVariableButton.click({ timeout: 30000 });
@@ -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.1236",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",