@camunda/e2e-test-suite 0.0.968 → 0.0.970
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.9/ModelerCreatePage.d.ts +5 -0
- package/dist/pages/8.9/ModelerCreatePage.js +50 -0
- package/dist/pages/8.9/TaskDetailsPage.js +13 -3
- package/dist/pages/SM-8.8/NavigationPage.d.ts +5 -0
- package/dist/pages/SM-8.8/NavigationPage.js +30 -20
- package/dist/tests/8.9/connectors-user-flows.spec.js +3 -0
- package/dist/tests/SM-8.8/identity-user-flows.spec.js +1 -1
- package/package.json +1 -1
|
@@ -24,6 +24,9 @@ declare class ModelerCreatePage {
|
|
|
24
24
|
readonly additionalActionsButton: Locator;
|
|
25
25
|
readonly cancelButton: Locator;
|
|
26
26
|
readonly restConnectorOption: Locator;
|
|
27
|
+
readonly changeElementSearchInput: Locator;
|
|
28
|
+
readonly changeElementTabStrip: Locator;
|
|
29
|
+
readonly reusableAssetsTab: Locator;
|
|
27
30
|
readonly webhookMessageStartEventConnectorOption: Locator;
|
|
28
31
|
readonly webhookTab: Locator;
|
|
29
32
|
readonly webhookIsActiveButton: Locator;
|
|
@@ -154,6 +157,8 @@ declare class ModelerCreatePage {
|
|
|
154
157
|
clickNewForm(): Promise<void>;
|
|
155
158
|
clickDeploySubButton(): Promise<void>;
|
|
156
159
|
clickCancelButton(): Promise<void>;
|
|
160
|
+
private waitForVisible;
|
|
161
|
+
revealChangeElementTemplate(option: Locator, searchTerm: string): Promise<void>;
|
|
157
162
|
clickRestConnectorOption(): Promise<void>;
|
|
158
163
|
clickWebhookStartEventConnectorOption(): Promise<void>;
|
|
159
164
|
clickWebhookTab(): Promise<void>;
|
|
@@ -30,6 +30,9 @@ class ModelerCreatePage {
|
|
|
30
30
|
additionalActionsButton;
|
|
31
31
|
cancelButton;
|
|
32
32
|
restConnectorOption;
|
|
33
|
+
changeElementSearchInput;
|
|
34
|
+
changeElementTabStrip;
|
|
35
|
+
reusableAssetsTab;
|
|
33
36
|
webhookMessageStartEventConnectorOption;
|
|
34
37
|
webhookTab;
|
|
35
38
|
webhookIsActiveButton;
|
|
@@ -168,6 +171,12 @@ class ModelerCreatePage {
|
|
|
168
171
|
this.deploySubButton = page.locator('[data-test="deploy-action"]');
|
|
169
172
|
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
170
173
|
this.restConnectorOption = page.locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
|
|
174
|
+
this.changeElementSearchInput = page.locator('.djs-popup-search input');
|
|
175
|
+
this.changeElementTabStrip = page.locator('.djs-popup-tabs-container');
|
|
176
|
+
// The tab renders its label as text content, which wins over its `title`
|
|
177
|
+
// attribute in the accessible-name computation. Match case-insensitively:
|
|
178
|
+
// the label is capitalised differently across bpmn-js releases.
|
|
179
|
+
this.reusableAssetsTab = page.getByRole('tab', { name: /reusable assets/i });
|
|
171
180
|
// bpmn-js popup entries are `li[role="option"]` inside a `role="listbox"`,
|
|
172
181
|
// so getByRole('listitem') no longer matches. Their accessible name also
|
|
173
182
|
// includes the entry description, and three entries share the label
|
|
@@ -683,11 +692,51 @@ class ModelerCreatePage {
|
|
|
683
692
|
async clickCancelButton() {
|
|
684
693
|
await this.cancelButton.click({ timeout: 60000 });
|
|
685
694
|
}
|
|
695
|
+
// `waitFor` actually blocks up to the timeout; `isVisible()` would return
|
|
696
|
+
// immediately and report false while the popup is still rendering.
|
|
697
|
+
async waitForVisible(locator, timeout) {
|
|
698
|
+
return locator
|
|
699
|
+
.first()
|
|
700
|
+
.waitFor({ state: 'visible', timeout })
|
|
701
|
+
.then(() => true)
|
|
702
|
+
.catch(() => false);
|
|
703
|
+
}
|
|
704
|
+
// The change-element popup is now tabbed: every `replace.template-*` entry
|
|
705
|
+
// is assigned to the "Reusable Assets" tab, so connector templates are not
|
|
706
|
+
// in the DOM at all while the default "BPMN" tab is selected - which is why
|
|
707
|
+
// the template locators time out with `<element(s) not found>`.
|
|
708
|
+
// Opening that tab is version-tolerant: on an untabbed popup the strip is
|
|
709
|
+
// absent and this is a no-op.
|
|
710
|
+
// Search is the fallback, because it spans the full entry set across every
|
|
711
|
+
// tab. It must be typed with `pressSequentially`: the popup's search input
|
|
712
|
+
// is uncontrolled and its state is written by a `keyup` handler only, so
|
|
713
|
+
// `fill()` (which dispatches `input` alone) never applies the filter.
|
|
714
|
+
async revealChangeElementTemplate(option, searchTerm) {
|
|
715
|
+
if (await this.waitForVisible(option, 5000)) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (await this.waitForVisible(this.changeElementTabStrip, 5000)) {
|
|
719
|
+
await this.reusableAssetsTab.click({ timeout: 30000 });
|
|
720
|
+
if (await this.waitForVisible(option, 15000)) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (!(await this.waitForVisible(this.changeElementSearchInput, 5000))) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
await this.changeElementSearchInput.click();
|
|
728
|
+
await this.changeElementSearchInput.fill('');
|
|
729
|
+
await this.changeElementSearchInput.pressSequentially(searchTerm, {
|
|
730
|
+
delay: 50,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
686
733
|
async clickRestConnectorOption() {
|
|
734
|
+
await this.revealChangeElementTemplate(this.restConnectorOption, 'REST');
|
|
687
735
|
await (0, test_1.expect)(this.restConnectorOption).toBeVisible({ timeout: 15000 });
|
|
688
736
|
await this.restConnectorOption.click();
|
|
689
737
|
}
|
|
690
738
|
async clickWebhookStartEventConnectorOption() {
|
|
739
|
+
await this.revealChangeElementTemplate(this.webhookMessageStartEventConnectorOption, 'Webhook');
|
|
691
740
|
await this.webhookMessageStartEventConnectorOption.click({ timeout: 60000 });
|
|
692
741
|
}
|
|
693
742
|
async clickWebhookTab() {
|
|
@@ -940,6 +989,7 @@ class ModelerCreatePage {
|
|
|
940
989
|
await this.intermediateBoundaryEvent.click({ timeout: 60000 });
|
|
941
990
|
}
|
|
942
991
|
async clickIntermediateWebhookConnectorOption() {
|
|
992
|
+
await this.revealChangeElementTemplate(this.intermediateWebhookConnectorOption, 'Webhook');
|
|
943
993
|
await this.intermediateWebhookConnectorOption.click({ timeout: 60000 });
|
|
944
994
|
}
|
|
945
995
|
async clickCorrelationKeyProcessInput() {
|
|
@@ -121,11 +121,21 @@ class TaskDetailsPage {
|
|
|
121
121
|
async clickUnassignButton() {
|
|
122
122
|
await this.unassignButton.click();
|
|
123
123
|
}
|
|
124
|
+
// The "Task completed" toast is a transient confirmation: Tasklist's
|
|
125
|
+
// notification store auto-removes it, and it is never rendered at all when
|
|
126
|
+
// the refreshed task list deselects the completed task before the completion
|
|
127
|
+
// promise resolves (the nightly trace showed exactly that — the task was
|
|
128
|
+
// completed, the details panel rendered "Completed by Me" plus a completion
|
|
129
|
+
// date, and no toast ever entered the DOM). Accept the task's own completed
|
|
130
|
+
// state as well: the details panel only renders "Completion date" once the
|
|
131
|
+
// task has one, so this confirms the completion rather than weakening the
|
|
132
|
+
// check. Same fix already carried by pages/8.10/TaskDetailsPage.ts.
|
|
124
133
|
async clickCompleteTaskButton() {
|
|
125
134
|
await this.completeTaskButton.click({ timeout: 60000 });
|
|
126
|
-
await (0, test_1.expect)(this.page
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
await (0, test_1.expect)(this.page
|
|
136
|
+
.getByText('Task completed')
|
|
137
|
+
.or(this.detailsPanel.getByText('Completion date'))
|
|
138
|
+
.first()).toBeVisible({ timeout: 60000 });
|
|
129
139
|
await this.page.reload();
|
|
130
140
|
}
|
|
131
141
|
async clickAddVariableButton() {
|
|
@@ -30,10 +30,15 @@ declare class NavigationPage {
|
|
|
30
30
|
username: string;
|
|
31
31
|
password: string;
|
|
32
32
|
}): Promise<void>;
|
|
33
|
+
private goToWithoutAccess;
|
|
33
34
|
goToOptimizeWithoutAccess({ username, password, }?: {
|
|
34
35
|
username?: string;
|
|
35
36
|
password?: string;
|
|
36
37
|
}): Promise<void>;
|
|
38
|
+
goToModelerWithoutAccess({ username, password, }?: {
|
|
39
|
+
username?: string;
|
|
40
|
+
password?: string;
|
|
41
|
+
}): Promise<void>;
|
|
37
42
|
goToKeycloak(): Promise<void>;
|
|
38
43
|
goToOCIdentity(sleepTimeout?: number, credentials?: {
|
|
39
44
|
username: string;
|
|
@@ -5,6 +5,7 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const LoginPage_1 = require("./LoginPage");
|
|
6
6
|
const sleep_1 = require("../../utils/sleep");
|
|
7
7
|
const constants_1 = require("../../utils/constants");
|
|
8
|
+
const GATEWAY_ERROR_STATUSES = [502, 503, 504];
|
|
8
9
|
class NavigationPage {
|
|
9
10
|
page;
|
|
10
11
|
modelerPageBanner;
|
|
@@ -118,18 +119,34 @@ class NavigationPage {
|
|
|
118
119
|
async goToOptimize(sleepTimeout, credentials) {
|
|
119
120
|
await this.goTo('/optimize', this.optimizePageBanner, sleepTimeout, credentials);
|
|
120
121
|
}
|
|
121
|
-
// `goTo` ends by asserting the app banner, so it cannot be used to reach
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
122
|
+
// `goTo` ends by asserting the app banner, so it cannot be used to reach a
|
|
123
|
+
// component the user has no role for: the denied response carries neither
|
|
124
|
+
// the banner nor a login form, and every retry burns a full 60s timeout.
|
|
125
|
+
// Navigate and log in only; the caller asserts what the denied page shows.
|
|
126
|
+
// The ingress answers 5xx while a component is still rolling out (upgrade
|
|
127
|
+
// scenarios), so retry the request until the component itself responds.
|
|
128
|
+
async goToWithoutAccess(url, username, password) {
|
|
129
|
+
await (0, test_1.expect)(async () => {
|
|
130
|
+
const response = await this.page.goto(url, {
|
|
131
|
+
timeout: 60000,
|
|
132
|
+
waitUntil: 'domcontentloaded',
|
|
133
|
+
});
|
|
134
|
+
(0, test_1.expect)(GATEWAY_ERROR_STATUSES).not.toContain(response?.status());
|
|
135
|
+
}).toPass({ timeout: 120000, intervals: [5000, 10000, 15000] });
|
|
136
|
+
await new LoginPage_1.LoginPage(this.page).login(username, password);
|
|
137
|
+
}
|
|
138
|
+
// Optimize denies a user without the Optimize role with a `text/plain` 403
|
|
139
|
+
// page.
|
|
126
140
|
async goToOptimizeWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
|
|
127
141
|
'demo', } = {}) {
|
|
128
|
-
await this.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
await this.goToWithoutAccess('/optimize', username, password);
|
|
143
|
+
}
|
|
144
|
+
// Web Modeler denies a user without the Web Modeler role with a blank page.
|
|
145
|
+
// It briefly renders its banner before dropping it, so `goTo`'s closing
|
|
146
|
+
// banner assertion either fails outright or passes by winning that race.
|
|
147
|
+
async goToModelerWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
|
|
148
|
+
'demo', } = {}) {
|
|
149
|
+
await this.goToWithoutAccess('/modeler', username, password);
|
|
133
150
|
}
|
|
134
151
|
async goToKeycloak() {
|
|
135
152
|
const keycloakUrl = process.env.KEYCLOAK_URL
|
|
@@ -147,18 +164,11 @@ class NavigationPage {
|
|
|
147
164
|
async goToConsole(sleepTimeout, credentials) {
|
|
148
165
|
await this.goTo('/console', this.consolePageBanner, sleepTimeout, credentials);
|
|
149
166
|
}
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// Console's own "Something went wrong" error page, which has neither the
|
|
153
|
-
// banner nor a login form, and every retry burns a full 60s timeout.
|
|
154
|
-
// Navigate and log in only; the caller asserts what the denied page shows.
|
|
167
|
+
// Console denies a user without the Console role with its own "Something
|
|
168
|
+
// went wrong" error page.
|
|
155
169
|
async goToConsoleWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
|
|
156
170
|
'demo', } = {}) {
|
|
157
|
-
await this.
|
|
158
|
-
timeout: 60000,
|
|
159
|
-
waitUntil: 'domcontentloaded',
|
|
160
|
-
});
|
|
161
|
-
await new LoginPage_1.LoginPage(this.page).login(username, password);
|
|
171
|
+
await this.goToWithoutAccess('/console', username, password);
|
|
162
172
|
}
|
|
163
173
|
async goToManagementIdentity(sleepTimeout, credentials) {
|
|
164
174
|
await this.goTo('/identity', this.managementIdentityPageBanner, sleepTimeout, credentials);
|
|
@@ -132,6 +132,9 @@ _8_9_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
|
|
|
132
132
|
await modelerCreatePage.enterDiagramName(processName);
|
|
133
133
|
await (0, sleep_1.sleep)(10000);
|
|
134
134
|
await modelerCreatePage.clickChangeTypeButton();
|
|
135
|
+
// Connector templates now sit behind the change-element popup's
|
|
136
|
+
// "Reusable Assets" tab, so surface the entry before asserting on it.
|
|
137
|
+
await modelerCreatePage.revealChangeElementTemplate(modelerCreatePage.webhookMessageStartEventConnectorOption, 'Webhook');
|
|
135
138
|
await (0, test_1.expect)(modelerCreatePage.webhookMessageStartEventConnectorOption).toBeVisible({
|
|
136
139
|
timeout: 30000,
|
|
137
140
|
});
|
|
@@ -74,7 +74,7 @@ SM_8_8_1.test.describe('Identity User Flow Tests', () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
await SM_8_8_1.test.step('Ensure Modeler is not Accessible', async () => {
|
|
77
|
-
await navigationPage.
|
|
77
|
+
await navigationPage.goToModelerWithoutAccess(credentials);
|
|
78
78
|
await (0, test_1.expect)(modelerHomePage.modelerPageBanner).not.toBeVisible({
|
|
79
79
|
timeout: 60000,
|
|
80
80
|
});
|