@camunda/e2e-test-suite 0.0.627 → 0.0.628

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.
@@ -20,6 +20,7 @@ declare class LoginPage {
20
20
  readonly loginMessage: Locator;
21
21
  readonly errorMessage: Locator;
22
22
  readonly passwordHeading: Locator;
23
+ readonly rateLimitError: Locator;
23
24
  constructor(page: Page);
24
25
  fillUsername(username: string): Promise<void>;
25
26
  clickContinueButton(): Promise<void>;
@@ -19,13 +19,19 @@ class LoginPage {
19
19
  loginMessage;
20
20
  errorMessage;
21
21
  passwordHeading;
22
+ rateLimitError;
22
23
  constructor(page) {
23
24
  this.page = page;
24
25
  this.usernameInput = {
25
- locator: page.getByLabel('Email address'),
26
+ locator: page
27
+ .getByLabel(/Email address/i)
28
+ .and(page.locator(':not([id="c4-invite-email"])')),
26
29
  schema: userNameInputValidInputSchema,
27
30
  };
28
- this.passwordInput = page.getByLabel('Password *');
31
+ this.passwordInput = page
32
+ .getByLabel(/^Password\s*\*?$/i)
33
+ .or(page.locator('input[type="password"]'))
34
+ .first();
29
35
  this.continueButton = page.getByRole('button', {
30
36
  name: 'Continue',
31
37
  exact: true,
@@ -39,14 +45,20 @@ class LoginPage {
39
45
  exact: true,
40
46
  });
41
47
  this.errorMessage = page.getByText('Wrong email or password');
48
+ this.rateLimitError = page
49
+ .getByText(/rate limit has been reached/i)
50
+ .or(page.getByText(/We are sorry, an error occurred/i));
42
51
  }
43
52
  async fillUsername(username) {
53
+ await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
44
54
  await this.usernameInput.locator.fill(username);
45
55
  }
46
56
  async clickContinueButton() {
57
+ await (0, test_1.expect)(this.continueButton).toBeEnabled({ timeout: 30000 });
47
58
  await this.continueButton.click({ timeout: 30000 });
48
59
  }
49
60
  async fillPassword(password) {
61
+ await (0, test_1.expect)(this.passwordInput).toBeVisible({ timeout: 120000 });
50
62
  await this.passwordInput.fill(password);
51
63
  }
52
64
  async clickLoginButton() {
@@ -73,9 +85,38 @@ class LoginPage {
73
85
  await this.page
74
86
  .waitForURL((url) => !url.hostname.includes('console'), { timeout: 60000 })
75
87
  .catch(() => { });
76
- await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
77
- await this.fillUsername(username);
78
- await this.clickContinueButton();
88
+ let authStep = 'unknown';
89
+ const deadline = Date.now() + 120000;
90
+ while (Date.now() < deadline) {
91
+ const [passwordHeadingVisible, passwordInputVisible, usernameVisible, rateLimitVisible,] = await Promise.all([
92
+ this.passwordHeading.isVisible().catch(() => false),
93
+ this.passwordInput.isVisible().catch(() => false),
94
+ this.usernameInput.locator.isVisible().catch(() => false),
95
+ this.rateLimitError
96
+ .first()
97
+ .isVisible()
98
+ .catch(() => false),
99
+ ]);
100
+ if (rateLimitVisible) {
101
+ throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
102
+ }
103
+ if (passwordHeadingVisible || passwordInputVisible) {
104
+ authStep = 'password';
105
+ break;
106
+ }
107
+ if (usernameVisible) {
108
+ authStep = 'username';
109
+ break;
110
+ }
111
+ await this.page.waitForTimeout(1000);
112
+ }
113
+ await (0, test_1.expect)(authStep).not.toBe('unknown');
114
+ if (authStep === 'username') {
115
+ await (0, test_1.expect)(this.usernameInput.locator).toBeVisible({ timeout: 120000 });
116
+ await this.fillUsername(username);
117
+ await this.clickContinueButton();
118
+ await (0, test_1.expect)(this.passwordInput).toBeVisible({ timeout: 120000 });
119
+ }
79
120
  await this.fillPassword(password);
80
121
  await (0, test_1.expect)(this.loginButton).toBeVisible({ timeout: 120000 });
81
122
  await this.clickLoginButton();
@@ -768,7 +768,14 @@ class ModelerCreatePage {
768
768
  for (let retries = 0; retries < maxRetries; retries++) {
769
769
  try {
770
770
  if (retries <= 2) {
771
- await this.publicHolidayConnectorOption.click({ timeout: 60000 });
771
+ // Wait explicitly for the option to render in the change-type
772
+ // panel before clicking. Without this we burn an attempt every
773
+ // time the panel is still loading after download.
774
+ await this.publicHolidayConnectorOption.waitFor({
775
+ state: 'visible',
776
+ timeout: 60000,
777
+ });
778
+ await this.publicHolidayConnectorOption.click({ timeout: 30000 });
772
779
  }
773
780
  else {
774
781
  await this.clickMarketPlaceButton();
@@ -777,7 +784,11 @@ class ModelerCreatePage {
777
784
  await connectorMarketplacePage.fillSearchForConnectorTextbox('Public Holiday Connector');
778
785
  await (0, sleep_1.sleep)(10000);
779
786
  await connectorMarketplacePage.downloadConnectorToProject();
780
- await this.publicHolidayConnectorOption.click({ timeout: 120000 });
787
+ await this.publicHolidayConnectorOption.waitFor({
788
+ state: 'visible',
789
+ timeout: 120000,
790
+ });
791
+ await this.publicHolidayConnectorOption.click({ timeout: 30000 });
781
792
  }
782
793
  return;
783
794
  }
@@ -53,7 +53,12 @@ class OCIdentityClusterVariablesPage {
53
53
  await this.monacoEditor.click();
54
54
  await this.monacoEditorTextArea.clear();
55
55
  await this.monacoEditorTextArea.fill(newValue);
56
- await (0, test_1.expect)(this.saveVariableButton).toBeEnabled({ timeout: 15000 });
56
+ await this.monacoEditorTextArea.press('Tab');
57
+ await test_1.expect
58
+ .poll(async () => await this.saveVariableButton.isEnabled(), {
59
+ timeout: 30000,
60
+ })
61
+ .toBe(true);
57
62
  await this.saveVariableButton.click();
58
63
  await (0, test_1.expect)(this.successMessage).toBeVisible();
59
64
  await (0, test_1.expect)(this.editVariableModal).toBeHidden();
@@ -77,10 +77,10 @@ class OperateProcessInstancePage {
77
77
  }
78
78
  }
79
79
  async assertProcessVariableContainsText(variableName, text) {
80
- const maxRetries = 3;
80
+ const maxRetries = 5;
81
81
  for (let retries = 0; retries < maxRetries; retries++) {
82
82
  try {
83
- await (0, test_1.expect)(this.page.getByTestId(`variable-${variableName}`)).toContainText(text, { timeout: 30000 });
83
+ await (0, test_1.expect)(this.page.getByTestId(`variable-${variableName}`)).toContainText(text, { timeout: 60000 });
84
84
  return;
85
85
  }
86
86
  catch (error) {
@@ -1,7 +1,11 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
2
  declare class PlayPage {
3
3
  private page;
4
+ readonly diagram: Locator;
4
5
  readonly completeJobButton: Locator;
6
+ readonly configureTestPanel: Locator;
7
+ readonly configureTestPanelStartButton: Locator;
8
+ readonly startInstanceOverlayButton: Locator;
5
9
  constructor(page: Page);
6
10
  waitForCompleteJobButtonToBeAvailable(): Promise<void>;
7
11
  clickCompleteJobButton(): Promise<void>;
@@ -2,15 +2,33 @@
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;
9
+ diagram;
8
10
  completeJobButton;
11
+ configureTestPanel;
12
+ configureTestPanelStartButton;
13
+ startInstanceOverlayButton;
9
14
  constructor(page) {
10
15
  this.page = page;
11
- this.completeJobButton = page
12
- .getByTestId('diagram')
13
- .getByLabel('Complete job');
16
+ this.diagram = page.getByTestId('diagram');
17
+ this.completeJobButton = this.diagram.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 = this.diagram.getByLabel('Start instance', { exact: false });
14
32
  }
15
33
  async waitForCompleteJobButtonToBeAvailable() {
16
34
  await (0, test_1.expect)(this.completeJobButton).toBeVisible({
@@ -21,17 +39,32 @@ class PlayPage {
21
39
  await this.completeJobButton.click();
22
40
  }
23
41
  async clickStartInstanceButton() {
24
- await this.page
25
- .getByTestId('diagram')
26
- .getByLabel('Start instance', { exact: true })
27
- .click();
42
+ const startTrigger = this.configureTestPanelStartButton
43
+ .or(this.startInstanceOverlayButton)
44
+ .first();
45
+ await (0, clickLocatorWithRetry_1.clickLocatorWithRetry)(this.page, startTrigger, {
46
+ totalTimeout: 240000,
47
+ visibilityTimeout: 60000,
48
+ maxRetries: 8,
49
+ retryDelayMs: 5000,
50
+ });
28
51
  }
29
52
  async dismissStartModal() {
30
- await this.page
31
- .getByRole('button', {
32
- name: 'Start a process instance',
33
- })
34
- .click();
53
+ // The intro dialog cycles labels across Modeler versions/states.
54
+ // Try the known variations in order; first match wins.
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;
@@ -8,7 +8,7 @@ const sleep_1 = require("../../utils/sleep");
8
8
  const randomSleep_1 = require("../../utils/randomSleep");
9
9
  const fileUpload_1 = require("../../utils/fileUpload");
10
10
  const mailSlurpClient_1 = require("../../utils/mailSlurpClient");
11
- async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3) {
11
+ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 5) {
12
12
  let lastError;
13
13
  for (let attempt = 0; attempt < maxRetries; attempt++) {
14
14
  try {
@@ -35,14 +35,34 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
35
35
  .waitForLoadState('networkidle', { timeout: 30000 })
36
36
  .catch(() => { });
37
37
  await (0, sleep_1.sleep)(timeout);
38
+ // The identity provider occasionally returns a generic error / rate
39
+ // limit page that has none of the usual auth controls. Bail out early
40
+ // when that page is visible so the outer retry can back off.
41
+ if (await loginPage.rateLimitError
42
+ .first()
43
+ .isVisible()
44
+ .catch(() => false)) {
45
+ throw new Error('AUTH0_RATE_LIMIT: identity provider returned an error page');
46
+ }
47
+ // Auth0 may restore directly on password entry depending on session
48
+ // history, so allow either identifier screen or password screen.
49
+ await (0, test_1.expect)(loginPage.loginMessage.or(loginPage.passwordHeading)).toBeVisible({ timeout: 60000 });
38
50
  await loginPage.loginWithTestUser(testUser);
39
51
  return;
40
52
  }
41
53
  catch (error) {
42
54
  lastError = error;
43
55
  if (attempt < maxRetries - 1) {
56
+ const rateLimited = String(error).includes('AUTH0_RATE_LIMIT');
44
57
  console.warn(`Attempt ${attempt + 1} failed for logging in. Retrying with clean session...`, error);
45
- await (0, randomSleep_1.randomSleep)(10000, 20000);
58
+ if (rateLimited) {
59
+ // Auth0 rate-limit windows commonly last 30-90s. Back off long
60
+ // enough to outlast the window before the next attempt.
61
+ await (0, randomSleep_1.randomSleep)(60000, 90000);
62
+ }
63
+ else {
64
+ await (0, randomSleep_1.randomSleep)(10000, 20000);
65
+ }
46
66
  }
47
67
  else {
48
68
  console.error(lastError);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.627",
3
+ "version": "0.0.628",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",