@camunda/e2e-test-suite 0.0.970 → 0.0.972
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/ModelerCreatePage.d.ts +5 -0
- package/dist/pages/8.10/ModelerCreatePage.js +58 -1
- package/dist/pages/SM-8.10/ConnectorMarketplacePage.d.ts +1 -0
- package/dist/pages/SM-8.10/ConnectorMarketplacePage.js +43 -5
- package/dist/pages/SM-8.10/ModelerCreatePage.d.ts +2 -0
- package/dist/pages/SM-8.10/ModelerCreatePage.js +50 -13
- package/dist/tests/8.10/connectors-user-flows.spec.js +3 -0
- package/package.json +1 -1
|
@@ -25,6 +25,9 @@ declare class ModelerCreatePage {
|
|
|
25
25
|
readonly additionalActionsButton: Locator;
|
|
26
26
|
readonly cancelButton: Locator;
|
|
27
27
|
readonly restConnectorOption: Locator;
|
|
28
|
+
readonly changeElementSearchInput: Locator;
|
|
29
|
+
readonly changeElementTabStrip: Locator;
|
|
30
|
+
readonly reusableAssetsTab: Locator;
|
|
28
31
|
readonly webhookMessageStartEventConnectorOption: Locator;
|
|
29
32
|
readonly webhookTab: Locator;
|
|
30
33
|
readonly webhookIsActiveButton: Locator;
|
|
@@ -156,6 +159,8 @@ declare class ModelerCreatePage {
|
|
|
156
159
|
clickNewForm(): Promise<void>;
|
|
157
160
|
clickDeploySubButton(): Promise<void>;
|
|
158
161
|
clickCancelButton(): Promise<void>;
|
|
162
|
+
private waitForVisible;
|
|
163
|
+
revealChangeElementTemplate(option: Locator, searchTerm: string): Promise<void>;
|
|
159
164
|
clickRestConnectorOption(): Promise<void>;
|
|
160
165
|
clickWebhookStartEventConnectorOption(): Promise<void>;
|
|
161
166
|
clickWebhookTab(): Promise<void>;
|
|
@@ -31,6 +31,9 @@ class ModelerCreatePage {
|
|
|
31
31
|
additionalActionsButton;
|
|
32
32
|
cancelButton;
|
|
33
33
|
restConnectorOption;
|
|
34
|
+
changeElementSearchInput;
|
|
35
|
+
changeElementTabStrip;
|
|
36
|
+
reusableAssetsTab;
|
|
34
37
|
webhookMessageStartEventConnectorOption;
|
|
35
38
|
webhookTab;
|
|
36
39
|
webhookIsActiveButton;
|
|
@@ -170,6 +173,17 @@ class ModelerCreatePage {
|
|
|
170
173
|
this.deploySubButton = page.locator('[data-test="deploy-action"]');
|
|
171
174
|
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
|
|
172
175
|
this.restConnectorOption = page.locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
|
|
176
|
+
this.changeElementSearchInput = page.locator('.djs-popup-search input');
|
|
177
|
+
this.changeElementTabStrip = page.locator('.djs-popup-tabs-container');
|
|
178
|
+
// The tab renders its label as text content, which wins over its `title`
|
|
179
|
+
// attribute in the accessible-name computation. Match case-insensitively:
|
|
180
|
+
// the label is capitalised differently across bpmn-js releases. Scope the
|
|
181
|
+
// lookup to the popup's own tab strip -- the properties panel renders its
|
|
182
|
+
// own tablist (see webhookTab below), so an unscoped tab query is
|
|
183
|
+
// ambiguous.
|
|
184
|
+
this.reusableAssetsTab = this.changeElementTabStrip.getByRole('tab', {
|
|
185
|
+
name: /reusable assets/i,
|
|
186
|
+
});
|
|
173
187
|
// bpmn-js popup entries are `li[role="option"]` inside a `role="listbox"`,
|
|
174
188
|
// so getByRole('listitem') no longer matches. Their accessible name also
|
|
175
189
|
// includes the entry description, and three entries share the label
|
|
@@ -734,11 +748,53 @@ class ModelerCreatePage {
|
|
|
734
748
|
async clickCancelButton() {
|
|
735
749
|
await this.cancelButton.click({ timeout: 60000 });
|
|
736
750
|
}
|
|
751
|
+
// `waitFor` actually blocks up to the timeout; `isVisible()` would return
|
|
752
|
+
// immediately and report false while the popup is still rendering.
|
|
753
|
+
async waitForVisible(locator, timeout) {
|
|
754
|
+
return locator
|
|
755
|
+
.first()
|
|
756
|
+
.waitFor({ state: 'visible', timeout })
|
|
757
|
+
.then(() => true)
|
|
758
|
+
.catch(() => false);
|
|
759
|
+
}
|
|
760
|
+
// The change-element popup is now tabbed: every `replace.template-*` entry
|
|
761
|
+
// is assigned to the "Reusable Assets" tab, so connector templates are not
|
|
762
|
+
// in the DOM at all while the default "BPMN" tab is selected - which is why
|
|
763
|
+
// the template locators time out with `<element(s) not found>`.
|
|
764
|
+
// Opening that tab is version-tolerant: on an untabbed popup the strip is
|
|
765
|
+
// absent and this is a no-op.
|
|
766
|
+
// Search is the fallback, because it spans the full entry set across every
|
|
767
|
+
// tab. It must be typed with `pressSequentially`: the popup's search input
|
|
768
|
+
// is uncontrolled and its state is written by a `keyup` handler only, so
|
|
769
|
+
// `fill()` (which dispatches `input` alone) never applies the filter.
|
|
770
|
+
async revealChangeElementTemplate(option, searchTerm) {
|
|
771
|
+
if (await this.waitForVisible(option, 5000)) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (await this.waitForVisible(this.changeElementTabStrip, 5000)) {
|
|
775
|
+
// Do not throw: a failed tab click leaves the search fallback below
|
|
776
|
+
// intact, and search alone is enough on an untabbed popup.
|
|
777
|
+
await this.reusableAssetsTab.click({ timeout: 30000 }).catch(() => { });
|
|
778
|
+
if (await this.waitForVisible(option, 15000)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (!(await this.waitForVisible(this.changeElementSearchInput, 5000))) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
await this.changeElementSearchInput.click();
|
|
786
|
+
await this.changeElementSearchInput.fill('');
|
|
787
|
+
await this.changeElementSearchInput.pressSequentially(searchTerm, {
|
|
788
|
+
delay: 50,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
737
791
|
async clickRestConnectorOption() {
|
|
738
|
-
await
|
|
792
|
+
await this.revealChangeElementTemplate(this.restConnectorOption, 'REST');
|
|
793
|
+
await (0, test_1.expect)(this.restConnectorOption).toBeVisible({ timeout: 90000 });
|
|
739
794
|
await this.restConnectorOption.click();
|
|
740
795
|
}
|
|
741
796
|
async clickWebhookStartEventConnectorOption() {
|
|
797
|
+
await this.revealChangeElementTemplate(this.webhookMessageStartEventConnectorOption, 'Webhook');
|
|
742
798
|
await this.webhookMessageStartEventConnectorOption.click({ timeout: 60000 });
|
|
743
799
|
}
|
|
744
800
|
async clickWebhookTab() {
|
|
@@ -991,6 +1047,7 @@ class ModelerCreatePage {
|
|
|
991
1047
|
await this.intermediateBoundaryEvent.click({ timeout: 60000 });
|
|
992
1048
|
}
|
|
993
1049
|
async clickIntermediateWebhookConnectorOption() {
|
|
1050
|
+
await this.revealChangeElementTemplate(this.intermediateWebhookConnectorOption, 'Webhook');
|
|
994
1051
|
await this.intermediateWebhookConnectorOption.click({ timeout: 60000 });
|
|
995
1052
|
}
|
|
996
1053
|
async clickCorrelationKeyProcessInput() {
|
|
@@ -85,6 +85,9 @@ class ConnectorMarketplacePage {
|
|
|
85
85
|
await this.cancelButton.click({ timeout: 20000 });
|
|
86
86
|
throw new Error('Connector import modal offered no import action ("Add to project", "Replace resource" or "Save as copy"); the import was abandoned.');
|
|
87
87
|
}
|
|
88
|
+
// Hub imported without asking for confirmation, so the write is in flight
|
|
89
|
+
// here too.
|
|
90
|
+
await this.waitForImportToSettle();
|
|
88
91
|
}
|
|
89
92
|
// Hub's import modal labels its primary button after the project state:
|
|
90
93
|
// "Add to project" when the template is new to the project, "Replace
|
|
@@ -99,18 +102,53 @@ class ConnectorMarketplacePage {
|
|
|
99
102
|
this.replaceResourceButton,
|
|
100
103
|
this.saveAsCopyButton,
|
|
101
104
|
];
|
|
102
|
-
|
|
105
|
+
// Carbon animates the modal in, so a click issued mid-transition is
|
|
106
|
+
// rejected ("element is not stable") and by the next Playwright retry the
|
|
107
|
+
// button can already be gone; keep probing the labels until one click
|
|
108
|
+
// actually lands instead of burning the whole budget on a single button.
|
|
109
|
+
const deadline = Date.now() + 45000;
|
|
103
110
|
do {
|
|
104
111
|
for (const button of importButtons) {
|
|
105
|
-
if (await button.isVisible().catch(() => false)) {
|
|
106
|
-
|
|
107
|
-
await (0, test_1.expect)(button).toBeHidden({ timeout: 60000 });
|
|
108
|
-
return true;
|
|
112
|
+
if (!(await button.isVisible().catch(() => false))) {
|
|
113
|
+
continue;
|
|
109
114
|
}
|
|
115
|
+
// Register the waiter before the click: the write can be acknowledged
|
|
116
|
+
// before the modal finishes unmounting.
|
|
117
|
+
const templateWritten = this.page
|
|
118
|
+
.waitForResponse((response) => response.request().method() === 'POST' &&
|
|
119
|
+
response.url().includes('/internal/files'), { timeout: 30000 })
|
|
120
|
+
.catch(() => null);
|
|
121
|
+
const clicked = await button
|
|
122
|
+
.click({ timeout: 5000 })
|
|
123
|
+
.then(() => true)
|
|
124
|
+
.catch(() => false);
|
|
125
|
+
if (!clicked) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
await (0, test_1.expect)(button).toBeHidden({ timeout: 60000 });
|
|
129
|
+
const written = await templateWritten;
|
|
130
|
+
// A rejected write leaves the project without the template just like
|
|
131
|
+
// an aborted one, so surface the status here instead of letting it
|
|
132
|
+
// resurface 60s later as a missing "Authentication" group in
|
|
133
|
+
// ModelerCreatePage.
|
|
134
|
+
if (written && !written.ok()) {
|
|
135
|
+
throw new Error(`Connector template import failed: writing it into the project returned HTTP ${written.status()}.`);
|
|
136
|
+
}
|
|
137
|
+
await this.waitForImportToSettle();
|
|
138
|
+
return true;
|
|
110
139
|
}
|
|
111
140
|
await (0, sleep_1.sleep)(1000);
|
|
112
141
|
} while (Date.now() < deadline);
|
|
113
142
|
return false;
|
|
114
143
|
}
|
|
144
|
+
// Hub unmounts the ImportModal as soon as the click is handled, but writes the
|
|
145
|
+
// template into the project with a POST that is still in flight at that point.
|
|
146
|
+
// Returning right after the button hides let the caller's `page.reload()`
|
|
147
|
+
// abort that write ~20ms later, leaving the project's connector-template list
|
|
148
|
+
// empty and every templated task on the diagram at "Template: Not found" --
|
|
149
|
+
// the properties panel then never renders the "Authentication" group.
|
|
150
|
+
async waitForImportToSettle() {
|
|
151
|
+
await this.page.waitForLoadState('networkidle').catch(() => { });
|
|
152
|
+
}
|
|
115
153
|
}
|
|
116
154
|
exports.ConnectorMarketplacePage = ConnectorMarketplacePage;
|
|
@@ -30,6 +30,8 @@ declare class ModelerCreatePage {
|
|
|
30
30
|
readonly cancelButton: Locator;
|
|
31
31
|
readonly restConnectorOption: Locator;
|
|
32
32
|
readonly changeElementSearchInput: Locator;
|
|
33
|
+
readonly changeElementTabStrip: Locator;
|
|
34
|
+
readonly reusableAssetsTab: Locator;
|
|
33
35
|
readonly marketPlaceButton: Locator;
|
|
34
36
|
readonly clientIdTextbox: Locator;
|
|
35
37
|
readonly clientSecretTextbox: Locator;
|
|
@@ -35,6 +35,8 @@ class ModelerCreatePage {
|
|
|
35
35
|
cancelButton;
|
|
36
36
|
restConnectorOption;
|
|
37
37
|
changeElementSearchInput;
|
|
38
|
+
changeElementTabStrip;
|
|
39
|
+
reusableAssetsTab;
|
|
38
40
|
marketPlaceButton;
|
|
39
41
|
clientIdTextbox;
|
|
40
42
|
clientSecretTextbox;
|
|
@@ -156,6 +158,12 @@ class ModelerCreatePage {
|
|
|
156
158
|
.locator('.djs-popup-results')
|
|
157
159
|
.locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
|
|
158
160
|
this.changeElementSearchInput = page.locator('.djs-popup-search input');
|
|
161
|
+
this.changeElementTabStrip = page.locator('.djs-popup-tabs-container');
|
|
162
|
+
// The tab renders its label as text content, and the product source
|
|
163
|
+
// spells it both "Reusable Assets" and "Reusable assets".
|
|
164
|
+
this.reusableAssetsTab = page.getByRole('tab', {
|
|
165
|
+
name: /reusable assets/i,
|
|
166
|
+
});
|
|
159
167
|
// A title query is ambiguous: when the filtered result list has no connector
|
|
160
168
|
// match the popup also renders an empty-state link carrying the same title,
|
|
161
169
|
// so the query resolves to two elements and clicking throws in strict mode.
|
|
@@ -965,16 +973,44 @@ class ModelerCreatePage {
|
|
|
965
973
|
}
|
|
966
974
|
}
|
|
967
975
|
}
|
|
968
|
-
// Connector templates
|
|
969
|
-
//
|
|
970
|
-
//
|
|
976
|
+
// Connector templates now live in the popup's "Reusable assets" tab, not
|
|
977
|
+
// the default "BPMN" one, so they do not render until either that tab is
|
|
978
|
+
// selected or a search is active (search spans every tab and flattens the
|
|
979
|
+
// result list).
|
|
980
|
+
// Use pressSequentially (not fill): the popup syncs its search state from a
|
|
981
|
+
// keyup listener only, and fill() dispatches just an input event, so the
|
|
982
|
+
// filter never applies and the unfiltered BPMN tab keeps rendering.
|
|
971
983
|
async filterChangeElementPopup(searchTerm) {
|
|
984
|
+
// Two independent paths put the template in the DOM, and neither is
|
|
985
|
+
// reliable alone: selecting the tab renders the template entries
|
|
986
|
+
// directly, while searching spans every tab and flattens the result
|
|
987
|
+
// list. Run 32243588212 showed the search registering (the tab strip
|
|
988
|
+
// hid) with the template still absent from .djs-popup-results, so the
|
|
989
|
+
// tab click runs first and the search backs it up.
|
|
990
|
+
const hasTabs = await this.changeElementTabStrip
|
|
991
|
+
.isVisible({ timeout: 5000 })
|
|
992
|
+
.catch(() => false);
|
|
993
|
+
if (hasTabs) {
|
|
994
|
+
// A failed tab click leaves the search path intact, so do not throw.
|
|
995
|
+
await this.reusableAssetsTab.click({ timeout: 30000 }).catch(() => { });
|
|
996
|
+
}
|
|
972
997
|
const hasSearch = await this.changeElementSearchInput
|
|
973
998
|
.isVisible({ timeout: 5000 })
|
|
974
999
|
.catch(() => false);
|
|
975
|
-
if (hasSearch) {
|
|
976
|
-
|
|
1000
|
+
if (!hasSearch) {
|
|
1001
|
+
return;
|
|
977
1002
|
}
|
|
1003
|
+
await this.changeElementSearchInput.click();
|
|
1004
|
+
await this.changeElementSearchInput.fill('');
|
|
1005
|
+
await this.changeElementSearchInput.pressSequentially(searchTerm, {
|
|
1006
|
+
delay: 50,
|
|
1007
|
+
});
|
|
1008
|
+
// The popup hides its tab strip while searching. Wait on that as a
|
|
1009
|
+
// readiness signal, but do not assert it: with the tab already selected
|
|
1010
|
+
// the template is reachable whether or not the search registered.
|
|
1011
|
+
await (0, test_1.expect)(this.changeElementTabStrip)
|
|
1012
|
+
.toBeHidden({ timeout: 30000 })
|
|
1013
|
+
.catch(() => { });
|
|
978
1014
|
}
|
|
979
1015
|
async clickWebhookMessageStartEventConnectorOption() {
|
|
980
1016
|
const maxRetries = 3;
|
|
@@ -988,6 +1024,11 @@ class ModelerCreatePage {
|
|
|
988
1024
|
}
|
|
989
1025
|
else {
|
|
990
1026
|
await this.page.reload();
|
|
1027
|
+
// A reload drops the selection, so re-select the start event before
|
|
1028
|
+
// reopening the popup -- the context pad only renders for a selected
|
|
1029
|
+
// element and "Change element" would otherwise never appear.
|
|
1030
|
+
await (0, test_1.expect)(this.startEventElement).toBeVisible({ timeout: 60000 });
|
|
1031
|
+
await this.startEventElement.click({ force: true, timeout: 30000 });
|
|
991
1032
|
await this.changeTypeButton.click({ force: true, timeout: 30000 });
|
|
992
1033
|
await this.filterChangeElementPopup('Webhook Message Start Event Connector');
|
|
993
1034
|
await this.webhookMessageStartEventConnectorOption.click({
|
|
@@ -1168,14 +1209,10 @@ class ModelerCreatePage {
|
|
|
1168
1209
|
const max = 3;
|
|
1169
1210
|
for (let i = 0; i < max; i++) {
|
|
1170
1211
|
try {
|
|
1171
|
-
// Filter the popup to surface the webhook connector
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
-
|
|
1175
|
-
.isVisible({ timeout: 3000 })
|
|
1176
|
-
.catch(() => false)) {
|
|
1177
|
-
await this.changeElementSearchInput.fill('Webhook');
|
|
1178
|
-
}
|
|
1212
|
+
// Filter the popup to surface the webhook connector: it sits in the
|
|
1213
|
+
// "Reusable assets" tab, which the popup only flattens into the result
|
|
1214
|
+
// list while a search is active.
|
|
1215
|
+
await this.filterChangeElementPopup('Webhook');
|
|
1179
1216
|
await (0, test_1.expect)(this.intermediateWebhookConnectorOption).toBeVisible({
|
|
1180
1217
|
timeout: 30000,
|
|
1181
1218
|
});
|
|
@@ -132,6 +132,9 @@ _8_10_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
|
});
|