@camunda/e2e-test-suite 0.0.1273 → 0.0.1275
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/AppsPage.js +11 -3
- package/dist/pages/8.7/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.7/ClusterDetailsPage.js +22 -64
- package/dist/pages/8.7/ModelerCreatePage.js +56 -14
- package/dist/pages/8.7/PlayPage.js +12 -1
- package/dist/pages/8.7/SettingsPage.js +15 -2
- package/dist/pages/8.7/UtilitiesPage.js +22 -0
- package/dist/pages/8.8/AppsPage.d.ts +2 -0
- package/dist/pages/8.8/AppsPage.js +122 -0
- package/dist/pages/8.8/ClusterDetailsPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterDetailsPage.js +29 -10
- package/dist/pages/8.8/ClusterPage.js +35 -2
- package/dist/pages/8.8/ClusterSecretsPage.d.ts +1 -0
- package/dist/pages/8.8/ClusterSecretsPage.js +62 -2
- package/dist/pages/8.8/FormJsPage.js +55 -24
- package/dist/pages/8.8/HomePage.d.ts +3 -0
- package/dist/pages/8.8/HomePage.js +62 -7
- package/dist/pages/8.8/LoginPage.js +32 -1
- package/dist/pages/8.8/ModelerCreatePage.d.ts +1 -0
- package/dist/pages/8.8/ModelerCreatePage.js +78 -16
- package/dist/pages/8.8/ModelerHomePage.d.ts +10 -0
- package/dist/pages/8.8/ModelerHomePage.js +339 -14
- package/dist/pages/8.8/UtilitiesPage.js +47 -2
- package/dist/tests/8.7/rba-enabled-user-flows.spec.js +5 -1
- package/dist/tests/8.7/web-modeler-user-flows.spec.js +7 -4
- package/dist/tests/8.8/test-setup.spec.js +29 -4
- package/dist/utils/consoleApiHelpers.d.ts +5 -0
- package/dist/utils/consoleApiHelpers.js +177 -2
- package/dist/utils/hubDeployDialog.d.ts +3 -0
- package/dist/utils/hubDeployDialog.js +74 -0
- package/dist/utils/hubResourceRename.d.ts +4 -0
- package/dist/utils/hubResourceRename.js +74 -0
- package/package.json +1 -1
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
2
|
declare class ModelerHomePage {
|
|
3
3
|
private defaultFolderName;
|
|
4
|
+
private defaultClusterName;
|
|
4
5
|
private page;
|
|
5
6
|
private processApplicationPage;
|
|
6
7
|
private processApplicationUrl?;
|
|
7
8
|
private processApplicationName?;
|
|
8
9
|
readonly modelerPageBanner: Locator;
|
|
9
10
|
readonly createNewProjectButton: Locator;
|
|
11
|
+
readonly createWorkspaceButton: Locator;
|
|
12
|
+
readonly workspaceWizardNameInput: Locator;
|
|
13
|
+
readonly workspaceWizardSubmitButton: Locator;
|
|
14
|
+
readonly workspaceWizardMembersStep: Locator;
|
|
15
|
+
readonly workspaceWizardEnvironmentsHeading: Locator;
|
|
10
16
|
readonly projectNameInput: Locator;
|
|
11
17
|
readonly formNameInput: Locator;
|
|
12
18
|
readonly chooseBpmnTemplateButton: Locator;
|
|
@@ -49,6 +55,7 @@ declare class ModelerHomePage {
|
|
|
49
55
|
reload(): Promise<void>;
|
|
50
56
|
getProjectNames(name: string): Promise<string[]>;
|
|
51
57
|
clickProcessDiagram(name: string): Promise<void>;
|
|
58
|
+
private openProjectCreateMenuWithResources;
|
|
52
59
|
clickDiagramTypeDropdown(): Promise<void>;
|
|
53
60
|
clickWorkspaceDiagramTypeDropdown(): Promise<void>;
|
|
54
61
|
clickBpmnTemplateOption(): Promise<void>;
|
|
@@ -61,7 +68,9 @@ declare class ModelerHomePage {
|
|
|
61
68
|
private ensureInResourceContainer;
|
|
62
69
|
returnToResourceContainer(): Promise<void>;
|
|
63
70
|
startBlankBpmnDiagram(): Promise<void>;
|
|
71
|
+
showAllWorkspaces(probeTimeout: number): Promise<void>;
|
|
64
72
|
private becameVisible;
|
|
73
|
+
private createWorkspaceViaWizard;
|
|
65
74
|
clickHomeBreadcrumb(): Promise<void>;
|
|
66
75
|
private dismissBlockingDialogs;
|
|
67
76
|
clickOpenOrganizationsButton(): Promise<void>;
|
|
@@ -70,6 +79,7 @@ declare class ModelerHomePage {
|
|
|
70
79
|
clickCrossComponentProjectFolder(): Promise<void>;
|
|
71
80
|
private clickCrossComponentProjectFolderWithRetry;
|
|
72
81
|
createCrossComponentProjectFolder(): Promise<void>;
|
|
82
|
+
private enterProjectInsideWorkspace;
|
|
73
83
|
clickManageButton(retries?: number): Promise<void>;
|
|
74
84
|
clickUploadFilesButton(): Promise<void>;
|
|
75
85
|
clickMessageBanner(): Promise<void>;
|
|
@@ -7,8 +7,16 @@ const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocat
|
|
|
7
7
|
const sessionRecovery_1 = require("../../utils/sessionRecovery");
|
|
8
8
|
const randomName_1 = require("../../utils/randomName");
|
|
9
9
|
const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
|
|
10
|
+
const submitModalWithRetry_1 = require("../../utils/assertionHelpers/submitModalWithRetry");
|
|
11
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
12
|
+
const hubResourceRename_1 = require("../../utils/hubResourceRename");
|
|
10
13
|
class ModelerHomePage {
|
|
11
14
|
defaultFolderName = 'Cross Component Test Project';
|
|
15
|
+
// The default cluster test-setup.spec.ts's "Create Default Cluster" test
|
|
16
|
+
// provisions for the whole suite -- assigned to the shared workspace's
|
|
17
|
+
// Environments step so downstream specs can deploy to it without a manual
|
|
18
|
+
// assignment step first.
|
|
19
|
+
defaultClusterName = 'Test Cluster';
|
|
12
20
|
page;
|
|
13
21
|
// Since camunda/camunda-hub#25831 files and folders cannot sit at a
|
|
14
22
|
// workspace ("project" in today's UI) root -- only process applications and
|
|
@@ -25,6 +33,11 @@ class ModelerHomePage {
|
|
|
25
33
|
processApplicationName;
|
|
26
34
|
modelerPageBanner;
|
|
27
35
|
createNewProjectButton;
|
|
36
|
+
createWorkspaceButton;
|
|
37
|
+
workspaceWizardNameInput;
|
|
38
|
+
workspaceWizardSubmitButton;
|
|
39
|
+
workspaceWizardMembersStep;
|
|
40
|
+
workspaceWizardEnvironmentsHeading;
|
|
28
41
|
projectNameInput;
|
|
29
42
|
formNameInput;
|
|
30
43
|
chooseBpmnTemplateButton;
|
|
@@ -58,9 +71,24 @@ class ModelerHomePage {
|
|
|
58
71
|
constructor(page) {
|
|
59
72
|
this.page = page;
|
|
60
73
|
this.processApplicationPage = new ProcessApplicationPage_1.ProcessApplicationPage(page);
|
|
61
|
-
|
|
74
|
+
// Hub's design-system shell renders the app header as a role=banner with no
|
|
75
|
+
// accessible name after the Camunda Hub rebrand, so the classic
|
|
76
|
+
// name-matched banner no longer resolves; the Modeler logo link inside it
|
|
77
|
+
// carries "Camunda Hub home" when the org has workspacesEnabled, else
|
|
78
|
+
// "Camunda Modeler home" (camunda-hub NavShellDs.common.tsx) -- match
|
|
79
|
+
// either. Keep the legacy named-banner branch for shells where the rename
|
|
80
|
+
// has not shipped yet.
|
|
81
|
+
this.modelerPageBanner = page
|
|
82
|
+
.getByRole('banner', {
|
|
62
83
|
name: 'Camunda Modeler',
|
|
63
|
-
})
|
|
84
|
+
})
|
|
85
|
+
.or(page
|
|
86
|
+
.getByRole('link', { name: 'Camunda Hub home' })
|
|
87
|
+
.and(page.locator(':not([href$="/login"])')))
|
|
88
|
+
.or(page
|
|
89
|
+
.getByRole('link', { name: 'Camunda Modeler home' })
|
|
90
|
+
.and(page.locator(':not([href$="/login"])')))
|
|
91
|
+
.first();
|
|
64
92
|
// Hub renamed this button to "Create project" on both the classic and the
|
|
65
93
|
// design-system shell. The legacy label is kept so the flow still works
|
|
66
94
|
// against Modeler builds where the rename has not shipped yet. exact:true
|
|
@@ -73,8 +101,32 @@ class ModelerHomePage {
|
|
|
73
101
|
name: 'Create project',
|
|
74
102
|
exact: true,
|
|
75
103
|
}));
|
|
104
|
+
// Camunda Hub, which replaced Console + Web Modeler on SaaS, tops the
|
|
105
|
+
// hierarchy with a Workspaces overview whose "Create workspace" CTA opens a
|
|
106
|
+
// two-step wizard (General -> Members) that names the workspace up front.
|
|
107
|
+
// Same hooks as pages/8.9/ModelerHomePage.ts. exact:true keeps the
|
|
108
|
+
// split button's options chevron out of the match.
|
|
109
|
+
this.createWorkspaceButton = page.getByRole('button', {
|
|
110
|
+
name: 'Create workspace',
|
|
111
|
+
exact: true,
|
|
112
|
+
});
|
|
113
|
+
this.workspaceWizardNameInput = page.locator('#workspace-name');
|
|
114
|
+
this.workspaceWizardSubmitButton = page.locator('[data-test="workspace-wizard-submit"]');
|
|
115
|
+
this.workspaceWizardMembersStep = page.locator('[data-test="step-members"]');
|
|
116
|
+
// When the org has environments enabled, an "Assign environments" step is
|
|
117
|
+
// inserted after Members -- camunda-hub's own
|
|
118
|
+
// e2e-playwright/pages/CreateWorkspacePage.ts detects it the same way.
|
|
119
|
+
this.workspaceWizardEnvironmentsHeading = page.getByRole('heading', {
|
|
120
|
+
name: 'Assign environments',
|
|
121
|
+
});
|
|
76
122
|
this.projectNameInput = page.locator('[data-test="editable-input"]');
|
|
77
|
-
|
|
123
|
+
// Hub's rename flow moved into a dialog whose field is
|
|
124
|
+
// `[data-test="rename-input"]` (#rename-input); the legacy shells still
|
|
125
|
+
// rename through the inline editable title. Match either.
|
|
126
|
+
this.formNameInput = page
|
|
127
|
+
.locator('[data-test="rename-input"]')
|
|
128
|
+
.or(page.locator('[data-test="editable-input"]'))
|
|
129
|
+
.first();
|
|
78
130
|
this.idpApplicationNameInput = page.getByPlaceholder('New IDP Application');
|
|
79
131
|
this.createIdpApplicationButton = page.getByRole('button', {
|
|
80
132
|
name: 'Create',
|
|
@@ -90,27 +142,60 @@ class ModelerHomePage {
|
|
|
90
142
|
this.idpEmptyStateButton = page.getByRole('button', {
|
|
91
143
|
name: 'Create IDP application',
|
|
92
144
|
});
|
|
93
|
-
|
|
145
|
+
// Camunda Hub's design-system shell renders this trigger as
|
|
146
|
+
// `create-action-dropdown` (CreateActionDs.tsx, accessible name "Create
|
|
147
|
+
// new"); `diagram-dropdown` is the legacy shell's id and survives only in
|
|
148
|
+
// hub's own older cypress specs. Match either so both shells work.
|
|
149
|
+
this.diagramTypeDropdown = page
|
|
150
|
+
.locator('[data-test="create-action-dropdown"]')
|
|
151
|
+
.or(page.locator('[data-test="diagram-dropdown"]'))
|
|
152
|
+
.first();
|
|
94
153
|
// Matched on the test id alone: hub's design-system variant of the create
|
|
95
154
|
// menu renders "BPMN diagram" (lowercase d), so a text filter breaks the
|
|
96
155
|
// day that variant ships.
|
|
97
156
|
this.bpmnTemplateOption = page.locator('[data-test="create-bpmn-diagram"]');
|
|
98
157
|
this.formTemplateOption = page.locator('[data-test="create-form"]');
|
|
99
158
|
this.diagramPropertiesPanel = page.locator('[data-group-id="group-general"]');
|
|
100
|
-
|
|
159
|
+
// Hub's breadcrumbs carry no data-test at all (confirmed by a DOM audit of
|
|
160
|
+
// a live run: none of the seven breadcrumb-* ids this file used are
|
|
161
|
+
// present). Its trail is a `navigation` landmark whose entries are links,
|
|
162
|
+
// so address the project by its own name there and keep the legacy id for
|
|
163
|
+
// shells that still emit it.
|
|
164
|
+
this.projectBreadcrumb = page
|
|
165
|
+
.locator('[data-test="breadcrumb-project"]')
|
|
166
|
+
.or((0, hubResourceRename_1.hubBreadcrumbs)(page).getByRole('link', {
|
|
167
|
+
name: this.defaultFolderName,
|
|
168
|
+
exact: true,
|
|
169
|
+
}))
|
|
170
|
+
.first();
|
|
101
171
|
// Kept separate from projectBreadcrumb, which now means "the container my
|
|
102
172
|
// resources live in". When hub's workspaces terminology flag flips,
|
|
103
173
|
// breadcrumb-project comes to denote the process application, so the
|
|
104
174
|
// workspace level needs its own name from the start.
|
|
175
|
+
// Neither breadcrumb-project nor breadcrumb-workspace exists on Hub (the
|
|
176
|
+
// DOM audit found none of the breadcrumb-* ids), so this click waited out
|
|
177
|
+
// its whole 60s. Hub's trail reads "<org> / <workspace> / <project>", and
|
|
178
|
+
// the workspace and its project share this name -- take the LAST entry, the
|
|
179
|
+
// project, since that is the container callers come back here to add
|
|
180
|
+
// resources to.
|
|
105
181
|
this.workspaceBreadcrumb = page
|
|
106
182
|
.locator('[data-test="breadcrumb-project"]')
|
|
107
183
|
.or(page.locator('[data-test="breadcrumb-workspace"]'))
|
|
184
|
+
.or((0, hubResourceRename_1.hubBreadcrumbs)(page)
|
|
185
|
+
.getByRole('link', { name: this.defaultFolderName, exact: true })
|
|
186
|
+
.last())
|
|
187
|
+
.or((0, hubResourceRename_1.hubBreadcrumbs)(page)
|
|
188
|
+
.getByRole('button', { name: this.defaultFolderName, exact: true })
|
|
189
|
+
.last())
|
|
108
190
|
.first();
|
|
109
191
|
this.homeBreadcrumb = page.locator('[data-test="breadcrumb-home"]');
|
|
110
192
|
this.openOrganizationsButton = page.getByLabel('Open Organizations');
|
|
111
193
|
this.manageButton = page.getByRole('menuitem', { name: 'Manage' });
|
|
194
|
+
// On the Camunda Hub Workspaces overview the folder is a workspace tile
|
|
195
|
+
// whose "Open workspace <name>" button navigates into it.
|
|
112
196
|
this.crossComponentProjectFolder = page
|
|
113
|
-
.
|
|
197
|
+
.getByRole('button', { name: `Open workspace ${this.defaultFolderName}` })
|
|
198
|
+
.or(page.getByTitle(this.defaultFolderName, { exact: true }))
|
|
114
199
|
.first();
|
|
115
200
|
this.rows = page.getByRole('row');
|
|
116
201
|
this.uploadFilesButton = page.getByRole('menuitem', { name: 'Upload files' });
|
|
@@ -347,6 +432,24 @@ class ModelerHomePage {
|
|
|
347
432
|
}
|
|
348
433
|
}
|
|
349
434
|
async enterNewProjectName(name) {
|
|
435
|
+
// Camunda Hub names a project up front in a creation dialog
|
|
436
|
+
// (`#create-project-name`) instead of the legacy inline rename on a
|
|
437
|
+
// project that already exists. When that dialog is what "Create project"
|
|
438
|
+
// opened, the inline editor below never mounts, so name it here and
|
|
439
|
+
// return -- the caller only needs the project to exist and be named.
|
|
440
|
+
const createProjectNameInput = this.page.locator('#create-project-name');
|
|
441
|
+
if (await this.becameVisible(createProjectNameInput, 10000)) {
|
|
442
|
+
const dialog = this.page.getByRole('dialog').filter({
|
|
443
|
+
has: createProjectNameInput,
|
|
444
|
+
});
|
|
445
|
+
await createProjectNameInput.fill(name);
|
|
446
|
+
await (0, test_1.expect)(createProjectNameInput).toHaveValue(name, { timeout: 10000 });
|
|
447
|
+
await (0, submitModalWithRetry_1.submitModalWithRetry)(dialog
|
|
448
|
+
.getByRole('button', { name: 'Create project', exact: true })
|
|
449
|
+
.or(dialog.getByRole('button', { name: 'Create', exact: true }))
|
|
450
|
+
.first(), dialog);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
350
453
|
// The inline rename silently drops the value when the editable title field
|
|
351
454
|
// is not yet in edit mode as fill() runs, leaving the project named
|
|
352
455
|
// "New project". Re-enter until the field reflects the requested name.
|
|
@@ -442,7 +545,34 @@ class ModelerHomePage {
|
|
|
442
545
|
// workspace root no longer offers BPMN, Form or Upload -- those resources
|
|
443
546
|
// cannot live there -- so the container is entered first. Callers are
|
|
444
547
|
// unchanged, which is why the existing spec call sites need no edit.
|
|
548
|
+
// Camunda Hub lets blank resources live directly in a project: the project's
|
|
549
|
+
// own "Create new" menu (ProjectActionButtonDs, data-test="diagram-dropdown")
|
|
550
|
+
// renders BPMN diagram / DMN diagram / Form / RPA script / Element template /
|
|
551
|
+
// README / Folder whenever the current container is not the project ROOT, and
|
|
552
|
+
// offers "Process application" only at that root. Opening it and looking for
|
|
553
|
+
// the resource items is the only way to tell the two menus apart, since both
|
|
554
|
+
// share the trigger.
|
|
555
|
+
//
|
|
556
|
+
// Returns true with the menu left OPEN for the caller's option click.
|
|
557
|
+
async openProjectCreateMenuWithResources() {
|
|
558
|
+
if (!(await this.becameVisible(this.diagramTypeDropdown, 15000))) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
await this.dismissOverlays();
|
|
562
|
+
await this.diagramTypeDropdown.click({ timeout: 30000 });
|
|
563
|
+
if (await this.becameVisible(this.formTemplateOption.or(this.bpmnTemplateOption).first(), 10000)) {
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
// The root menu instead: close it so the process-application path below
|
|
567
|
+
// starts from a page with no open dropdown holding the pointer-events lock
|
|
568
|
+
// Radix puts on <body> while a menu is open.
|
|
569
|
+
await this.page.keyboard.press('Escape').catch(() => { });
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
445
572
|
async clickDiagramTypeDropdown() {
|
|
573
|
+
if (await this.openProjectCreateMenuWithResources()) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
446
576
|
await this.enterResourceContainer();
|
|
447
577
|
const dropdown = this.processApplicationPage.createNewDropdown;
|
|
448
578
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
@@ -539,10 +669,8 @@ class ModelerHomePage {
|
|
|
539
669
|
}
|
|
540
670
|
async enterFormName(name) {
|
|
541
671
|
await (0, sessionRecovery_1.recoverStuckModelerLogin)(this.page);
|
|
542
|
-
|
|
543
|
-
await this.
|
|
544
|
-
await this.formNameInput.fill(name, { timeout: 60000 });
|
|
545
|
-
await this.formNameInput.press('Enter');
|
|
672
|
+
// "New form" is the server default the form still carries at this point.
|
|
673
|
+
await (0, hubResourceRename_1.renameHubResource)(this.page, 'New form', name);
|
|
546
674
|
}
|
|
547
675
|
// Kept under its original name because it is what the existing call sites
|
|
548
676
|
// already mean: "go back to where my resources live". That is now the
|
|
@@ -642,13 +770,110 @@ class ModelerHomePage {
|
|
|
642
770
|
}
|
|
643
771
|
// Boolean visibility probe. waitFor rather than isVisible, which resolves
|
|
644
772
|
// immediately and would report a false negative on a still-rendering page.
|
|
773
|
+
// Since camunda-hub #28956 the Workspaces overview defaults org-elevated
|
|
774
|
+
// users to "My workspaces" -- only workspaces they are an explicit member
|
|
775
|
+
// of -- and resets to it on every load. The shared folder is created by
|
|
776
|
+
// whichever test user wins the race, so every other user sees "No
|
|
777
|
+
// workspaces match this filter" and never finds it. Widen the scope to
|
|
778
|
+
// "All workspaces" first. The control is absent outside Hub and for
|
|
779
|
+
// non-elevated users (their list is membership-scoped anyway), so a missing
|
|
780
|
+
// control is a no-op. Ported from pages/8.10/ModelerHomePage.ts (#3710).
|
|
781
|
+
async showAllWorkspaces(probeTimeout) {
|
|
782
|
+
const scopeFilter = this.page.getByRole('combobox', {
|
|
783
|
+
name: 'Filter workspaces',
|
|
784
|
+
});
|
|
785
|
+
if (!(await this.becameVisible(scopeFilter, probeTimeout)))
|
|
786
|
+
return;
|
|
787
|
+
if ((await scopeFilter.innerText()).includes('All workspaces'))
|
|
788
|
+
return;
|
|
789
|
+
await scopeFilter.click({ timeout: 10000 });
|
|
790
|
+
await this.page
|
|
791
|
+
.getByRole('option', { name: 'All workspaces' })
|
|
792
|
+
.click({ timeout: 10000 });
|
|
793
|
+
await (0, test_1.expect)(scopeFilter).toContainText('All workspaces', {
|
|
794
|
+
timeout: 10000,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
645
797
|
async becameVisible(locator, timeout) {
|
|
646
798
|
return locator
|
|
647
799
|
.waitFor({ state: 'visible', timeout })
|
|
648
800
|
.then(() => true)
|
|
649
801
|
.catch(() => false);
|
|
650
802
|
}
|
|
803
|
+
// Drives Camunda Hub's workspace-creation wizard. The name is entered on
|
|
804
|
+
// the General step, which enables "Next"; the same submit button then
|
|
805
|
+
// relabels itself through the remaining steps and finally reads "Create
|
|
806
|
+
// workspace" once it creates the workspace and routes the creator into its
|
|
807
|
+
// projects listing. Gate the Members-step click on that step becoming
|
|
808
|
+
// active so the shared submit button is not clicked twice while still on
|
|
809
|
+
// General.
|
|
810
|
+
async createWorkspaceViaWizard(name) {
|
|
811
|
+
await this.dismissOverlays();
|
|
812
|
+
await this.createWorkspaceButton.click({ timeout: 60000 });
|
|
813
|
+
await this.workspaceWizardNameInput.fill(name, { timeout: 60000 });
|
|
814
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
815
|
+
await (0, test_1.expect)(this.workspaceWizardMembersStep).toHaveAttribute('data-state', 'active', { timeout: 30000 });
|
|
816
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
817
|
+
// When the org has environments enabled, this only advances the wizard
|
|
818
|
+
// into an "Assign environments" step instead of submitting. Assign the
|
|
819
|
+
// suite's default cluster here rather than submitting with an empty
|
|
820
|
+
// selection -- otherwise this shared workspace starts with no
|
|
821
|
+
// environments and every downstream spec that deploys to it needs its
|
|
822
|
+
// own manual assignment first. Each card's accessible name is
|
|
823
|
+
// "Select <cluster name>" before it is picked (camunda-hub's own
|
|
824
|
+
// create-workspace-environments.spec.tsx asserts the same pattern).
|
|
825
|
+
// Sampled immediately (isVisible() doesn't wait) since the step change
|
|
826
|
+
// is a synchronous client-side re-render.
|
|
827
|
+
// Waited for, not sampled: the step transition is not always synchronous,
|
|
828
|
+
// and an immediate isVisible() read false would skip the cluster
|
|
829
|
+
// assignment and then wait out the projects-URL check while the wizard is
|
|
830
|
+
// still open. Matches the 8.10 flow.
|
|
831
|
+
if (await this.becameVisible(this.workspaceWizardEnvironmentsHeading, 5000)) {
|
|
832
|
+
// "Create Default Cluster" runs in parallel with this flow
|
|
833
|
+
// (test-setup.spec.ts's mode: 'parallel'), so the cluster this picker
|
|
834
|
+
// lists may still be provisioning -- wait for it a generous 90s rather
|
|
835
|
+
// than sampling once. Fail loudly rather than silently creating the
|
|
836
|
+
// shared workspace without its default environment, which would turn a
|
|
837
|
+
// setup problem here into confusing failures in unrelated specs later.
|
|
838
|
+
const clusterOption = this.page.getByRole('button', {
|
|
839
|
+
name: new RegExp(`^Select ${this.defaultClusterName}`),
|
|
840
|
+
});
|
|
841
|
+
if (await this.becameVisible(clusterOption, 90000)) {
|
|
842
|
+
await clusterOption.click({ timeout: 10000 });
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
throw new Error(`"${this.defaultClusterName}" was not selectable on the Environments step within 90s -- refusing to create the shared workspace without its default environment assigned, since every downstream spec that deploys to it depends on that assignment.`);
|
|
846
|
+
}
|
|
847
|
+
await this.workspaceWizardSubmitButton.click({ timeout: 60000 });
|
|
848
|
+
}
|
|
849
|
+
await this.page.waitForURL(/\/workspaces?\/[^/]+\/projects/, {
|
|
850
|
+
timeout: 120000,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
651
853
|
async clickHomeBreadcrumb() {
|
|
854
|
+
// Camunda Hub has no Modeler home breadcrumb: its home is the Workspaces
|
|
855
|
+
// overview, reached through the header logo link so the SPA keeps its
|
|
856
|
+
// in-memory session (a full navigation re-runs the whole Auth0 hand-off).
|
|
857
|
+
// The link's accessible name is "Camunda Hub home" when the org has
|
|
858
|
+
// workspacesEnabled, else "Camunda Modeler home" (camunda-hub
|
|
859
|
+
// NavShellDs.common.tsx) -- match either.
|
|
860
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
861
|
+
// The href stays literally "/login" until the client's auth state
|
|
862
|
+
// catches up just after login, so clicking it in that window logs the
|
|
863
|
+
// session out instead of navigating home. Exclude that href and wait
|
|
864
|
+
// for the authenticated one, as AppsPage and the 8.9/8.10 flows do.
|
|
865
|
+
const header = this.page.locator('header[data-slot="app-header"]');
|
|
866
|
+
const notLogin = this.page.locator(':not([href$="/login"])');
|
|
867
|
+
await header
|
|
868
|
+
.getByRole('link', { name: 'Camunda Hub home' })
|
|
869
|
+
.and(notLogin)
|
|
870
|
+
.or(header
|
|
871
|
+
.getByRole('link', { name: 'Camunda Modeler home' })
|
|
872
|
+
.and(notLogin))
|
|
873
|
+
.first()
|
|
874
|
+
.click({ timeout: 30000 });
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
652
877
|
// The Console shell's HelpCenter panel ("Your recommendations") renders on
|
|
653
878
|
// top of Web Modeler as a modal dialog that covers the whole page, so the
|
|
654
879
|
// breadcrumb click retries against an element whose pointer events the
|
|
@@ -723,6 +948,13 @@ class ModelerHomePage {
|
|
|
723
948
|
async clickCrossComponentProjectFolder() {
|
|
724
949
|
try {
|
|
725
950
|
await this.clickCrossComponentProjectFolderWithRetry();
|
|
951
|
+
// Opening the workspace lands on its projects LIST, which renders no
|
|
952
|
+
// resource-creation control. Callers go straight on to createForm() /
|
|
953
|
+
// clickCreateProcessApplication(), whose controls only exist inside an
|
|
954
|
+
// open project, so the hop belongs here rather than in
|
|
955
|
+
// createCrossComponentProjectFolder() -- smoke-tests and the rba specs
|
|
956
|
+
// reach the workspace through this method and never through that one.
|
|
957
|
+
await this.enterProjectInsideWorkspace();
|
|
726
958
|
}
|
|
727
959
|
catch (error) {
|
|
728
960
|
if (this.consecutiveSessionRecoveries > 0) {
|
|
@@ -767,6 +999,13 @@ class ModelerHomePage {
|
|
|
767
999
|
// it at all: the migration notice ignores Escape.
|
|
768
1000
|
await this.dismissBlockingDialog();
|
|
769
1001
|
await this.clickMessageBanner();
|
|
1002
|
+
// postAction's reload resets Hub's scope filter to "My workspaces".
|
|
1003
|
+
// Let a failed scope switch propagate like dismissBlockingDialog()
|
|
1004
|
+
// and clickMessageBanner() above -- clickLocatorWithRetry's own
|
|
1005
|
+
// preAction handling already logs and retries it; swallowing it here
|
|
1006
|
+
// would otherwise leave the tile wait running against the wrong
|
|
1007
|
+
// ("My workspaces") scope for its full visibilityTimeout.
|
|
1008
|
+
await this.showAllWorkspaces(5000);
|
|
770
1009
|
},
|
|
771
1010
|
postAction: async () => {
|
|
772
1011
|
// Before anything else: a 5xx from Modeler's own /api/internal/login
|
|
@@ -785,9 +1024,22 @@ class ModelerHomePage {
|
|
|
785
1024
|
// and every remaining attempt just waits out its 60s visibility
|
|
786
1025
|
// timeout before the helper throws. Step back out to home first.
|
|
787
1026
|
const tileVisible = await this.becameVisible(this.crossComponentProjectFolder, 2000);
|
|
788
|
-
if (!tileVisible
|
|
789
|
-
|
|
790
|
-
|
|
1027
|
+
if (!tileVisible) {
|
|
1028
|
+
// Hub renders no `[data-test="breadcrumb-home"]` at all, so probing
|
|
1029
|
+
// that locator was a guaranteed miss there and the reload below just
|
|
1030
|
+
// reloaded whatever page we were stuck on. That matters because
|
|
1031
|
+
// recoverStuckModelerLogin returns to the login route's returnUrl,
|
|
1032
|
+
// which is the workspace's own /projects page -- INSIDE the
|
|
1033
|
+
// workspace, where this tile does not exist by construction. Every
|
|
1034
|
+
// retry then burned its full 60s visibility timeout against a page
|
|
1035
|
+
// that could never show the tile. Go home the way Hub allows (the
|
|
1036
|
+
// header logo link) instead.
|
|
1037
|
+
if (new URL(this.page.url()).hostname.startsWith('hub.')) {
|
|
1038
|
+
await this.clickHomeBreadcrumb();
|
|
1039
|
+
}
|
|
1040
|
+
else if (await this.becameVisible(this.homeBreadcrumb, 5000)) {
|
|
1041
|
+
await this.homeBreadcrumb.click({ timeout: 15000 });
|
|
1042
|
+
}
|
|
791
1043
|
}
|
|
792
1044
|
await this.page.reload();
|
|
793
1045
|
await this.page.waitForLoadState('domcontentloaded');
|
|
@@ -796,11 +1048,84 @@ class ModelerHomePage {
|
|
|
796
1048
|
}
|
|
797
1049
|
async createCrossComponentProjectFolder() {
|
|
798
1050
|
await this.dismissOverlays();
|
|
799
|
-
|
|
1051
|
+
// Before the probe below, not after: under "My workspaces" the shared
|
|
1052
|
+
// folder is invisible to every user who did not create it, so probing
|
|
1053
|
+
// first would send them all down the create path and race duplicate
|
|
1054
|
+
// workspaces of the same name.
|
|
1055
|
+
await this.showAllWorkspaces(10000);
|
|
1056
|
+
// becameVisible, not isVisible: the timeout argument does not make
|
|
1057
|
+
// isVisible() wait, so a slow workspace list read as "missing" and this
|
|
1058
|
+
// parallel flow created another fixed-name workspace -- durable
|
|
1059
|
+
// duplicates that every later run then has to disambiguate.
|
|
1060
|
+
if (await this.becameVisible(this.crossComponentProjectFolder, 10000)) {
|
|
800
1061
|
console.log('Cross Component Project folder already exists. Clicking into it');
|
|
801
1062
|
await this.clickCrossComponentProjectFolder();
|
|
802
1063
|
return;
|
|
803
1064
|
}
|
|
1065
|
+
if (await this.becameVisible(this.createWorkspaceButton, 5000)) {
|
|
1066
|
+
await this.createWorkspaceViaWizard(this.defaultFolderName);
|
|
1067
|
+
await this.enterProjectInsideWorkspace();
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
await this.clickCreateNewProjectButton();
|
|
1071
|
+
await this.enterNewProjectName(this.defaultFolderName);
|
|
1072
|
+
}
|
|
1073
|
+
// Camunda Hub nests Workspace > Project > resources, and opening a workspace
|
|
1074
|
+
// (or finishing its creation wizard) lands on /workspaces/<id>/projects --
|
|
1075
|
+
// the projects LIST, where no resource-creation control exists. Callers go
|
|
1076
|
+
// straight on to clickDiagramTypeDropdown()/clickCreateProcessApplication(),
|
|
1077
|
+
// whose controls only exist inside an open project, so the hop has to happen
|
|
1078
|
+
// here.
|
|
1079
|
+
//
|
|
1080
|
+
// Keyed on the "New project" / "Create project" button, which hub renders
|
|
1081
|
+
// only on the projects list and never inside an open project, so it answers
|
|
1082
|
+
// which view we are on directly -- the project tile is the wrong signal,
|
|
1083
|
+
// since on a slow list render it is still absent, which would read as
|
|
1084
|
+
// "already inside" and skip a hop that was needed. Same shape as
|
|
1085
|
+
// pages/8.10/ModelerHomePage.ts.
|
|
1086
|
+
async enterProjectInsideWorkspace() {
|
|
1087
|
+
if (!(await this.becameVisible(this.createNewProjectButton, 15000))) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
// Scoped to a table row, NOT reusing crossComponentProjectFolder: that
|
|
1091
|
+
// locator matches workspace-level shapes only (an "Open workspace <name>"
|
|
1092
|
+
// button, or a `title` attribute), and the projects list renders neither --
|
|
1093
|
+
// it is a data table whose name cell holds a plain link. Reusing it made
|
|
1094
|
+
// detection fail on every run, so the create branch below ran every time
|
|
1095
|
+
// and the workspace accumulated duplicate same-named projects.
|
|
1096
|
+
//
|
|
1097
|
+
// The row scope also keeps the breadcrumb out of the match: it renders the
|
|
1098
|
+
// workspace's own name, identical to the project's, and clicking it would
|
|
1099
|
+
// navigate back out to the workspace instead of into the project.
|
|
1100
|
+
const projectEntryRow = this.page
|
|
1101
|
+
.getByRole('row')
|
|
1102
|
+
.filter({ hasText: this.defaultFolderName })
|
|
1103
|
+
.first();
|
|
1104
|
+
// The name cell holds no link and no ordinary button: its only button is
|
|
1105
|
+
// `data-slot="data-table-row-primary-action"`, an absolutely-positioned
|
|
1106
|
+
// overlay carrying `pointer-events-none`, so every clickable-element
|
|
1107
|
+
// locator came up empty here and the create branch below ran on every
|
|
1108
|
+
// run, accumulating duplicate same-named projects.
|
|
1109
|
+
//
|
|
1110
|
+
// The row's kebab exposes the project's own id as `data-project-id`, so
|
|
1111
|
+
// read it and navigate straight there -- deterministic, and it sidesteps
|
|
1112
|
+
// both the dead overlay and the ambiguity of several identically named
|
|
1113
|
+
// rows. Fall back to clicking the row itself (it is `cursor-pointer` and
|
|
1114
|
+
// handles the row activation) when the id is not exposed.
|
|
1115
|
+
const rowMenuTrigger = projectEntryRow.locator('[data-test="project-row-menu-trigger"]');
|
|
1116
|
+
if (await this.becameVisible(rowMenuTrigger, 10000)) {
|
|
1117
|
+
const projectId = await rowMenuTrigger.getAttribute('data-project-id');
|
|
1118
|
+
const workspaceId = new URL(this.page.url()).pathname.match(/\/workspaces\/([^/]+)/)?.[1];
|
|
1119
|
+
if (workspaceId) {
|
|
1120
|
+
(0, consoleApiHelpers_1.rememberHubWorkspaceId)(this.page, workspaceId);
|
|
1121
|
+
}
|
|
1122
|
+
if (projectId && workspaceId) {
|
|
1123
|
+
await this.page.goto(new URL(`/workspaces/${workspaceId}/projects/${projectId}`, this.page.url()).toString());
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
await projectEntryRow.click({ timeout: 30000 });
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
804
1129
|
await this.clickCreateNewProjectButton();
|
|
805
1130
|
await this.enterNewProjectName(this.defaultFolderName);
|
|
806
1131
|
}
|
|
@@ -9,6 +9,7 @@ const sleep_1 = require("../../utils/sleep");
|
|
|
9
9
|
const randomSleep_1 = require("../../utils/randomSleep");
|
|
10
10
|
const SettingsPage_1 = require("./SettingsPage");
|
|
11
11
|
const fileUpload_1 = require("../../utils/fileUpload");
|
|
12
|
+
const consoleApiHelpers_1 = require("../../utils/consoleApiHelpers");
|
|
12
13
|
async function forceLogoutIfNeeded(page) {
|
|
13
14
|
const settingsPage = new SettingsPage_1.SettingsPage(page);
|
|
14
15
|
const loginMessage = page.getByText('Log in to continue to Camunda.');
|
|
@@ -37,7 +38,29 @@ exports.forceLogoutIfNeeded = forceLogoutIfNeeded;
|
|
|
37
38
|
// pre-flight stagger below, so a worker only pays it once per process
|
|
38
39
|
// lifetime rather than on every call -- see the comment at the call site.
|
|
39
40
|
const staggeredWorkerTimeouts = new Set();
|
|
41
|
+
const onboardingTourSuppressed = new WeakSet();
|
|
42
|
+
// Mirrors camunda-hub@452978f: pre-seed the onboarding tour's "seen" localStorage flag
|
|
43
|
+
// (SEEN_KEY_PREFIX in .../tour-dialog/use-onboarding-seen.ts) so it never auto-opens. The
|
|
44
|
+
// "Welcome to Camunda Hub" tour mounts app-wide -- Console included, not just Modeler -- and
|
|
45
|
+
// hides the post-login banner link from the accessibility tree until dismissed, which times
|
|
46
|
+
// out assertTestUsesCorrectOrganization's banner check. addInitScript runs before any page
|
|
47
|
+
// script on every navigation/reload, so there's no login-response race to lose; the flag is
|
|
48
|
+
// matched by prefix since the user id isn't known ahead of login.
|
|
49
|
+
async function suppressOnboardingTour(page) {
|
|
50
|
+
if (onboardingTourSuppressed.has(page)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
onboardingTourSuppressed.add(page);
|
|
54
|
+
await page.addInitScript((prefixPattern) => {
|
|
55
|
+
const pattern = new RegExp(prefixPattern);
|
|
56
|
+
const originalGetItem = Storage.prototype.getItem;
|
|
57
|
+
Storage.prototype.getItem = function (key) {
|
|
58
|
+
return pattern.test(key) ? 'true' : originalGetItem.call(this, key);
|
|
59
|
+
};
|
|
60
|
+
}, '^hub\\.onboarding\\..*\\.seen\\.');
|
|
61
|
+
}
|
|
40
62
|
async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
|
|
63
|
+
await suppressOnboardingTour(page);
|
|
41
64
|
let lastError;
|
|
42
65
|
// Stagger the first navigation across parallel workers. Without this, every
|
|
43
66
|
// worker's initial page.goto('/') fires within the same second -- with 18+
|
|
@@ -56,6 +79,11 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5
|
|
|
56
79
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
57
80
|
try {
|
|
58
81
|
await page.context().clearCookies();
|
|
82
|
+
// The Hub bearer-token cache in consoleApiHelpers.ts is keyed by Page
|
|
83
|
+
// for the tab's whole lifetime; without clearing it here alongside
|
|
84
|
+
// cookies/storage, a flow that logs a different user into the same
|
|
85
|
+
// page could have getUserToken() return the PREVIOUS user's token.
|
|
86
|
+
(0, consoleApiHelpers_1.resetHubBearerToken)(page);
|
|
59
87
|
await page.goto('about:blank');
|
|
60
88
|
await page
|
|
61
89
|
.evaluate(() => {
|
|
@@ -407,8 +435,12 @@ async function assertTestUsesCorrectOrganization(page) {
|
|
|
407
435
|
const homePage = new HomePage_1.HomePage(page);
|
|
408
436
|
if (page.url().includes('?code=') || page.url().includes('&state=')) {
|
|
409
437
|
console.log('OAuth callback detected, waiting for redirect...');
|
|
438
|
+
// Camunda Hub (which replaced Console on SaaS) only exchanges the code
|
|
439
|
+
// once its SPA has booted, and on CI runners that boot alone was measured
|
|
440
|
+
// at ~45s past /login-callback -- a 30s wait aborted the exchange on every
|
|
441
|
+
// attempt. Ported from the already-verified 8.9/8.10 fix.
|
|
410
442
|
await page.waitForURL((url) => !url.toString().includes('?code='), {
|
|
411
|
-
timeout:
|
|
443
|
+
timeout: 120000,
|
|
412
444
|
});
|
|
413
445
|
await (0, sleep_1.sleep)(5000);
|
|
414
446
|
}
|
|
@@ -418,7 +450,20 @@ async function assertTestUsesCorrectOrganization(page) {
|
|
|
418
450
|
await (0, test_1.expect)(homePage.modelerBanner).toBeVisible({ timeout: 30000 });
|
|
419
451
|
}
|
|
420
452
|
else {
|
|
421
|
-
await (0, test_1.expect)(homePage.consoleBanner).toBeVisible({
|
|
453
|
+
await (0, test_1.expect)(homePage.consoleBanner.or(homePage.hubHeader)).toBeVisible({
|
|
454
|
+
timeout: 60000,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
// Camunda Hub replaces Console on SaaS (8.10 release notes), and the Hub
|
|
458
|
+
// shell has neither the "Camunda logo Console" link nor the "Open
|
|
459
|
+
// Organizations" switcher, and lands on /workspaces without the org id in
|
|
460
|
+
// the URL. Its sidebar scopes the Clusters/Organization links to the active
|
|
461
|
+
// org, so assert the expected org id there instead.
|
|
462
|
+
if (await homePage.hubHeader.isVisible()) {
|
|
463
|
+
await (0, test_1.expect)(homePage.hubSidebar
|
|
464
|
+
.locator(`a[href*="/org/${process.env.ORG_ID}/"]`)
|
|
465
|
+
.first()).toBeVisible({ timeout: 180000 });
|
|
466
|
+
return;
|
|
422
467
|
}
|
|
423
468
|
await (0, test_1.expect)(homePage.openOrganizationButton).toBeVisible({ timeout: 180000 });
|
|
424
469
|
try {
|
|
@@ -27,9 +27,13 @@ _8_7_1.test.describe('RBA Enabled User Flows Test', () => {
|
|
|
27
27
|
'RBA On User Flow - Permission for One Process': mainUser,
|
|
28
28
|
'RBA On User Flow - Permission for All Processes': allProcessesUser,
|
|
29
29
|
};
|
|
30
|
-
_8_7_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage }, testInfo) => {
|
|
30
|
+
_8_7_1.test.beforeEach(async ({ page, homePage, clusterPage, loginPage, appsPage }, testInfo) => {
|
|
31
31
|
const currentUser = usersByTitle[testInfo.title] ?? mainUser;
|
|
32
32
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, currentUser, (testInfo.workerIndex + 1) * 1000);
|
|
33
|
+
// Whatever route login lands on may be sidebarMode 'workspace' (no
|
|
34
|
+
// Environments/Clusters item at all) rather than 'default' -- the same
|
|
35
|
+
// gap clickConsoleLink() now closes by navigating to '/workspaces'.
|
|
36
|
+
await appsPage.clickConsoleLink();
|
|
33
37
|
await (0, test_1.expect)(homePage.clusterTab).toBeVisible({ timeout: 120000 });
|
|
34
38
|
await homePage.clickClusters();
|
|
35
39
|
(0, test_1.expect)(clusterNames[testInfo.title]).toBeTruthy();
|
|
@@ -135,10 +135,13 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
|
|
|
135
135
|
// clickCamundaApps() is a no-op on Hub (no global app-switcher panel to
|
|
136
136
|
// open -- see its own comment), so modelerLink has nothing to assert
|
|
137
137
|
// there: Hub's authenticated shell already proves Modeler access by
|
|
138
|
-
// having been reached at all. consoleLink
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
138
|
+
// having been reached at all. consoleLink used to be unconditional on
|
|
139
|
+
// Hub, but camunda-hub commit cb1f9eb ("restructure SaaS Console
|
|
140
|
+
// navigation") gates it to Owner/Admin/DevOps org roles once the
|
|
141
|
+
// environmentsEnabled flag is on -- Analyst is deliberately excluded
|
|
142
|
+
// (organization-store.saas.ts CLUSTER_OPERATOR_ROLE_IDS), so the
|
|
143
|
+
// just-downgraded-to-Analyst user here should no longer see it either.
|
|
144
|
+
await (0, test_1.expect)(appsPage.consoleLink).not.toBeVisible();
|
|
142
145
|
await appsPage.assertOperatePresent(clusterName, false);
|
|
143
146
|
await appsPage.assertTasklistPresent(clusterName, false);
|
|
144
147
|
await appsPage.clickModeler();
|