@camunda/e2e-test-suite 0.0.1287 → 0.0.1288

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.
Files changed (33) hide show
  1. package/dist/pages/8.9/AppsPage.d.ts +9 -3
  2. package/dist/pages/8.9/AppsPage.js +262 -47
  3. package/dist/pages/8.9/ClusterDetailsPage.d.ts +5 -0
  4. package/dist/pages/8.9/ClusterDetailsPage.js +92 -9
  5. package/dist/pages/8.9/ClusterSecretsPage.js +5 -1
  6. package/dist/pages/8.9/ConnectorMarketplacePage.d.ts +1 -0
  7. package/dist/pages/8.9/ConnectorMarketplacePage.js +46 -3
  8. package/dist/pages/8.9/ConsoleOrganizationPage.d.ts +3 -2
  9. package/dist/pages/8.9/ConsoleOrganizationPage.js +47 -22
  10. package/dist/pages/8.9/FormJsPage.d.ts +4 -0
  11. package/dist/pages/8.9/FormJsPage.js +53 -19
  12. package/dist/pages/8.9/IdpCreatePage.js +5 -0
  13. package/dist/pages/8.9/ModelerCreatePage.d.ts +5 -0
  14. package/dist/pages/8.9/ModelerCreatePage.js +153 -21
  15. package/dist/pages/8.9/ModelerHomePage.d.ts +1 -0
  16. package/dist/pages/8.9/ModelerHomePage.js +29 -4
  17. package/dist/pages/8.9/PlayPage.js +11 -1
  18. package/dist/pages/8.9/ProcessApplicationPage.js +48 -4
  19. package/dist/pages/8.9/UtilitiesPage.js +57 -17
  20. package/dist/pages/8.9/WorkspaceMembersPage.d.ts +21 -0
  21. package/dist/pages/8.9/WorkspaceMembersPage.js +264 -0
  22. package/dist/tests/8.9/access-user-role-management.spec.js +111 -26
  23. package/dist/tests/8.9/console-user-flows.spec.js +4 -4
  24. package/dist/tests/8.9/idp-user-flows.spec.js +2 -1
  25. package/dist/tests/8.9/rba-enabled-v1-user-flows.spec.js +13 -4
  26. package/dist/tests/8.9/rba-enabled-v2-user-flows.spec.js +13 -4
  27. package/dist/tests/8.9/test-setup.spec.js +57 -4
  28. package/dist/tests/8.9/utr-enabled-user-flows.spec.js +18 -0
  29. package/dist/tests/8.9/web-modeler-user-flows.spec.js +6 -2
  30. package/dist/utils/consoleApiHelpers.d.ts +2 -0
  31. package/dist/utils/consoleApiHelpers.js +50 -4
  32. package/dist/utils/resetSession.js +6 -1
  33. package/package.json +1 -1
@@ -301,7 +301,11 @@ class ClusterSecretsPage {
301
301
  timeout: 60000,
302
302
  });
303
303
  await this.page.reload();
304
- await (0, sleep_1.sleep)(5000);
304
+ // Hub keeps the selected cluster tab out of the URL, so the reload lands
305
+ // on Overview, where assertSecretsOrCreate finds no search box and tries
306
+ // to create every secret. Re-select the tab, as pages/8.8 does (45eae7b).
307
+ await this.clusterSecretTab.first().click({ timeout: 60000 });
308
+ await (0, test_1.expect)(this.createNewSecretButton).toBeVisible({ timeout: 60000 });
305
309
  }
306
310
  }
307
311
  exports.ClusterSecretsPage = ClusterSecretsPage;
@@ -8,6 +8,7 @@ declare class ConnectorMarketplacePage {
8
8
  readonly replaceResourceButton: Locator;
9
9
  readonly addToProjectButton: Locator;
10
10
  readonly cancelButton: Locator;
11
+ readonly browseAllMarketplaceDetailsButton: Locator;
11
12
  constructor(page: Page);
12
13
  clickSearchForConnectorTextbox(): Promise<void>;
13
14
  fillSearchForConnectorTextbox(connectorName: string): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectorMarketplacePage = void 0;
4
+ const test_1 = require("@playwright/test");
4
5
  class ConnectorMarketplacePage {
5
6
  page;
6
7
  searchForConnectorTextbox;
@@ -10,9 +11,25 @@ class ConnectorMarketplacePage {
10
11
  replaceResourceButton;
11
12
  addToProjectButton;
12
13
  cancelButton;
14
+ browseAllMarketplaceDetailsButton;
13
15
  constructor(page) {
14
16
  this.page = page;
15
- this.searchForConnectorTextbox = page.getByPlaceholder('Search for a connector');
17
+ // The legacy marketplace modal, or camunda-hub's "Browse all" dialog
18
+ // (BrowseAllDialog.tsx), which lists Marketplace connectors alongside the
19
+ // diagram's own elements and reusable assets. Same shape as pages/8.8.
20
+ this.searchForConnectorTextbox = page
21
+ .getByPlaceholder('Search for a connector')
22
+ .or(page.locator('#browse-all-search'))
23
+ .or(page.getByRole('searchbox', {
24
+ name: 'Search elements and reusable assets',
25
+ }))
26
+ .first();
27
+ // A Browse all Marketplace card opens its review step ("Add to project")
28
+ // through this button; self-hosted connectors only link to setup docs.
29
+ this.browseAllMarketplaceDetailsButton = page
30
+ .getByRole('list', { name: 'Marketplace connectors' })
31
+ .getByRole('button', { name: /^View details for .+ from Marketplace$/ })
32
+ .first();
16
33
  this.downloadToProjectButton = page
17
34
  .getByRole('button', { name: 'Download to project' })
18
35
  .first();
@@ -45,7 +62,26 @@ class ConnectorMarketplacePage {
45
62
  await this.replaceResourceButton.click({ timeout: 60000 });
46
63
  }
47
64
  async downloadConnectorToProject() {
48
- await this.clickDownloadToProjectButton();
65
+ // Browse all has no "Download to project": a result is a card that opens
66
+ // its own review step, which is where the import action lives. Wait for
67
+ // whichever entry point this shell renders before deciding.
68
+ await (0, test_1.expect)(this.downloadToProjectButton
69
+ .or(this.browseAllMarketplaceDetailsButton)
70
+ .first()).toBeVisible({ timeout: 60000 });
71
+ if (await this.downloadToProjectButton.isVisible()) {
72
+ await this.clickDownloadToProjectButton();
73
+ }
74
+ else {
75
+ await this.browseAllMarketplaceDetailsButton.click({ timeout: 30000 });
76
+ }
77
+ // The Browse all review step fetches the connector's details before it
78
+ // renders "Add to project"; let an outcome render before the 20s race.
79
+ await this.addToProjectButton
80
+ .or(this.replaceResourceButton)
81
+ .or(this.cancelButton)
82
+ .first()
83
+ .waitFor({ state: 'visible', timeout: 60000 })
84
+ .catch(() => { });
49
85
  let imported = false;
50
86
  try {
51
87
  await Promise.race([
@@ -67,7 +103,14 @@ class ConnectorMarketplacePage {
67
103
  await this.snackbar
68
104
  .waitFor({ state: 'visible', timeout: 60000 })
69
105
  .catch(() => { });
70
- if (await this.closeButton.isVisible({ timeout: 5000 }).catch(() => false)) {
106
+ // Only close the marketplace if it is still open: a bare "Close"
107
+ // button otherwise matches the properties panel's own close (x) and
108
+ // collapses it, hiding the connector's "Input" tab (see pages/8.8).
109
+ const marketplaceStillOpen = await this.searchForConnectorTextbox
110
+ .isVisible()
111
+ .catch(() => false);
112
+ if (marketplaceStillOpen &&
113
+ (await this.closeButton.isVisible({ timeout: 5000 }).catch(() => false))) {
71
114
  await this.closeButton.click({ timeout: 10000 });
72
115
  }
73
116
  }
@@ -45,8 +45,9 @@ declare class ConsoleOrganizationPage {
45
45
  selectAnalystRole(username: string): Promise<void>;
46
46
  selectModelerRole(username: string): Promise<void>;
47
47
  selectAdminRole(username: string): Promise<void>;
48
- clickOptionsButton(): Promise<void>;
49
- clickEditUserMenuItem(): Promise<void>;
48
+ private userOptionsButton;
49
+ clickOptionsButton(username: string): Promise<void>;
50
+ clickEditUserMenuItem(username: string): Promise<void>;
50
51
  checkModelerCheckbox(): Promise<void>;
51
52
  checkAdminCheckbox(): Promise<void>;
52
53
  checkAnalystCheckbox(): Promise<void>;
@@ -64,18 +64,22 @@ class ConsoleOrganizationPage {
64
64
  // (confirmed live: the name-scoped version timed out the same as the
65
65
  // original role="group" one).
66
66
  const openDialog = page.getByRole('dialog');
67
- this.modelerCheckbox = openDialog.getByRole('checkbox', {
68
- name: 'Modeler',
69
- exact: true,
70
- });
71
- this.adminCheckbox = openDialog.getByRole('checkbox', {
72
- name: 'Admin',
73
- exact: true,
74
- });
75
- this.analystCheckbox = openDialog.getByRole('checkbox', {
76
- name: 'Analyst',
77
- exact: true,
78
- });
67
+ // Hub renamed the platform roles, and the "Edit user" dialog states each
68
+ // mapping in the role's own description (8.9 nightly trace DOM, checkbox
69
+ // ids c4-management-change-org-roles-*): "Member ... Formerly the Modeler
70
+ // role.", "Organization Admin ... Formerly the Admin role.", "DevOps ...
71
+ // Formerly the Operations Engineer role."; Analyst is unchanged. There is
72
+ // no "Modeler" checkbox at all any more, which is why the exact-name
73
+ // lookup resolved to nothing. Anchored regex rather than `exact`, since
74
+ // the accessible name can carry the description after the label. Same
75
+ // locators as pages/8.8.
76
+ const roleCheckbox = (current, legacy) => openDialog
77
+ .getByRole('checkbox', { name: current })
78
+ .or(openDialog.getByRole('checkbox', { name: legacy, exact: true }))
79
+ .first();
80
+ this.modelerCheckbox = roleCheckbox(/^Member\b/, 'Modeler');
81
+ this.adminCheckbox = roleCheckbox(/^Organization Admin\b/, 'Admin');
82
+ this.analystCheckbox = roleCheckbox(/^Analyst\b/, 'Analyst');
79
83
  this.confirmButton = page.getByRole('button', { name: 'Confirm' });
80
84
  this.settingsTab = page.getByRole('tab', { name: 'Settings' });
81
85
  this.optInButton = page.getByRole('button', { name: 'Opt-in', exact: true });
@@ -184,8 +188,8 @@ class ConsoleOrganizationPage {
184
188
  }
185
189
  async selectAnalystRole(username) {
186
190
  await this.filterTable([username]);
187
- await this.clickOptionsButton();
188
- await this.clickEditUserMenuItem();
191
+ await this.clickOptionsButton(username);
192
+ await this.clickEditUserMenuItem(username);
189
193
  await this.checkAnalystCheckbox();
190
194
  await this.uncheckModelerCheckbox();
191
195
  await this.uncheckAdminCheckbox();
@@ -196,8 +200,8 @@ class ConsoleOrganizationPage {
196
200
  }
197
201
  async selectModelerRole(username) {
198
202
  await this.filterTable([username]);
199
- await this.clickOptionsButton();
200
- await this.clickEditUserMenuItem();
203
+ await this.clickOptionsButton(username);
204
+ await this.clickEditUserMenuItem(username);
201
205
  await this.uncheckAnalystCheckbox();
202
206
  await this.uncheckAdminCheckbox();
203
207
  await this.checkModelerCheckbox();
@@ -208,8 +212,8 @@ class ConsoleOrganizationPage {
208
212
  }
209
213
  async selectAdminRole(username) {
210
214
  await this.filterTable([username]);
211
- await this.clickOptionsButton();
212
- await this.clickEditUserMenuItem();
215
+ await this.clickOptionsButton(username);
216
+ await this.clickEditUserMenuItem(username);
213
217
  await this.uncheckAnalystCheckbox();
214
218
  await this.uncheckModelerCheckbox();
215
219
  await this.checkAdminCheckbox();
@@ -218,11 +222,32 @@ class ConsoleOrganizationPage {
218
222
  timeout: 90000,
219
223
  });
220
224
  }
221
- async clickOptionsButton() {
222
- await this.optionsButton.click({ timeout: 60000 });
225
+ // Scoped to the user's own row: the table can still list other users when
226
+ // the menu opens, and a page-wide `.first()` would edit their roles.
227
+ userOptionsButton(username) {
228
+ const row = this.page.getByRole('row').filter({ hasText: username });
229
+ return row
230
+ .getByRole('button', { name: 'Options' })
231
+ .or(row.getByRole('button', { name: 'Row actions' }))
232
+ .first();
223
233
  }
224
- async clickEditUserMenuItem() {
225
- await this.editUserMenuItem.click({ timeout: 60000 });
234
+ async clickOptionsButton(username) {
235
+ await this.userOptionsButton(username).click({ timeout: 60000 });
236
+ }
237
+ // The row menu can close under the click when the Users table re-renders
238
+ // (nightly: "element was detached from the DOM", after which the item never
239
+ // returns), so reopen it and retry rather than wait on a closed menu.
240
+ async clickEditUserMenuItem(username) {
241
+ await (0, test_1.expect)(async () => {
242
+ const menuOpen = await this.editUserMenuItem
243
+ .waitFor({ state: 'visible', timeout: 5000 })
244
+ .then(() => true)
245
+ .catch(() => false);
246
+ if (!menuOpen) {
247
+ await this.userOptionsButton(username).click({ timeout: 10000 });
248
+ }
249
+ await this.editUserMenuItem.click({ timeout: 10000 });
250
+ }).toPass({ timeout: 90000, intervals: [1000, 2000, 5000] });
226
251
  }
227
252
  async checkModelerCheckbox() {
228
253
  if (!(await this.modelerCheckbox.isChecked({ timeout: 60000 }))) {
@@ -3,6 +3,9 @@ declare class FormJsPage {
3
3
  private page;
4
4
  private processApplicationPage;
5
5
  readonly aiFormGeneratorButton: Locator;
6
+ readonly copilotCtaButton: Locator;
7
+ readonly copilotChatInput: Locator;
8
+ readonly copilotSendButton: Locator;
6
9
  readonly formRequestInput: Locator;
7
10
  readonly generateFormButton: Locator;
8
11
  readonly formEditor: Locator;
@@ -22,6 +25,7 @@ declare class FormJsPage {
22
25
  readonly deploySuccessNotification: Locator;
23
26
  constructor(page: Page);
24
27
  generateAIForm(request?: string): Promise<void>;
28
+ private generateFormWithCopilot;
25
29
  clickAIFormGeneratorButton(): Promise<void>;
26
30
  clickFormRequestInput(): Promise<void>;
27
31
  fillFormRequestInput(formRequest: string): Promise<void>;
@@ -5,10 +5,14 @@ const test_1 = require("@playwright/test");
5
5
  const sleep_1 = require("../../utils/sleep");
6
6
  const ProcessApplicationPage_1 = require("./ProcessApplicationPage");
7
7
  const modelerCluster_1 = require("../../utils/modelerCluster");
8
+ const hubDeployDialog_1 = require("../../utils/hubDeployDialog");
8
9
  class FormJsPage {
9
10
  page;
10
11
  processApplicationPage;
11
12
  aiFormGeneratorButton;
13
+ copilotCtaButton;
14
+ copilotChatInput;
15
+ copilotSendButton;
12
16
  formRequestInput;
13
17
  generateFormButton;
14
18
  formEditor;
@@ -32,6 +36,16 @@ class FormJsPage {
32
36
  this.aiFormGeneratorButton = page.getByRole('button', {
33
37
  name: 'AI Form Generator',
34
38
  });
39
+ // camunda-hub #21398 retired the form page's AI Form Generator in favour
40
+ // of the Camunda Copilot sidecar: with One Copilot on, the empty form
41
+ // offers "Try Camunda Copilot" instead (FormDs.tsx / CopilotCTA.tsx), and
42
+ // the sidecar's chat input and send button come from
43
+ // camunda-copilot-client's ChatInput. Same shape as pages/8.7.
44
+ this.copilotCtaButton = page.getByRole('button', {
45
+ name: 'Try Camunda Copilot',
46
+ });
47
+ this.copilotChatInput = page.getByPlaceholder('Type a message...');
48
+ this.copilotSendButton = page.getByRole('button', { name: 'Send message' });
35
49
  this.formRequestInput = page.getByPlaceholder('A form for an american standard loan request');
36
50
  this.generateFormButton = page.getByRole('button', { name: 'Generate form' });
37
51
  this.formEditor = page.getByLabel('Form Definition');
@@ -57,21 +71,40 @@ class FormJsPage {
57
71
  // Inside a process application hub titles this modal for the application
58
72
  // rather than for the form, so 'Deploy form' never matches and the dialog
59
73
  // reads as absent. pages/SM-8.10 filters on 'Deploy' for the same reason.
74
+ // Camunda Hub's design-system shell renders no Carbon markup: the nightly
75
+ // trace shows the "Deploy project" dialog open with no `cds--*` classes,
76
+ // so the Carbon container selector matched nothing and the dialog read as
77
+ // absent. Same locator pages/8.8 uses.
60
78
  this.dialog = page
79
+ .getByRole('dialog')
80
+ .filter({ hasText: 'Deploy' })
81
+ .or(page
61
82
  .locator('.cds--modal-container[role="dialog"]')
62
- .filter({ hasText: 'Deploy' });
83
+ .filter({ hasText: 'Deploy' }))
84
+ .first();
63
85
  this.deploySubButton = this.dialog.getByRole('button', {
64
86
  name: 'Deploy',
65
87
  exact: true,
66
88
  });
89
+ // Same Carbon-to-design-system move as the dialog above: Hub's success
90
+ // toast is not a `cds--toast-*` node. With the scope narrowed to "Only
91
+ // this resource" Hub still titles it "Form deployed!" (camunda-hub
92
+ // deploy/get-success-message.ts), so match that text anywhere.
67
93
  this.deploySuccessNotification = page
94
+ .getByText('Form deployed!')
95
+ .or(page
68
96
  .locator('.cds--toast-notification--success')
69
- .filter({ hasText: 'Form deployed!' });
97
+ .filter({ hasText: 'Form deployed!' }))
98
+ .first();
70
99
  }
71
100
  async generateAIForm(request = 'Create a form with the following fields: 1. A text field with the label "Full Name" 2. A number with the label "Count" 3. A date input with the label "Date of birth"4. A Checkbox with the label "Agree"') {
72
- await (0, test_1.expect)(this.aiFormGeneratorButton).toBeVisible({
101
+ await (0, test_1.expect)(this.aiFormGeneratorButton.or(this.copilotCtaButton).first()).toBeVisible({
73
102
  timeout: 90000,
74
103
  });
104
+ if (!(await this.aiFormGeneratorButton.isVisible())) {
105
+ await this.generateFormWithCopilot(request);
106
+ return;
107
+ }
75
108
  await this.clickAIFormGeneratorButton();
76
109
  await this.clickFormRequestInput();
77
110
  await this.fillFormRequestInput(request);
@@ -80,6 +113,19 @@ class FormJsPage {
80
113
  await (0, test_1.expect)(this.page.getByText('Successfully generated smart form.')).toBeVisible({ timeout: 300000 });
81
114
  await (0, sleep_1.sleep)(60000);
82
115
  }
116
+ async generateFormWithCopilot(request) {
117
+ await this.copilotCtaButton.click({ timeout: 30000 });
118
+ await (0, test_1.expect)(this.copilotChatInput).toBeVisible({ timeout: 60000 });
119
+ await this.copilotChatInput.fill(request);
120
+ await this.copilotSendButton.click({ timeout: 30000 });
121
+ // Copilot edits the open form in place; the generated form is ready once
122
+ // every requested field shows up on the canvas.
123
+ for (const label of ['Full Name', 'Count', 'Date of birth', 'Agree']) {
124
+ await (0, test_1.expect)(this.formEditor.getByText(label).first()).toBeVisible({
125
+ timeout: 300000,
126
+ });
127
+ }
128
+ }
83
129
  async clickAIFormGeneratorButton() {
84
130
  await this.aiFormGeneratorButton.click();
85
131
  }
@@ -219,22 +265,10 @@ class FormJsPage {
219
265
  // silently turning it into a process-application deploy. The combobox only
220
266
  // exists inside a container, hence the boolean probe.
221
267
  async selectOnlyThisResource() {
222
- const scopeSelect = this.dialog.getByRole('combobox', {
223
- name: 'Resources to deploy',
224
- });
225
- const hasScopeSelect = await scopeSelect
226
- .waitFor({ state: 'visible', timeout: 10000 })
227
- .then(() => true)
228
- .catch(() => false);
229
- if (!hasScopeSelect) {
230
- return;
231
- }
232
- await scopeSelect.click();
233
- await this.dialog
234
- .getByRole('option', { name: 'Only this resource' })
235
- .or(this.dialog.getByText('Only this resource', { exact: true }))
236
- .first()
237
- .click({ timeout: 30000 });
268
+ // Hub's scope select is named by its value ("All resources"), not by the
269
+ // "Resources to deploy" label, so the name lookup this used matched
270
+ // nothing. The shared helper addresses it by id, as pages/8.8 does.
271
+ await (0, hubDeployDialog_1.selectOnlyThisResourceInDialog)(this.page, this.dialog);
238
272
  }
239
273
  async deployForm(clusterName) {
240
274
  await this.processApplicationPage.ensureStageCluster(clusterName);
@@ -74,8 +74,13 @@ class IdpCreatePage {
74
74
  this.idpExtractionDescriptionField = page
75
75
  .getByRole('region', { name: 'Create extraction template' })
76
76
  .locator('#idp-project-description');
77
+ // Hub now renders "Create classification template" as a design-system
78
+ // dialog (role="dialog", data-slot="dialog-content") rather than a
79
+ // labelled region -- the 8.9 nightly trace shows the field inside it, so
80
+ // the region-scoped lookup timed out. Accept either container.
77
81
  this.idpClassificationDescriptionField = page
78
82
  .getByRole('region', { name: 'Create classification template' })
83
+ .or(page.getByRole('dialog', { name: 'Create classification template' }))
79
84
  .locator('#idp-project-description');
80
85
  this.createIdpExtractationTemplateButton = page.getByRole('button', {
81
86
  name: 'Create template',
@@ -105,6 +105,8 @@ declare class ModelerCreatePage {
105
105
  readonly eighthPlacedElement: Locator;
106
106
  readonly deployAndRunMainButton: Locator;
107
107
  readonly deployAndRunSubButton: Locator;
108
+ readonly deployRunActions: Locator;
109
+ readonly deployAndRunMenuItem: Locator;
108
110
  readonly dialog: Locator;
109
111
  readonly backupDeployButton: Locator;
110
112
  readonly globalConnectToolButton: Locator;
@@ -143,6 +145,7 @@ declare class ModelerCreatePage {
143
145
  clickAdditionalActionsButton(): Promise<void>;
144
146
  clickDeployMenuItem(): Promise<void>;
145
147
  deployDiagram(clusterName: string): Promise<void>;
148
+ assertDeployAndRunNotPermitted(): Promise<void>;
146
149
  clickStartInstanceMainButton(): Promise<void>;
147
150
  clickStartInstanceSubButton(): Promise<void>;
148
151
  runProcessInstance(clusterName: string, variables?: string, formVariables?: {
@@ -216,9 +219,11 @@ declare class ModelerCreatePage {
216
219
  fillJobTypeInput(name: string): Promise<void>;
217
220
  private suppressTestModeAnnouncement;
218
221
  switchToPlay(): Promise<void>;
222
+ private selectPlayDeploySingleResource;
219
223
  completePlayConfiguration(clusterName: string): Promise<void>;
220
224
  embedForm(formName: string): Promise<void>;
221
225
  fillStartEventVariablesForDocHandling(zeebeUrl: string, zeebeClientId: string, zeebeClientSecret: string): Promise<void>;
226
+ private openOutputMappingEntry;
222
227
  fillZeebeClientId(clientId: string): Promise<void>;
223
228
  fillZeebeSecretValue(zeebeSecret: string): Promise<void>;
224
229
  fillZeebeRestValue(zeebeUrl: string): Promise<void>;