@camunda/e2e-test-suite 0.0.564 → 0.0.565

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.
@@ -260,6 +260,10 @@ class AppsPage {
260
260
  await (0, test_1.expect)(this.consoleLink).toBeVisible({ timeout: 30000 });
261
261
  await this.consoleLink.click();
262
262
  }
263
+ // Console may land on the root URL first and then redirect to the
264
+ // org-specific URL (/org/<uuid>/...) before the header banner renders.
265
+ // Wait for that redirect so callers can immediately assert page elements.
266
+ await this.page.waitForURL(/\/org\//, { timeout: 90000 }).catch(() => { });
263
267
  return;
264
268
  }
265
269
  catch (error) {
@@ -378,9 +378,9 @@ class ClusterDetailsPage {
378
378
  async assertComponentsHealth(components = ['Zeebe', 'Tasklist', 'Operate', 'Optimize']) {
379
379
  for (const component of components) {
380
380
  await (0, expectLocatorWithRetry_1.expectLocatorWithRetry)(this.page, this.page.getByText(`${component}Healthy`, { exact: true }), {
381
- visibilityTimeout: 60000,
382
- totalTimeout: 400000,
383
- maxRetries: 7,
381
+ visibilityTimeout: 120000,
382
+ totalTimeout: 600000,
383
+ maxRetries: 10,
384
384
  preAction: async () => {
385
385
  await this.page.reload();
386
386
  },
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClusterSecretsPage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const sleep_1 = require("../../utils/sleep");
5
6
  class ClusterSecretsPage {
6
7
  page;
7
8
  createNewSecretButton;
@@ -101,6 +102,16 @@ class ClusterSecretsPage {
101
102
  }
102
103
  }
103
104
  async clickClusterBanner() {
105
+ try {
106
+ const blockingModal = this.page.locator('.cds--modal.is-visible');
107
+ if ((await blockingModal.count()) > 0) {
108
+ await this.page.keyboard.press('Escape');
109
+ await (0, sleep_1.sleep)(1000);
110
+ }
111
+ }
112
+ catch (_e) {
113
+ // no blocking modal to dismiss
114
+ }
104
115
  await (0, test_1.expect)(this.clusterBanner).toBeVisible({ timeout: 30000 });
105
116
  await this.clusterBanner.click({ timeout: 60000 });
106
117
  }
@@ -203,7 +203,17 @@ class ConsoleOrganizationPage {
203
203
  });
204
204
  }
205
205
  async clickOptionsButton() {
206
- await this.optionsButton.click({ timeout: 90000 });
206
+ const dataRow = this.rows.nth(1);
207
+ await dataRow.hover({ timeout: 30000 }).catch(() => { });
208
+ // Try scoped click first; fall back to page-level with force
209
+ try {
210
+ await dataRow
211
+ .getByRole('button', { name: 'Options' })
212
+ .click({ timeout: 30000 });
213
+ }
214
+ catch (_e) {
215
+ await this.optionsButton.first().click({ force: true, timeout: 60000 });
216
+ }
207
217
  }
208
218
  async clickEditUserMenuItem() {
209
219
  await this.editUserMenuItem.click({ timeout: 60000 });
@@ -461,6 +471,13 @@ class ConsoleOrganizationPage {
461
471
  await (0, test_1.expect)(this.filterTableSearchbox).toBeVisible({ timeout: 120000 });
462
472
  await this.filterTableSearchbox.click({ timeout: 60000 });
463
473
  await this.filterTableSearchbox.fill(name);
474
+ await this.page
475
+ .waitForLoadState('networkidle', { timeout: 30000 })
476
+ .catch(() => { });
477
+ // Wait for at least one data row to appear after filtering
478
+ await (0, test_1.expect)(this.rows.nth(1))
479
+ .toBeVisible({ timeout: 30000 })
480
+ .catch(() => { });
464
481
  }
465
482
  }
466
483
  async clickAssignSubButton() {
@@ -60,14 +60,27 @@ class LoginPage {
60
60
  }
61
61
  async loginWithTestUser(credentials = {}) {
62
62
  const { username = process.env.C8_USERNAME_TEST, password = process.env.C8_PASSWORD_TEST, } = credentials;
63
- // Navigate to app root to trigger auth redirect. The Console app (at
64
- // console.* hostnames) embeds a hidden invite-user email input also labeled
65
- // "Email address" that would otherwise satisfy the locator below before the
66
- // auth0 redirect has had a chance to complete.
63
+ // Navigate to app root to start the auth flow.
67
64
  await this.page.goto('/');
68
- await this.page
69
- .waitForURL((url) => !url.hostname.includes('console'), { timeout: 60000 })
70
- .catch(() => { });
65
+ // SSO re-auth may silently log us back in as the previous user after
66
+ // logout/session-reset, keeping the page on Console instead of redirecting
67
+ // to the Auth0 login form. Detect Console (settings button visible) and
68
+ // force logout until the Auth0 email input appears.
69
+ const settingsBtn = this.page.getByLabel('Open Settings');
70
+ const logoutBtn = this.page.getByRole('button', { name: 'Log out' });
71
+ for (let i = 0; i < 3; i++) {
72
+ const onAuth0 = await this.usernameInput
73
+ .waitFor({ state: 'visible', timeout: 15000 })
74
+ .then(() => true)
75
+ .catch(() => false);
76
+ if (onAuth0)
77
+ break;
78
+ if (await settingsBtn.isVisible({ timeout: 3000 })) {
79
+ await settingsBtn.click({ timeout: 30000 });
80
+ await logoutBtn.click({ timeout: 30000 });
81
+ await this.page.waitForLoadState('domcontentloaded', { timeout: 30000 });
82
+ }
83
+ }
71
84
  await (0, test_1.expect)(this.usernameInput).toBeVisible({ timeout: 120000 });
72
85
  await this.fillUsername(username);
73
86
  await this.clickContinueButton();
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ModelerCreatePage = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const sleep_1 = require("../../utils/sleep");
6
- const ConnectorMarketplacePage_1 = require("../8.8/ConnectorMarketplacePage");
7
6
  class ModelerCreatePage {
8
7
  page;
9
8
  generalPanel;
@@ -708,29 +707,55 @@ class ModelerCreatePage {
708
707
  }
709
708
  async clickPublicHolidayConnectorOption() {
710
709
  const maxRetries = 4;
711
- for (let retries = 0; retries < maxRetries; retries++) {
712
- try {
713
- if (retries <= 2) {
714
- await this.publicHolidayConnectorOption.click({ timeout: 60000 });
715
- }
716
- else {
717
- await this.clickMarketPlaceButton();
718
- const connectorMarketplacePage = new ConnectorMarketplacePage_1.ConnectorMarketplacePage(this.page);
719
- await connectorMarketplacePage.clickSearchForConnectorTextbox();
720
- await connectorMarketplacePage.fillSearchForConnectorTextbox('Public Holiday Connector');
710
+ const setupModelerState = async () => {
711
+ // Navigate back from marketplace (full-page nav in 8.7) if needed
712
+ const onModeler = await this.page
713
+ .locator('[data-test="modeler"]')
714
+ .waitFor({ state: 'visible', timeout: 5000 })
715
+ .then(() => true)
716
+ .catch(() => false);
717
+ if (!onModeler) {
718
+ await this.page.keyboard.press('Escape');
719
+ await this.page.waitForTimeout(2000);
720
+ const afterEscape = await this.page
721
+ .locator('[data-test="modeler"]')
722
+ .waitFor({ state: 'visible', timeout: 3000 })
723
+ .then(() => true)
724
+ .catch(() => false);
725
+ if (!afterEscape) {
726
+ await this.page.goBack();
721
727
  await this.page.waitForTimeout(5000);
722
- await connectorMarketplacePage.downloadConnectorToProject();
723
- await this.publicHolidayConnectorOption.click({ timeout: 120000 });
724
728
  }
729
+ await this.page.reload();
730
+ await this.page.waitForTimeout(5000);
731
+ }
732
+ await this.clickCanvas();
733
+ await this.secondElement.click({ timeout: 60000 });
734
+ await this.clickChangeTypeButton();
735
+ // Wait for change-type panel to be fully loaded before clicking connector
736
+ await (0, test_1.expect)(this.marketPlaceButton).toBeVisible({ timeout: 60000 });
737
+ };
738
+ for (let retries = 0; retries < maxRetries; retries++) {
739
+ try {
740
+ await setupModelerState();
741
+ await this.publicHolidayConnectorOption.click({ timeout: 120000 });
725
742
  return;
726
743
  }
727
744
  catch (error) {
728
745
  console.error(`Click attempt ${retries + 1} failed: ${error}`);
746
+ if (retries >= maxRetries - 1)
747
+ break;
748
+ const onModeler = await this.page
749
+ .locator('[data-test="modeler"]')
750
+ .waitFor({ state: 'visible', timeout: 3000 })
751
+ .then(() => true)
752
+ .catch(() => false);
753
+ if (!onModeler) {
754
+ await this.page.goBack();
755
+ await this.page.waitForTimeout(3000);
756
+ }
729
757
  await this.page.reload();
730
758
  await this.page.waitForTimeout(5000);
731
- await this.clickCanvas();
732
- await this.secondElement.click({ timeout: 60000 });
733
- await this.clickChangeTypeButton();
734
759
  }
735
760
  }
736
761
  throw new Error(`Failed to click the public holiday connector after ${maxRetries} attempts.`);
@@ -78,7 +78,18 @@ class ModelerHomePage {
78
78
  });
79
79
  }
80
80
  async clickCreateNewProjectButton() {
81
- await this.createNewProjectButton.click({ timeout: 90000 });
81
+ // Wait for any top-bar survey/banner overlay to clear before clicking
82
+ await this.page
83
+ .locator('[data-test="top-bar"]')
84
+ .waitFor({ state: 'hidden', timeout: 10000 })
85
+ .catch(() => { });
86
+ try {
87
+ await this.createNewProjectButton.click({ timeout: 30000 });
88
+ }
89
+ catch {
90
+ // Overlay still present — force the click through
91
+ await this.createNewProjectButton.click({ force: true, timeout: 30000 });
92
+ }
82
93
  }
83
94
  async enterNewProjectName(name) {
84
95
  await this.projectNameInput.click({ timeout: 60000 });
@@ -143,7 +154,14 @@ class ModelerHomePage {
143
154
  await this.diagramTypeDropdown.click({ timeout: 120000 });
144
155
  }
145
156
  async clickBpmnTemplateOption() {
146
- await this.bpmnTemplateOption.click({ timeout: 120000 });
157
+ try {
158
+ await this.bpmnTemplateOption.click({ timeout: 120000 });
159
+ }
160
+ catch {
161
+ // Element may be detaching during re-render; wait briefly then force-click
162
+ await this.page.waitForTimeout(3000);
163
+ await this.bpmnTemplateOption.click({ force: true, timeout: 60000 });
164
+ }
147
165
  }
148
166
  async clickIdpApplicationTemplateOption() {
149
167
  await this.idpTemplateOption.click({ timeout: 120000 });
@@ -206,14 +224,28 @@ class ModelerHomePage {
206
224
  await this.optionsButton.click({ timeout: 30000 });
207
225
  for (let i = 0; i < retries; i++) {
208
226
  try {
209
- await this.manageButton.click({ timeout: 30000 });
210
- return; // Click succeeded, exit the loop
227
+ await (0, sleep_1.sleep)(5000);
228
+ if (await this.manageButton.isVisible({ timeout: 30000 })) {
229
+ await this.manageButton.click({ timeout: 10000 });
230
+ }
231
+ else {
232
+ await this.clickOpenOrganizationsButton();
233
+ await this.optionsButton.click({ timeout: 30000 });
234
+ await (0, test_1.expect)(this.manageButton).toBeVisible({ timeout: 30000 });
235
+ await this.manageButton.click({ timeout: 30000 });
236
+ }
237
+ // Wait for the Console org management page to finish loading
238
+ await this.page
239
+ .waitForLoadState('networkidle', { timeout: 30000 })
240
+ .catch(() => { });
241
+ return;
211
242
  }
212
243
  catch (error) {
213
244
  console.error(`Attempt ${i + 1} to click manage button failed: ${error}`);
245
+ await (0, sleep_1.sleep)(10000);
214
246
  }
215
247
  }
216
- throw new Error(`Failed to click deploy button after ${retries} retries`);
248
+ throw new Error(`Failed to click manage button after ${retries} retries`);
217
249
  }
218
250
  async clickUploadFilesButton() {
219
251
  await this.uploadFilesButton.click({ timeout: 60000 });
@@ -45,12 +45,10 @@ class PlayPage {
45
45
  let attempts = 0;
46
46
  while (attempts < maxRetries) {
47
47
  try {
48
- await (0, test_1.expect)(this.page.getByText(/process instance key/i)).toBeVisible({
49
- timeout: maxWaitTimeSeconds,
50
- });
51
- await (0, test_1.expect)(this.page.getByText(/This process instance has no variables/i)).toBeVisible({
52
- timeout: maxWaitTimeSeconds,
53
- });
48
+ await (0, test_1.expect)(this.page.getByText(/process instance key|instance key/i).first()).toBeVisible({ timeout: maxWaitTimeSeconds });
49
+ await (0, test_1.expect)(this.page
50
+ .getByText(/This process instance has no variables|No variables/i)
51
+ .first()).toBeVisible({ timeout: maxWaitTimeSeconds });
54
52
  return;
55
53
  }
56
54
  catch (error) {
@@ -13,12 +13,15 @@ class SettingsPage {
13
13
  async clickOpenSettingsButton(retries = 3) {
14
14
  for (let i = 0; i < retries; i++) {
15
15
  try {
16
- await this.openSettingsButton.click({ timeout: 180000 });
16
+ await this.openSettingsButton.click({ timeout: 60000 });
17
17
  return; // If successful, exit the function
18
18
  }
19
19
  catch (error) {
20
20
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
21
- await sleep(10000);
21
+ await sleep(5000);
22
+ // If the button isn't visible, navigate to root and try again
23
+ await this.page.goto('/').catch(() => { });
24
+ await sleep(5000);
22
25
  }
23
26
  }
24
27
  throw new Error(`Failed to click button after ${retries} retries`);
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SignUpPage = void 0;
4
- const test_1 = require("@playwright/test");
5
4
  class SignUpPage {
6
5
  page;
7
6
  firstNameInput;
@@ -74,36 +73,27 @@ class SignUpPage {
74
73
  name: 'thank you for signing up for camunda',
75
74
  });
76
75
  this.runProcessRadioButton = page.getByRole('radio', { name: 'yes' });
77
- this.verifyEmailText = page.getByText('Verify your email');
76
+ this.verifyEmailText = page.getByText(/Verify your email|Check your email|Confirm your email/i);
78
77
  }
79
78
  async signupToC8(emailAddress, password) {
80
- const maxRetries = 3;
81
- for (let attempt = 0; attempt < maxRetries; attempt++) {
82
- try {
83
- await this.page.goto('https://accounts.ultrawombat.com/signup');
84
- await this.clickFirstNameInput();
85
- await this.fillFirstNameInput('QA');
86
- await this.clickLastNameInput();
87
- await this.fillLastNameInput('Camunda');
88
- await this.clickEmailInput();
89
- await this.fillEmailInput(emailAddress);
90
- await this.clickPasswordInput();
91
- await this.fillPasswordInput(password);
92
- await this.clickSignupButton();
93
- await (0, test_1.expect)(this.verifyEmailText.first()).toBeVisible({
94
- timeout: 180000,
95
- });
96
- return;
97
- }
98
- catch (error) {
99
- if (attempt < maxRetries - 1) {
100
- console.warn(`Signup attempt ${attempt + 1} failed. Retrying...`);
101
- }
102
- else {
103
- throw error;
104
- }
105
- }
79
+ await this.page.goto('https://accounts.ultrawombat.com/signup');
80
+ await this.clickFirstNameInput();
81
+ await this.fillFirstNameInput('QA');
82
+ await this.clickLastNameInput();
83
+ await this.fillLastNameInput('Camunda');
84
+ await this.clickEmailInput();
85
+ await this.fillEmailInput(emailAddress);
86
+ // Fill Company field if the form now requires it
87
+ if (await this.companyInput.isVisible({ timeout: 3000 }).catch(() => false)) {
88
+ await this.companyInput.fill('QA Camunda');
106
89
  }
90
+ await this.clickPasswordInput();
91
+ await this.fillPasswordInput(password);
92
+ await this.clickSignupButton();
93
+ // Detect completion: URL leaves /signup OR the submit button disappears.
94
+ // waitForFunction handles any Auth0 post-signup page variant.
95
+ await this.page.waitForFunction(() => window.location.pathname !== '/signup' ||
96
+ !document.querySelector('button.signUpButton'), undefined, { timeout: 180000 });
107
97
  }
108
98
  async clickFirstNameInput() {
109
99
  await this.firstNameInput.click({ timeout: 30000 });
@@ -12,21 +12,30 @@ async function loginWithRetry(page, loginPage, testUser, timeout, maxRetries = 3
12
12
  for (let attempt = 0; attempt < maxRetries; attempt++) {
13
13
  try {
14
14
  await page.goto('/');
15
- // Wait for the auth redirect to leave the Console hostname before login.
16
- // Console embeds a hidden invite-user email input also labeled "Email
17
- // address" that would otherwise satisfy the locator inside login() before
18
- // auth0 has had a chance to load.
19
- await page
20
- .waitForURL((url) => !url.hostname.includes('console'), {
21
- timeout: 60000,
22
- })
23
- .catch(() => { });
24
15
  await (0, sleep_1.sleep)(timeout);
16
+ // Always detect SSO re-auth: after a logout + clearCookies, Auth0 may
17
+ // silently log the previous user back in without showing the login form.
18
+ // Force logout until the Auth0 email input appears.
19
+ const settingsBtn = page.getByLabel('Open Settings');
20
+ const logoutBtn = page.getByRole('button', { name: 'Log out' });
21
+ for (let i = 0; i < 3; i++) {
22
+ const onAuth0 = await loginPage.usernameInput
23
+ .waitFor({ state: 'visible', timeout: 15000 })
24
+ .then(() => true)
25
+ .catch(() => false);
26
+ if (onAuth0)
27
+ break;
28
+ if (await settingsBtn.isVisible({ timeout: 3000 })) {
29
+ await settingsBtn.click({ timeout: 30000 });
30
+ await logoutBtn.click({ timeout: 30000 });
31
+ await page.waitForLoadState('domcontentloaded', { timeout: 30000 });
32
+ }
33
+ }
25
34
  if (skipOrgAssertion) {
26
35
  await loginPage.loginWithoutOrgAssertion(testUser);
27
36
  }
28
37
  else {
29
- await loginPage.login(testUser);
38
+ await loginPage.loginWithTestUser(testUser);
30
39
  }
31
40
  return;
32
41
  }
@@ -225,10 +225,12 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
225
225
  });
226
226
  await settingsPage.clickOpenSettingsButton();
227
227
  await settingsPage.clickLogoutButton();
228
- await loginPage.login(testUser);
228
+ await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, testUser, 1000);
229
229
  await (0, sleep_1.sleep)(20000);
230
+ await appsPage.clickCamundaApps();
231
+ await appsPage.clickModeler();
230
232
  await (0, test_1.expect)(modelerHomePage.modelerPageBanner).toBeVisible({
231
- timeout: 60000,
233
+ timeout: 90000,
232
234
  });
233
235
  await modelerHomePage.clickMessageBanner();
234
236
  await (0, test_1.expect)(modelerHomePage.openOrganizationsButton).toBeVisible({
@@ -237,7 +239,6 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
237
239
  await modelerHomePage.clickOpenOrganizationsButton();
238
240
  await modelerHomePage.clickManageButton();
239
241
  await consoleOrganizationsPage.clickUsersTab();
240
- await page.reload();
241
242
  await consoleOrganizationsPage.filterTable([
242
243
  process.env.C8_USERNAME_TEST_3,
243
244
  ]);
@@ -447,8 +448,29 @@ _8_7_1.test.describe('Web Modeler User Flow Tests', () => {
447
448
  await homePage.clickSkipCustomization();
448
449
  });
449
450
  await _8_7_1.test.step('Navigate to Console', async () => {
450
- await appsPage.clickCamundaApps();
451
- await appsPage.clickConsoleLink();
451
+ // After clickSkipCustomization the browser is already on Console at
452
+ // /org/<uuid>/... – avoid triggering a redundant app-switcher navigation
453
+ // which can cause Auth0 to re-authenticate new users (taking >120 s).
454
+ const alreadyOnOrg = await page
455
+ .waitForURL(/\/org\//, { timeout: 10000 })
456
+ .then(() => true)
457
+ .catch(() => false);
458
+ if (!alreadyOnOrg) {
459
+ await appsPage.clickCamundaApps();
460
+ await appsPage.clickConsoleLink();
461
+ }
462
+ // If Auth0 re-auth was triggered (new-user first Console visit), log back in.
463
+ const onAuth0 = await loginPage.usernameInput
464
+ .waitFor({ state: 'visible', timeout: 10000 })
465
+ .then(() => true)
466
+ .catch(() => false);
467
+ if (onAuth0) {
468
+ await loginPage.fillUsername(emailAddress);
469
+ await loginPage.clickContinueButton();
470
+ await loginPage.fillPassword(password);
471
+ await loginPage.clickLoginButton();
472
+ await page.waitForURL(/\/org\//, { timeout: 90000 });
473
+ }
452
474
  await (0, test_1.expect)(homePage.consoleBanner).toBeVisible({
453
475
  timeout: 120000,
454
476
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camunda/e2e-test-suite",
3
- "version": "0.0.564",
3
+ "version": "0.0.565",
4
4
  "description": "End-to-end test helpers for Camunda 8",
5
5
  "repository": {
6
6
  "type": "git",