@camunda/e2e-test-suite 0.0.1009 → 0.0.1011

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.
@@ -56,8 +56,10 @@ class NavigationPage {
56
56
  });
57
57
  // TODO: Drop support for Identity once we fully rename it to Admin
58
58
  this.identityPageBanner = page
59
+ .getByRole('link', { name: 'Camunda Admin', exact: true })
60
+ .or(page
59
61
  .locator('a')
60
- .filter({ hasText: /^Camunda logo\s*(Identity|Admin)$/ })
62
+ .filter({ hasText: /^Camunda logo\s*(Identity|Admin)$/ }))
61
63
  .first();
62
64
  this.managementIdentityPageBanner = page
63
65
  .locator('a')
@@ -19,34 +19,57 @@ class OCIdentityHomePage {
19
19
  constructor(page) {
20
20
  this.page = page;
21
21
  this.adminBanner = page
22
- .locator('a')
23
- .filter({ hasText: /^Camunda logo\s*Admin$/ })
22
+ .getByRole('link', { name: 'Camunda Admin', exact: true })
23
+ .or(page.locator('a').filter({ hasText: /^Camunda logo\s*Admin$/ }))
24
24
  .first();
25
25
  this.groupsTab = page
26
+ .getByRole('navigation', { name: 'Main navigation' })
27
+ .getByRole('link', { name: 'Groups', exact: true })
28
+ .or(page
26
29
  .getByRole('banner')
27
30
  .locator('a')
28
- .filter({ hasText: /^Groups$/ });
31
+ .filter({ hasText: /^Groups$/ }))
32
+ .first();
29
33
  this.mappingRulesTab = page
34
+ .getByRole('navigation', { name: 'Main navigation' })
35
+ .getByRole('link', { name: 'Mapping rules', exact: true })
36
+ .or(page
30
37
  .getByRole('banner')
31
38
  .locator('a')
32
- .filter({ hasText: /^Mapping rules$/ })
39
+ .filter({ hasText: /^Mapping rules$/ }))
33
40
  .first();
34
41
  this.tenantsTab = page
42
+ .getByRole('navigation', { name: 'Main navigation' })
43
+ .getByRole('link', { name: 'Tenants', exact: true })
44
+ .or(page
35
45
  .getByRole('banner')
36
46
  .locator('a')
37
- .filter({ hasText: /^Tenants$/ });
47
+ .filter({ hasText: /^Tenants$/ }))
48
+ .first();
38
49
  this.rolesTab = page
50
+ .getByRole('navigation', { name: 'Main navigation' })
51
+ .getByRole('link', { name: 'Roles', exact: true })
52
+ .or(page
39
53
  .getByRole('banner')
40
54
  .locator('a')
41
- .filter({ hasText: 'Roles' });
55
+ .filter({ hasText: /^Roles$/ }))
56
+ .first();
42
57
  this.authorizationsTab = page
58
+ .getByRole('navigation', { name: 'Main navigation' })
59
+ .getByRole('link', { name: 'Authorizations', exact: true })
60
+ .or(page
43
61
  .getByRole('banner')
44
62
  .locator('a')
45
- .filter({ hasText: /^Authorizations$/ });
63
+ .filter({ hasText: /^Authorizations$/ }))
64
+ .first();
46
65
  this.clusterVariablesTab = page
66
+ .getByRole('navigation', { name: 'Main navigation' })
67
+ .getByRole('link', { name: 'Cluster variables', exact: true })
68
+ .or(page
47
69
  .getByRole('banner')
48
70
  .locator('a')
49
- .filter({ hasText: /Cluster variables/i });
71
+ .filter({ hasText: /Cluster variables/i }))
72
+ .first();
50
73
  this.licenseKeyTagNonProduction = page
51
74
  .getByText('Non-production license')
52
75
  .first();
@@ -87,9 +87,12 @@ class OCIdentityRolesPage {
87
87
  });
88
88
  this.adminRole = page.getByRole('cell', { name: 'admin', exact: true });
89
89
  this.rolesNavTab = page
90
+ .getByRole('navigation', { name: 'Main navigation' })
91
+ .getByRole('link', { name: 'Roles', exact: true })
92
+ .or(page
90
93
  .getByRole('banner')
91
94
  .locator('a')
92
- .filter({ hasText: /^Roles$/ })
95
+ .filter({ hasText: /^Roles$/ }))
93
96
  .first();
94
97
  this.assignUserButton = page.getByRole('button', { name: 'Assign user' });
95
98
  this.usernameTextBox = page.getByRole('textbox', { name: 'Username' });
@@ -0,0 +1,23 @@
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 titleBar: Locator;
13
+ readonly createNewDropdown: Locator;
14
+ readonly inlineNameInput: Locator;
15
+ constructor(page: Page);
16
+ private pageReady;
17
+ waitForProcessApplication(timeout?: number): Promise<void>;
18
+ clickCreateProcessApplicationButton(): Promise<void>;
19
+ clickCreateProcessApplicationOption(): Promise<void>;
20
+ nameNewProcessApplication(name: string): Promise<void>;
21
+ openByName(name: string): Promise<void>;
22
+ }
23
+ export { ProcessApplicationPage };
@@ -0,0 +1,132 @@
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
+ // Since camunda/camunda-hub#25831 a process application is a first-class
7
+ // container rather than a folder, and a workspace root ("project" in today's
8
+ // UI) holds nothing but process applications and IDP applications. Every file
9
+ // and folder this suite creates therefore has to live inside a process
10
+ // application. This page object owns that container's surface only; entering
11
+ // and leaving it is driven from ModelerHomePage so the ~150 existing spec call
12
+ // sites keep working unchanged.
13
+ class ProcessApplicationPage {
14
+ page;
15
+ // Empty workspace root: a plain primary button rendered next to the "Create
16
+ // new" menu (hub's EmptyStateActionButtons).
17
+ createProcessApplicationButton;
18
+ // Non-empty root: the item inside the "Create new" menu. Hub passes
19
+ // hideProcessApplication at workspace level, so this is the fallback rather
20
+ // than the primary path -- its own unit test asserts the item is absent
21
+ // there. Kept because the Hub PR is still moving this surface.
22
+ createProcessApplicationOption;
23
+ creationDialog;
24
+ nameInput;
25
+ submitButton;
26
+ cancelButton;
27
+ breadcrumb;
28
+ folderExplorer;
29
+ titleBar;
30
+ createNewDropdown;
31
+ inlineNameInput;
32
+ constructor(page) {
33
+ this.page = page;
34
+ // .first() defensively only: hub renders exactly one of these today, in the
35
+ // workspace-root empty state. A future second placement should not turn
36
+ // every PA-creating test into a strict-mode violation.
37
+ this.createProcessApplicationButton = page
38
+ .getByRole('button', { name: 'Create process application' })
39
+ .first();
40
+ this.createProcessApplicationOption = page.locator('[data-test="create-process-application"]');
41
+ this.creationDialog = page.getByRole('dialog', {
42
+ name: 'Create a process application',
43
+ });
44
+ // Deliberately scoped to the dialog: ModelerCreatePage owns an unscoped
45
+ // getByLabel('Name', {exact: true}) for the properties panel, which would
46
+ // resolve to two elements while the creation modal is open.
47
+ this.nameInput = this.creationDialog.getByLabel('Name', { exact: true });
48
+ this.submitButton = this.creationDialog.getByRole('button', {
49
+ name: 'Create',
50
+ exact: true,
51
+ });
52
+ this.cancelButton = this.creationDialog.getByRole('button', {
53
+ name: 'Cancel',
54
+ exact: true,
55
+ });
56
+ this.breadcrumb = page.locator('[data-test="breadcrumb-process-application-menu"]');
57
+ // The Design System shell renames this region "Resources"; the Carbon shell
58
+ // calls it "Folder explorer". Both hold the resource table and its toolbar,
59
+ // so matching either keeps createNewDropdown below correctly scoped.
60
+ this.folderExplorer = page
61
+ .getByRole('region', { name: 'Folder explorer' })
62
+ .or(page.getByRole('region', { name: 'Resources' }))
63
+ .first();
64
+ // Design System proof that we are on a process application page: the
65
+ // Carbon-era breadcrumb hook is gone under that shell, the same way
66
+ // breadcrumb-project is.
67
+ this.titleBar = page.locator('[data-test="process-application-title-bar"]');
68
+ // The process application page renders more than one diagram-dropdown
69
+ // (hub's own unit tests reach for it with getAllByTestId), so the dropdown
70
+ // has to be resolved inside the folder explorer rather than page-wide.
71
+ this.createNewDropdown = this.folderExplorer
72
+ .locator('[data-test="diagram-dropdown"]')
73
+ .first();
74
+ this.inlineNameInput = page.locator('[data-test="editable-input"]');
75
+ }
76
+ // Any one of the title bar, the resource region or the breadcrumb proves we
77
+ // are on the process application page. Three branches because the Design
78
+ // System and Carbon shells expose different subsets: only the title bar is
79
+ // present under both. .first() because several match once it has rendered.
80
+ pageReady() {
81
+ return this.titleBar.or(this.folderExplorer).or(this.breadcrumb).first();
82
+ }
83
+ async waitForProcessApplication(timeout = 90000) {
84
+ await (0, test_1.expect)(this.pageReady()).toBeVisible({ timeout });
85
+ }
86
+ async clickCreateProcessApplicationButton() {
87
+ await (0, test_1.expect)(this.createProcessApplicationButton).toBeVisible({
88
+ timeout: 60000,
89
+ });
90
+ await this.createProcessApplicationButton.click({ timeout: 60000 });
91
+ }
92
+ async clickCreateProcessApplicationOption() {
93
+ await (0, test_1.expect)(this.createProcessApplicationOption).toBeVisible({
94
+ timeout: 60000,
95
+ });
96
+ await this.createProcessApplicationOption.click({ timeout: 60000 });
97
+ }
98
+ // Hub creates a process application through a modal today. It used an inline
99
+ // editable title before, and #27593 is still moving this surface, so the
100
+ // inline path is kept as a fallback — this is the one method that has to
101
+ // change if naming moves back inline.
102
+ async nameNewProcessApplication(name) {
103
+ const isModal = await this.creationDialog
104
+ .waitFor({ state: 'visible', timeout: 15000 })
105
+ .then(() => true)
106
+ .catch(() => false);
107
+ if (isModal) {
108
+ await this.nameInput.click({ timeout: 30000 });
109
+ await this.nameInput.fill(name);
110
+ await (0, test_1.expect)(this.nameInput).toHaveValue(name, { timeout: 10000 });
111
+ await (0, submitModalWithRetry_1.submitModalWithRetry)(this.submitButton, this.creationDialog);
112
+ await this.waitForProcessApplication();
113
+ return;
114
+ }
115
+ await (0, test_1.expect)(this.inlineNameInput).toBeVisible({ timeout: 30000 });
116
+ await this.inlineNameInput.click({ timeout: 60000 });
117
+ await this.inlineNameInput.fill(name);
118
+ await this.inlineNameInput.press('Enter');
119
+ await this.waitForProcessApplication();
120
+ }
121
+ async openByName(name) {
122
+ const processApplication = this.page
123
+ .getByRole('row')
124
+ .filter({ hasText: name })
125
+ .getByTitle(name)
126
+ .first();
127
+ await (0, test_1.expect)(processApplication).toBeVisible({ timeout: 90000 });
128
+ await processApplication.click({ timeout: 60000 });
129
+ await this.waitForProcessApplication();
130
+ }
131
+ }
132
+ exports.ProcessApplicationPage = ProcessApplicationPage;
@@ -8,9 +8,18 @@ class SettingsPage {
8
8
  openUserSidebarButton;
9
9
  constructor(page) {
10
10
  this.page = page;
11
- this.openSettingsButton = page.getByLabel('Open Settings');
12
- this.openUserSidebarButton = page.getByLabel('Open User Sidebar');
13
- this.logoutButton = page.getByRole('button', { name: 'Log out' });
11
+ this.openSettingsButton = page
12
+ .getByRole('button', { name: /^User menu/ })
13
+ .or(page.getByLabel('Open Settings'))
14
+ .first();
15
+ this.openUserSidebarButton = page
16
+ .getByRole('button', { name: /^User menu/ })
17
+ .or(page.getByLabel('Open User Sidebar'))
18
+ .first();
19
+ this.logoutButton = page
20
+ .getByRole('menuitem', { name: /log out/i })
21
+ .or(page.getByRole('button', { name: 'Log out' }))
22
+ .first();
14
23
  }
15
24
  async clickOpenSettingsButton() {
16
25
  await this.openSettingsButton.click({ timeout: 120000 });
@@ -416,6 +416,14 @@ async function modelAndRunConnectorsTimerEventDiagram(modelerCreatePage, modeler
416
416
  timeout: 60000,
417
417
  });
418
418
  await connectorMarketplacePage.downloadConnectorToProject();
419
+ // Both lookups are container-relative: clickProjectBreadcrumb() returns to
420
+ // this test's process application, which is where the uploaded diagram
421
+ // lives. The downloaded element template is the open question -- hub
422
+ // publishes element templates to the workspace, but the marketplace
423
+ // download is initiated from inside the container, so it should land there.
424
+ // If a trace shows the template at workspace root instead, the first of
425
+ // these two calls becomes clickWorkspaceBreadcrumb() followed by
426
+ // returnToResourceContainer() before the diagram is reopened.
419
427
  await modelerHomePage.clickProjectBreadcrumb();
420
428
  await modelerHomePage.clickProcessDiagram('REST Outbound Connector');
421
429
  await connectorTemplatePage.publishConnector();
@@ -125,7 +125,7 @@ _8_10_1.test.describe('User Roles User Flow', () => {
125
125
  await modelerCreatePage.clickAppendEndEventButton();
126
126
  await (0, sleep_1.sleep)(20000);
127
127
  await modelerCreatePage.runProcessInstance(clusterName);
128
- await (0, test_1.expect)(page.getByText('The diagram failed to deploy & run')).toBeVisible({ timeout: 60000 });
128
+ await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
129
129
  await modelerCreatePage.clickCloseButton();
130
130
  });
131
131
  });
@@ -216,7 +216,7 @@ _8_10_1.test.describe('User Roles User Flow', () => {
216
216
  await modelerCreatePage.clickAppendEndEventButton();
217
217
  await (0, sleep_1.sleep)(20000);
218
218
  await modelerCreatePage.runProcessInstance(clusterName, '', {}, '', false);
219
- await (0, test_1.expect)(page.getByText('The diagram failed to deploy & run')).toBeVisible({ timeout: 60000 });
219
+ await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).toBeVisible({ timeout: 60000 });
220
220
  await modelerCreatePage.clickCloseButton();
221
221
  });
222
222
  });
@@ -312,7 +312,7 @@ _8_10_1.test.describe('User Roles User Flow', () => {
312
312
  await modelerCreatePage.clickAppendEndEventButton();
313
313
  await (0, sleep_1.sleep)(20000);
314
314
  await modelerCreatePage.runProcessInstance(clusterName);
315
- await (0, test_1.expect)(page.getByText('The diagram failed to deploy & run')).not.toBeVisible({ timeout: 60000 });
315
+ await (0, test_1.expect)(page.getByText(/The (diagram|application) failed to deploy & run/)).not.toBeVisible({ timeout: 60000 });
316
316
  await (0, test_1.expect)(modelerCreatePage.viewProcessInstanceLink).toBeVisible({
317
317
  timeout: 120000,
318
318
  });
@@ -95,7 +95,9 @@ _8_10_1.test.describe('IDP User Flow Tests', () => {
95
95
  await modelerHomePage.clickCrossComponentProjectFolder();
96
96
  });
97
97
  await _8_10_1.test.step('Add an IDP application to the project', async () => {
98
- await modelerHomePage.clickDiagramTypeDropdown();
98
+ // Workspace-level menu: an IDP application can only be created at
99
+ // workspace root, never inside a process application.
100
+ await modelerHomePage.clickWorkspaceDiagramTypeDropdown();
99
101
  await modelerHomePage.clickIdpApplicationTemplateOption();
100
102
  });
101
103
  await _8_10_1.test.step('Creates a document extraction template', async () => {
@@ -168,7 +170,9 @@ _8_10_1.test.describe('IDP User Flow Tests', () => {
168
170
  await modelerHomePage.clickCrossComponentProjectFolder();
169
171
  });
170
172
  await _8_10_1.test.step('Add an IDP application to the project', async () => {
171
- await modelerHomePage.clickDiagramTypeDropdown();
173
+ // Workspace-level menu: an IDP application can only be created at
174
+ // workspace root, never inside a process application.
175
+ await modelerHomePage.clickWorkspaceDiagramTypeDropdown();
172
176
  await modelerHomePage.clickIdpApplicationTemplateOption();
173
177
  });
174
178
  await _8_10_1.test.step('Creates a document extraction template', async () => {
@@ -249,7 +253,9 @@ _8_10_1.test.describe('IDP User Flow Tests', () => {
249
253
  await modelerHomePage.clickCrossComponentProjectFolder();
250
254
  });
251
255
  await _8_10_1.test.step('Add an IDP application to the project', async () => {
252
- await modelerHomePage.clickDiagramTypeDropdown();
256
+ // Workspace-level menu: an IDP application can only be created at
257
+ // workspace root, never inside a process application.
258
+ await modelerHomePage.clickWorkspaceDiagramTypeDropdown();
253
259
  await modelerHomePage.clickIdpApplicationTemplateOption();
254
260
  });
255
261
  await _8_10_1.test.step('Creates a classification template', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.1009",
3
+ "version": "0.0.1011",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",