diff --git a/.github/workflows/dotnet-core-master.yml b/.github/workflows/dotnet-core-master.yml index 11badeba02..8a123243eb 100644 --- a/.github/workflows/dotnet-core-master.yml +++ b/.github/workflows/dotnet-core-master.yml @@ -54,7 +54,7 @@ jobs: - name: Wait for MariaDB run: | for i in {1..30}; do - if docker exec mariadb-test mysql -uroot -psecretpassword -e "SELECT 1" &>/dev/null; then + if docker exec mariadbtest mysql -uroot -psecretpassword -e "SELECT 1" &>/dev/null; then echo "MariaDB is ready!" break fi @@ -95,6 +95,22 @@ jobs: config-file: cypress.config.ts working-directory: eform-client command-prefix: "--" + - name: Wait for application to be ready + run: | + echo "Waiting for application at http://localhost:4200 to be ready..." + for i in {1..60}; do + if curl -f -s http://localhost:4200 >/dev/null; then + echo "Application is ready!" + break + fi + echo "Waiting for application... ($i/60)" + sleep 2 + done + # Final check + if ! curl -f -s http://localhost:4200 >/dev/null; then + echo "Application failed to start within 120 seconds" + exit 1 + fi - name: testheadless2${{matrix.test}} run: cd eform-client && npm run testheadless2${{matrix.test}} - name: Stop the newly build Docker container diff --git a/.github/workflows/dotnet-core-pr.yml b/.github/workflows/dotnet-core-pr.yml index ac988de6f6..d96de1282b 100644 --- a/.github/workflows/dotnet-core-pr.yml +++ b/.github/workflows/dotnet-core-pr.yml @@ -55,7 +55,7 @@ jobs: - name: Wait for MariaDB run: | for i in {1..30}; do - if docker exec mariadb-test mysql -uroot -psecretpassword -e "SELECT 1" &>/dev/null; then + if docker exec mariadbtest mysql -uroot -psecretpassword -e "SELECT 1" &>/dev/null; then echo "MariaDB is ready!" break fi @@ -96,6 +96,22 @@ jobs: config-file: cypress.config.ts working-directory: eform-client command-prefix: "--" + - name: Wait for application to be ready + run: | + echo "Waiting for application at http://localhost:4200 to be ready..." + for i in {1..60}; do + if curl -f -s http://localhost:4200 >/dev/null; then + echo "Application is ready!" + break + fi + echo "Waiting for application... ($i/60)" + sleep 2 + done + # Final check + if ! curl -f -s http://localhost:4200 >/dev/null; then + echo "Application failed to start within 120 seconds" + exit 1 + fi - name: testheadless2${{matrix.test}} run: cd eform-client && npm run testheadless2${{matrix.test}} - name: Stop the newly build Docker container diff --git a/eform-client/cypress/e2e/Login.page.ts b/eform-client/cypress/e2e/Login.page.ts index 599cc45fdf..6a85abfbbd 100644 --- a/eform-client/cypress/e2e/Login.page.ts +++ b/eform-client/cypress/e2e/Login.page.ts @@ -39,7 +39,9 @@ class LoginPage { loginWithNewPassword() { this.getUsernameInput().type(loginConstants.username); this.getPasswordInput().type(loginConstants.newPassword); + cy.intercept('POST', '**/api/templates/index').as('login'); this.getLoginButton().click(); + cy.wait('@login', { timeout: 60000 }); cy.get('#newEFormBtn').should('be.visible'); } diff --git a/eform-client/cypress/e2e/Navbar.page.ts b/eform-client/cypress/e2e/Navbar.page.ts index 660cc795c2..6c17f3c557 100644 --- a/eform-client/cypress/e2e/Navbar.page.ts +++ b/eform-client/cypress/e2e/Navbar.page.ts @@ -91,7 +91,8 @@ export class Navbar { this.signOutDropdown().click(); cy.wait(500); this.logoutBtn().click(); - cy.wait(500); + // Wait for login page to be fully loaded + cy.get('#loginBtn', { timeout: 10000 }).should('be.visible'); } public goToProfileSettings() { diff --git a/eform-client/cypress/e2e/PasswordSettings.page.ts b/eform-client/cypress/e2e/PasswordSettings.page.ts index 27bf5a9260..b6c86bd49d 100644 --- a/eform-client/cypress/e2e/PasswordSettings.page.ts +++ b/eform-client/cypress/e2e/PasswordSettings.page.ts @@ -25,11 +25,9 @@ class PasswordSettingsPage extends PageWithNavbarPage { cy.wait(500); this.newPasswordConfirmationField().should('be.visible').clear().type(newPassword); cy.wait(500); - cy.intercept('PUT', '**/api/auth/change-password').as('changePassword'); + cy.intercept('POST', '**/api/account/change-password').as('changePassword'); this.saveBtn().should('be.visible').should('be.enabled').click(); cy.wait('@changePassword', { timeout: 10000 }); - cy.get('#spinner-animation').should('not.exist'); - cy.wait(500); } revertToOldPassword(oldPassword = loginConstants.newPassword, newPassword = loginConstants.password) { @@ -39,11 +37,9 @@ class PasswordSettingsPage extends PageWithNavbarPage { cy.wait(500); this.newPasswordConfirmationField().should('be.visible').clear().type(newPassword); cy.wait(500); - cy.intercept('PUT', '**/api/auth/change-password').as('changePassword'); + cy.intercept('POST', '**/api/account/change-password').as('changePassword'); this.saveBtn().should('be.visible').should('be.enabled').click(); cy.wait('@changePassword', { timeout: 10000 }); - cy.get('#spinner-animation').should('not.exist'); - cy.wait(500); } } diff --git a/eform-client/cypress/e2e/e/password-settings.change-password.spec.cy.ts b/eform-client/cypress/e2e/e/password-settings.change-password.spec.cy.ts index 233e8d632a..32f6b8e582 100644 --- a/eform-client/cypress/e2e/e/password-settings.change-password.spec.cy.ts +++ b/eform-client/cypress/e2e/e/password-settings.change-password.spec.cy.ts @@ -5,47 +5,41 @@ describe('Password settings - Change password', function () { before(() => { cy.visit('http://localhost:4200'); loginPage.login(); - passwordSettingsPage.Navbar.goToPasswordSettings(); - cy.get('#oldPassword', { timeout: 10000 }).should('be.visible'); }); - it('should change password to new password', () => { + it('should change password and revert it back', () => { + // Navigate to password settings + passwordSettingsPage.Navbar.goToPasswordSettings(); + cy.get('#oldPassword', { timeout: 10000 }).should('be.visible'); + // Change password to new password passwordSettingsPage.setNewPassword(); - // Logout + // Logout (waits for login page to load) passwordSettingsPage.Navbar.logout(); - cy.get('#loginBtn').should('be.visible'); - // Login with new password + // Login with new password to verify change worked cy.visit('http://localhost:4200'); loginPage.loginWithNewPassword(); // Verify we're logged in by checking for the new eForm button - cy.get('#newEFormBtn').should('be.visible'); + cy.get('#newEFormBtn', { timeout: 10000 }).should('be.visible'); - // Navigate to password settings + // Navigate to password settings to revert passwordSettingsPage.Navbar.goToPasswordSettings(); - cy.get('#oldPassword').should('be.visible'); - }); - - it('should revert password back to original', () => { + cy.get('#oldPassword', { timeout: 10000 }).should('be.visible'); + // Revert password back to original passwordSettingsPage.revertToOldPassword(); - // Logout + // Logout (waits for login page to load) passwordSettingsPage.Navbar.logout(); - cy.get('#loginBtn').should('be.visible'); - // Login with original password + // Login with original password to verify revert worked cy.visit('http://localhost:4200'); loginPage.login(); // Verify we're logged in - cy.get('#newEFormBtn').should('be.visible'); - - // Navigate to password settings to verify we can access it - passwordSettingsPage.Navbar.goToPasswordSettings(); - cy.get('#oldPassword').should('be.visible'); + cy.get('#newEFormBtn', { timeout: 10000 }).should('be.visible'); }); }); diff --git a/eform-client/e2e/Page objects/Login.page.ts b/eform-client/e2e/Page objects/Login.page.ts index 7987df01bd..a7a37b5caa 100644 --- a/eform-client/e2e/Page objects/Login.page.ts +++ b/eform-client/e2e/Page objects/Login.page.ts @@ -41,17 +41,47 @@ class LoginPage extends Page { } public async login(): Promise { + console.log('[LOGIN DEBUG] Starting login process...'); + console.log('[LOGIN DEBUG] Current URL:', await browser.getUrl()); + await (await this.loginBtn()).waitForDisplayed({ timeout: 60000 }); + console.log('[LOGIN DEBUG] Login button is displayed'); + // await (await this.usernameInput()).waitForDisplayed({ timeout: 60000 }); await (await this.usernameInput()).setValue(LoginConstants.username); + console.log('[LOGIN DEBUG] Username set'); + await (await this.passwordInput()).setValue(LoginConstants.password); + console.log('[LOGIN DEBUG] Password set'); + await (await this.loginBtn()).click(); + console.log('[LOGIN DEBUG] Login button clicked'); + console.log('[LOGIN DEBUG] URL after click:', await browser.getUrl()); + // Add pause after login click to allow application to start loading on slow environments await browser.pause(2000); + console.log('[LOGIN DEBUG] Waited 2 seconds, now looking for newEFormBtn...'); + console.log('[LOGIN DEBUG] Current URL:', await browser.getUrl()); + + // Take screenshot before waiting for newEFormBtn to help debug + try { + const screenshotPath = './errorShots/before-newEFormBtn-wait.png'; + await browser.saveScreenshot(screenshotPath); + console.log('[LOGIN DEBUG] Screenshot saved to:', screenshotPath); + } catch (e) { + console.log('[LOGIN DEBUG] Could not save screenshot:', e.message); + } + const newEFormBtn = await $('#newEFormBtn'); // Increased timeout for slow environments - application may take longer to initialize + console.log('[LOGIN DEBUG] Waiting for newEFormBtn to be displayed (120s timeout)...'); await newEFormBtn.waitForDisplayed({timeout: 120000}); + console.log('[LOGIN DEBUG] newEFormBtn is displayed'); + + console.log('[LOGIN DEBUG] Waiting for newEFormBtn to be clickable (120s timeout)...'); await newEFormBtn.waitForClickable({timeout: 120000}); + console.log('[LOGIN DEBUG] newEFormBtn is clickable - login complete!'); + console.log('[LOGIN DEBUG] Final URL:', await browser.getUrl()); } public async loginWithNewPassword(): Promise { await (await this.usernameInput()).waitForDisplayed({ timeout: 60000 }); diff --git a/eform-client/e2e/Tests/application-settings/application-settings.login-page.spec.ts b/eform-client/e2e/Tests/application-settings/application-settings.login-page.spec.ts index 3272cca8a6..67bb8a7290 100644 --- a/eform-client/e2e/Tests/application-settings/application-settings.login-page.spec.ts +++ b/eform-client/e2e/Tests/application-settings/application-settings.login-page.spec.ts @@ -4,7 +4,7 @@ import applicationSettingsPage from '../../Page objects/ApplicationSettings.page import ApplicationSettingsConstants from '../../Constants/ApplicationSettingsConstants'; import { expect } from 'chai'; -describe('Application settings page - site header section', function () { +describe('Application settings page - login page section', function () { before(async () => { await loginPage.open('/auth'); await loginPage.login(); diff --git a/eform-client/e2e/Tests/password-settings/password-settings.change-password.spec.ts b/eform-client/e2e/Tests/password-settings/password-settings.change-password.spec.ts index 2e079fde84..d527fbe77c 100644 --- a/eform-client/e2e/Tests/password-settings/password-settings.change-password.spec.ts +++ b/eform-client/e2e/Tests/password-settings/password-settings.change-password.spec.ts @@ -8,27 +8,36 @@ describe('Password settings', function () { before(async () => { loginPage.open('/'); loginPage.login(); - myEformsPage.Navbar.goToPasswordSettings(); }); - it('should set password to 2Times2WillDo', async () => { + it('should change password and revert it back', async () => { + // Navigate to password settings + myEformsPage.Navbar.goToPasswordSettings(); + + // Change password to new password passwordSettings.setNewPassword(); + + // Logout passwordSettings.Navbar.logout(); + + // Login with new password to verify change worked loginPage.open('/'); loginPage.loginWithNewPassword(); + + // Navigate to password settings to revert myEformsPage.Navbar.goToPasswordSettings(); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('My eForms')).equal('My eForms'); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('Device Users')).equal('Device Users'); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('Advanced')).equal('Advanced'); - }); - it('should revert to old password', async () => { + + // Revert password back to original passwordSettings.revertToOldPassword(); + + // Logout passwordSettings.Navbar.logout(); + + // Login with original password to verify revert worked loginPage.open('/'); loginPage.login(); + + // Verify we can access password settings again myEformsPage.Navbar.goToPasswordSettings(); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('Meine eForms')).equal('Meine eForms'); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('Gerätebenutzer')).equal('Gerätebenutzer'); - // expect(myEformsPage.Navbar.verifyHeaderMenuItem('Fortgeschritten')).equal('Fortgeschritten'); }); }); diff --git a/eform-client/wdio-headless-step2e.conf.ts b/eform-client/wdio-headless-step2e.conf.ts index 8e087e1676..c70dc7895a 100644 --- a/eform-client/wdio-headless-step2e.conf.ts +++ b/eform-client/wdio-headless-step2e.conf.ts @@ -73,7 +73,9 @@ export const config: WebdriverIO.Config = { // maxInstances can get overwritten per capability. So if you have an in-house Selenium // grid with only 5 firefox instances available you can make sure that not more than // 5 instances get started at a time. - maxInstances: 5, + // NOTE: Set to 1 for step2e because these tests modify shared application settings + // and must run sequentially to avoid race conditions + maxInstances: 1, // browserName: 'chrome', 'goog:chromeOptions': { @@ -176,7 +178,7 @@ export const config: WebdriverIO.Config = { ui: 'bdd', //require: 'ts-node/register', //compilers: ['tsconfig-paths/register'], - timeout: 90000 + timeout: 240000 }, // // ===== @@ -230,25 +232,60 @@ export const config: WebdriverIO.Config = { * Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts. * @param {Object} test test details */ - // beforeTest: function (test) { - // }, + beforeTest: async function (test) { + console.log('[DEBUG] Starting test:', test.title); + console.log('[DEBUG] Test file:', test.file); + try { + const url = await browser.getUrl(); + console.log('[DEBUG] Initial URL:', url); + } catch (e) { + console.log('[DEBUG] Could not retrieve initial URL:', e.message); + } + }, /** * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling * beforeEach in Mocha) */ - // beforeHook: function () { - // }, + beforeHook: function (test, context, hookName) { + console.log('[DEBUG] Before hook:', hookName); + if (hookName && hookName.includes('before all')) { + console.log('[DEBUG] This is the "before all" hook - where login happens'); + } + }, /** * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling * afterEach in Mocha) */ - // afterHook: function () { - // }, + afterHook: async function (test, context, { error, result, duration, passed }, hookName) { + if (error) { + console.log('[DEBUG] Hook failed:', hookName); + console.log('[DEBUG] Hook error:', error.message); + console.log('[DEBUG] Hook duration:', duration); + + // Take screenshot on hook failure + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const screenshotPath = `./errorShots/hook-failure-${timestamp}.png`; + await browser.saveScreenshot(screenshotPath); + console.log('[DEBUG] Hook failure screenshot saved to:', screenshotPath); + } catch (e) { + console.log('[DEBUG] Could not save hook failure screenshot:', e.message); + } + + // Try to get current URL + try { + const url = await browser.getUrl(); + console.log('[DEBUG] URL at hook failure:', url); + } catch (e) { + console.log('[DEBUG] Could not retrieve URL:', e.message); + } + } + }, /** * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends. * @param {Object} test test details */ - afterTest(test, context, { error, result, duration, passed, retries }) { + afterTest: async function (test, context, { error, result, duration, passed, retries }) { const path = require('path'); // if test passed, ignore, else take and save screenshot. @@ -256,6 +293,29 @@ export const config: WebdriverIO.Config = { return; } + console.log('[DEBUG] Test failed:', test.title); + console.log('[DEBUG] Error:', error ? error.message : 'No error message'); + console.log('[DEBUG] Duration:', duration); + + // Capture browser console logs + try { + const logs = await browser.getLogs('browser'); + console.log('[DEBUG] Browser console logs:'); + logs.forEach(log => { + console.log(` [${log.level}] ${log.message}`); + }); + } catch (e) { + console.log('[DEBUG] Could not retrieve browser logs:', e.message); + } + + // Capture current URL + try { + const url = await browser.getUrl(); + console.log('[DEBUG] Current URL:', url); + } catch (e) { + console.log('[DEBUG] Could not retrieve URL:', e.message); + } + /* * get the current date and clean it * const date = (new Date()).toString().replace(/\s/g, '-').replace(/-\(\w+\)/, ''); @@ -279,7 +339,7 @@ export const config: WebdriverIO.Config = { const filePath = path.resolve(this.screenshotPath, `${filename}.png`); console.log('Saving screenshot to:', filePath); - browser.saveScreenshot(filePath); + await browser.saveScreenshot(filePath); console.log('Saved screenshot to:', filePath); }, /**