diff --git a/.github/workflows/cypress_tests.yml b/.github/workflows/cypress_tests.yml index 88d4455b1..7cf501182 100644 --- a/.github/workflows/cypress_tests.yml +++ b/.github/workflows/cypress_tests.yml @@ -42,7 +42,7 @@ jobs: uses: cypress-io/github-action@v6 with: browser: chrome - spec: cypress/e2e/spec.cy.js + spec: e2e/spec.cy.js working-directory: ./cypress - name: Archive screenshots on failure if: failure() diff --git a/cypress/cypress.config.js b/cypress/cypress.config.js index 8376fc4c6..0d683a4cc 100644 --- a/cypress/cypress.config.js +++ b/cypress/cypress.config.js @@ -1,32 +1,34 @@ const { defineConfig } = require('cypress') const os = require('os'); + module.exports = defineConfig({ projectId: 'u864mu', env: { codeCoverage: { url: 'http://localhost:3000/__coverage__' }, - home: os.homedir(), + home: os.homedir(), + localhost_password: '123456', }, e2e: { experimentalStudio: true, + // Project root is ./cypress (CI working-directory). Prefer top-level e2e/support + // over the legacy nested cypress/cypress/ tree. + specPattern: 'e2e/**/*.cy.{js,jsx,ts,tsx}', + supportFile: 'support/e2e.js', setupNodeEvents(on, config) { on('task', { log(message) { - // Then to see the log messages in the terminal - // cy.task("log", "my message"); - console.log(message +'\n\n'); + console.log(message + '\n\n'); return null; }, }); on('after:screenshot', (details) => { console.log('Screenshot taken:', details); return null; - });} - + }); }, - screenshotsFolder: 'cypress/screenshots', - videosFolder: 'cypress/videos', - - + }, + screenshotsFolder: 'screenshots', + videosFolder: 'videos', }) diff --git a/cypress/cypress/e2e/spec.cy.js b/cypress/cypress/e2e/spec.cy.js index 64adf44bf..cd74e42e3 100644 --- a/cypress/cypress/e2e/spec.cy.js +++ b/cypress/cypress/e2e/spec.cy.js @@ -3,6 +3,7 @@ Cypress.on('uncaught:exception', (err, runnable) => { // Returning false prevents Cypress from failing the test console.error('Uncaught exception occurred:', err); + return false; }); @@ -15,21 +16,150 @@ describe('Application Build Check', () => { }); }); it('Navigating through intro', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('#main-general-content').click(); - cy.get('.lead > b').should('be.visible'); + // visitGeoweaver already logs in and dismisses IntroJS when present + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#main-general-content', { timeout: 10000 }).should('exist').click({ force: true }); + cy.get('.lead').should('be.visible'); + cy.get('.lead b, .lead > b').should('exist'); }) }); + +// describe('User Signup Test', () => { + // it('Test unsuccessful signup with already registered email address', () => { + + // cy.visit('http://localhost:8070/Geoweaver'); + // cy.get('.introjs-skipbutton').click(); + + // cy.get('#toolbar-loginout-a > .fa').click() + + // cy.get('[onclick="GW.user.signupdialog()"]', { timeout: 10000 }).should('be.visible'); + // cy.get('[onclick="GW.user.signupdialog()"]').click() + + // cy.get('#username').type('newuser'); + // cy.get('input[name="username"]'); + + // cy.get('#password').type('Geoweaver@123'); + + // cy.get('#rpassword').type('Geoweaver@123'); + // cy.get('input[name="email"]').type('newuser@example.com'); + // cy.get('#agree_yes').click() + + // // Submit the form + + // cy.get('[onclick="GW.user.register()"]').click() + // cy.contains("the email address has already been registered") + // }); + +// it('Test unsuccessful signup without email address', () => { + +// cy.visit('http://localhost:8070/Geoweaver'); +// cy.get('.introjs-skipbutton').click(); + +// cy.get('#toolbar-loginout-a > .fa').click() + +// cy.get('[onclick="GW.user.signupdialog()"]').click() + +// cy.get('#username').type('newuser'); +// cy.get('input[name="username"]') + +// cy.get('#password').type('Geoweaver@123'); + +// cy.get('#rpassword').type('Geoweaver@123'); + +// cy.get('#agree_yes').click(); + +// cy.get('[onclick="GW.user.register()"]').click(); +// cy.contains("Email is missing!"); +// }); + + +// it('Test unsuccessful signup if user didnot agree for the terms and conditions', () => { + +// cy.visit('http://localhost:8070/Geoweaver'); +// cy.get('.introjs-skipbutton').click(); + +// cy.get('#toolbar-loginout-a > .fa').click() + +// cy.get('[onclick="GW.user.signupdialog()"]').click() + +// cy.get('#username').type('newuser'); +// cy.get('input[name="username"]') + +// cy.get('#password').type('Geoweaver@123'); + +// cy.get('#rpassword').type('Geoweaver'); +// cy.get('input[name="email"]').type('newuser@example.com'); +// cy.get('#agree_no').click() + +// cy.get('[onclick="GW.user.register()"]').click() +// cy.contains("The reentered password doesn't match!") +// }); +// }); + +// describe('User Login Test', () => { +// it('allows an existing user to log in', () => { +// cy.visit('http://localhost:8070/Geoweaver'); +// cy.get('.introjs-skipbutton').click(); + +// cy.get('#toolbar-loginout-a > .fa').click(); + +// cy.get('[onclick="GW.user.logindialog()"]', { timeout: 10000 }).should('be.visible'); + +// cy.get('#username').type('newuser'); +// cy.get('#password').type('Geoweaver@123'); + +// cy.get('[onclick="GW.user.login()"]').click(); +// }); + +// it('Test unsuccessful Login with incorrect password', () => { +// cy.visit('http://localhost:8070/Geoweaver'); +// cy.get('.introjs-skipbutton').click(); +// cy.get('#toolbar-loginout-a > .fa').click(); + +// cy.get('#username').type('testuser'); +// cy.get('#password').type('wrongPassword123'); + +// cy.get('[onclick="GW.user.login()"]').click(); + +// cy.contains("Failed to log in.") +// }); + +// it('Test unsuccessful Login without password', () => { +// cy.visit('http://localhost:8070/Geoweaver'); +// cy.get('.introjs-skipbutton').click(); +// cy.get('#toolbar-loginout-a > .fa').click(); + +// cy.get('#username').type('testuser'); + + +// cy.get('[onclick="GW.user.login()"]').click(); + +// cy.contains("Either usename or password is missing.") +// }); +// }); + +// describe('Forgot Password Test', () => { +// it('forgot password test', () => { +// cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); +// cy.get('.introjs-skipbutton').click(); +// cy.get('#toolbar-loginout-a > .fa').click(); +// cy.get(':nth-child(4) > .col-md-12 > .btn').click(); +// cy.get('#email').type('newuser@example.com'); +// cy.get('#reset-password-form > :nth-child(1) > .col-md-12').click(); +// cy.get('[onclick="GW.user.resetPassword()"]').click(); +// cy.get('#reset-password-form > h3').click(); +// cy.get('#reset-password-form').click(); +// cy.get('[style="border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; visibility: visible; position: absolute; left: 0px; box-sizing: content-box; top: 0px; width: 100%; height: 100%; background-color: rgb(255, 255, 255); overflow: auto;"]').click(); +// cy.get('#reset-password-form > h3').click(); +// cy.get('#reset-password-form > h3').should('be.visible'); +// cy.get('#reset-password-form > :nth-child(3) > a').should('be.visible'); +// }) +// }) + describe('Navigation', () => { it('should navigate to different pages', () => { - cy.visit('http://localhost:8070/Geoweaver'); - cy.get('.introjs-skipbutton').click(); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.contains('Log' , { timeout: 10000 }).click(); cy.contains('Logging').should('be.visible'); @@ -57,24 +187,22 @@ describe('Navigation', () => { }); }); - describe('Host Testing', () => { it('should submit a create new host form successfully', () => { - cy.visit('http://localhost:8070/Geoweaver'); - cy.get('.introjs-skipbutton').click(); - cy.get('#newhost').click(); - - cy.get('#hostip').type('1.1.1.1'); - cy.get('#hostport').type('8000'); - cy.get('#username').type('newuser'); - - cy.get('#host-add-btn').click(); - cy.get('.nav-side-menu').contains('New Host') - }); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#newhost').click(); + + cy.get('#hostip').type('1.1.1.1'); + cy.get('#hostport').type('8000'); + cy.get('#username').type('newuser'); + + cy.get('#host-add-btn').click(); + cy.get('.nav-side-menu').contains('New Host') + }); + it('Search result should be successful after creating the host', () => { /* ==== Generated with Cypress Studio ==== */ - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click(); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('[data-intro="All the other tools"]').click(); cy.get('#toolbar-search').click(); cy.get('#keywords').clear('New Host'); @@ -92,86 +220,364 @@ describe('Host Testing', () => { '[style="position: absolute; top: -18px; left: 8px; width: 8px; height: 8px; cursor: pointer; margin: 0px; padding: 0px; box-sizing: content-box; font-family: sans-serif; text-align: center; font-size: 8px; line-height: 8px; border-width: 1px; border-radius: 5px; border-color: rgb(252, 97, 92); border-style: solid; background-color: rgb(252, 97, 92); color: white; z-index: 50; user-select: none;"]' ).click(); }) - - -// it('Global Search for Host', () => { -// cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); -// cy.get('.introjs-skipbutton').click(); -// cy.get('#instant_search_bar').clear('N'); -// cy.get('#instant_search_bar').type('New '); -// cy.get('#host-b4ijvz').should('be.visible'); -// }) }); describe('Process Testing', () => { + it('Create Shell Process', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#newprocess').click().then(() => { + // Ensure the window or form is fully loaded and active + cy.get('form', { timeout: 10000 }).should('be.visible'); // Wait until the form is visible + }); + cy.get('form > :nth-child(1) > :nth-child(4)').clear('t'); + cy.get('form > :nth-child(1) > :nth-child(4)').type('shell_test').then( + () => { + cy.get('.new-process-code-area', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('.modal-footer').contains('Add').click().then( + () => { + cy.get('ul#process_folder_shell_target', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('ul#process_folder_shell_target').should('contain', 'shell_test'); + }) - it('Create Python Process', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click(); - cy.get('#newprocess').click(); - cy.get('form select.form-control.form-control-sm').select('Python'); - cy.get('form > :nth-child(1) > :nth-child(4)').clear('t'); - cy.get('form > :nth-child(1) > :nth-child(4)').type('python_test'); - cy.get('.modal-footer').contains('Add').click(); - cy.get('ul#process_folder_python_target').should('contain', 'python_test'); - }) + it('Create Python Process', () => { + // Visit the Geoweaver web page + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#newprocess').click(); + + // Ensure that the new process code area is visible before proceeding + cy.get(".new-process-code-area", { timeout: 10000 }).should("be.visible"); + + // Select Python from the form dropdown + cy.get('form select.form-control.form-control-sm').select('Python'); + + // Clear the input field and type the new process name + cy.get('form > :nth-child(1) > :nth-child(4)').clear().type('python_test_2'); + + // Click the "Add" button and ensure the process folder is visible + cy.get('.modal-footer').contains('Add').click(); + + // Use cy.wait() to ensure any delays are accounted for before checking the process folder + cy.wait(3000); // You can increase this value if necessary to ensure the process list is visible + + // Ensure the process folder is visible and contains the new process + cy.get('ul#process_folder_python_target', { timeout: 10000 }).should('be.visible'); + cy.get('ul#process_folder_python_target').should('contain', 'python_test_2'); + }); + }); describe('Add Process to Weaver', () => { - it('Add to weaver - python', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click(); - cy.get('#process_folder_python').click(); - cy.wait(3000); - cy.get('ul#process_folder_python_target').contains('button', 'Add to Weaver').click(); + it('Add to weaver', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( + () => { + cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('#process_folder_shell').click().then( + () => { + cy.get('ul#process_folder_shell_target', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('ul#process_folder_shell_target').contains('button', 'Add to Weaver').click(); cy.get('circle').should('be.visible'); }) }); +describe('Edit Process Name', () => { + it('Add to weaver', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( + () => { + cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('#process_folder_shell').click().then( + () => { + cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('ul#process_folder_shell_target').contains('shell_test').click().then( + () => { + cy.get('#processname', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('#processname').should('be.visible').and('not.be.disabled'); + cy.get('#processname').clear(); + cy.get('#processname', { timeout: 10000 }).should('be.visible'); // Waits for up to 10 seconds + cy.get('#processname').type('updated_shell_test', { force: true }) // Type the text + .should('have.value', 'updated_shell_test'); // Check the value + cy.get('.process-edit-right-icon').click(); + cy.get('ul#process_folder_shell_target').should('contain', 'updated_shell_test'); + + }) + it('process category and id should be disabled', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( + () => { + cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('#process_folder_shell').click(); + cy.get('ul#process_folder_shell_target').contains('updated_shell_test').click(); + cy.get('#processcategory').should('be.disabled'); + cy.get('#processid').should('be.disabled') + }) + + it('Create and Edit Python Process', function() { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#process_folder_python').click(); + cy.get('#newprocess').click(); + cy.get('form select.form-control.form-control-sm').select('python'); + cy.get('form > :nth-child(1) > :nth-child(4)').clear('te'); + cy.get('form > :nth-child(1) > :nth-child(4)').type('test_edit'); + + // Wait for the Monaco editor to load + cy.window().then((win) => { + return new Cypress.Promise((resolve) => { + const checkMonaco = () => { + const editors = win.monaco?.editor?.getEditors(); // Get all Monaco editors + cy.log(`Total Monaco Editors found: ${editors?.length || 0}`); // Log editor count + + if (editors && editors.length > 0) { + const visibleEditor = editors.find((editor) => { + const isVisible = Cypress.$(editor.getDomNode()).is(':visible'); // Check if visible + cy.log(`Editor visible: ${isVisible}`); + return isVisible; + }); + + if (visibleEditor) { + cy.log("Using visible Monaco editor"); + resolve(visibleEditor); + } else { + cy.log("No visible Monaco editor found, retrying..."); + setTimeout(checkMonaco, 500); // Retry if no visible editor is found + } + } else { + setTimeout(checkMonaco, 500); // Retry if no editors are found + } + }; + checkMonaco(); + }); + }).then((editor) => { + if (editor) { + cy.log("Found visible Monaco Editor:", editor); + const model = editor.getModel(); + if (model) { + cy.wrap(null).should(() => { + // Use Monaco's API to simulate actual typing + editor.focus(); // Ensure the editor is focused + editor.executeEdits("", [{ range: model.getFullModelRange(), text: "print('hello world!')" }]); + editor.trigger("keyboard", "type", { text: "" }); // Simulate a keystroke to trigger Monaco's change detection + }); + + // Wait for the Monaco editor to reflect the change + cy.wrap(null).should(() => { + expect(model.getValue()).to.contains("hello world"); + }); + } + } + }); + + cy.get('.view-lines').click(); + cy.get('.modal-footer').contains('Add').click(); + + // Wait for the Monaco editor to load + cy.window().then((win) => { + return new Cypress.Promise((resolve) => { + const checkMonaco = () => { + const editors = win.monaco?.editor?.getEditors(); // Get all Monaco editors + cy.log(`Total Monaco Editors found: ${editors?.length || 0}`); // Log editor count + + if (editors && editors.length > 0) { + const visibleEditor = editors.find((editor) => { + const isVisible = Cypress.$(editor.getDomNode()).is(':visible'); // Check if visible + cy.log(`Editor visible: ${isVisible}`); + return isVisible; + }); + + if (visibleEditor) { + cy.log("Using visible Monaco editor"); + resolve(visibleEditor); + } else { + cy.log("No visible Monaco editor found, retrying..."); + setTimeout(checkMonaco, 500); // Retry if no visible editor is found + } + } else { + setTimeout(checkMonaco, 500); // Retry if no editors are found + } + }; + checkMonaco(); + }); + }).then((editor) => { + if (editor) { + cy.log("Found visible Monaco Editor:", editor); + const model = editor.getModel(); + if (model) { + cy.wrap(null).should(() => { + // Use Monaco's API to simulate actual typing + editor.focus(); // Ensure the editor is focused + editor.executeEdits("", [{ range: model.getFullModelRange(), text: "\ntest edit" }]); + editor.trigger("keyboard", "type", { text: "" }); // Simulate a keystroke to trigger Monaco's change detection + }); + + // Wait for the Monaco editor to reflect the change + cy.wrap(null).should(() => { + expect(model.getValue()).to.contains("test edit"); + }); + } + } + }); + }); +}); describe('Delete Process', () => { + it('Delete Shell Process', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( + () => { + cy.get('#processes', { timeout: 5000 }).should('be.visible'); + } + ); + cy.get('#process_folder_shell').click().then( + () => { + cy.get('#process_folder_shell_target', { timeout: 10000 }).should('be.visible'); + } + ); + cy.get('ul#process_folder_shell_target').contains('updated_shell_test').click(); + // Delete button now only has icon, no text - find by title or icon + cy.get('button[title="Delete Process"]').click(); + cy.get('#del-confirm-btn').click(); + cy.get('#main-general-content').click(); + cy.get('[style="color:rgb(38, 90, 139);text-align:center;font-family:\'lato\', sans-serif;font-size:80px"]').should('be.visible'); + }) + it('Delete Python Process', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click().then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { - cy.get('#process_folder_python > a', { timeout: 10000 }).should('be.visible'); + cy.get('#process_folder_python', { timeout: 10000 }).should("be.visible") } - ) + ); cy.get('#process_folder_python').click(); - cy.wait(1000) - cy.get('ul#process_folder_python_target').contains('python_test').click(); + cy.get('ul#process_folder_python_target').contains('python_test_2').click(); // Delete button now only has icon, no text - find by title or icon cy.get('button[title="Delete Process"]').click(); cy.get('#del-confirm-btn').click(); - cy.wait(1000); cy.get('#main-general-content').click(); - cy.wait(1000); cy.get('[style="color:rgb(38, 90, 139);text-align:center;font-family:\'lato\', sans-serif;font-size:80px"]').should('be.visible'); }) }); +describe('Write Password into .secret', () => { + it('Should write secret to a file', () => { + // Hash value to be written to the file + const dataToWrite = '4205c81c1aaafae4406dc56bd6c8b26edeb816c6d18294cf0aeee4a948146e0fa3e7cf0ea3e3a6de0b7fe990d7de28ec3060f953b88e4cef5ade04c12ff917ee'; + const homeDirectory = Cypress.env('home'); + + cy.log('Home Directory:', homeDirectory); // Debug statement + const filePath = `${homeDirectory}/gw-workspace/.secret`; + console.log(filePath) + cy.debug('Detailed debugging information home dire is ',homeDirectory); + cy.task('log', 'Logging home dire' + homeDirectory) + cy.task('log', 'Logging filepath' + filePath) + cy.log('File Path:', filePath); // Debug statement + cy.writeFile(filePath, dataToWrite, 'binary') + .then(() => { + cy.readFile(filePath).should('contain', dataToWrite); + cy.readFile(filePath).then((fileContents) => { + cy.task('log', 'File Contents: ' + fileContents); + cy.log('File Contents:', fileContents); + }); + }) + }); +}); + describe('Create Python process and run it', () => { - it('creates python process and runs test', function() { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click(); - cy.get('#process_folder_python').click(); + it('creates python process and runs', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('#newprocess').click(); - cy.get('[id^="processcategory-"]').select('python'); - cy.get('[id^="processname-"]').clear().type('check_this'); - cy.get('.view-lines').click(); - cy.get('[id^="add-process-"]').last().click(); - cy.get('[id^="process-"] > .row > .col-md-8 > span').last().click(); - cy.get('[onclick*="runProcess"]').last().click(); + + cy.get('form select.form-control.form-control-sm').select('Python'); + cy.get('form > :nth-child(1) > :nth-child(4)').type('hello_world.py'); + + cy.get('.modal-footer').contains('Add').click(); + + cy.get('ul#process_folder_python_target').contains('hello_world.py').click(); + + cy.window().then((win) => { + return new Cypress.Promise((resolve) => { + const checkMonaco = () => { + const editors = win.monaco?.editor?.getEditors(); // Get all Monaco editors + cy.log(`Total Monaco Editors found: ${editors?.length || 0}`); // Log editor count + + if (editors && editors.length > 0) { + const visibleEditor = editors.find((editor) => { + const isVisible = Cypress.$(editor.getDomNode()).is(':visible'); // Check if visible + cy.log(`Editor visible: ${isVisible}`); + return isVisible; + }); + + if (visibleEditor) { + cy.log("Using visible Monaco editor"); + resolve(visibleEditor); + } else { + cy.log("No visible Monaco editor found, retrying..."); + setTimeout(checkMonaco, 500); // Retry if no visible editor is found + } + } else { + setTimeout(checkMonaco, 500); // Retry if no editors are found + } + }; + checkMonaco(); + }); + }).then((editor) => { + if (editor) { + cy.log("Found visible Monaco Editor:", editor); + const model = editor.getModel(); + if (model) { + cy.wrap(null).should(() => { + editor.focus(); // Ensure the editor is focused + model.setValue("print('hello world!')"); // Set value + editor.trigger("keyboard", "type", { text: "" }); // Simulate keystroke to force UI update + }); + + // **Wait until Monaco editor actually reflects the new value** + cy.wrap(null).should(() => { + expect(model.getValue().trim()).to.equal("print('hello world!')"); + }); + } + } + }); + + cy.get('#main-process-info', { timeout: 10000 }).should("be.visible") + + cy.log("now should change the content") + + cy.get('#processid').then(($input) => { + const processId = $input.val(); // Get the value of the input field + console.log('process id ',processId) + const selector = `[onclick="GW.process.runProcess('${processId}', 'hello_world.py', 'python')"]`; + cy.get(selector).click(); // Perform actions using the dynamically constructed selector + }); + cy.intercept('POST', '/Geoweaver/web/executeProcess').as('executeProcess'); // Intercept the POST request cy.get('#host-execute-btn').click(); + cy.get('#process-confirm-btn').click(); - cy.get('#inputpswd').clear().type('123456'); + cy.get('#inputpswd').clear('1'); + cy.get('#inputpswd').type('123456'); cy.get('#pswd-confirm-btn').click(); - cy.get('#process-log-window > :nth-child(3)').click(); - cy.get('#process-log-window > :nth-child(3) > span').should('be.visible'); + + cy.wait(5000); + + cy.get('#single-console-content').should('contain', 'hello world!'); + cy.get('#single-console-content').should('contain', 'Exit Code: 0'); + cy.get('#process-log-window').click(); + cy.get('#process-log-window').should('be.visible'); + cy.wait('@executeProcess').its('response.statusCode').should('eq', 200); }); }); @@ -179,8 +585,7 @@ describe('Create Python process and run it', () => { describe('Hosts Testing', () => { it('Create New Host', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click(); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('#newhost').click(); cy.get('#hostip').clear('1'); cy.get('#hostip').type('1.1.1.1'); @@ -193,11 +598,7 @@ describe('Hosts Testing', () => { }) it('Delete New Host', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click().then(() => { - // Ensure the window or form is fully loaded and active - cy.get('#host_folder_ssh > a', { timeout: 10000 }).should('be.visible'); // Wait until the form is visible - }); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('#host_folder_ssh > a').click(); cy.get('ul#host_folder_ssh_target').contains('New Host').click(); // Delete button now uses fa-trash icon instead of fa-minus, and has "Delete" text @@ -205,63 +606,31 @@ describe('Hosts Testing', () => { cy.get('#del-confirm-btn').click(); }) + it('LocalHost testing', () => { + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); + cy.get('#host_folder_ssh > a').click(); + cy.get('#host-100001').click(); + cy.get('#_host_name').should('have.value', 'Localhost'); + cy.get('#_host_ip').should('have.value', '127.0.0.1'); + cy.get('#_host_port').should('have.value', '22'); + cy.get('#_host_username').should('have.value', 'publicuser'); + cy.get('#_host_url').should('have.value', 'http://localhost/'); + cy.get('#_host_type').click(); + cy.get('#_host_type').should('have.text', 'ssh'); + }) + it('LocalHost Read Python Env', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click().then(() => { - // Ensure the window or form is fully loaded and active - cy.get('#host_folder_ssh > a', { timeout: 10000 }).should('be.visible'); // Wait until the form is visible - }); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('#host_folder_ssh > a').click(); cy.get('#host-100001').click(); - // Click on Python Env tab instead of .fab cy.contains('Python Env').click(); - cy.get('#inputpswd').clear('1'); - cy.get('#inputpswd').type('123456'); + cy.intercept('POST', '**/readEnvironment').as('readEnvironment'); + cy.get('#inputpswd').clear().type('123456'); cy.get('#pswd-confirm-btn').click(); - cy.intercept('POST', '/Geoweaver/web/readEnvironment').as('readEnvironment'); cy.wait('@readEnvironment').then((interception) => { - // Log the full response to the console - console.log('Read Environment Response:', interception.response.body); - - // Assert the HTTP status code is 200 + cy.log(interception.response); expect(interception.response.statusCode).to.equal(200); - - // Verify that the response body exists - expect(interception.response.body).to.exist; - - // You can add more specific assertions about the response content - // For example, if the response contains a success message or specific data structure - // expect(interception.response.body).to.have.property('success'); - }); - }) - - it('LocalHost File Upload', () => { - cy.visit('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('.introjs-skipbutton').click().then(() => { - // Ensure the window or form is fully loaded and active - cy.get('#host_folder_ssh > a', { timeout: 10000 }).should('be.visible'); // Wait until the form is visible }); - cy.get('#host_folder_ssh > a').click(); - cy.get('#host-100001').click(); - // Click on Upload tab instead of p > .fa-upload - cy.contains('Upload').click(); - - // Set up intercept BEFORE clicking the confirm button - cy.intercept('POST', '**/authenticateUser').as('authenticateUser'); - - cy.get('#inputpswd').clear('1'); - cy.get('#inputpswd').type('123456'); - cy.get('#pswd-confirm-btn').click(); - - // Wait for authenticateUser request (for localhost, this authenticates Geoweaver password) - cy.wait('@authenticateUser', { timeout: 10000 }).its('response.statusCode').should('eq', 200); - - // File uploader is now in upload-tab-pane, not host-file-uploader - // Wait for upload dialog to appear in the tab pane after authentication - cy.get('#upload-tab-pane', { timeout: 10000 }).should('be.visible'); - cy.get('#upload-tab-pane').should('contain', 'File Uploader'); - // Verify the drag-and-drop zone is present - cy.get('#drag-and-drop-zone', { timeout: 5000 }).should('be.visible'); }) }); diff --git a/cypress/cypress/support/commands.js b/cypress/cypress/support/commands.js index 66ea16ef0..12cba9fc7 100644 --- a/cypress/cypress/support/commands.js +++ b/cypress/cypress/support/commands.js @@ -1,25 +1,97 @@ // *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file + // Custom Cypress commands for Geoweaver E2E tests + // *********************************************** + +/** + * Login to Geoweaver with localhost password when login_required is enabled. + */ +Cypress.Commands.add('loginIfRequired', () => { + cy.request({ + url: 'http://localhost:8070/Geoweaver/web/key', + method: 'GET', + failOnStatusCode: false, + }).then((keyResponse) => { + if (keyResponse.status === 200 && keyResponse.body && keyResponse.body.publicKey) { + const publicKey = keyResponse.body.publicKey; + const password = Cypress.env('localhost_password') || '123456'; + + cy.window().then((win) => { + if (!win.JSEncrypt) { + cy.log('JSEncrypt not found in window; UI login path will be used if needed'); + return; + } + + const encrypt = new win.JSEncrypt(); + encrypt.setPublicKey(publicKey); + const encryptedPassword = encrypt.encrypt(password); + + cy.request({ + url: 'http://localhost:8070/Geoweaver/web/authenticateLocalhost', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `encryptedPassword=${encodeURIComponent(encryptedPassword)}`, + followRedirect: false, + failOnStatusCode: false, + }).then(() => { + cy.log('API login completed'); + }); + }); + } else { + cy.log('Login not required, skipping authentication'); + } + }); +}); + +/** + * Dismiss IntroJS if it is present. Safe when intro never appears + * (e.g. after login redirect races, or intro disabled). + */ +Cypress.Commands.add('skipIntroIfPresent', () => { + cy.get('body', { timeout: 15000 }).should('be.visible'); + // IntroJS starts asynchronously after GW.main.init + cy.wait(1500); + cy.get('body').then(($body) => { + if ($body.find('.introjs-overlay').length === 0) { + return; + } + if ($body.find('.introjs-skipbutton').length > 0) { + cy.get('.introjs-skipbutton').click({ force: true }); + } else if ($body.find('.introjs-donebutton').length > 0) { + cy.get('.introjs-donebutton').click({ force: true }); + } else if ($body.find('.introjs-nextbutton').length > 0) { + for (let i = 0; i < 8; i++) { + cy.get('body').then(($b) => { + if ($b.find('.introjs-nextbutton').length > 0) { + cy.get('.introjs-nextbutton').click({ force: true }); + } else if ($b.find('.introjs-donebutton').length > 0) { + cy.get('.introjs-donebutton').click({ force: true }); + } + }); + } + } + }); + cy.get('.introjs-overlay').should('not.exist'); +}); + +/** + * Visit Geoweaver, handle localhost login when required, then dismiss intro. + */ +Cypress.Commands.add('visitGeoweaver', (url = 'http://localhost:8070/Geoweaver/web/geoweaver') => { + cy.visit(url); + + cy.url({ timeout: 15000 }).then((currentUrl) => { + if (currentUrl.includes('/localhost-login')) { + const password = Cypress.env('localhost_password') || '123456'; + cy.get('input[type="password"]', { timeout: 10000 }).should('be.visible').clear().type(password); + cy.get('button').contains(/login/i, { timeout: 10000 }).click(); + cy.url({ timeout: 15000 }).should('not.include', '/localhost-login'); + } + }); + + cy.get('body', { timeout: 15000 }).should('be.visible'); + // Side menu is a reliable signal that the main app shell loaded + cy.get('.nav-side-menu', { timeout: 20000 }).should('exist'); + cy.skipIntroIfPresent(); +}); diff --git a/cypress/e2e/spec.cy.js b/cypress/e2e/spec.cy.js index ca6a388bb..cd74e42e3 100644 --- a/cypress/e2e/spec.cy.js +++ b/cypress/e2e/spec.cy.js @@ -16,21 +16,11 @@ describe('Application Build Check', () => { }); }); it('Navigating through intro', () => { + // visitGeoweaver already logs in and dismisses IntroJS when present cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - // Wait for IntroJS to appear, or skip if it doesn't appear (login might have skipped it) - cy.get('body').then(($body) => { - if ($body.find('.introjs-nextbutton').length > 0) { - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - cy.get('.introjs-nextbutton').click(); - } else if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); - cy.get('#main-general-content').click(); - cy.get('.lead > b').should('be.visible'); + cy.get('#main-general-content', { timeout: 10000 }).should('exist').click({ force: true }); + cy.get('.lead').should('be.visible'); + cy.get('.lead b, .lead > b').should('exist'); }) }); @@ -169,13 +159,7 @@ describe('Application Build Check', () => { describe('Navigation', () => { it('should navigate to different pages', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - // Skip intro if it appears - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.contains('Log' , { timeout: 10000 }).click(); cy.contains('Logging').should('be.visible'); @@ -205,12 +189,7 @@ describe('Navigation', () => { describe('Host Testing', () => { it('should submit a create new host form successfully', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); cy.get('#newhost').click(); cy.get('#hostip').type('1.1.1.1'); @@ -224,11 +203,6 @@ describe('Host Testing', () => { it('Search result should be successful after creating the host', () => { /* ==== Generated with Cypress Studio ==== */ cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('[data-intro="All the other tools"]').click(); cy.get('#toolbar-search').click(); cy.get('#keywords').clear('New Host'); @@ -251,11 +225,6 @@ describe('Host Testing', () => { describe('Process Testing', () => { it('Create Shell Process', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#newprocess').click().then(() => { // Ensure the window or form is fully loaded and active cy.get('form', { timeout: 10000 }).should('be.visible'); // Wait until the form is visible @@ -277,13 +246,6 @@ describe('Process Testing', () => { it('Create Python Process', () => { // Visit the Geoweaver web page cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - - // Skip intro and go to process creation - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#newprocess').click(); // Ensure that the new process code area is visible before proceeding @@ -312,12 +274,7 @@ describe('Process Testing', () => { describe('Add Process to Weaver', () => { it('Add to weaver', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }).then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); } @@ -334,12 +291,7 @@ describe('Add Process to Weaver', () => { describe('Edit Process Name', () => { it('Add to weaver', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }).then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); } @@ -364,12 +316,7 @@ describe('Edit Process Name', () => { }) it('process category and id should be disabled', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }).then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { cy.get('#process_folder_shell', { timeout: 10000 }).should('be.visible'); } @@ -382,11 +329,6 @@ describe('Edit Process Name', () => { it('Create and Edit Python Process', function() { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#process_folder_python').click(); cy.get('#newprocess').click(); cy.get('form select.form-control.form-control-sm').select('python'); @@ -495,12 +437,7 @@ describe('Edit Process Name', () => { describe('Delete Process', () => { it('Delete Shell Process', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }).then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { cy.get('#processes', { timeout: 5000 }).should('be.visible'); } @@ -519,12 +456,7 @@ describe('Delete Process', () => { }) it('Delete Python Process', () => { - cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }).then( + cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver').then( () => { cy.get('#process_folder_python', { timeout: 10000 }).should("be.visible") } @@ -567,11 +499,6 @@ describe('Write Password into .secret', () => { describe('Create Python process and run it', () => { it('creates python process and runs', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#newprocess').click(); cy.get('form select.form-control.form-control-sm').select('Python'); @@ -659,11 +586,6 @@ describe('Create Python process and run it', () => { describe('Hosts Testing', () => { it('Create New Host', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#newhost').click(); cy.get('#hostip').clear('1'); cy.get('#hostip').type('1.1.1.1'); @@ -677,11 +599,6 @@ describe('Hosts Testing', () => { it('Delete New Host', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#host_folder_ssh > a').click(); cy.get('ul#host_folder_ssh_target').contains('New Host').click(); // Delete button now uses fa-trash icon instead of fa-minus, and has "Delete" text @@ -691,11 +608,6 @@ describe('Hosts Testing', () => { it('LocalHost testing', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#host_folder_ssh > a').click(); cy.get('#host-100001').click(); cy.get('#_host_name').should('have.value', 'Localhost'); @@ -709,22 +621,14 @@ describe('Hosts Testing', () => { it('LocalHost Read Python Env', () => { cy.visitGeoweaver('http://localhost:8070/Geoweaver/web/geoweaver'); - cy.get('body').then(($body) => { - if ($body.find('.introjs-skipbutton').length > 0) { - cy.get('.introjs-skipbutton').click(); - } - }); cy.get('#host_folder_ssh > a').click(); cy.get('#host-100001').click(); - // Click on Python Env tab instead of .fab cy.contains('Python Env').click(); - cy.get('#inputpswd').clear('1'); - cy.get('#inputpswd').type('123456'); + cy.intercept('POST', '**/readEnvironment').as('readEnvironment'); + cy.get('#inputpswd').clear().type('123456'); cy.get('#pswd-confirm-btn').click(); - cy.intercept('POST', '/Geoweaver/web/readEnvironment').as('readEnvironment'); - cy.wait('@readEnvironment').then((interception) => { - cy.log(interception.response) + cy.log(interception.response); expect(interception.response.statusCode).to.equal(200); }); }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index d80d07e1b..12cba9fc7 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,41 +1,30 @@ // *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** + // Custom Cypress commands for Geoweaver E2E tests + // *********************************************** /** - * Login to Geoweaver with localhost password - * This command handles the login flow if login_required is enabled + * Login to Geoweaver with localhost password when login_required is enabled. */ Cypress.Commands.add('loginIfRequired', () => { - // First, try to get the RSA public key cy.request({ url: 'http://localhost:8070/Geoweaver/web/key', - method: 'GET' + method: 'GET', + failOnStatusCode: false, }).then((keyResponse) => { if (keyResponse.status === 200 && keyResponse.body && keyResponse.body.publicKey) { - // Login is required, perform login const publicKey = keyResponse.body.publicKey; - const password = Cypress.env('localhost_password') || '123456'; // Default password for testing - - // Encrypt password using JSEncrypt (we'll need to do this in the browser context) + const password = Cypress.env('localhost_password') || '123456'; + cy.window().then((win) => { - // Load JSEncrypt if not already loaded if (!win.JSEncrypt) { - cy.log('JSEncrypt not found, attempting to load it'); - return cy.wrap(null); + cy.log('JSEncrypt not found in window; UI login path will be used if needed'); + return; } - + const encrypt = new win.JSEncrypt(); encrypt.setPublicKey(publicKey); const encryptedPassword = encrypt.encrypt(password); - - // Perform login + cy.request({ url: 'http://localhost:8070/Geoweaver/web/authenticateLocalhost', method: 'POST', @@ -43,46 +32,66 @@ Cypress.Commands.add('loginIfRequired', () => { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `encryptedPassword=${encodeURIComponent(encryptedPassword)}`, - followRedirect: false - }).then((loginResponse) => { - // Login should succeed - cy.log('Login completed'); + followRedirect: false, + failOnStatusCode: false, + }).then(() => { + cy.log('API login completed'); }); }); } else { - // Login not required, skip cy.log('Login not required, skipping authentication'); } }); }); /** - * Visit Geoweaver page and handle login if required + * Dismiss IntroJS if it is present. Safe when intro never appears + * (e.g. after login redirect races, or intro disabled). + */ +Cypress.Commands.add('skipIntroIfPresent', () => { + cy.get('body', { timeout: 15000 }).should('be.visible'); + // IntroJS starts asynchronously after GW.main.init + cy.wait(1500); + cy.get('body').then(($body) => { + if ($body.find('.introjs-overlay').length === 0) { + return; + } + if ($body.find('.introjs-skipbutton').length > 0) { + cy.get('.introjs-skipbutton').click({ force: true }); + } else if ($body.find('.introjs-donebutton').length > 0) { + cy.get('.introjs-donebutton').click({ force: true }); + } else if ($body.find('.introjs-nextbutton').length > 0) { + for (let i = 0; i < 8; i++) { + cy.get('body').then(($b) => { + if ($b.find('.introjs-nextbutton').length > 0) { + cy.get('.introjs-nextbutton').click({ force: true }); + } else if ($b.find('.introjs-donebutton').length > 0) { + cy.get('.introjs-donebutton').click({ force: true }); + } + }); + } + } + }); + cy.get('.introjs-overlay').should('not.exist'); +}); + +/** + * Visit Geoweaver, handle localhost login when required, then dismiss intro. */ Cypress.Commands.add('visitGeoweaver', (url = 'http://localhost:8070/Geoweaver/web/geoweaver') => { cy.visit(url); - - // Check if we're on the login page - cy.url({ timeout: 5000 }).then((currentUrl) => { + + cy.url({ timeout: 15000 }).then((currentUrl) => { if (currentUrl.includes('/localhost-login')) { - // We're on login page, need to login - cy.get('input[type="password"]', { timeout: 10000 }).should('be.visible'); - - // Get the password from env or use default const password = Cypress.env('localhost_password') || '123456'; - - // Fill in password and submit - cy.get('input[type="password"]').type(password); - cy.get('button').contains('Login', { timeout: 10000 }).click(); - - // Wait for redirect to main page - cy.url({ timeout: 10000 }).should('not.include', '/localhost-login'); + cy.get('input[type="password"]', { timeout: 10000 }).should('be.visible').clear().type(password); + cy.get('button').contains(/login/i, { timeout: 10000 }).click(); + cy.url({ timeout: 15000 }).should('not.include', '/localhost-login'); } }); - - // Wait for page to fully load - cy.get('body', { timeout: 10000 }).should('be.visible'); - - // Wait a bit more for IntroJS to potentially start - cy.wait(1500); -}); \ No newline at end of file + + cy.get('body', { timeout: 15000 }).should('be.visible'); + // Side menu is a reliable signal that the main app shell loaded + cy.get('.nav-side-menu', { timeout: 20000 }).should('exist'); + cy.skipIntroIfPresent(); +});