@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.
- package/dist/pages/8.10/OCIdentityHomePage.d.ts +1 -0
- package/dist/pages/8.10/OCIdentityHomePage.js +22 -8
- package/dist/pages/8.10/OCIdentityRolesPage.d.ts +2 -0
- package/dist/pages/8.10/OCIdentityRolesPage.js +39 -3
- package/dist/pages/8.10/TaskPanelPage.d.ts +1 -1
- package/dist/pages/8.10/TaskPanelPage.js +31 -3
- package/dist/pages/c8Run-8.10/OperateHomePage.d.ts +2 -0
- package/dist/pages/c8Run-8.10/OperateHomePage.js +60 -12
- package/dist/tests/8.10/rba-enabled-v2-user-flows.spec.js +7 -16
- package/dist/utils/users.d.ts +4 -0
- package/dist/utils/users.js +35 -1
- package/package.json +1 -1
|
@@ -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
|
|
78
|
-
await this.authorizationsTab.click({ timeout: 30000 });
|
|
95
|
+
await this.openTab(this.authorizationsTab);
|
|
79
96
|
}
|
|
80
97
|
async clickRolesTab() {
|
|
81
|
-
await
|
|
82
|
-
await this.rolesTab.click({ timeout: 30000 });
|
|
98
|
+
await this.openTab(this.rolesTab);
|
|
83
99
|
}
|
|
84
100
|
async clickGroupsTab() {
|
|
85
|
-
await
|
|
86
|
-
await this.groupsTab.click({ timeout: 30000 });
|
|
101
|
+
await this.openTab(this.groupsTab);
|
|
87
102
|
}
|
|
88
103
|
async clickClusterVariablesTab() {
|
|
89
|
-
await
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 =
|
|
60
|
-
|
|
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.
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)(
|
|
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 () => {
|
package/dist/utils/users.d.ts
CHANGED
package/dist/utils/users.js
CHANGED
|
@@ -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;
|