@camunda/e2e-test-suite 0.0.968 → 0.0.970

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.
@@ -24,6 +24,9 @@ declare class ModelerCreatePage {
24
24
  readonly additionalActionsButton: Locator;
25
25
  readonly cancelButton: Locator;
26
26
  readonly restConnectorOption: Locator;
27
+ readonly changeElementSearchInput: Locator;
28
+ readonly changeElementTabStrip: Locator;
29
+ readonly reusableAssetsTab: Locator;
27
30
  readonly webhookMessageStartEventConnectorOption: Locator;
28
31
  readonly webhookTab: Locator;
29
32
  readonly webhookIsActiveButton: Locator;
@@ -154,6 +157,8 @@ declare class ModelerCreatePage {
154
157
  clickNewForm(): Promise<void>;
155
158
  clickDeploySubButton(): Promise<void>;
156
159
  clickCancelButton(): Promise<void>;
160
+ private waitForVisible;
161
+ revealChangeElementTemplate(option: Locator, searchTerm: string): Promise<void>;
157
162
  clickRestConnectorOption(): Promise<void>;
158
163
  clickWebhookStartEventConnectorOption(): Promise<void>;
159
164
  clickWebhookTab(): Promise<void>;
@@ -30,6 +30,9 @@ class ModelerCreatePage {
30
30
  additionalActionsButton;
31
31
  cancelButton;
32
32
  restConnectorOption;
33
+ changeElementSearchInput;
34
+ changeElementTabStrip;
35
+ reusableAssetsTab;
33
36
  webhookMessageStartEventConnectorOption;
34
37
  webhookTab;
35
38
  webhookIsActiveButton;
@@ -168,6 +171,12 @@ class ModelerCreatePage {
168
171
  this.deploySubButton = page.locator('[data-test="deploy-action"]');
169
172
  this.cancelButton = page.getByRole('button', { name: 'Cancel' });
170
173
  this.restConnectorOption = page.locator('[data-id="replace.template-io.camunda.connectors.HttpJson.v2"]');
174
+ this.changeElementSearchInput = page.locator('.djs-popup-search input');
175
+ this.changeElementTabStrip = page.locator('.djs-popup-tabs-container');
176
+ // The tab renders its label as text content, which wins over its `title`
177
+ // attribute in the accessible-name computation. Match case-insensitively:
178
+ // the label is capitalised differently across bpmn-js releases.
179
+ this.reusableAssetsTab = page.getByRole('tab', { name: /reusable assets/i });
171
180
  // bpmn-js popup entries are `li[role="option"]` inside a `role="listbox"`,
172
181
  // so getByRole('listitem') no longer matches. Their accessible name also
173
182
  // includes the entry description, and three entries share the label
@@ -683,11 +692,51 @@ class ModelerCreatePage {
683
692
  async clickCancelButton() {
684
693
  await this.cancelButton.click({ timeout: 60000 });
685
694
  }
695
+ // `waitFor` actually blocks up to the timeout; `isVisible()` would return
696
+ // immediately and report false while the popup is still rendering.
697
+ async waitForVisible(locator, timeout) {
698
+ return locator
699
+ .first()
700
+ .waitFor({ state: 'visible', timeout })
701
+ .then(() => true)
702
+ .catch(() => false);
703
+ }
704
+ // The change-element popup is now tabbed: every `replace.template-*` entry
705
+ // is assigned to the "Reusable Assets" tab, so connector templates are not
706
+ // in the DOM at all while the default "BPMN" tab is selected - which is why
707
+ // the template locators time out with `<element(s) not found>`.
708
+ // Opening that tab is version-tolerant: on an untabbed popup the strip is
709
+ // absent and this is a no-op.
710
+ // Search is the fallback, because it spans the full entry set across every
711
+ // tab. It must be typed with `pressSequentially`: the popup's search input
712
+ // is uncontrolled and its state is written by a `keyup` handler only, so
713
+ // `fill()` (which dispatches `input` alone) never applies the filter.
714
+ async revealChangeElementTemplate(option, searchTerm) {
715
+ if (await this.waitForVisible(option, 5000)) {
716
+ return;
717
+ }
718
+ if (await this.waitForVisible(this.changeElementTabStrip, 5000)) {
719
+ await this.reusableAssetsTab.click({ timeout: 30000 });
720
+ if (await this.waitForVisible(option, 15000)) {
721
+ return;
722
+ }
723
+ }
724
+ if (!(await this.waitForVisible(this.changeElementSearchInput, 5000))) {
725
+ return;
726
+ }
727
+ await this.changeElementSearchInput.click();
728
+ await this.changeElementSearchInput.fill('');
729
+ await this.changeElementSearchInput.pressSequentially(searchTerm, {
730
+ delay: 50,
731
+ });
732
+ }
686
733
  async clickRestConnectorOption() {
734
+ await this.revealChangeElementTemplate(this.restConnectorOption, 'REST');
687
735
  await (0, test_1.expect)(this.restConnectorOption).toBeVisible({ timeout: 15000 });
688
736
  await this.restConnectorOption.click();
689
737
  }
690
738
  async clickWebhookStartEventConnectorOption() {
739
+ await this.revealChangeElementTemplate(this.webhookMessageStartEventConnectorOption, 'Webhook');
691
740
  await this.webhookMessageStartEventConnectorOption.click({ timeout: 60000 });
692
741
  }
693
742
  async clickWebhookTab() {
@@ -940,6 +989,7 @@ class ModelerCreatePage {
940
989
  await this.intermediateBoundaryEvent.click({ timeout: 60000 });
941
990
  }
942
991
  async clickIntermediateWebhookConnectorOption() {
992
+ await this.revealChangeElementTemplate(this.intermediateWebhookConnectorOption, 'Webhook');
943
993
  await this.intermediateWebhookConnectorOption.click({ timeout: 60000 });
944
994
  }
945
995
  async clickCorrelationKeyProcessInput() {
@@ -121,11 +121,21 @@ class TaskDetailsPage {
121
121
  async clickUnassignButton() {
122
122
  await this.unassignButton.click();
123
123
  }
124
+ // The "Task completed" toast is a transient confirmation: Tasklist's
125
+ // notification store auto-removes it, and it is never rendered at all when
126
+ // the refreshed task list deselects the completed task before the completion
127
+ // promise resolves (the nightly trace showed exactly that — the task was
128
+ // completed, the details panel rendered "Completed by Me" plus a completion
129
+ // date, and no toast ever entered the DOM). Accept the task's own completed
130
+ // state as well: the details panel only renders "Completion date" once the
131
+ // task has one, so this confirms the completion rather than weakening the
132
+ // check. Same fix already carried by pages/8.10/TaskDetailsPage.ts.
124
133
  async clickCompleteTaskButton() {
125
134
  await this.completeTaskButton.click({ timeout: 60000 });
126
- await (0, test_1.expect)(this.page.getByText('Task completed')).toBeVisible({
127
- timeout: 60000,
128
- });
135
+ await (0, test_1.expect)(this.page
136
+ .getByText('Task completed')
137
+ .or(this.detailsPanel.getByText('Completion date'))
138
+ .first()).toBeVisible({ timeout: 60000 });
129
139
  await this.page.reload();
130
140
  }
131
141
  async clickAddVariableButton() {
@@ -30,10 +30,15 @@ declare class NavigationPage {
30
30
  username: string;
31
31
  password: string;
32
32
  }): Promise<void>;
33
+ private goToWithoutAccess;
33
34
  goToOptimizeWithoutAccess({ username, password, }?: {
34
35
  username?: string;
35
36
  password?: string;
36
37
  }): Promise<void>;
38
+ goToModelerWithoutAccess({ username, password, }?: {
39
+ username?: string;
40
+ password?: string;
41
+ }): Promise<void>;
37
42
  goToKeycloak(): Promise<void>;
38
43
  goToOCIdentity(sleepTimeout?: number, credentials?: {
39
44
  username: string;
@@ -5,6 +5,7 @@ const test_1 = require("@playwright/test");
5
5
  const LoginPage_1 = require("./LoginPage");
6
6
  const sleep_1 = require("../../utils/sleep");
7
7
  const constants_1 = require("../../utils/constants");
8
+ const GATEWAY_ERROR_STATUSES = [502, 503, 504];
8
9
  class NavigationPage {
9
10
  page;
10
11
  modelerPageBanner;
@@ -118,18 +119,34 @@ class NavigationPage {
118
119
  async goToOptimize(sleepTimeout, credentials) {
119
120
  await this.goTo('/optimize', this.optimizePageBanner, sleepTimeout, credentials);
120
121
  }
121
- // `goTo` ends by asserting the app banner, so it cannot be used to reach
122
- // Optimize as a user without the Optimize role: that request ends on a
123
- // `text/plain` 403 page carrying neither the banner nor a login form, and
124
- // every retry burns a full 60s timeout. Navigate and log in only; the
125
- // caller asserts what the denied page shows.
122
+ // `goTo` ends by asserting the app banner, so it cannot be used to reach a
123
+ // component the user has no role for: the denied response carries neither
124
+ // the banner nor a login form, and every retry burns a full 60s timeout.
125
+ // Navigate and log in only; the caller asserts what the denied page shows.
126
+ // The ingress answers 5xx while a component is still rolling out (upgrade
127
+ // scenarios), so retry the request until the component itself responds.
128
+ async goToWithoutAccess(url, username, password) {
129
+ await (0, test_1.expect)(async () => {
130
+ const response = await this.page.goto(url, {
131
+ timeout: 60000,
132
+ waitUntil: 'domcontentloaded',
133
+ });
134
+ (0, test_1.expect)(GATEWAY_ERROR_STATUSES).not.toContain(response?.status());
135
+ }).toPass({ timeout: 120000, intervals: [5000, 10000, 15000] });
136
+ await new LoginPage_1.LoginPage(this.page).login(username, password);
137
+ }
138
+ // Optimize denies a user without the Optimize role with a `text/plain` 403
139
+ // page.
126
140
  async goToOptimizeWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
127
141
  'demo', } = {}) {
128
- await this.page.goto('/optimize', {
129
- timeout: 60000,
130
- waitUntil: 'domcontentloaded',
131
- });
132
- await new LoginPage_1.LoginPage(this.page).login(username, password);
142
+ await this.goToWithoutAccess('/optimize', username, password);
143
+ }
144
+ // Web Modeler denies a user without the Web Modeler role with a blank page.
145
+ // It briefly renders its banner before dropping it, so `goTo`'s closing
146
+ // banner assertion either fails outright or passes by winning that race.
147
+ async goToModelerWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
148
+ 'demo', } = {}) {
149
+ await this.goToWithoutAccess('/modeler', username, password);
133
150
  }
134
151
  async goToKeycloak() {
135
152
  const keycloakUrl = process.env.KEYCLOAK_URL
@@ -147,18 +164,11 @@ class NavigationPage {
147
164
  async goToConsole(sleepTimeout, credentials) {
148
165
  await this.goTo('/console', this.consolePageBanner, sleepTimeout, credentials);
149
166
  }
150
- // `goTo` ends by asserting the app banner, so it cannot be used to reach
151
- // Console as a user without the Console role: that request renders
152
- // Console's own "Something went wrong" error page, which has neither the
153
- // banner nor a login form, and every retry burns a full 60s timeout.
154
- // Navigate and log in only; the caller asserts what the denied page shows.
167
+ // Console denies a user without the Console role with its own "Something
168
+ // went wrong" error page.
155
169
  async goToConsoleWithoutAccess({ username = 'demo', password = process.env.DISTRO_QA_E2E_TESTS_IDENTITY_FIRSTUSER_PASSWORD ??
156
170
  'demo', } = {}) {
157
- await this.page.goto('/console', {
158
- timeout: 60000,
159
- waitUntil: 'domcontentloaded',
160
- });
161
- await new LoginPage_1.LoginPage(this.page).login(username, password);
171
+ await this.goToWithoutAccess('/console', username, password);
162
172
  }
163
173
  async goToManagementIdentity(sleepTimeout, credentials) {
164
174
  await this.goTo('/identity', this.managementIdentityPageBanner, sleepTimeout, credentials);
@@ -132,6 +132,9 @@ _8_9_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
132
132
  await modelerCreatePage.enterDiagramName(processName);
133
133
  await (0, sleep_1.sleep)(10000);
134
134
  await modelerCreatePage.clickChangeTypeButton();
135
+ // Connector templates now sit behind the change-element popup's
136
+ // "Reusable Assets" tab, so surface the entry before asserting on it.
137
+ await modelerCreatePage.revealChangeElementTemplate(modelerCreatePage.webhookMessageStartEventConnectorOption, 'Webhook');
135
138
  await (0, test_1.expect)(modelerCreatePage.webhookMessageStartEventConnectorOption).toBeVisible({
136
139
  timeout: 30000,
137
140
  });
@@ -74,7 +74,7 @@ SM_8_8_1.test.describe('Identity User Flow Tests', () => {
74
74
  });
75
75
  });
76
76
  await SM_8_8_1.test.step('Ensure Modeler is not Accessible', async () => {
77
- await navigationPage.goToModeler(undefined, credentials);
77
+ await navigationPage.goToModelerWithoutAccess(credentials);
78
78
  await (0, test_1.expect)(modelerHomePage.modelerPageBanner).not.toBeVisible({
79
79
  timeout: 60000,
80
80
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.968",
3
+ "version": "0.0.970",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",