@camunda/e2e-test-suite 0.0.624 → 0.0.626

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.
@@ -68,7 +68,11 @@ class AppsPage {
68
68
  await cluster.click();
69
69
  }
70
70
  async clickModeler() {
71
- const maxRetries = 3;
71
+ const maxRetries = 5;
72
+ const modelerErrorPage = this.page
73
+ .getByText(/local_rate_limited/i)
74
+ .or(this.page.getByText(/Well, that's awkward/i))
75
+ .first();
72
76
  for (let retries = 0; retries < maxRetries; retries++) {
73
77
  try {
74
78
  if (retries === 0) {
@@ -80,11 +84,31 @@ class AppsPage {
80
84
  await (0, test_1.expect)(this.modelerLink).toBeVisible({ timeout: 30000 });
81
85
  await this.modelerLink.click();
82
86
  }
87
+ // After clicking, the browser is redirected through
88
+ // modeler.ultrawombat.com/login which can return 429
89
+ // (`local_rate_limited`) or a generic "Well, that's awkward" error
90
+ // page when the Modeler backend is being rate-limited by SaaS infra.
91
+ // Detect those pages and trigger a clean retry instead of letting
92
+ // the caller's banner-visibility assertion burn its timeout.
93
+ if (await modelerErrorPage.isVisible({ timeout: 5000 }).catch(() => false)) {
94
+ throw new Error('MODELER_RATE_LIMIT: navigation landed on a rate-limit / error page');
95
+ }
83
96
  return;
84
97
  }
85
98
  catch (error) {
99
+ const rateLimited = String(error).includes('MODELER_RATE_LIMIT');
86
100
  console.warn(`Click attempt ${retries + 1} failed: ${error}`);
87
- await new Promise((resolve) => setTimeout(resolve, 10000));
101
+ if (rateLimited) {
102
+ // Modeler login throttling windows commonly need 30-60s to clear.
103
+ await this.page.context().clearCookies();
104
+ await new Promise((resolve) => setTimeout(resolve, 45000));
105
+ await this.page
106
+ .goto('/', { waitUntil: 'domcontentloaded', timeout: 60000 })
107
+ .catch(() => { });
108
+ }
109
+ else {
110
+ await new Promise((resolve) => setTimeout(resolve, 10000));
111
+ }
88
112
  }
89
113
  }
90
114
  throw new Error(`Failed to click the modeler link after ${maxRetries} attempts.`);
@@ -22,10 +22,15 @@ class LoginPage {
22
22
  constructor(page) {
23
23
  this.page = page;
24
24
  this.usernameInput = {
25
- locator: page.getByLabel('Email address'),
25
+ locator: page
26
+ .getByLabel('Email address')
27
+ .and(page.locator(':not([id="c4-invite-email"])')),
26
28
  schema: userNameInputValidInputSchema,
27
29
  };
28
- this.passwordInput = page.getByLabel('Password *');
30
+ this.passwordInput = page
31
+ .getByLabel(/^Password\s*\*?$/i)
32
+ .or(page.locator('input[type="password"]'))
33
+ .first();
29
34
  this.continueButton = page.getByRole('button', {
30
35
  name: 'Continue',
31
36
  exact: true,
@@ -47,6 +52,7 @@ class LoginPage {
47
52
  await this.continueButton.click({ timeout: 30000 });
48
53
  }
49
54
  async fillPassword(password) {
55
+ await this.passwordInput.waitFor({ state: 'visible', timeout: 120000 });
50
56
  await this.passwordInput.fill(password);
51
57
  }
52
58
  async clickLoginButton() {
@@ -72,9 +78,15 @@ class LoginPage {
72
78
  await this.page
73
79
  .waitForURL((url) => !url.hostname.includes('console'), { timeout: 60000 })
74
80
  .catch(() => { });
75
- await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
76
- await this.fillUsername(username);
77
- await this.clickContinueButton();
81
+ const onPasswordStep = (await this.passwordHeading
82
+ .isVisible({ timeout: 5000 })
83
+ .catch(() => false)) ||
84
+ (await this.passwordInput.isVisible({ timeout: 5000 }).catch(() => false));
85
+ if (!onPasswordStep) {
86
+ await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
87
+ await this.fillUsername(username);
88
+ await this.clickContinueButton();
89
+ }
78
90
  await this.fillPassword(password);
79
91
  await (0, test_1.expect)(this.loginButton).toBeVisible({ timeout: 120000 });
80
92
  await this.clickLoginButton();
@@ -58,12 +58,17 @@ class OperateHomePage {
58
58
  preAction: async () => {
59
59
  await this.clickMessageBanner();
60
60
  },
61
+ maxRetries: 5,
62
+ totalTimeout: 120000,
63
+ visibilityTimeout: 30000,
61
64
  });
62
65
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.processPageHeading, {
63
66
  preAction: async () => {
64
67
  await (0, test_1.expect)(this.processesTab).toBeVisible({ timeout: 10000 });
65
68
  await this.processesTab.click({ timeout: 10000 });
66
69
  },
70
+ maxRetries: 5,
71
+ totalTimeout: 120000,
67
72
  });
68
73
  }
69
74
  async closeInformationDialog() {
@@ -2,6 +2,9 @@ import { Page, Locator } from '@playwright/test';
2
2
  declare class PlayPage {
3
3
  private page;
4
4
  readonly completeJobButton: Locator;
5
+ readonly configureTestPanel: Locator;
6
+ readonly configureTestPanelStartButton: Locator;
7
+ readonly startInstanceOverlayButton: Locator;
5
8
  constructor(page: Page);
6
9
  waitForCompleteJobButtonToBeAvailable(): Promise<void>;
7
10
  clickCompleteJobButton(): Promise<void>;
@@ -2,15 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PlayPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const clickLocatorWithRetry_1 = require("../../utils/assertionHelpers/clickLocatorWithRetry");
5
6
  const maxWaitTimeSeconds = 120000;
6
7
  class PlayPage {
7
8
  page;
8
9
  completeJobButton;
10
+ configureTestPanel;
11
+ configureTestPanelStartButton;
12
+ startInstanceOverlayButton;
9
13
  constructor(page) {
10
14
  this.page = page;
11
15
  this.completeJobButton = page
12
16
  .getByTestId('diagram')
13
17
  .getByLabel('Complete job');
18
+ // Newer Modeler Play UI: the "Configure Scenario" floating card hosts a
19
+ // primary "Start" Carbon button (variants: "Start", "Start with
20
+ // variables", "Start with a form"). Source:
21
+ // camunda-hub frontend/packages/modeler/play/src/Definition/
22
+ // ConfigureTestPanel/index.tsx — renders <FloatingCard
23
+ // data-testid="configure-test-panel"> with a <Button> whose visible
24
+ // text starts with "Start".
25
+ this.configureTestPanel = page.getByTestId('configure-test-panel');
26
+ this.configureTestPanelStartButton = this.configureTestPanel.getByRole('button', { name: /^Start( with .*)?$/, exact: false });
27
+ // Legacy bpmn-js canvas overlay button (still emitted by older Modeler
28
+ // builds, also relabeled with cached/example-data suffixes). Kept as a
29
+ // fallback for environments that haven't shipped the ConfigureTestPanel
30
+ // yet.
31
+ this.startInstanceOverlayButton = page
32
+ .getByTestId('diagram')
33
+ .getByLabel('Start instance', { exact: false });
14
34
  }
15
35
  async waitForCompleteJobButtonToBeAvailable() {
16
36
  await (0, test_1.expect)(this.completeJobButton).toBeVisible({
@@ -21,17 +41,30 @@ class PlayPage {
21
41
  await this.completeJobButton.click();
22
42
  }
23
43
  async clickStartInstanceButton() {
24
- await this.page
25
- .getByTestId('diagram')
26
- .getByLabel('Start instance', { exact: true })
27
- .click();
44
+ const startTrigger = this.configureTestPanelStartButton
45
+ .or(this.startInstanceOverlayButton)
46
+ .first();
47
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, startTrigger, {
48
+ totalTimeout: maxWaitTimeSeconds,
49
+ visibilityTimeout: 30000,
50
+ maxRetries: 5,
51
+ });
28
52
  }
29
53
  async dismissStartModal() {
30
- await this.page
31
- .getByRole('button', {
32
- name: 'Start a process instance',
33
- })
34
- .click();
54
+ // The intro dialog cycles labels across Modeler versions/states.
55
+ const buttonVariations = [
56
+ 'Start a process instance',
57
+ 'Start another instance',
58
+ 'Start new instance',
59
+ 'Start instance',
60
+ ];
61
+ for (const buttonName of buttonVariations) {
62
+ const button = this.page.getByRole('button', { name: buttonName });
63
+ if ((await button.count()) > 0) {
64
+ await button.first().click();
65
+ return;
66
+ }
67
+ }
35
68
  }
36
69
  async waitForInstanceDetailsToBeLoaded() {
37
70
  const maxRetries = 2;
@@ -39,7 +39,20 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
39
39
  // Without this check the locator for 'Email address' can match a hidden
40
40
  // invite-user input on the Console page when the logout redirect has not
41
41
  // yet completed, causing a 200 s wait before failing.
42
- await (0, test_1.expect)(loginPage.loginMessage).toBeVisible({ timeout: 60000 });
42
+ await test_1.expect
43
+ .poll(async () => {
44
+ const [loginMessageVisible, passwordHeadingVisible, usernameVisible, passwordVisible,] = await Promise.all([
45
+ loginPage.loginMessage.isVisible().catch(() => false),
46
+ loginPage.passwordHeading.isVisible().catch(() => false),
47
+ loginPage.usernameInput.locator.isVisible().catch(() => false),
48
+ loginPage.passwordInput.isVisible().catch(() => false),
49
+ ]);
50
+ return (loginMessageVisible ||
51
+ passwordHeadingVisible ||
52
+ usernameVisible ||
53
+ passwordVisible);
54
+ }, { timeout: 60000 })
55
+ .toBe(true);
43
56
  await loginPage.loginWithTestUser(testUser);
44
57
  return;
45
58
  }
@@ -179,7 +179,39 @@ class IdentityPage {
179
179
  }
180
180
  }
181
181
  async clickAssignRolesButton() {
182
- await this.assignRolesButton.click();
182
+ const rolesLoadError = this.page.getByText('The list of roles could not be loaded.');
183
+ const retryLink = this.page.getByText('Retry', { exact: true });
184
+ const maxRetries = 3;
185
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
186
+ await this.assignRolesButton.click();
187
+ try {
188
+ await this.operateCheckbox.waitFor({ state: 'visible', timeout: 10000 });
189
+ return;
190
+ }
191
+ catch {
192
+ if (await rolesLoadError.isVisible()) {
193
+ console.warn(`clickAssignRolesButton attempt ${attempt}: roles list failed to load, clicking Retry`);
194
+ await retryLink.click();
195
+ try {
196
+ await this.operateCheckbox.waitFor({
197
+ state: 'visible',
198
+ timeout: 10000,
199
+ });
200
+ return;
201
+ }
202
+ catch {
203
+ await this.page
204
+ .getByRole('button', { name: 'Cancel' })
205
+ .click()
206
+ .catch(() => { });
207
+ }
208
+ }
209
+ if (attempt === maxRetries) {
210
+ throw new Error(`Roles list failed to load after ${maxRetries} retries`);
211
+ }
212
+ await this.page.waitForTimeout(1000);
213
+ }
214
+ }
183
215
  }
184
216
  async clickAddButton() {
185
217
  await this.addButton.click();
@@ -230,7 +230,6 @@ async function modelWebhookConnector(modelerCreatePage, connectorMarketplacePage
230
230
  timeout: 30000,
231
231
  });
232
232
  await connectorMarketplacePage.downloadConnectorToProject();
233
- await modelerCreatePage.clickChangeTypeButton();
234
233
  }
235
234
  await modelerCreatePage.clickWebhookStartEventConnectorOption();
236
235
  await modelerCreatePage.clickWebhookIdInput();
@@ -8,6 +8,7 @@ const sleep_1 = require("../../utils/sleep");
8
8
  const fileUpload_1 = require("../../utils/fileUpload");
9
9
  const UtilitiesPage_2 = require("../../pages/8.10/UtilitiesPage");
10
10
  const users_1 = require("../../utils/users");
11
+ const expectLocatorWithRetry_1 = require("../../utils/assertionHelpers/expectLocatorWithRetry");
11
12
  const testUser = (0, users_1.getTestUser)('fifteenthUser');
12
13
  _8_10_1.test.describe.configure({ mode: 'parallel' });
13
14
  _8_10_1.test.describe('AWS Cluster User Flows Test', () => {
@@ -154,8 +155,13 @@ _8_10_1.test.describe('AWS Cluster User Flows Test', () => {
154
155
  await _8_10_1.test.step('View Process Instance in Operate, assert it completes and assert variable values are correct', async () => {
155
156
  await appsPage.clickCamundaApps();
156
157
  await appsPage.clickOperate(clusterName);
157
- await (0, test_1.expect)(operateHomePage.operateBanner).toBeVisible({
158
- timeout: 60000,
158
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(page, operateHomePage.operateBanner, {
159
+ totalTimeout: 120000,
160
+ visibilityTimeout: 30000,
161
+ maxRetries: 5,
162
+ postAction: async () => {
163
+ await page.reload();
164
+ },
159
165
  });
160
166
  await operateHomePage.clickProcessesTab();
161
167
  await operateProcessesPage.clickProcessCompletedCheckbox();
@@ -241,8 +241,13 @@ _8_10_1.test.describe('Connectors User Flow Tests @tasklistV2', () => {
241
241
  await _8_10_1.test.step('View Process Instance in Operate, assert it completes and assert result expression', async () => {
242
242
  await appsPage.clickCamundaApps();
243
243
  await appsPage.clickOperate(awsCluster);
244
- await (0, test_1.expect)(operateHomePage.operateBanner).toBeVisible({
245
- timeout: 30000,
244
+ await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(page, operateHomePage.operateBanner, {
245
+ totalTimeout: 120000,
246
+ visibilityTimeout: 30000,
247
+ maxRetries: 5,
248
+ postAction: async () => {
249
+ await page.reload();
250
+ },
246
251
  });
247
252
  await operateHomePage.clickProcessesTab();
248
253
  await operateProcessesPage.clickProcessCompletedCheckbox();
@@ -135,7 +135,11 @@ _8_10_1.test.describe('HTO User Flow Tests', () => {
135
135
  await (0, test_1.expect)(taskPanelPage.taskListPageBanner).toBeVisible({
136
136
  timeout: 20000,
137
137
  });
138
- await (0, expectTextWithRetry_1.expectTextWithRetry)(page, processName);
138
+ await (0, expectTextWithRetry_1.expectTextWithRetry)(page, processName, {
139
+ totalTimeout: 120000,
140
+ visibilityTimeout: 30000,
141
+ maxRetries: 5,
142
+ });
139
143
  await taskPanelPage.openTask(processName);
140
144
  await (0, test_1.expect)(page.getByText('testVariable')).toBeVisible({
141
145
  timeout: 60000,
@@ -218,7 +222,11 @@ _8_10_1.test.describe('HTO User Flow Tests', () => {
218
222
  await (0, test_1.expect)(taskPanelPage.taskListPageBanner).toBeVisible({
219
223
  timeout: 15000,
220
224
  });
221
- await (0, expectTextWithRetry_1.expectTextWithRetry)(page, processName);
225
+ await (0, expectTextWithRetry_1.expectTextWithRetry)(page, processName, {
226
+ totalTimeout: 120000,
227
+ visibilityTimeout: 30000,
228
+ maxRetries: 5,
229
+ });
222
230
  await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, `${taskName}4`, 'critical');
223
231
  await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, `${taskName}3`, 'high');
224
232
  await (0, UtilitiesPage_1.completeTaskWithRetry)(taskPanelPage, taskDetailsPage, `${taskName}2`, 'medium');
@@ -82,6 +82,7 @@ SM_8_7_1.test.describe.parallel('Connectors User Flow Tests', () => {
82
82
  });
83
83
  });
84
84
  (0, SM_8_7_1.test)('Message Start Event Webhook Connector No Auth User Flow', async ({ modelerHomePage, navigationPage, modelerCreatePage, request, operateHomePage, operateProcessInstancePage, operateProcessesPage, connectorMarketplacePage, context, }) => {
85
+ SM_8_7_1.test.slow();
85
86
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
86
87
  const processName = 'Start_Event_Webhook_Connector_No_Auth_Process' + randomString;
87
88
  await SM_8_7_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
@@ -52,6 +52,7 @@ SM_8_7_1.test.describe.parallel('Optimize User Flow Tests', () => {
52
52
  });
53
53
  });
54
54
  (0, SM_8_7_1.test)('Job Worker User Task User Flow', async ({ page, context, modelerHomePage, navigationPage, modelerCreatePage, operateHomePage, operateProcessesPage, operateProcessInstancePage, optimizeHomePage, optimizeCollectionsPage, optimizeReportPage, }) => {
55
+ SM_8_7_1.test.slow();
55
56
  const reportName = await (0, _setup_1.generateRandomStringAsync)(5);
56
57
  const processName = 'Optimize_Job_Worker_User_Task_Diagram' + reportName;
57
58
  await SM_8_7_1.test.step('Open Cross Component Test Project and Create a BPMN Diagram Template', async () => {
@@ -18,6 +18,7 @@ SM_8_7_1.test.describe('Deploy and run a process in Play', () => {
18
18
  (0, loggingUtils_1.cleanupTestLogging)();
19
19
  });
20
20
  (0, SM_8_7_1.test)('User Tasks and Service Task', async ({ page, modelerHomePage, modelerCreatePage, playPage, context, navigationPage, }) => {
21
+ SM_8_7_1.test.slow();
21
22
  const randomString = await (0, _setup_1.generateRandomStringAsync)(3);
22
23
  const processName = 'Play_Test_Process' + randomString;
23
24
  await SM_8_7_1.test.step('Open Cross Component Test Project', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.624",
3
+ "version": "0.0.626",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",