@camunda/e2e-test-suite 0.0.1013 → 0.0.1015
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/ModelerHomePage.d.ts +6 -0
- package/dist/pages/8.10/ModelerHomePage.js +61 -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.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 ModelerHomePage {
|
|
3
3
|
private defaultFolderName;
|
|
4
|
+
private defaultNewProjectName;
|
|
4
5
|
private page;
|
|
5
6
|
private processApplicationPage;
|
|
6
7
|
private processApplicationUrl?;
|
|
@@ -29,10 +30,15 @@ declare class ModelerHomePage {
|
|
|
29
30
|
readonly closeButton: Locator;
|
|
30
31
|
readonly optionsButton: Locator;
|
|
31
32
|
readonly formNameBreadcrumb: (formName: string) => Locator;
|
|
33
|
+
readonly breadcrumbProjectMenu: Locator;
|
|
34
|
+
readonly renameProjectMenuItem: Locator;
|
|
35
|
+
readonly defaultNamedProjectRow: Locator;
|
|
36
|
+
readonly renameMenuItem: Locator;
|
|
32
37
|
constructor(page: Page);
|
|
33
38
|
dismissOverlays(): Promise<void>;
|
|
34
39
|
clickCreateNewProjectButton(): Promise<void>;
|
|
35
40
|
enterNewProjectName(name: string): Promise<void>;
|
|
41
|
+
private openProjectRenameEditor;
|
|
36
42
|
enterIdpApplicationName(name: string): Promise<void>;
|
|
37
43
|
clickChooseBpmnTemplateButton(): Promise<void>;
|
|
38
44
|
reload(): Promise<void>;
|
|
@@ -8,6 +8,8 @@ const randomName_1 = require("../../utils/randomName");
|
|
|
8
8
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
9
9
|
class ModelerHomePage {
|
|
10
10
|
defaultFolderName = 'Cross Component Test Project';
|
|
11
|
+
// The name hub gives a project on create, before the inline rename commits.
|
|
12
|
+
defaultNewProjectName = 'New project';
|
|
11
13
|
page;
|
|
12
14
|
// Since camunda/camunda-hub#25831 files and folders cannot sit at a
|
|
13
15
|
// workspace ("project" in today's UI) root -- only process applications and
|
|
@@ -46,6 +48,10 @@ class ModelerHomePage {
|
|
|
46
48
|
closeButton;
|
|
47
49
|
optionsButton;
|
|
48
50
|
formNameBreadcrumb;
|
|
51
|
+
breadcrumbProjectMenu;
|
|
52
|
+
renameProjectMenuItem;
|
|
53
|
+
defaultNamedProjectRow;
|
|
54
|
+
renameMenuItem;
|
|
49
55
|
constructor(page) {
|
|
50
56
|
this.page = page;
|
|
51
57
|
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
@@ -104,6 +110,14 @@ class ModelerHomePage {
|
|
|
104
110
|
this.formNameBreadcrumb = (formName) => page.locator('button[data-test="breadcrumb-form"]', {
|
|
105
111
|
hasText: formName,
|
|
106
112
|
});
|
|
113
|
+
this.breadcrumbProjectMenu = page.locator('[data-test="breadcrumb-project-menu"]');
|
|
114
|
+
this.renameProjectMenuItem = page.locator('[data-test="rename-project"]');
|
|
115
|
+
// EntityTable stamps `entity-<row id>` on the row, and on the projects list
|
|
116
|
+
// the row id is the project's name (camunda-hub
|
|
117
|
+
// frontend/apps/hub/src/components/entity-table/EntityTable.tsx), so this
|
|
118
|
+
// addresses the still-unnamed project the create flow just added.
|
|
119
|
+
this.defaultNamedProjectRow = page.locator(`[data-test="entity-${this.defaultNewProjectName}"]`);
|
|
120
|
+
this.renameMenuItem = page.getByRole('menuitem', { name: 'Rename' });
|
|
107
121
|
}
|
|
108
122
|
async dismissOverlays() {
|
|
109
123
|
for (const locator of [
|
|
@@ -149,6 +163,13 @@ class ModelerHomePage {
|
|
|
149
163
|
if (await committedProjectName.first().isVisible()) {
|
|
150
164
|
return;
|
|
151
165
|
}
|
|
166
|
+
// Bounded, not instantaneous: the editor can still be mounting, and
|
|
167
|
+
// reading that as "closed" would send the recovery to the projects-list
|
|
168
|
+
// branch while the browser sits on the project page, where the row it
|
|
169
|
+
// wants does not exist.
|
|
170
|
+
if (!(await this.becameVisible(this.projectNameInput.first(), 10000))) {
|
|
171
|
+
await this.openProjectRenameEditor();
|
|
172
|
+
}
|
|
152
173
|
await this.projectNameInput.click({ timeout: 15000 });
|
|
153
174
|
await this.projectNameInput.fill(name);
|
|
154
175
|
// Confirm the value actually landed in the edit field before committing;
|
|
@@ -163,6 +184,32 @@ class ModelerHomePage {
|
|
|
163
184
|
await (0, test_1.expect)(committedProjectName.first()).toBeVisible({ timeout: 15000 });
|
|
164
185
|
}).toPass({ timeout: 120000 });
|
|
165
186
|
}
|
|
187
|
+
// Reopens the inline name editor when the create flow has left it closed.
|
|
188
|
+
// Two shapes, both hub's own (frontend/apps/hub/src/app/pages/project/
|
|
189
|
+
// ProjectHeader.tsx and .../home/features/projects-list/EntityAction.tsx):
|
|
190
|
+
// inside the project the breadcrumb dropdown carries a Rename item, and on
|
|
191
|
+
// the projects list the row's overflow menu carries one that toggles the row
|
|
192
|
+
// into edit mode. Both mount the same InlineEditable, so the caller keeps
|
|
193
|
+
// filling `[data-test="editable-input"]` either way.
|
|
194
|
+
//
|
|
195
|
+
// `breadcrumb-project-menu` and not `breadcrumb-project`: the latter is the
|
|
196
|
+
// loading/text fallback hub renders before the project has loaded, and
|
|
197
|
+
// clicking it opens nothing. Hub's own suite draws the same distinction.
|
|
198
|
+
async openProjectRenameEditor() {
|
|
199
|
+
if (await this.becameVisible(this.breadcrumbProjectMenu, 10000)) {
|
|
200
|
+
await this.breadcrumbProjectMenu.click({ timeout: 10000 });
|
|
201
|
+
await this.renameProjectMenuItem.click({ timeout: 10000 });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Still on the projects list: hub created the project but never navigated
|
|
205
|
+
// into it, so the breadcrumb editor was never mounted at all. Rename the
|
|
206
|
+
// row in place rather than clicking "New project" again, which would leave
|
|
207
|
+
// a second, permanently unnamed project behind in the org.
|
|
208
|
+
await this.defaultNamedProjectRow
|
|
209
|
+
.locator('[data-test="entity-context-dropdown"]')
|
|
210
|
+
.click({ timeout: 30000 });
|
|
211
|
+
await this.renameMenuItem.click({ timeout: 10000 });
|
|
212
|
+
}
|
|
166
213
|
async enterIdpApplicationName(name) {
|
|
167
214
|
await this.idpApplicationNameInput.click({ timeout: 60000 });
|
|
168
215
|
await this.idpApplicationNameInput.fill(name);
|
|
@@ -423,6 +470,20 @@ class ModelerHomePage {
|
|
|
423
470
|
}
|
|
424
471
|
await this.clickCreateNewProjectButton();
|
|
425
472
|
await this.enterNewProjectName(this.defaultFolderName);
|
|
473
|
+
// Both branches must leave the browser inside the workspace: the callers
|
|
474
|
+
// go straight on to clickDiagramTypeDropdown(), whose create control only
|
|
475
|
+
// exists there. Creating normally navigates into the new project, but when
|
|
476
|
+
// hub leaves the browser on the projects list the rename happens in the row
|
|
477
|
+
// and no navigation follows.
|
|
478
|
+
//
|
|
479
|
+
// Keyed on "New project", which hub renders only on the projects list and
|
|
480
|
+
// never inside an open project, so it answers which view we are on
|
|
481
|
+
// directly. The folder tile is the wrong signal: on a slow list render it
|
|
482
|
+
// is still absent, which would read as "already inside" and skip a hop
|
|
483
|
+
// that was needed.
|
|
484
|
+
if (await this.becameVisible(this.createNewProjectButton, 5000)) {
|
|
485
|
+
await this.clickCrossComponentProjectFolder();
|
|
486
|
+
}
|
|
426
487
|
}
|
|
427
488
|
async clickManageButton(retries = 3) {
|
|
428
489
|
await this.optionsButton.click({ timeout: 30000 });
|
|
@@ -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;
|
|
@@ -34,6 +35,7 @@ declare class FormJsPage {
|
|
|
34
35
|
selectCluster(clusterName: string): Promise<void>;
|
|
35
36
|
clickDeployButton(): Promise<void>;
|
|
36
37
|
clickDeploySubButton(): Promise<void>;
|
|
38
|
+
selectOnlyThisResource(): Promise<void>;
|
|
37
39
|
deployForm(clusterName: string): Promise<void>;
|
|
38
40
|
}
|
|
39
41
|
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;
|
|
@@ -25,6 +27,7 @@ class FormJsPage {
|
|
|
25
27
|
deployErrorMessage;
|
|
26
28
|
constructor(page) {
|
|
27
29
|
this.page = page;
|
|
30
|
+
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
28
31
|
this.aiFormGeneratorButton = page.getByRole('button', {
|
|
29
32
|
name: 'AI Form Generator',
|
|
30
33
|
});
|
|
@@ -47,9 +50,12 @@ class FormJsPage {
|
|
|
47
50
|
this.deployButton = page
|
|
48
51
|
.locator('[data-test="action-bar"]')
|
|
49
52
|
.getByRole('button', { name: 'Deploy', exact: true });
|
|
53
|
+
// Inside a process application hub titles this modal for the application
|
|
54
|
+
// rather than for the form, so 'Deploy form' never matches and the dialog
|
|
55
|
+
// reads as absent. pages/SM-8.10 filters on 'Deploy' for the same reason.
|
|
50
56
|
this.dialog = page
|
|
51
57
|
.locator('.cds--modal-container[role="dialog"]')
|
|
52
|
-
.filter({ hasText: 'Deploy
|
|
58
|
+
.filter({ hasText: 'Deploy' });
|
|
53
59
|
this.deploySubButton = this.dialog.getByRole('button', {
|
|
54
60
|
name: 'Deploy',
|
|
55
61
|
exact: true,
|
|
@@ -112,6 +118,46 @@ class FormJsPage {
|
|
|
112
118
|
async selectCluster(clusterName) {
|
|
113
119
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
|
|
114
120
|
const healthCheckTimeout = 120000;
|
|
121
|
+
// Inside a process application the deploy dialog renders
|
|
122
|
+
// ProcessAppClusterSelection, not the per-deploy ClusterSelection toggle
|
|
123
|
+
// list: it shows only clusters assigned to one of the application's stages
|
|
124
|
+
// and auto-selects the first, so there is no label to click. Confirm the
|
|
125
|
+
// intended cluster is the one on offer instead.
|
|
126
|
+
//
|
|
127
|
+
// The unconfigured case is called out explicitly: without this the symptom
|
|
128
|
+
// is the cluster toggle never appearing, retried until the test times out,
|
|
129
|
+
// which says nothing about the actual cause.
|
|
130
|
+
const notConfigured = this.dialog.getByText('Clusters not configured for stages');
|
|
131
|
+
if (await notConfigured.isVisible().catch(() => false)) {
|
|
132
|
+
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
133
|
+
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
134
|
+
}
|
|
135
|
+
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
136
|
+
hasText: new RegExp(`^${clusterName}`),
|
|
137
|
+
});
|
|
138
|
+
const isLegacyDialog = await legacyClusterToggle
|
|
139
|
+
.first()
|
|
140
|
+
.isVisible()
|
|
141
|
+
.catch(() => false);
|
|
142
|
+
if (!isLegacyDialog) {
|
|
143
|
+
const offered = await this.dialog
|
|
144
|
+
.getByText(clusterName, { exact: false })
|
|
145
|
+
.first()
|
|
146
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
147
|
+
.then(() => true)
|
|
148
|
+
.catch(() => false);
|
|
149
|
+
if (!offered) {
|
|
150
|
+
// Forget the binding before failing. The dialog lists exactly the
|
|
151
|
+
// clusters this application's stages point at, so a missing cluster
|
|
152
|
+
// means the binding did not take -- and no amount of reopening the
|
|
153
|
+
// dialog will make it appear. Clearing the record lets the caller's
|
|
154
|
+
// next attempt rebind instead of re-checking an unchanged dialog.
|
|
155
|
+
this.processApplicationPage.forgetStageCluster();
|
|
156
|
+
throw new Error(`The deploy dialog does not offer "${clusterName}": its stage ` +
|
|
157
|
+
'binding did not take. It will be rebound on the next attempt.');
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
115
161
|
const healthyRegex = new RegExp(`${clusterName}[\\s\\S]*?HealthydevManage`);
|
|
116
162
|
const cluster = this.dialog.locator(`[title="${clusterName}"]`);
|
|
117
163
|
await (0, test_1.expect)(cluster).toBeVisible({ timeout: 30000 });
|
|
@@ -130,7 +176,33 @@ class FormJsPage {
|
|
|
130
176
|
async clickDeploySubButton() {
|
|
131
177
|
await this.deploySubButton.click({ timeout: 60000 });
|
|
132
178
|
}
|
|
179
|
+
// Since camunda/camunda-hub#25831 a form lives inside a process application,
|
|
180
|
+
// and Hub then scopes Deploy to the WHOLE application by default -- it calls
|
|
181
|
+
// deployProcessApplication, not deployFile, so the "Form deployed!" toast
|
|
182
|
+
// never fires (hub's own form-deploy-action.spec.sm.tsx asserts exactly that
|
|
183
|
+
// default). Narrowing the scope keeps this a form-deploy test instead of
|
|
184
|
+
// silently turning it into a process-application deploy. The combobox only
|
|
185
|
+
// exists inside a container, hence the boolean probe.
|
|
186
|
+
async selectOnlyThisResource() {
|
|
187
|
+
const scopeSelect = this.dialog.getByRole('combobox', {
|
|
188
|
+
name: 'Resources to deploy',
|
|
189
|
+
});
|
|
190
|
+
const hasScopeSelect = await scopeSelect
|
|
191
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
192
|
+
.then(() => true)
|
|
193
|
+
.catch(() => false);
|
|
194
|
+
if (!hasScopeSelect) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
await scopeSelect.click();
|
|
198
|
+
await this.dialog
|
|
199
|
+
.getByRole('option', { name: 'Only this resource' })
|
|
200
|
+
.or(this.dialog.getByText('Only this resource', { exact: true }))
|
|
201
|
+
.first()
|
|
202
|
+
.click({ timeout: 30000 });
|
|
203
|
+
}
|
|
133
204
|
async deployForm(clusterName) {
|
|
205
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
134
206
|
// After a 8.7->8.8 upgrade, orchestration-cluster authorization commands
|
|
135
207
|
// propagate across partitions for several minutes; deploy is rejected with
|
|
136
208
|
// FORBIDDEN until the deployer's grant lands. In migration runs, reload and
|
|
@@ -143,6 +215,7 @@ class FormJsPage {
|
|
|
143
215
|
timeout: 30000,
|
|
144
216
|
});
|
|
145
217
|
await this.selectCluster(clusterName);
|
|
218
|
+
await this.selectOnlyThisResource();
|
|
146
219
|
await this.clickDeploySubButton();
|
|
147
220
|
if (attempt === maxAttempts) {
|
|
148
221
|
await (0, test_1.expect)(this.deploySuccessNotification).toBeVisible({
|
|
@@ -5,8 +5,10 @@ const test_1 = require("@playwright/test");
|
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
7
7
|
const ConnectorMarketplacePage_1 = require("./ConnectorMarketplacePage");
|
|
8
|
+
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
8
9
|
class ModelerCreatePage {
|
|
9
10
|
page;
|
|
11
|
+
processApplicationPage;
|
|
10
12
|
generalPanel;
|
|
11
13
|
deployDiagramDialog;
|
|
12
14
|
processIdInput;
|
|
@@ -115,6 +117,7 @@ class ModelerCreatePage {
|
|
|
115
117
|
diagramNameBreadcrumb;
|
|
116
118
|
constructor(page) {
|
|
117
119
|
this.page = page;
|
|
120
|
+
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
118
121
|
this.generalPanel = page.locator('[data-group-id="group-general"]');
|
|
119
122
|
this.processIdInput = page.getByLabel('ID', { exact: true });
|
|
120
123
|
this.startEventElement = page.locator('.djs-hit').first();
|
|
@@ -253,8 +256,12 @@ class ModelerCreatePage {
|
|
|
253
256
|
this.rbaEnabledDeployedDialog = page.getByRole('dialog', {
|
|
254
257
|
name: 'Process successfully deployed',
|
|
255
258
|
});
|
|
259
|
+
// Since camunda/camunda-hub#25831 a diagram deploys as part of its process
|
|
260
|
+
// application and hub titles the modal for the application, not for the
|
|
261
|
+
// single resource, so an exact 'Deploy diagram' name no longer matches.
|
|
262
|
+
// Match on the verb -- pages/SM-8.10 already leaves this unfiltered.
|
|
256
263
|
this.deployDiagramDialog = page.getByRole('dialog', {
|
|
257
|
-
name:
|
|
264
|
+
name: /^Deploy/,
|
|
258
265
|
});
|
|
259
266
|
this.appendGatewayButton = page.getByRole('button', {
|
|
260
267
|
name: 'Append gateway',
|
|
@@ -565,6 +572,7 @@ class ModelerCreatePage {
|
|
|
565
572
|
await this.deployMenuItem.click({ timeout: 60000 });
|
|
566
573
|
}
|
|
567
574
|
async deployDiagram(clusterName) {
|
|
575
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
568
576
|
await this.clickDeployButtonWithRetry();
|
|
569
577
|
await (0, test_1.expect)(this.deployDiagramDialog).toBeVisible({
|
|
570
578
|
timeout: 30000,
|
|
@@ -605,6 +613,10 @@ class ModelerCreatePage {
|
|
|
605
613
|
const retryDelayMs = 20000;
|
|
606
614
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
607
615
|
try {
|
|
616
|
+
// Inside the loop, not before it: a no-op once bound, but selectCluster
|
|
617
|
+
// clears the binding when the dialog does not offer the cluster, so the
|
|
618
|
+
// next attempt rebinds rather than reopening an unchanged dialog.
|
|
619
|
+
await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
608
620
|
await this.clickStartInstanceMainButton();
|
|
609
621
|
await this.selectCluster(clusterName);
|
|
610
622
|
if (variables.length > 0) {
|
|
@@ -661,6 +673,46 @@ class ModelerCreatePage {
|
|
|
661
673
|
async selectCluster(clusterName) {
|
|
662
674
|
await (0, test_1.expect)(this.dialog).toBeVisible({ timeout: 30000 });
|
|
663
675
|
const healthCheckTimeout = 30000;
|
|
676
|
+
// Inside a process application the deploy dialog renders
|
|
677
|
+
// ProcessAppClusterSelection, not the per-deploy ClusterSelection toggle
|
|
678
|
+
// list: it shows only clusters assigned to one of the application's stages
|
|
679
|
+
// and auto-selects the first, so there is no label to click. Confirm the
|
|
680
|
+
// intended cluster is the one on offer instead.
|
|
681
|
+
//
|
|
682
|
+
// The unconfigured case is called out explicitly: without this the symptom
|
|
683
|
+
// is the cluster toggle never appearing, retried until the test times out,
|
|
684
|
+
// which says nothing about the actual cause.
|
|
685
|
+
const notConfigured = this.dialog.getByText('Clusters not configured for stages');
|
|
686
|
+
if (await notConfigured.isVisible().catch(() => false)) {
|
|
687
|
+
throw new Error(`Process application has no cluster assigned to any stage, so "${clusterName}" cannot be deployed to. ` +
|
|
688
|
+
'ProcessApplicationPage.ensureStageCluster() should have bound it before the deploy dialog was opened.');
|
|
689
|
+
}
|
|
690
|
+
const legacyClusterToggle = this.dialog.locator('label').filter({
|
|
691
|
+
hasText: new RegExp(`^${clusterName}`),
|
|
692
|
+
});
|
|
693
|
+
const isLegacyDialog = await legacyClusterToggle
|
|
694
|
+
.first()
|
|
695
|
+
.isVisible()
|
|
696
|
+
.catch(() => false);
|
|
697
|
+
if (!isLegacyDialog) {
|
|
698
|
+
const offered = await this.dialog
|
|
699
|
+
.getByText(clusterName, { exact: false })
|
|
700
|
+
.first()
|
|
701
|
+
.waitFor({ state: 'visible', timeout: 30000 })
|
|
702
|
+
.then(() => true)
|
|
703
|
+
.catch(() => false);
|
|
704
|
+
if (!offered) {
|
|
705
|
+
// Forget the binding before failing. The dialog lists exactly the
|
|
706
|
+
// clusters this application's stages point at, so a missing cluster
|
|
707
|
+
// means the binding did not take -- and no amount of reopening the
|
|
708
|
+
// dialog will make it appear. Clearing the record lets the caller's
|
|
709
|
+
// next attempt rebind instead of re-checking an unchanged dialog.
|
|
710
|
+
this.processApplicationPage.forgetStageCluster();
|
|
711
|
+
throw new Error(`The deploy dialog does not offer "${clusterName}": its stage ` +
|
|
712
|
+
'binding did not take. It will be rebound on the next attempt.');
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
664
716
|
const healthyRegex = new RegExp(`${clusterName}.*?HealthydevManage`);
|
|
665
717
|
const clusterLabel = this.dialog
|
|
666
718
|
.locator('label')
|
|
@@ -704,8 +756,45 @@ class ModelerCreatePage {
|
|
|
704
756
|
async clickDiagramBreadcrumb() {
|
|
705
757
|
await this.diagramBreadcrumb.click({ timeout: 10000 });
|
|
706
758
|
}
|
|
759
|
+
// Opening the breadcrumb menu leaves the pointer on the breadcrumb button,
|
|
760
|
+
// and hub renders that button's truncated-name tooltip on top of the menu,
|
|
761
|
+
// where it swallows the click on "Rename" -- playwright reports
|
|
762
|
+
// `<div role="tooltip">…</div> intercepts pointer events`. Since
|
|
763
|
+
// camunda/camunda-hub#25831 the breadcrumb carries an extra
|
|
764
|
+
// process-application crumb, so the name truncates, and the tooltip renders,
|
|
765
|
+
// as the rule rather than the exception. Park the pointer away from the
|
|
766
|
+
// breadcrumb and let the tooltip unmount before clicking; reopen the menu if
|
|
767
|
+
// a previous attempt closed it.
|
|
707
768
|
async clickEditDiagramNameButton() {
|
|
708
|
-
|
|
769
|
+
const openTooltip = this.page.locator('[role="tooltip"][aria-hidden="false"]');
|
|
770
|
+
await (0, test_1.expect)(async () => {
|
|
771
|
+
const menuOpen = await this.renameDiagramNameButton
|
|
772
|
+
.isVisible()
|
|
773
|
+
.catch(() => false);
|
|
774
|
+
if (!menuOpen) {
|
|
775
|
+
await this.clickDiagramBreadcrumb();
|
|
776
|
+
}
|
|
777
|
+
// Park the pointer so the breadcrumb's own tooltip gets a chance to
|
|
778
|
+
// unmount -- best effort, because it is not reliably hover-driven, and
|
|
779
|
+
// requiring it to go turned an interceptable click into a hard failure.
|
|
780
|
+
await this.page.mouse.move(0, 0);
|
|
781
|
+
await openTooltip
|
|
782
|
+
.first()
|
|
783
|
+
.waitFor({ state: 'hidden', timeout: 2000 })
|
|
784
|
+
.catch(() => false);
|
|
785
|
+
try {
|
|
786
|
+
await this.renameDiagramNameButton.click({ timeout: 10000 });
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
// The tooltip renders over the menu and swallows the click (playwright
|
|
790
|
+
// reports `<div role="tooltip">…</div> intercepts pointer events`).
|
|
791
|
+
// Dispatching on the element bypasses hit-testing, which no amount of
|
|
792
|
+
// waiting achieves when the tooltip never unmounts. Not a swallowed
|
|
793
|
+
// failure: the assertion below is what decides whether this worked.
|
|
794
|
+
await this.renameDiagramNameButton.dispatchEvent('click');
|
|
795
|
+
}
|
|
796
|
+
await (0, test_1.expect)(this.diagramNameInput).toBeVisible({ timeout: 10000 });
|
|
797
|
+
}).toPass({ timeout: 60000, intervals: [1000, 2000, 5000] });
|
|
709
798
|
}
|
|
710
799
|
async enterDiagramName(name) {
|
|
711
800
|
await this.diagramNameInput.fill(name, { timeout: 90000 });
|
|
@@ -1278,6 +1367,16 @@ class ModelerCreatePage {
|
|
|
1278
1367
|
await this.playTab.click();
|
|
1279
1368
|
}
|
|
1280
1369
|
async completePlayConfiguration(clusterName) {
|
|
1370
|
+
// Play is a tab on the diagram page, not its own URL, so the stage-cluster
|
|
1371
|
+
// detour's goto() lands back on the Modeler tab and every Play locator
|
|
1372
|
+
// below would then look for a panel that is no longer rendered. Re-enter
|
|
1373
|
+
// Play when the detour actually navigated. Binding cannot simply move
|
|
1374
|
+
// before switchToPlay(): this is the first point in the flow that knows
|
|
1375
|
+
// which cluster the test plays on.
|
|
1376
|
+
const reboundStageCluster = await this.processApplicationPage.ensureStageCluster(clusterName);
|
|
1377
|
+
if (reboundStageCluster) {
|
|
1378
|
+
await this.switchToPlay();
|
|
1379
|
+
}
|
|
1281
1380
|
const timeout = 30000;
|
|
1282
1381
|
// New flow (8.10+): "Setup environment" panel with three steps:
|
|
1283
1382
|
// 1. Connect cluster → "Configure environment" modal → select cluster → Save
|
|
@@ -1301,19 +1400,37 @@ class ModelerCreatePage {
|
|
|
1301
1400
|
name: /^Configure (test case|scenario)$/,
|
|
1302
1401
|
}))
|
|
1303
1402
|
.first();
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1403
|
+
// Play settles into exactly ONE of three states, and which one is not
|
|
1404
|
+
// knowable up front, so race them instead of timing out on each in turn:
|
|
1405
|
+
// the setup overlay, the legacy Continue button, or the configure-test
|
|
1406
|
+
// panel (meaning setup is already done for this diagram).
|
|
1407
|
+
//
|
|
1408
|
+
// Waiting only for the first two and reading "neither" as "already ready"
|
|
1409
|
+
// was wrong once the diagram moved inside a process application.
|
|
1410
|
+
// ConfigurationOverlay renders null until isDeploymentStateValidated, and
|
|
1411
|
+
// it sets that flag synchronously ONLY when processApplicationId is
|
|
1412
|
+
// undefined -- exactly what a project-root diagram used to give it. With
|
|
1413
|
+
// the diagram in a process application the id is defined, so the overlay
|
|
1414
|
+
// must first await checkExistingDeployment(). The old 15s probe raced that
|
|
1415
|
+
// async call, concluded "already ready" and returned while the overlay was
|
|
1416
|
+
// still about to appear. ConfigureTestPanel returns null for as long as the
|
|
1417
|
+
// overlay is up, so the Start button never existed and
|
|
1418
|
+
// clickStartInstanceButton burned its entire retry budget looking for it.
|
|
1419
|
+
const configurationOverlay = this.page.locator('[data-test="play-configuration-overlay"]');
|
|
1420
|
+
const configureTestPanel = this.page.getByTestId('configure-test-panel');
|
|
1421
|
+
await (0, test_1.expect)(configurationOverlay
|
|
1422
|
+
.or(this.continueToPlayButton)
|
|
1423
|
+
.or(configureTestPanel)
|
|
1424
|
+
.first()).toBeVisible({ timeout: 90000 });
|
|
1425
|
+
if (await configureTestPanel.isVisible()) {
|
|
1426
|
+
// Already past setup for this diagram -- nothing to drive.
|
|
1314
1427
|
return;
|
|
1315
1428
|
}
|
|
1316
|
-
|
|
1429
|
+
// Keyed on the overlay itself, not on its Deploy button: when
|
|
1430
|
+
// checkExistingDeployment finds a deployment the overlay opens with the
|
|
1431
|
+
// deploy step already complete, so there is no Deploy button to see and
|
|
1432
|
+
// keying on it would fall through to the legacy Continue flow.
|
|
1433
|
+
const isNewFlow = await configurationOverlay.isVisible();
|
|
1317
1434
|
if (isNewFlow) {
|
|
1318
1435
|
// Step 1: connect cluster if not yet connected (modal with cluster dropdown)
|
|
1319
1436
|
const connectClusterButton = this.page.getByRole('button', {
|
|
@@ -1352,13 +1469,33 @@ class ModelerCreatePage {
|
|
|
1352
1469
|
.getByRole('button', { name: 'Save' })
|
|
1353
1470
|
.click({ timeout });
|
|
1354
1471
|
}
|
|
1355
|
-
// Step 2: deploy
|
|
1356
|
-
//
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1472
|
+
// Step 2: deploy, unless the overlay already opened with the
|
|
1473
|
+
// deploy step complete. checkExistingDeployment only runs when
|
|
1474
|
+
// processApplicationId is defined, which it now is, so re-entering
|
|
1475
|
+
// Play on an already-deployed process application legitimately
|
|
1476
|
+
// has nothing to deploy and renders no Deploy button at all.
|
|
1477
|
+
const alreadyDeployed = await configureScenarioButton
|
|
1478
|
+
.isVisible({ timeout: 3000 })
|
|
1479
|
+
.catch(() => false);
|
|
1480
|
+
if (!alreadyDeployed) {
|
|
1481
|
+
// Step 2: deploy — wait for enabled; button stays disabled until the cluster
|
|
1482
|
+
// connection is confirmed by the backend after the Save in step 1.
|
|
1483
|
+
await (0, test_1.expect)(setupDeployButton).toBeEnabled({ timeout: 30000 });
|
|
1484
|
+
await setupDeployButton.click({ timeout });
|
|
1485
|
+
await (0, test_1.expect)(this.page.getByText('Process has been successfully deployed')).toBeVisible({ timeout: 90000 });
|
|
1486
|
+
}
|
|
1487
|
+
// Step 3: leave the overlay, and confirm we actually left it. The
|
|
1488
|
+
// button only calls hideConfigurationOverlay(); the overlay's own
|
|
1489
|
+
// cluster-change effect can then fire resetDeployment and bring it
|
|
1490
|
+
// straight back, and ConfigureTestPanel stays null while it is up.
|
|
1491
|
+
// Assert the panel really rendered, and re-drive the click if the
|
|
1492
|
+
// overlay returned.
|
|
1493
|
+
await (0, test_1.expect)(async () => {
|
|
1494
|
+
if (await configurationOverlay.isVisible()) {
|
|
1495
|
+
await configureScenarioButton.click({ timeout });
|
|
1496
|
+
}
|
|
1497
|
+
await (0, test_1.expect)(configureTestPanel).toBeVisible({ timeout: 15000 });
|
|
1498
|
+
}).toPass({ timeout: 90000 });
|
|
1362
1499
|
return;
|
|
1363
1500
|
}
|
|
1364
1501
|
// Legacy flow: select cluster via dialog, then click Continue.
|
|
@@ -2,6 +2,9 @@ import { Page, Locator } from '@playwright/test';
|
|
|
2
2
|
declare class ModelerHomePage {
|
|
3
3
|
private defaultFolderName;
|
|
4
4
|
private page;
|
|
5
|
+
private processApplicationPage;
|
|
6
|
+
private processApplicationUrl?;
|
|
7
|
+
private processApplicationName?;
|
|
5
8
|
readonly modelerPageBanner: Locator;
|
|
6
9
|
readonly createNewProjectButton: Locator;
|
|
7
10
|
readonly projectNameInput: Locator;
|
|
@@ -11,6 +14,7 @@ declare class ModelerHomePage {
|
|
|
11
14
|
readonly bpmnTemplateOption: Locator;
|
|
12
15
|
readonly formTemplateOption: Locator;
|
|
13
16
|
readonly projectBreadcrumb: Locator;
|
|
17
|
+
readonly workspaceBreadcrumb: Locator;
|
|
14
18
|
readonly openOrganizationsButton: Locator;
|
|
15
19
|
readonly manageButton: Locator;
|
|
16
20
|
readonly crossComponentProjectFolder: Locator;
|
|
@@ -37,10 +41,18 @@ declare class ModelerHomePage {
|
|
|
37
41
|
getProjectNames(name: string): Promise<string[]>;
|
|
38
42
|
clickProcessDiagram(name: string): Promise<void>;
|
|
39
43
|
clickDiagramTypeDropdown(): Promise<void>;
|
|
44
|
+
clickWorkspaceDiagramTypeDropdown(): Promise<void>;
|
|
40
45
|
clickBpmnTemplateOption(): Promise<void>;
|
|
41
46
|
clickFormOption(): Promise<void>;
|
|
42
47
|
enterFormName(name: string): Promise<void>;
|
|
43
48
|
clickProjectBreadcrumb(): Promise<void>;
|
|
49
|
+
clickWorkspaceBreadcrumb(): Promise<void>;
|
|
50
|
+
enterResourceContainer(name?: string): Promise<void>;
|
|
51
|
+
private clickCreateProcessApplication;
|
|
52
|
+
private ensureInResourceContainer;
|
|
53
|
+
returnToResourceContainer(): Promise<void>;
|
|
54
|
+
startBlankBpmnDiagram(): Promise<void>;
|
|
55
|
+
private becameVisible;
|
|
44
56
|
clickHomeBreadcrumb(): Promise<void>;
|
|
45
57
|
private dismissBlockingDialogs;
|
|
46
58
|
clickOpenOrganizationsButton(): Promise<void>;
|
|
@@ -4,9 +4,24 @@ exports.ModelerHomePage = void 0;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const sleep_1 = require("../../utils/sleep");
|
|
6
6
|
const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
|
|
7
|
+
const randomName_1 = require("../../utils/randomName");
|
|
8
|
+
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
7
9
|
class ModelerHomePage {
|
|
8
10
|
defaultFolderName = 'Cross Component Test Project';
|
|
9
11
|
page;
|
|
12
|
+
// Since camunda/camunda-hub#25831 files and folders cannot sit at a
|
|
13
|
+
// workspace ("project" in today's UI) root -- only process applications and
|
|
14
|
+
// IDP applications can. The container is therefore entered lazily, at the
|
|
15
|
+
// point a resource is created, and remembered for the lifetime of this page
|
|
16
|
+
// object (one instance per test, see fixtures/8.8.ts). Specs that only
|
|
17
|
+
// need the workspace itself -- collaborators, IDP applications -- never
|
|
18
|
+
// trigger the hop.
|
|
19
|
+
processApplicationPage;
|
|
20
|
+
// Load-bearing: hub renders no process-application breadcrumb on a diagram
|
|
21
|
+
// page at container root, so the remembered URL -- not a breadcrumb click --
|
|
22
|
+
// is how we get back. Hub's own Playwright suite does the same.
|
|
23
|
+
processApplicationUrl;
|
|
24
|
+
processApplicationName;
|
|
10
25
|
modelerPageBanner;
|
|
11
26
|
createNewProjectButton;
|
|
12
27
|
projectNameInput;
|
|
@@ -16,6 +31,7 @@ class ModelerHomePage {
|
|
|
16
31
|
bpmnTemplateOption;
|
|
17
32
|
formTemplateOption;
|
|
18
33
|
projectBreadcrumb;
|
|
34
|
+
workspaceBreadcrumb;
|
|
19
35
|
openOrganizationsButton;
|
|
20
36
|
manageButton;
|
|
21
37
|
crossComponentProjectFolder;
|
|
@@ -34,6 +50,7 @@ class ModelerHomePage {
|
|
|
34
50
|
buttonSkipCustomization;
|
|
35
51
|
constructor(page) {
|
|
36
52
|
this.page = page;
|
|
53
|
+
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
37
54
|
this.modelerPageBanner = page.getByRole('banner', {
|
|
38
55
|
name: 'Camunda Modeler',
|
|
39
56
|
});
|
|
@@ -55,13 +72,20 @@ class ModelerHomePage {
|
|
|
55
72
|
.locator('[data-test="create-idp-application"]')
|
|
56
73
|
.getByText('IDP Application');
|
|
57
74
|
this.diagramTypeDropdown = page.locator('[data-test="diagram-dropdown"]');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
.getByText('Form');
|
|
75
|
+
// Matched on the test id alone: hub's design-system variant of the create
|
|
76
|
+
// menu renders "BPMN diagram" (lowercase d), so a text filter breaks the
|
|
77
|
+
// day that variant ships.
|
|
78
|
+
this.bpmnTemplateOption = page.locator('[data-test="create-bpmn-diagram"]');
|
|
79
|
+
this.formTemplateOption = page.locator('[data-test="create-form"]');
|
|
64
80
|
this.projectBreadcrumb = page.locator('[data-test="breadcrumb-project"]');
|
|
81
|
+
// Kept separate from projectBreadcrumb, which now means "the container my
|
|
82
|
+
// resources live in". When hub's workspaces terminology flag flips,
|
|
83
|
+
// breadcrumb-project comes to denote the process application, so the
|
|
84
|
+
// workspace level needs its own name from the start.
|
|
85
|
+
this.workspaceBreadcrumb = page
|
|
86
|
+
.locator('[data-test="breadcrumb-project"]')
|
|
87
|
+
.or(page.locator('[data-test="breadcrumb-workspace"]'))
|
|
88
|
+
.first();
|
|
65
89
|
this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
|
|
66
90
|
this.openOrganizationsButton = page.getByLabel('Open Organizations');
|
|
67
91
|
this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
|
|
@@ -157,12 +181,49 @@ class ModelerHomePage {
|
|
|
157
181
|
.map((el) => el.textContent || '')
|
|
158
182
|
.filter((text) => text !== ''));
|
|
159
183
|
}
|
|
184
|
+
// Opens a resource where this test put it. Several specs leave Modeler
|
|
185
|
+
// entirely (to Operate, to Optimize) and come back via
|
|
186
|
+
// clickCrossComponentProjectFolder(), which lands at workspace root -- the
|
|
187
|
+
// resource is one level down, so re-enter the container first.
|
|
160
188
|
async clickProcessDiagram(name) {
|
|
189
|
+
await this.ensureInResourceContainer();
|
|
161
190
|
const process = this.rows.filter({ hasText: name }).getByTitle(name).first();
|
|
162
191
|
await (0, test_1.expect)(process).toBeVisible({ timeout: 90000 });
|
|
163
192
|
await process.click();
|
|
164
193
|
}
|
|
194
|
+
// Opens the create menu inside this test's process application. The menu at
|
|
195
|
+
// workspace root no longer offers BPMN, Form or Upload -- those resources
|
|
196
|
+
// cannot live there -- so the container is entered first. Callers are
|
|
197
|
+
// unchanged, which is why the existing spec call sites need no edit.
|
|
165
198
|
async clickDiagramTypeDropdown() {
|
|
199
|
+
await this.enterResourceContainer();
|
|
200
|
+
const dropdown = this.processApplicationPage.createNewDropdown;
|
|
201
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
202
|
+
try {
|
|
203
|
+
await this.dismissOverlays();
|
|
204
|
+
await (0, test_1.expect)(dropdown).toBeVisible({
|
|
205
|
+
timeout: 45000,
|
|
206
|
+
});
|
|
207
|
+
await dropdown.click({ timeout: 30000 });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (attempt >= 3) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
await this.page.reload({ waitUntil: 'domcontentloaded' });
|
|
215
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
216
|
+
// Recover into the container rather than the workspace: the reload
|
|
217
|
+
// above can land on either, and the create menu we want is the
|
|
218
|
+
// container's.
|
|
219
|
+
await this.returnToResourceContainer();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Opens the create menu at workspace root, which is the only place process
|
|
224
|
+
// applications and IDP applications can be created. Everything else has to
|
|
225
|
+
// go through clickDiagramTypeDropdown().
|
|
226
|
+
async clickWorkspaceDiagramTypeDropdown() {
|
|
166
227
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
167
228
|
try {
|
|
168
229
|
await this.dismissOverlays();
|
|
@@ -194,8 +255,109 @@ class ModelerHomePage {
|
|
|
194
255
|
await this.formNameInput.fill(name, { timeout: 60000 });
|
|
195
256
|
await this.formNameInput.press('Enter');
|
|
196
257
|
}
|
|
258
|
+
// Kept under its original name because it is what the existing call sites
|
|
259
|
+
// already mean: "go back to where my resources live". That is now the
|
|
260
|
+
// process application, not the workspace root.
|
|
197
261
|
async clickProjectBreadcrumb() {
|
|
198
|
-
await this.
|
|
262
|
+
await this.returnToResourceContainer();
|
|
263
|
+
}
|
|
264
|
+
// The workspace ("project") level itself, for the surfaces that genuinely
|
|
265
|
+
// live there -- collaborators, IDP applications, published element
|
|
266
|
+
// templates.
|
|
267
|
+
async clickWorkspaceBreadcrumb() {
|
|
268
|
+
await this.workspaceBreadcrumb.click({ timeout: 60000 });
|
|
269
|
+
}
|
|
270
|
+
// Creates, once per test, the process application that holds this test's
|
|
271
|
+
// resources, and leaves the browser inside it. Idempotent: called again while
|
|
272
|
+
// already in the container it is a no-op, so a spec that creates several
|
|
273
|
+
// resources in a row pays for the container once.
|
|
274
|
+
//
|
|
275
|
+
// One container per test rather than one shared with the seeded workspace:
|
|
276
|
+
// deploying is process-application-wide, so a shared container would let one
|
|
277
|
+
// spec's deploy pick up every other spec's half-modelled diagram.
|
|
278
|
+
async enterResourceContainer(name) {
|
|
279
|
+
if (this.processApplicationUrl) {
|
|
280
|
+
const stillInside = await this.becameVisible(this.processApplicationPage.breadcrumb, 5000);
|
|
281
|
+
if (!stillInside) {
|
|
282
|
+
await this.returnToResourceContainer();
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
this.processApplicationName =
|
|
287
|
+
name ??
|
|
288
|
+
(await (0, randomName_1.randomNameAgregator)('Cross Component Process Application'));
|
|
289
|
+
console.log(`Creating process application ${this.processApplicationName} inside ${this.defaultFolderName}`);
|
|
290
|
+
await this.clickCreateProcessApplication();
|
|
291
|
+
await this.processApplicationPage.nameNewProcessApplication(this.processApplicationName);
|
|
292
|
+
this.processApplicationUrl = this.page.url();
|
|
293
|
+
// Remember the container; its stage cluster is bound on the first deploy,
|
|
294
|
+
// by ProcessApplicationPage.ensureStageCluster, which -- unlike this point
|
|
295
|
+
// in the flow -- knows which cluster the test is going to deploy to.
|
|
296
|
+
this.processApplicationPage.rememberResourceContainer(this.processApplicationUrl);
|
|
297
|
+
}
|
|
298
|
+
// Which control creates a process application depends on the hub version
|
|
299
|
+
// under test. Since camunda/camunda-hub#25831 it is a plain primary button on
|
|
300
|
+
// the workspace root, and the "Create new" menu explicitly hides the item
|
|
301
|
+
// (hideProcessApplication) because a process application is no longer just
|
|
302
|
+
// another resource. Before that change the menu item was the only way in.
|
|
303
|
+
// Race both entry points so neither version pays a full timeout, then use
|
|
304
|
+
// whichever one rendered.
|
|
305
|
+
async clickCreateProcessApplication() {
|
|
306
|
+
const button = this.processApplicationPage.createProcessApplicationButton;
|
|
307
|
+
const rootLoaded = await this.becameVisible(button.or(this.diagramTypeDropdown).first(), 60000);
|
|
308
|
+
if (!rootLoaded) {
|
|
309
|
+
await this.page.reload();
|
|
310
|
+
}
|
|
311
|
+
// Short: the joint probe above already settled which branch rendered, and
|
|
312
|
+
// hub renders exactly one of them (EntityTable renders the toolbar's create
|
|
313
|
+
// menu OR the empty state, never both), so this only confirms which.
|
|
314
|
+
if (await this.becameVisible(button, 5000)) {
|
|
315
|
+
await this.processApplicationPage.clickCreateProcessApplicationButton();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await this.clickWorkspaceDiagramTypeDropdown();
|
|
319
|
+
await this.processApplicationPage.clickCreateProcessApplicationOption();
|
|
320
|
+
}
|
|
321
|
+
// Re-enters this test's container if it already has one, without creating
|
|
322
|
+
// one: opening an existing resource must not have the side effect of
|
|
323
|
+
// creating a container for a spec that never asked for it.
|
|
324
|
+
async ensureInResourceContainer() {
|
|
325
|
+
if (!this.processApplicationUrl) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
await this.enterResourceContainer();
|
|
329
|
+
}
|
|
330
|
+
// Navigates back to this test's process application by the remembered URL
|
|
331
|
+
// rather than by a breadcrumb click: hub renders no process-application
|
|
332
|
+
// breadcrumb on a diagram page at container root, and the breadcrumb that
|
|
333
|
+
// does exist on the container page doubles as its rename control. Hub's own
|
|
334
|
+
// Playwright suite navigates back the same way.
|
|
335
|
+
async returnToResourceContainer() {
|
|
336
|
+
if (!this.processApplicationUrl) {
|
|
337
|
+
await this.clickWorkspaceBreadcrumb();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
await this.page.goto(this.processApplicationUrl, {
|
|
341
|
+
waitUntil: 'domcontentloaded',
|
|
342
|
+
});
|
|
343
|
+
await this.processApplicationPage.waitForProcessApplication();
|
|
344
|
+
await this.dismissOverlays();
|
|
345
|
+
}
|
|
346
|
+
// Single seam for "give me an empty BPMN diagram in this test's container".
|
|
347
|
+
// A process application always ships an auto-created main process; this adds
|
|
348
|
+
// a new diagram rather than adopting that one, so every existing modelling
|
|
349
|
+
// assertion keeps its current subject.
|
|
350
|
+
async startBlankBpmnDiagram() {
|
|
351
|
+
await this.clickDiagramTypeDropdown();
|
|
352
|
+
await this.clickBpmnTemplateOption();
|
|
353
|
+
}
|
|
354
|
+
// Boolean visibility probe. waitFor rather than isVisible, which resolves
|
|
355
|
+
// immediately and would report a false negative on a still-rendering page.
|
|
356
|
+
async becameVisible(locator, timeout) {
|
|
357
|
+
return locator
|
|
358
|
+
.waitFor({ state: 'visible', timeout })
|
|
359
|
+
.then(() => true)
|
|
360
|
+
.catch(() => false);
|
|
199
361
|
}
|
|
200
362
|
async clickHomeBreadcrumb() {
|
|
201
363
|
// The Console shell's HelpCenter panel ("Your recommendations") renders on
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Locator, Page } from '@playwright/test';
|
|
2
|
+
declare class ProcessApplicationPage {
|
|
3
|
+
private page;
|
|
4
|
+
readonly createProcessApplicationButton: Locator;
|
|
5
|
+
readonly createProcessApplicationOption: Locator;
|
|
6
|
+
readonly creationDialog: Locator;
|
|
7
|
+
readonly nameInput: Locator;
|
|
8
|
+
readonly submitButton: Locator;
|
|
9
|
+
readonly cancelButton: Locator;
|
|
10
|
+
readonly breadcrumb: Locator;
|
|
11
|
+
readonly folderExplorer: Locator;
|
|
12
|
+
readonly createNewDropdown: Locator;
|
|
13
|
+
readonly inlineNameInput: Locator;
|
|
14
|
+
readonly defineStagesDialog: Locator;
|
|
15
|
+
readonly developmentClusterSelect: Locator;
|
|
16
|
+
readonly defineStagesSaveButton: Locator;
|
|
17
|
+
constructor(page: Page);
|
|
18
|
+
private pageReady;
|
|
19
|
+
waitForProcessApplication(timeout?: number): Promise<void>;
|
|
20
|
+
clickCreateProcessApplicationButton(): Promise<void>;
|
|
21
|
+
clickCreateProcessApplicationOption(): Promise<void>;
|
|
22
|
+
nameNewProcessApplication(name: string): Promise<void>;
|
|
23
|
+
defineDevelopmentStage(clusterName?: string): Promise<void>;
|
|
24
|
+
rememberResourceContainer(url: string): void;
|
|
25
|
+
forgetStageCluster(): void;
|
|
26
|
+
ensureStageCluster(clusterName: string): Promise<boolean>;
|
|
27
|
+
private openStageDefinition;
|
|
28
|
+
openByName(name: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export { ProcessApplicationPage };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProcessApplicationPage = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
6
|
+
// Which process application holds the current test's resources, and which
|
|
7
|
+
// cluster its development stage currently points at. Keyed by the Playwright
|
|
8
|
+
// Page because the object that creates the container (ModelerHomePage) and the
|
|
9
|
+
// objects that later need a specific cluster inside it (ModelerCreatePage,
|
|
10
|
+
// FormJsPage) hold separate ProcessApplicationPage instances but share one page.
|
|
11
|
+
const resourceContainers = new WeakMap();
|
|
12
|
+
// Since camunda/camunda-hub#25831 a process application is a first-class
|
|
13
|
+
// container rather than a folder, and a workspace root ("project" in today's
|
|
14
|
+
// UI) holds nothing but process applications and IDP applications. Every file
|
|
15
|
+
// and folder this suite creates therefore has to live inside a process
|
|
16
|
+
// application. This page object owns that container's surface only; entering
|
|
17
|
+
// and leaving it is driven from ModelerHomePage so the ~150 existing spec call
|
|
18
|
+
// sites keep working unchanged.
|
|
19
|
+
class ProcessApplicationPage {
|
|
20
|
+
page;
|
|
21
|
+
// Empty workspace root: a plain primary button rendered next to the "Create
|
|
22
|
+
// new" menu (hub's EmptyStateActionButtons).
|
|
23
|
+
createProcessApplicationButton;
|
|
24
|
+
// Non-empty root: the item inside the "Create new" menu. Hub passes
|
|
25
|
+
// hideProcessApplication at workspace level, so this is the fallback rather
|
|
26
|
+
// than the primary path -- its own unit test asserts the item is absent
|
|
27
|
+
// there. Kept because the Hub PR is still moving this surface.
|
|
28
|
+
createProcessApplicationOption;
|
|
29
|
+
creationDialog;
|
|
30
|
+
nameInput;
|
|
31
|
+
submitButton;
|
|
32
|
+
cancelButton;
|
|
33
|
+
breadcrumb;
|
|
34
|
+
folderExplorer;
|
|
35
|
+
createNewDropdown;
|
|
36
|
+
inlineNameInput;
|
|
37
|
+
// "Define stages" dialog, reached from the process application's "Connected
|
|
38
|
+
// clusters" card (or its /connected-clusters deep link). Assigning a cluster
|
|
39
|
+
// to a stage here is what makes it deployable: DeployModal renders
|
|
40
|
+
// ProcessAppClusterSelection for a process application, and that filters the
|
|
41
|
+
// cluster list through getProcessAppClustersByStage -- so a process
|
|
42
|
+
// application with no stage assigned offers no cluster at all.
|
|
43
|
+
defineStagesDialog;
|
|
44
|
+
developmentClusterSelect;
|
|
45
|
+
defineStagesSaveButton;
|
|
46
|
+
constructor(page) {
|
|
47
|
+
this.page = page;
|
|
48
|
+
// .first() defensively only: hub renders exactly one of these today, in the
|
|
49
|
+
// workspace-root empty state. A future second placement should not turn
|
|
50
|
+
// every PA-creating test into a strict-mode violation.
|
|
51
|
+
this.createProcessApplicationButton = page
|
|
52
|
+
.getByRole('button', { name: 'Create process application' })
|
|
53
|
+
.first();
|
|
54
|
+
this.createProcessApplicationOption = page.locator('[data-test="create-process-application"]');
|
|
55
|
+
this.creationDialog = page.getByRole('dialog', {
|
|
56
|
+
name: 'Create a process application',
|
|
57
|
+
});
|
|
58
|
+
// Deliberately scoped to the dialog: ModelerCreatePage owns an unscoped
|
|
59
|
+
// getByLabel('Name', {exact: true}) for the properties panel, which would
|
|
60
|
+
// resolve to two elements while the creation modal is open.
|
|
61
|
+
this.nameInput = this.creationDialog.getByLabel('Name', { exact: true });
|
|
62
|
+
this.submitButton = this.creationDialog.getByRole('button', {
|
|
63
|
+
name: 'Create',
|
|
64
|
+
exact: true,
|
|
65
|
+
});
|
|
66
|
+
this.cancelButton = this.creationDialog.getByRole('button', {
|
|
67
|
+
name: 'Cancel',
|
|
68
|
+
exact: true,
|
|
69
|
+
});
|
|
70
|
+
this.breadcrumb = page.locator('[data-test="breadcrumb-process-application-menu"]');
|
|
71
|
+
this.folderExplorer = page.getByRole('region', { name: 'Folder explorer' });
|
|
72
|
+
// The process application page renders more than one diagram-dropdown
|
|
73
|
+
// (hub's own unit tests reach for it with getAllByTestId), so the dropdown
|
|
74
|
+
// has to be resolved inside the folder explorer rather than page-wide.
|
|
75
|
+
this.createNewDropdown = this.folderExplorer
|
|
76
|
+
.locator('[data-test="diagram-dropdown"]')
|
|
77
|
+
.first();
|
|
78
|
+
this.inlineNameInput = page.locator('[data-test="editable-input"]');
|
|
79
|
+
this.defineStagesDialog = page
|
|
80
|
+
.getByRole('dialog')
|
|
81
|
+
.filter({ hasText: 'Define stages' });
|
|
82
|
+
// Carbon Dropdown / design-system Select both keep this id.
|
|
83
|
+
this.developmentClusterSelect = this.defineStagesDialog.locator('#dropdown-development-cluster');
|
|
84
|
+
this.defineStagesSaveButton = this.defineStagesDialog.getByRole('button', {
|
|
85
|
+
name: 'Save',
|
|
86
|
+
exact: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Either the folder explorer or the breadcrumb proves we are on the process
|
|
90
|
+
// application page; .first() because both are present once it has rendered.
|
|
91
|
+
pageReady() {
|
|
92
|
+
return this.folderExplorer.or(this.breadcrumb).first();
|
|
93
|
+
}
|
|
94
|
+
async waitForProcessApplication(timeout = 90000) {
|
|
95
|
+
await (0, test_1.expect)(this.pageReady()).toBeVisible({ timeout });
|
|
96
|
+
}
|
|
97
|
+
async clickCreateProcessApplicationButton() {
|
|
98
|
+
await (0, test_1.expect)(this.createProcessApplicationButton).toBeVisible({
|
|
99
|
+
timeout: 60000,
|
|
100
|
+
});
|
|
101
|
+
await this.createProcessApplicationButton.click({ timeout: 60000 });
|
|
102
|
+
}
|
|
103
|
+
async clickCreateProcessApplicationOption() {
|
|
104
|
+
await (0, test_1.expect)(this.createProcessApplicationOption).toBeVisible({
|
|
105
|
+
timeout: 60000,
|
|
106
|
+
});
|
|
107
|
+
await this.createProcessApplicationOption.click({ timeout: 60000 });
|
|
108
|
+
}
|
|
109
|
+
// Hub creates a process application through a modal today. It used an inline
|
|
110
|
+
// editable title before, and #27593 is still moving this surface, so the
|
|
111
|
+
// inline path is kept as a fallback — this is the one method that has to
|
|
112
|
+
// change if naming moves back inline.
|
|
113
|
+
async nameNewProcessApplication(name) {
|
|
114
|
+
const isModal = await this.creationDialog
|
|
115
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
116
|
+
.then(() => true)
|
|
117
|
+
.catch(() => false);
|
|
118
|
+
if (isModal) {
|
|
119
|
+
await this.nameInput.click({ timeout: 30000 });
|
|
120
|
+
await this.nameInput.fill(name);
|
|
121
|
+
await (0, test_1.expect)(this.nameInput).toHaveValue(name, { timeout: 10000 });
|
|
122
|
+
await (0, submitModalWithRetry_1.submitModalWithRetry)(this.submitButton, this.creationDialog);
|
|
123
|
+
await this.waitForProcessApplication();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await (0, test_1.expect)(this.inlineNameInput).toBeVisible({ timeout: 30000 });
|
|
127
|
+
await this.inlineNameInput.click({ timeout: 60000 });
|
|
128
|
+
await this.inlineNameInput.fill(name);
|
|
129
|
+
await this.inlineNameInput.press('Enter');
|
|
130
|
+
await this.waitForProcessApplication();
|
|
131
|
+
}
|
|
132
|
+
// Assigns a cluster to this process application's development stage.
|
|
133
|
+
//
|
|
134
|
+
// Deploying from inside a process application goes through
|
|
135
|
+
// ProcessAppClusterSelection, which only lists clusters the application has
|
|
136
|
+
// assigned to a stage, and does not auto-select anything when that list is
|
|
137
|
+
// empty. A freshly created process application has no stages, so the deploy
|
|
138
|
+
// dialog shows "Clusters not configured for stages" and offers nothing to
|
|
139
|
+
// pick -- which is why the old per-deploy cluster toggle was nowhere to be
|
|
140
|
+
// found. Assign the stage once, at creation, and the deploy dialog then
|
|
141
|
+
// auto-selects it.
|
|
142
|
+
//
|
|
143
|
+
// clusterName is matched as a prefix because the dropdown renders each
|
|
144
|
+
// cluster as "<name> <generation>". Without it, the first real cluster is
|
|
145
|
+
// taken -- callers that care assert the name in the deploy dialog anyway.
|
|
146
|
+
async defineDevelopmentStage(clusterName) {
|
|
147
|
+
await (0, test_1.expect)(this.defineStagesDialog).toBeVisible({ timeout: 60000 });
|
|
148
|
+
await this.developmentClusterSelect.click({ timeout: 30000 });
|
|
149
|
+
// Escape before interpolating: a cluster name can carry regex
|
|
150
|
+
// metacharacters -- "Test Cluster (v2)" is the common case -- which would
|
|
151
|
+
// otherwise mis-match or throw. Same treatment as
|
|
152
|
+
// utils/modelerCluster.selectModelerCluster.
|
|
153
|
+
const escapedClusterName = clusterName?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
154
|
+
const option = clusterName
|
|
155
|
+
? this.defineStagesDialog
|
|
156
|
+
.getByRole('option', { name: new RegExp(`^${escapedClusterName}`) })
|
|
157
|
+
.first()
|
|
158
|
+
: this.defineStagesDialog
|
|
159
|
+
.getByRole('option')
|
|
160
|
+
.filter({ hasNotText: 'Select a development cluster' })
|
|
161
|
+
.first();
|
|
162
|
+
await option.click({ timeout: 30000 });
|
|
163
|
+
await this.defineStagesSaveButton.click({ timeout: 30000 });
|
|
164
|
+
await (0, test_1.expect)(this.defineStagesDialog).toBeHidden({ timeout: 60000 });
|
|
165
|
+
}
|
|
166
|
+
rememberResourceContainer(url) {
|
|
167
|
+
resourceContainers.set(this.page, { url });
|
|
168
|
+
}
|
|
169
|
+
// Drops the recorded development-stage binding so the next ensureStageCluster
|
|
170
|
+
// call binds again. Called when the deploy dialog turns out not to offer the
|
|
171
|
+
// cluster we believe we bound: the dialog offers exactly what the
|
|
172
|
+
// application's stages point at, so if the cluster is missing the binding
|
|
173
|
+
// did not take, and reopening the dialog can never change that. Without
|
|
174
|
+
// this, a caller's retries all re-check a state nothing is changing.
|
|
175
|
+
forgetStageCluster() {
|
|
176
|
+
const container = resourceContainers.get(this.page);
|
|
177
|
+
if (container) {
|
|
178
|
+
container.developmentCluster = undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// A process application deploys to the clusters bound to its stages, and
|
|
182
|
+
// nothing else: inside a container DeployModal renders
|
|
183
|
+
// ProcessAppClusterSelection, which lists only those and auto-selects the
|
|
184
|
+
// first. Which cluster a test wants is known only once a deploy flow names
|
|
185
|
+
// it, so the binding happens here, on demand -- leave the diagram, bind the
|
|
186
|
+
// cluster to the development stage, come back. Repeat calls for the same
|
|
187
|
+
// cluster cost nothing, and a test deploying to a second cluster rebinds.
|
|
188
|
+
// Returns whether it navigated: the caller may have been on a view that a
|
|
189
|
+
// reload does not restore -- Play is a tab click, not a URL -- and has to
|
|
190
|
+
// re-enter it.
|
|
191
|
+
async ensureStageCluster(clusterName) {
|
|
192
|
+
const container = resourceContainers.get(this.page);
|
|
193
|
+
if (!container || container.developmentCluster === clusterName) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const returnTo = this.page.url();
|
|
197
|
+
const paUrl = container.url.replace(/[?#].*$/, '').replace(/\/$/, '');
|
|
198
|
+
// Retried as a whole, re-entering the page on every attempt. This binding
|
|
199
|
+
// is a one-shot precondition for every deploy the test goes on to make,
|
|
200
|
+
// and unlike the deploy itself it sits outside runProcessInstance's retry
|
|
201
|
+
// loop -- so a single transient miss (the session bounced to /login, the
|
|
202
|
+
// cluster list not yet arrived when the dropdown opened) failed the test
|
|
203
|
+
// outright instead of being absorbed the way every other modal in this
|
|
204
|
+
// suite is. Re-navigating is the recovery; waiting longer on a page that
|
|
205
|
+
// has already gone wrong is not, which is why the inner timeouts stay
|
|
206
|
+
// short. A genuinely unbindable cluster still fails, with the same error.
|
|
207
|
+
await (0, test_1.expect)(async () => {
|
|
208
|
+
await this.page.goto(`${paUrl}/connected-clusters`, {
|
|
209
|
+
waitUntil: 'domcontentloaded',
|
|
210
|
+
});
|
|
211
|
+
await this.openStageDefinition();
|
|
212
|
+
await this.defineDevelopmentStage(clusterName);
|
|
213
|
+
}).toPass({ timeout: 240000, intervals: [1000, 2000, 5000] });
|
|
214
|
+
container.developmentCluster = clusterName;
|
|
215
|
+
await this.page.goto(returnTo, { waitUntil: 'domcontentloaded' });
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
// The /connected-clusters deep link opens the Define stages dialog by itself
|
|
219
|
+
// while the application has no stage bound. Once one is bound -- a test that
|
|
220
|
+
// deploys to a second cluster -- it has to be reopened from the page, so try
|
|
221
|
+
// the affordances hub offers before failing with exactly what was looked for.
|
|
222
|
+
// Worth the noise: an unbound cluster otherwise surfaces as a deploy dialog
|
|
223
|
+
// that silently offers nothing, which says nothing about its own cause.
|
|
224
|
+
async openStageDefinition() {
|
|
225
|
+
const opened = await this.defineStagesDialog
|
|
226
|
+
.waitFor({ state: 'visible', timeout: 15000 })
|
|
227
|
+
.then(() => true)
|
|
228
|
+
.catch(() => false);
|
|
229
|
+
if (opened) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Page-scoped .first(): hub renders one such control on this page, and a
|
|
233
|
+
// second placement must not turn every rebinding test into a strict-mode
|
|
234
|
+
// violation.
|
|
235
|
+
const reopen = this.page
|
|
236
|
+
.getByRole('button', { name: /define stages|edit stages|configure/i })
|
|
237
|
+
.first();
|
|
238
|
+
const canReopen = await reopen
|
|
239
|
+
.waitFor({ state: 'visible', timeout: 10000 })
|
|
240
|
+
.then(() => true)
|
|
241
|
+
.catch(() => false);
|
|
242
|
+
if (!canReopen) {
|
|
243
|
+
throw new Error('Could not open the "Define stages" dialog on /connected-clusters: it ' +
|
|
244
|
+
'did not open by itself and no "Define stages" / "Edit stages" / ' +
|
|
245
|
+
'"Configure" control was found.');
|
|
246
|
+
}
|
|
247
|
+
await reopen.click({ timeout: 30000 });
|
|
248
|
+
}
|
|
249
|
+
async openByName(name) {
|
|
250
|
+
const processApplication = this.page
|
|
251
|
+
.getByRole('row')
|
|
252
|
+
.filter({ hasText: name })
|
|
253
|
+
.getByTitle(name)
|
|
254
|
+
.first();
|
|
255
|
+
await (0, test_1.expect)(processApplication).toBeVisible({ timeout: 90000 });
|
|
256
|
+
await processApplication.click({ timeout: 60000 });
|
|
257
|
+
await this.waitForProcessApplication();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
exports.ProcessApplicationPage = ProcessApplicationPage;
|
|
@@ -509,6 +509,14 @@ async function modelAndRunConnectorsTimerEventDiagram(modelerCreatePage, modeler
|
|
|
509
509
|
timeout: 60000,
|
|
510
510
|
});
|
|
511
511
|
await connectorMarketplacePage.downloadConnectorToProject();
|
|
512
|
+
// Both lookups are container-relative: clickProjectBreadcrumb() returns to
|
|
513
|
+
// this test's process application, which is where the uploaded diagram
|
|
514
|
+
// lives. The downloaded element template is the open question -- hub
|
|
515
|
+
// publishes element templates to the workspace, but the marketplace download
|
|
516
|
+
// is initiated from inside the container, so it should land there. If a
|
|
517
|
+
// trace shows the template at workspace root instead, this call becomes
|
|
518
|
+
// clickWorkspaceBreadcrumb() followed by returnToResourceContainer() before
|
|
519
|
+
// the diagram is reopened.
|
|
512
520
|
await modelerHomePage.clickProjectBreadcrumb();
|
|
513
521
|
await modelerHomePage.clickProcessDiagram('REST Outbound Connector');
|
|
514
522
|
await connectorTemplatePage.publishConnector();
|
|
@@ -129,7 +129,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
129
129
|
await modelerCreatePage.clickAppendEndEventButton();
|
|
130
130
|
await (0, sleep_1.sleep)(20000);
|
|
131
131
|
await modelerCreatePage.runProcessInstance(clusterName);
|
|
132
|
-
await (0, test_1.expect)(page.getByText(
|
|
132
|
+
await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
|
|
133
133
|
await modelerCreatePage.clickCloseButton();
|
|
134
134
|
});
|
|
135
135
|
});
|
|
@@ -219,7 +219,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
219
219
|
await modelerCreatePage.clickAppendEndEventButton();
|
|
220
220
|
await (0, sleep_1.sleep)(20000);
|
|
221
221
|
await modelerCreatePage.runProcessInstance(clusterName, '', {}, '', true);
|
|
222
|
-
await (0, test_1.expect)(page.getByText(
|
|
222
|
+
await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
|
|
223
223
|
await modelerCreatePage.clickCloseButton();
|
|
224
224
|
});
|
|
225
225
|
});
|
|
@@ -314,7 +314,7 @@ _8_8_1.test.describe('User Roles User Flow', () => {
|
|
|
314
314
|
await modelerCreatePage.clickAppendEndEventButton();
|
|
315
315
|
await (0, sleep_1.sleep)(20000);
|
|
316
316
|
await modelerCreatePage.runProcessInstance(clusterName);
|
|
317
|
-
await (0, test_1.expect)(page.getByText(
|
|
317
|
+
await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).not.toBeVisible({ timeout: 60000 });
|
|
318
318
|
await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
|
|
319
319
|
timeout: 120000,
|
|
320
320
|
});
|
|
@@ -87,7 +87,9 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
|
|
|
87
87
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
88
88
|
});
|
|
89
89
|
await _8_8_1.test.step('Add an IDP application to the project', async () => {
|
|
90
|
-
|
|
90
|
+
// Workspace-level menu: an IDP application can only be created at
|
|
91
|
+
// workspace root, never inside a process application.
|
|
92
|
+
await modelerHomePage.clickWorkspaceDiagramTypeDropdown();
|
|
91
93
|
await modelerHomePage.clickIdpApplicationTemplateOption();
|
|
92
94
|
});
|
|
93
95
|
await _8_8_1.test.step('Creates a document extraction template', async () => {
|
|
@@ -159,7 +161,9 @@ _8_8_1.test.describe('IDP User Flow Tests', () => {
|
|
|
159
161
|
await modelerHomePage.clickCrossComponentProjectFolder();
|
|
160
162
|
});
|
|
161
163
|
await _8_8_1.test.step('Add an IDP application to the project', async () => {
|
|
162
|
-
|
|
164
|
+
// Workspace-level menu: an IDP application can only be created at
|
|
165
|
+
// workspace root, never inside a process application.
|
|
166
|
+
await modelerHomePage.clickWorkspaceDiagramTypeDropdown();
|
|
163
167
|
await modelerHomePage.clickIdpApplicationTemplateOption();
|
|
164
168
|
});
|
|
165
169
|
await _8_8_1.test.step('Creates a document extraction template', async () => {
|