@camunda/e2e-test-suite 0.0.1014 → 0.0.1016
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.7/FormJsPage.d.ts +2 -0
- package/dist/pages/8.7/FormJsPage.js +74 -1
- package/dist/pages/8.7/ModelerCreatePage.d.ts +2 -1
- package/dist/pages/8.7/ModelerCreatePage.js +176 -37
- package/dist/pages/8.7/ModelerHomePage.d.ts +12 -0
- package/dist/pages/8.7/ModelerHomePage.js +152 -7
- package/dist/pages/8.7/ProcessApplicationPage.d.ts +30 -0
- package/dist/pages/8.7/ProcessApplicationPage.js +260 -0
- package/dist/pages/8.7/UtilitiesPage.js +8 -0
- package/dist/pages/8.8/FormJsPage.d.ts +2 -0
- package/dist/pages/8.8/FormJsPage.js +74 -1
- package/dist/pages/8.8/ModelerCreatePage.d.ts +1 -0
- package/dist/pages/8.8/ModelerCreatePage.js +157 -20
- package/dist/pages/8.8/ModelerHomePage.d.ts +12 -0
- package/dist/pages/8.8/ModelerHomePage.js +169 -7
- package/dist/pages/8.8/ProcessApplicationPage.d.ts +30 -0
- package/dist/pages/8.8/ProcessApplicationPage.js +260 -0
- package/dist/pages/8.8/UtilitiesPage.js +8 -0
- package/dist/tests/8.7/idp-user-flows.spec.js +3 -1
- package/dist/tests/8.8/access-user-role-management.spec.js +3 -3
- package/dist/tests/8.8/idp-user-flows.spec.js +6 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
declare class FormJsPage {
|
|
3
3
|
private page;
|
|
4
|
+
private processApplicationPage;
|
|
4
5
|
readonly aiFormGeneratorButton: Locator;
|
|
5
6
|
readonly formRequestInput: Locator;
|
|
6
7
|
readonly generateFormButton: Locator;
|
|
@@ -31,6 +32,7 @@ declare class FormJsPage {
|
|
|
31
32
|
selectCluster(clusterName: string): Promise<void>;
|
|
32
33
|
clickDeployButton(): Promise<void>;
|
|
33
34
|
clickDeploySubButton(): Promise<void>;
|
|
35
|
+
selectOnlyThisResource(): Promise<void>;
|
|
34
36
|
deployForm(clusterName: string): Promise<void>;
|
|
35
37
|
}
|
|
36
38
|
export { FormJsPage };
|
|
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FormJsPage = void 0;
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
|
+
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
6
7
|
class FormJsPage {
|
|
7
8
|
page;
|
|
9
|
+
processApplicationPage;
|
|
8
10
|
aiFormGeneratorButton;
|
|
9
11
|
formRequestInput;
|
|
10
12
|
generateFormButton;
|
|
@@ -23,6 +25,7 @@ class FormJsPage {
|
|
|
23
25
|
deploySuccessNotification;
|
|
24
26
|
constructor(page) {
|
|
25
27
|
this.page = page;
|
|
28
|
+
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
26
29
|
this.aiFormGeneratorButton = page.getByRole('button', {
|
|
27
30
|
name: 'AI Form Generator',
|
|
28
31
|
});
|
|
@@ -44,9 +47,12 @@ class FormJsPage {
|
|
|
44
47
|
this.deployButton = page
|
|
45
48
|
.locator('[data-test="action-bar"]')
|
|
46
49
|
.getByRole('button', { name: 'Deploy', exact: true });
|
|
50
|
+
// Inside a process application hub titles this modal for the application
|
|
51
|
+
// rather than for the form, so 'Deploy form' never matches and the dialog
|
|
52
|
+
// reads as absent. pages/SM-8.10 filters on 'Deploy' for the same reason.
|
|
47
53
|
this.dialog = page
|
|
48
54
|
.locator('.cds--modal-container[role="dialog"]')
|
|
49
|
-
.filter({ hasText: 'Deploy
|
|
55
|
+
.filter({ hasText: 'Deploy' });
|
|
50
56
|
this.deploySubButton = this.dialog.getByRole('button', {
|
|
51
57
|
name: 'Deploy',
|
|
52
58
|
exact: true,
|
|
@@ -99,6 +105,46 @@ class FormJsPage {
|
|
|
99
105
|
async selectCluster(clusterName) {
|
|
100
106
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
|
|
101
107
|
const healthCheckTimeout = 30000;
|
|
108
|
+
// Inside a process application the deploy dialog renders
|
|
109
|
+
// ProcessAppClusterSelection, not the per-deploy ClusterSelection toggle
|
|
110
|
+
// list: it shows only clusters assigned to one of the application's stages
|
|
111
|
+
// and auto-selects the first, so there is no label to click. Confirm the
|
|
112
|
+
// intended cluster is the one on offer instead.
|
|
113
|
+
//
|
|
114
|
+
// The unconfigured case is called out explicitly: without this the symptom
|
|
115
|
+
// is the cluster toggle never appearing, retried until the test times out,
|
|
116
|
+
// which says nothing about the actual cause.
|
|
117
|
+
const notConfigured = this.dialog.getByText('Clusters not configured for stages');
|
|
118
|
+
if (await notConfigured.isVisible().catch(() => false)) {
|
|
119
|
+
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
120
|
+
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
121
|
+
}
|
|
122
|
+
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
123
|
+
hasText: new RegExp(`^${clusterName}`),
|
|
124
|
+
});
|
|
125
|
+
const isLegacyDialog = await legacyClusterToggle
|
|
126
|
+
.first()
|
|
127
|
+
.isVisible()
|
|
128
|
+
.catch(() => false);
|
|
129
|
+
if (!isLegacyDialog) {
|
|
130
|
+
const offered = await this.dialog
|
|
131
|
+
.getByText(clusterName, { exact: false })
|
|
132
|
+
.first()
|
|
133
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
134
|
+
.then(() => true)
|
|
135
|
+
.catch(() => false);
|
|
136
|
+
if (!offered) {
|
|
137
|
+
// Forget the binding before failing. The dialog lists exactly the
|
|
138
|
+
// clusters this application's stages point at, so a missing cluster
|
|
139
|
+
// means the binding did not take -- and no amount of reopening the
|
|
140
|
+
// dialog will make it appear. Clearing the record lets the caller's
|
|
141
|
+
// next attempt rebind instead of re-checking an unchanged dialog.
|
|
142
|
+
this.processApplicationPage.forgetStageCluster();
|
|
143
|
+
throw new Error(`The deploy dialog does not offer "${clusterName}": its stage ` +
|
|
144
|
+
'binding did not take. It will be rebound on the next attempt.');
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
102
148
|
const healthyRegex = new RegExp(`${clusterName}[\\s\\S]*?HealthydevManage`);
|
|
103
149
|
const cluster = this.dialog.locator(`[title="${clusterName}"]`);
|
|
104
150
|
await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
|
|
@@ -117,12 +163,39 @@ class FormJsPage {
|
|
|
117
163
|
async clickDeploySubButton() {
|
|
118
164
|
await this.deploySubButton.click({ timeout: 60000 });
|
|
119
165
|
}
|
|
166
|
+
// Since camunda/camunda-hub#25831 a form lives inside a process application,
|
|
167
|
+
// and Hub then scopes Deploy to the WHOLE application by default -- it calls
|
|
168
|
+
// deployProcessApplication, not deployFile, so the "Form deployed!" toast
|
|
169
|
+
// never fires (hub's own form-deploy-action.spec.sm.tsx asserts exactly that
|
|
170
|
+
// default). Narrowing the scope keeps this a form-deploy test instead of
|
|
171
|
+
// silently turning it into a process-application deploy. The combobox only
|
|
172
|
+
// exists inside a container, hence the boolean probe.
|
|
173
|
+
async selectOnlyThisResource() {
|
|
174
|
+
const scopeSelect = this.dialog.getByRole('combobox', {
|
|
175
|
+
name: 'Resources to deploy',
|
|
176
|
+
});
|
|
177
|
+
const hasScopeSelect = await scopeSelect
|
|
178
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
179
|
+
.then(() => true)
|
|
180
|
+
.catch(() => false);
|
|
181
|
+
if (!hasScopeSelect) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
await scopeSelect.click();
|
|
185
|
+
await this.dialog
|
|
186
|
+
.getByRole('option', { name: 'Only this resource' })
|
|
187
|
+
.or(this.dialog.getByText('Only this resource', { exact: true }))
|
|
188
|
+
.first()
|
|
189
|
+
.click({ timeout: 30000 });
|
|
190
|
+
}
|
|
120
191
|
async deployForm(clusterName) {
|
|
192
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
121
193
|
await this.clickDeployButton();
|
|
122
194
|
await (0, test_1.expect)(this.dialog).toBeVisible({
|
|
123
195
|
timeout: 30000,
|
|
124
196
|
});
|
|
125
197
|
await this.selectCluster(clusterName);
|
|
198
|
+
await this.selectOnlyThisResource();
|
|
126
199
|
await this.clickDeploySubButton();
|
|
127
200
|
await (0, test_1.expect)(this.deploySuccessNotification).toBeVisible({ timeout: 30000 });
|
|
128
201
|
await (0, test_1.expect)(this.deploySuccessNotification).not.toBeVisible({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
declare class ModelerCreatePage {
|
|
3
3
|
private page;
|
|
4
|
+
private processApplicationPage;
|
|
4
5
|
readonly generalPanel: Locator;
|
|
5
6
|
private deployDiagramDialog;
|
|
6
7
|
readonly processIdInput: Locator;
|
|
@@ -216,7 +217,7 @@ declare class ModelerCreatePage {
|
|
|
216
217
|
private suppressTestModeAnnouncement;
|
|
217
218
|
switchToPlay(): Promise<void>;
|
|
218
219
|
completePlayConfiguration(clusterName: string): Promise<void>;
|
|
219
|
-
selectCluster(clusterName: string): Promise<void>;
|
|
220
|
+
selectCluster(clusterName: string, healthCheckTimeout?: number): Promise<void>;
|
|
220
221
|
embedForm(formName: string): Promise<void>;
|
|
221
222
|
fillStartEventVariablesForDocHandling(zeebeUrl: string, zeebeClientId: string, zeebeClientSecret: string): Promise<void>;
|
|
222
223
|
fillZeebeClientId(clientId: string): Promise<void>;
|
|
@@ -4,8 +4,10 @@ exports.ModelerCreatePage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const ConnectorMarketplacePage_1 = require("./ConnectorMarketplacePage");
|
|
7
|
+
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
7
8
|
class ModelerCreatePage {
|
|
8
9
|
page;
|
|
10
|
+
processApplicationPage;
|
|
9
11
|
generalPanel;
|
|
10
12
|
deployDiagramDialog;
|
|
11
13
|
processIdInput;
|
|
@@ -109,6 +111,7 @@ class ModelerCreatePage {
|
|
|
109
111
|
diagramNameBreadcrumb;
|
|
110
112
|
constructor(page) {
|
|
111
113
|
this.page = page;
|
|
114
|
+
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
112
115
|
this.generalPanel = page.locator('[data-group-id="group-general"]');
|
|
113
116
|
this.processIdInput = page.getByLabel('ID', { exact: true });
|
|
114
117
|
this.startEventElement = page.locator('.djs-hit').first();
|
|
@@ -220,8 +223,12 @@ class ModelerCreatePage {
|
|
|
220
223
|
this.rbaEnabledDeployedDialog = page.getByRole('dialog', {
|
|
221
224
|
name: 'Process successfully deployed',
|
|
222
225
|
});
|
|
226
|
+
// Since camunda/camunda-hub#25831 a diagram deploys as part of its process
|
|
227
|
+
// application and hub titles the modal for the application, not for the
|
|
228
|
+
// single resource, so an exact 'Deploy diagram' name no longer matches.
|
|
229
|
+
// Match on the verb -- pages/SM-8.10 already leaves this unfiltered.
|
|
223
230
|
this.deployDiagramDialog = page.getByRole('dialog', {
|
|
224
|
-
name:
|
|
231
|
+
name: /^Deploy/,
|
|
225
232
|
});
|
|
226
233
|
this.appendGatewayButton = page.getByRole('button', {
|
|
227
234
|
name: 'Append gateway',
|
|
@@ -472,6 +479,7 @@ class ModelerCreatePage {
|
|
|
472
479
|
await this.deployMenuItem.click({ timeout: 60000 });
|
|
473
480
|
}
|
|
474
481
|
async deployDiagram(clusterName) {
|
|
482
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
475
483
|
await this.clickDeployButtonWithRetry();
|
|
476
484
|
await (0, test_1.expect)(this.deployDiagramDialog).toBeVisible({
|
|
477
485
|
timeout: 30000,
|
|
@@ -521,24 +529,29 @@ class ModelerCreatePage {
|
|
|
521
529
|
await this.deployAndRunSubButton.click({ timeout: 30000 });
|
|
522
530
|
}
|
|
523
531
|
async runProcessInstance(clusterName, variables = '', formVariables = {}, textToVisible = '') {
|
|
532
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
524
533
|
await this.clickDeployAndRunMainButton();
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
534
|
+
// Routed through selectCluster so this path gets the same
|
|
535
|
+
// legacy-toggle-vs-ProcessAppClusterSelection branch as every other deploy:
|
|
536
|
+
// inside a process application there is no cluster toggle to click. Keeps
|
|
537
|
+
// this path's own 120s health wait rather than selectCluster's 30s default.
|
|
538
|
+
try {
|
|
539
|
+
await this.selectCluster(clusterName, 120000);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
// selectCluster clears the stage binding when the dialog does not offer
|
|
543
|
+
// the cluster, so ensureStageCluster rebinds here and reports that it
|
|
544
|
+
// navigated. Any other failure leaves the binding in place, it returns
|
|
545
|
+
// false, and the original error stands -- there is nothing to redo.
|
|
546
|
+
// Unlike the 8.8+ flows this one has no surrounding retry loop, hence
|
|
547
|
+
// the one explicit second attempt.
|
|
548
|
+
const rebound = await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
549
|
+
if (!rebound) {
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
await this.clickDeployAndRunMainButton();
|
|
553
|
+
await this.selectCluster(clusterName, 120000);
|
|
554
|
+
}
|
|
542
555
|
if (variables.length > 0) {
|
|
543
556
|
await (0, test_1.expect)(this.variableInput).toBeVisible({
|
|
544
557
|
timeout: 60000,
|
|
@@ -633,8 +646,45 @@ class ModelerCreatePage {
|
|
|
633
646
|
}
|
|
634
647
|
}
|
|
635
648
|
}
|
|
649
|
+
// Opening the breadcrumb menu leaves the pointer on the breadcrumb button,
|
|
650
|
+
// and hub renders that button's truncated-name tooltip on top of the menu,
|
|
651
|
+
// where it swallows the click on "Rename" -- playwright reports
|
|
652
|
+
// `<div role="tooltip">…</div> intercepts pointer events`. Since
|
|
653
|
+
// camunda/camunda-hub#25831 the breadcrumb carries an extra
|
|
654
|
+
// process-application crumb, so the name truncates, and the tooltip renders,
|
|
655
|
+
// as the rule rather than the exception. Park the pointer away from the
|
|
656
|
+
// breadcrumb and let the tooltip unmount before clicking; reopen the menu if
|
|
657
|
+
// a previous attempt closed it.
|
|
636
658
|
async clickEditDiagramNameButton() {
|
|
637
|
-
|
|
659
|
+
const openTooltip = this.page.locator('[role="tooltip"][aria-hidden="false"]');
|
|
660
|
+
await (0, test_1.expect)(async () => {
|
|
661
|
+
const menuOpen = await this.renameDiagramNameButton
|
|
662
|
+
.isVisible()
|
|
663
|
+
.catch(() => false);
|
|
664
|
+
if (!menuOpen) {
|
|
665
|
+
await this.clickDiagramBreadcrumb();
|
|
666
|
+
}
|
|
667
|
+
// Park the pointer so the breadcrumb's own tooltip gets a chance to
|
|
668
|
+
// unmount -- best effort, because it is not reliably hover-driven, and
|
|
669
|
+
// requiring it to go turned an interceptable click into a hard failure.
|
|
670
|
+
await this.page.mouse.move(0, 0);
|
|
671
|
+
await openTooltip
|
|
672
|
+
.first()
|
|
673
|
+
.waitFor({ state: 'hidden', timeout: 2000 })
|
|
674
|
+
.catch(() => false);
|
|
675
|
+
try {
|
|
676
|
+
await this.renameDiagramNameButton.click({ timeout: 10000 });
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
// The tooltip renders over the menu and swallows the click (playwright
|
|
680
|
+
// reports `<div role="tooltip">…</div> intercepts pointer events`).
|
|
681
|
+
// Dispatching on the element bypasses hit-testing, which no amount of
|
|
682
|
+
// waiting achieves when the tooltip never unmounts. Not a swallowed
|
|
683
|
+
// failure: the assertion below is what decides whether this worked.
|
|
684
|
+
await this.renameDiagramNameButton.dispatchEvent('click');
|
|
685
|
+
}
|
|
686
|
+
await (0, test_1.expect)(this.diagramNameInput).toBeVisible({ timeout: 10000 });
|
|
687
|
+
}).toPass({ timeout: 60000, intervals: [1000, 2000, 5000] });
|
|
638
688
|
}
|
|
639
689
|
async enterDiagramName(name) {
|
|
640
690
|
// The inline name field stays in the DOM after the rename action closes,
|
|
@@ -1137,6 +1187,16 @@ class ModelerCreatePage {
|
|
|
1137
1187
|
await this.playTab.click();
|
|
1138
1188
|
}
|
|
1139
1189
|
async completePlayConfiguration(clusterName) {
|
|
1190
|
+
// Play is a tab on the diagram page, not its own URL, so the stage-cluster
|
|
1191
|
+
// detour's goto() lands back on the Modeler tab and every Play locator
|
|
1192
|
+
// below would then look for a panel that is no longer rendered. Re-enter
|
|
1193
|
+
// Play when the detour actually navigated. Binding cannot simply move
|
|
1194
|
+
// before switchToPlay(): this is the first point in the flow that knows
|
|
1195
|
+
// which cluster the test plays on.
|
|
1196
|
+
const reboundStageCluster = await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
1197
|
+
if (reboundStageCluster) {
|
|
1198
|
+
await this.switchToPlay();
|
|
1199
|
+
}
|
|
1140
1200
|
const timeout = 30000;
|
|
1141
1201
|
// New flow (8.10+): "Setup environment" panel with three steps:
|
|
1142
1202
|
// 1. Connect cluster → "Configure environment" modal → select cluster → Save
|
|
@@ -1160,17 +1220,37 @@ class ModelerCreatePage {
|
|
|
1160
1220
|
name: /^Configure (test case|scenario)$/,
|
|
1161
1221
|
}))
|
|
1162
1222
|
.first();
|
|
1163
|
-
//
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1223
|
+
// Play settles into exactly ONE of three states, and which one is not
|
|
1224
|
+
// knowable up front, so race them instead of timing out on each in turn:
|
|
1225
|
+
// the setup overlay, the legacy Continue button, or the configure-test
|
|
1226
|
+
// panel (meaning setup is already done for this diagram).
|
|
1227
|
+
//
|
|
1228
|
+
// Waiting only for the first two and reading "neither" as "already ready"
|
|
1229
|
+
// was wrong once the diagram moved inside a process application.
|
|
1230
|
+
// ConfigurationOverlay renders null until isDeploymentStateValidated, and
|
|
1231
|
+
// it sets that flag synchronously ONLY when processApplicationId is
|
|
1232
|
+
// undefined -- exactly what a project-root diagram used to give it. With
|
|
1233
|
+
// the diagram in a process application the id is defined, so the overlay
|
|
1234
|
+
// must first await checkExistingDeployment(). The old 15s probe raced that
|
|
1235
|
+
// async call, concluded "already ready" and returned while the overlay was
|
|
1236
|
+
// still about to appear. ConfigureTestPanel returns null for as long as the
|
|
1237
|
+
// overlay is up, so the Start button never existed and
|
|
1238
|
+
// clickStartInstanceButton burned its entire retry budget looking for it.
|
|
1239
|
+
const configurationOverlay = this.page.locator('[data-test="play-configuration-overlay"]');
|
|
1240
|
+
const configureTestPanel = this.page.getByTestId('configure-test-panel');
|
|
1241
|
+
await (0, test_1.expect)(configurationOverlay
|
|
1242
|
+
.or(this.continueToPlayButton)
|
|
1243
|
+
.or(configureTestPanel)
|
|
1244
|
+
.first()).toBeVisible({ timeout: 90000 });
|
|
1245
|
+
if (await configureTestPanel.isVisible()) {
|
|
1246
|
+
// Already past setup for this diagram -- nothing to drive.
|
|
1171
1247
|
return;
|
|
1172
1248
|
}
|
|
1173
|
-
|
|
1249
|
+
// Keyed on the overlay itself, not on its Deploy button: when
|
|
1250
|
+
// checkExistingDeployment finds a deployment the overlay opens with the
|
|
1251
|
+
// deploy step already complete, so there is no Deploy button to see and
|
|
1252
|
+
// keying on it would fall through to the legacy Continue flow.
|
|
1253
|
+
const isNewFlow = await configurationOverlay.isVisible();
|
|
1174
1254
|
if (isNewFlow) {
|
|
1175
1255
|
// Step 1: connect cluster if not yet connected (modal with cluster dropdown)
|
|
1176
1256
|
const connectClusterButton = this.page.getByRole('button', {
|
|
@@ -1209,13 +1289,33 @@ class ModelerCreatePage {
|
|
|
1209
1289
|
.getByRole('button', { name: 'Save' })
|
|
1210
1290
|
.click({ timeout });
|
|
1211
1291
|
}
|
|
1212
|
-
// Step 2: deploy
|
|
1213
|
-
//
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1292
|
+
// Step 2: deploy, unless the overlay already opened with the
|
|
1293
|
+
// deploy step complete. checkExistingDeployment only runs when
|
|
1294
|
+
// processApplicationId is defined, which it now is, so re-entering
|
|
1295
|
+
// Play on an already-deployed process application legitimately
|
|
1296
|
+
// has nothing to deploy and renders no Deploy button at all.
|
|
1297
|
+
const alreadyDeployed = await configureScenarioButton
|
|
1298
|
+
.isVisible({ timeout: 3000 })
|
|
1299
|
+
.catch(() => false);
|
|
1300
|
+
if (!alreadyDeployed) {
|
|
1301
|
+
// Step 2: deploy — wait for enabled; button stays disabled until the cluster
|
|
1302
|
+
// connection is confirmed by the backend after the Save in step 1.
|
|
1303
|
+
await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 60000 });
|
|
1304
|
+
await setupDeployButton.click({ timeout });
|
|
1305
|
+
await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
|
|
1306
|
+
}
|
|
1307
|
+
// Step 3: leave the overlay, and confirm we actually left it. The
|
|
1308
|
+
// button only calls hideConfigurationOverlay(); the overlay's own
|
|
1309
|
+
// cluster-change effect can then fire resetDeployment and bring it
|
|
1310
|
+
// straight back, and ConfigureTestPanel stays null while it is up.
|
|
1311
|
+
// Assert the panel really rendered, and re-drive the click if the
|
|
1312
|
+
// overlay returned.
|
|
1313
|
+
await (0, test_1.expect)(async () => {
|
|
1314
|
+
if (await configurationOverlay.isVisible()) {
|
|
1315
|
+
await configureScenarioButton.click({ timeout });
|
|
1316
|
+
}
|
|
1317
|
+
await (0, test_1.expect)(configureTestPanel).toBeVisible({ timeout: 15000 });
|
|
1318
|
+
}).toPass({ timeout: 90000 });
|
|
1219
1319
|
return;
|
|
1220
1320
|
}
|
|
1221
1321
|
// Legacy flow: select cluster via dialog, then click Continue.
|
|
@@ -1238,9 +1338,48 @@ class ModelerCreatePage {
|
|
|
1238
1338
|
}
|
|
1239
1339
|
}
|
|
1240
1340
|
}
|
|
1241
|
-
async selectCluster(clusterName) {
|
|
1341
|
+
async selectCluster(clusterName, healthCheckTimeout = 30000) {
|
|
1242
1342
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
|
|
1243
|
-
|
|
1343
|
+
// Inside a process application the deploy dialog renders
|
|
1344
|
+
// ProcessAppClusterSelection, not the per-deploy ClusterSelection toggle
|
|
1345
|
+
// list: it shows only clusters assigned to one of the application's stages
|
|
1346
|
+
// and auto-selects the first, so there is no label to click. Confirm the
|
|
1347
|
+
// intended cluster is the one on offer instead.
|
|
1348
|
+
//
|
|
1349
|
+
// The unconfigured case is called out explicitly: without this the symptom
|
|
1350
|
+
// is the cluster toggle never appearing, retried until the test times out,
|
|
1351
|
+
// which says nothing about the actual cause.
|
|
1352
|
+
const notConfigured = this.dialog.getByText('Clusters not configured for stages');
|
|
1353
|
+
if (await notConfigured.isVisible().catch(() => false)) {
|
|
1354
|
+
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
1355
|
+
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
1356
|
+
}
|
|
1357
|
+
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
1358
|
+
hasText: new RegExp(`^${clusterName}`),
|
|
1359
|
+
});
|
|
1360
|
+
const isLegacyDialog = await legacyClusterToggle
|
|
1361
|
+
.first()
|
|
1362
|
+
.isVisible()
|
|
1363
|
+
.catch(() => false);
|
|
1364
|
+
if (!isLegacyDialog) {
|
|
1365
|
+
const offered = await this.dialog
|
|
1366
|
+
.getByText(clusterName, { exact: false })
|
|
1367
|
+
.first()
|
|
1368
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
1369
|
+
.then(() => true)
|
|
1370
|
+
.catch(() => false);
|
|
1371
|
+
if (!offered) {
|
|
1372
|
+
// Forget the binding before failing. The dialog lists exactly the
|
|
1373
|
+
// clusters this application's stages point at, so a missing cluster
|
|
1374
|
+
// means the binding did not take -- and no amount of reopening the
|
|
1375
|
+
// dialog will make it appear. Clearing the record lets the caller's
|
|
1376
|
+
// next attempt rebind instead of re-checking an unchanged dialog.
|
|
1377
|
+
this.processApplicationPage.forgetStageCluster();
|
|
1378
|
+
throw new Error(`The deploy dialog does not offer "${clusterName}": its stage ` +
|
|
1379
|
+
'binding did not take. It will be rebound on the next attempt.');
|
|
1380
|
+
}
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1244
1383
|
const healthyRegex = new RegExp(`${clusterName}.*?HealthydevManage`);
|
|
1245
1384
|
const clusterLabel = this.dialog
|
|
1246
1385
|
.locator('label')
|
|
@@ -3,6 +3,9 @@ import { LoginPage } from './LoginPage';
|
|
|
3
3
|
declare class ModelerHomePage {
|
|
4
4
|
private defaultFolderName;
|
|
5
5
|
private page;
|
|
6
|
+
private processApplicationPage;
|
|
7
|
+
private processApplicationUrl?;
|
|
8
|
+
private processApplicationName?;
|
|
6
9
|
readonly modelerPageBanner: Locator;
|
|
7
10
|
readonly createNewProjectButton: Locator;
|
|
8
11
|
readonly projectNameInput: Locator;
|
|
@@ -15,6 +18,7 @@ declare class ModelerHomePage {
|
|
|
15
18
|
readonly idpApplicationNameInput: Locator;
|
|
16
19
|
readonly createIdpApplicationButton: Locator;
|
|
17
20
|
readonly projectBreadcrumb: Locator;
|
|
21
|
+
readonly workspaceBreadcrumb: Locator;
|
|
18
22
|
readonly openOrganizationsButton: Locator;
|
|
19
23
|
readonly optionsButton: Locator;
|
|
20
24
|
readonly manageButton: Locator;
|
|
@@ -41,12 +45,20 @@ declare class ModelerHomePage {
|
|
|
41
45
|
getProjectNames(name: string): Promise<string[]>;
|
|
42
46
|
clickProcessDiagram(name: string): Promise<void>;
|
|
43
47
|
clickDiagramTypeDropdown(): Promise<void>;
|
|
48
|
+
clickWorkspaceDiagramTypeDropdown(): Promise<void>;
|
|
44
49
|
clickBpmnTemplateOption(): Promise<void>;
|
|
45
50
|
clickIdpApplicationTemplateOption(): Promise<void>;
|
|
46
51
|
clickFormOption(): Promise<void>;
|
|
47
52
|
enterFormName(name: string): Promise<void>;
|
|
48
53
|
openFormRenameEditor(): Promise<void>;
|
|
49
54
|
clickProjectBreadcrumb(): Promise<void>;
|
|
55
|
+
clickWorkspaceBreadcrumb(): Promise<void>;
|
|
56
|
+
enterResourceContainer(name?: string): Promise<void>;
|
|
57
|
+
private clickCreateProcessApplication;
|
|
58
|
+
private ensureInResourceContainer;
|
|
59
|
+
returnToResourceContainer(): Promise<void>;
|
|
60
|
+
startBlankBpmnDiagram(): Promise<void>;
|
|
61
|
+
private becameVisible;
|
|
50
62
|
clickHomeBreadcrumb(): Promise<void>;
|
|
51
63
|
clickOpenOrganizationsButton(): Promise<void>;
|
|
52
64
|
clickCrossComponentProjectFolder(): Promise<void>;
|