diff --git a/.eslintrc.json b/.eslintrc.json index 831be302..aa0278a0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "env": { "browser": true, - "es2021": true + "es2021": true, + "cypress/globals": true }, "extends": ["plugin:react/recommended", "airbnb", "prettier"], "parserOptions": { @@ -11,7 +12,7 @@ "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": ["react", "prettier"], + "plugins": ["react", "prettier", "cypress"], "rules": { "prettier/prettier": "warn", "no-unused-vars": "warn", diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 00000000..48cd18f5 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,51 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs +# From https://github.com/dhxnveer/bhanwari-devi/blob/main/.github/workflows/tests.yml + +name: Node.js CI + +on: + push: + branches: [ "*" ] + pull_request: + types: [ opened, reopened ] + +jobs: + build: + name: test + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x, 15.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v3 + - name: Builds and tests ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Set npm legacy-peer-deps + run: npm config set legacy-peer-deps true + - run: npm install + - name: Set Environment Variables + run: | + echo "VITE_API_URL=https://dev-join.navgurukul.org/api/" >> .env.development + echo "VITE_API_URL=https://join.navgurukul.org/api/" >> .env.production + - run: npm ci + - run: npm run build --if-present + env: + CI: false + - name: Run npm run dev + run: | + npm run dev & + sleep 2 + - name: Run Cypress tests + run: npx cypress run --config-file cypress.config.js + - name: Archive test artifacts + uses: actions/upload-artifact@v2 + if: always() # always upload videos, even if the test fails + with: + name: cypress-videos + path: cypress/videos diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 00000000..a0eb17f6 --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,7 @@ +const { defineConfig } = require("cypress"); + +module.exports = defineConfig({ + e2e: { + setupNodeEvents() {}, + }, +}); diff --git a/cypress/cypress/support/commands.js b/cypress/cypress/support/commands.js new file mode 100644 index 00000000..119ab03f --- /dev/null +++ b/cypress/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// 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) => { ... }) diff --git a/cypress/cypress/support/e2e.js b/cypress/cypress/support/e2e.js new file mode 100644 index 00000000..3a252243 --- /dev/null +++ b/cypress/cypress/support/e2e.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import "./commands"; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/cypress/e2e/chromeRecorder.cy.js b/cypress/e2e/chromeRecorder.cy.js new file mode 100644 index 00000000..5ef76921 --- /dev/null +++ b/cypress/e2e/chromeRecorder.cy.js @@ -0,0 +1,142 @@ +//Used chrome extenstion Cypress Chrome Recorder +import "cypress-file-upload"; + +const firstNames = [ + "John", + "Jane", + "David", + "Emily", + "Michael", + "Olivia", + "Daniel", + "Sophia", + "Matthew", + "Ava", +]; +const lastNames = [ + "Wick", + "Johnson", + "Brown", + "Davis", + "Wilson", + "Anderson", + "Taylor", + "Clark", + "Walker", + "Moore", +]; + +const getRandomValueFromArray = (array) => { + const randomIndex = Math.floor(Math.random() * array.length); + return array[randomIndex]; +}; + +const getRandomFirstName = () => getRandomValueFromArray(firstNames); + +const getRandomLastName = () => getRandomValueFromArray(lastNames); + +const getRandomPhoneNumber = () => { + const randomNumber = Math.floor(Math.random() * 900000000) + 100000000; + return `9${randomNumber.toString()}`; +}; + +const getRandomGmailAddress = () => { + const randomString = Math.random().toString(36).substring(7); + return `random${randomString}@gmail.com`; +}; + +const getRandomBirthday = () => { + const currentYear = new Date().getFullYear(); + const minYear = currentYear - 28; + const maxYear = currentYear - 17; + const year = Math.floor(Math.random() * (maxYear - minYear + 1)) + minYear; + const month = Math.floor(Math.random() * 12) + 1; + const day = Math.floor(Math.random() * 28) + 1; + return `${day.toString().padStart(2, "0")}/${month + .toString() + .padStart(2, "0")}/${year}`; +}; + +describe("JohnWickTest", () => { + it("tests JohnWickTest", () => { + cy.viewport(1268, 937); + cy.visit("http://localhost:8080/"); + const randomPhoneNumber = getRandomPhoneNumber(); + const randomGmailAddress = getRandomGmailAddress(); + const randomBirthday = getRandomBirthday(); + const randomFirstName = getRandomFirstName(); + const randomLastName = getRandomLastName(); + cy.get("div:nth-of-type(3) > div:nth-of-type(1) input").click(); + cy.get("div:nth-of-type(3) > div:nth-of-type(1) input").type( + randomFirstName + ); + cy.get("div:nth-of-type(4) > div:nth-of-type(1) input").click(); + cy.get("div:nth-of-type(4) > div:nth-of-type(1) input").type( + randomLastName + ); + cy.get("div:nth-of-type(2) > div.MuiBox-root").click(); + cy.get("#app > div > div:nth-of-type(2)").click(); + cy.get("div:nth-of-type(4) > div:nth-of-type(2) input").click(); + cy.get("div:nth-of-type(4) > div:nth-of-type(2) input").type( + randomPhoneNumber + ); + cy.get(`[data-cy=submitButton]`).click(); + cy.get("#mui-component-select-Language").click(); + cy.get('[data-cy="ma"]').click(); + cy.get("#mui-component-select-Language").click(); + cy.get(`[data-cy="hi"]`).click(); + cy.get("#mui-component-select-Language").click(); + cy.get(`[data-cy="en"]`).click(); + cy.get("#mui-component-select-Language").should("contain", "English"); + cy.get("div:nth-of-type(12) > button").click(); + cy.get("div:nth-of-type(11) > button").click(); + cy.get("div:nth-of-type(11) > button").click(); + cy.get("div:nth-of-type(2) > div div.MuiContainer-root").click(); + cy.get("div:nth-child(4) > div input").click(); + cy.get("div:nth-child(4) > div input").type(randomBirthday); + cy.get("div:nth-child(7) > div input").click(); + cy.get("div:nth-child(7) > div input").type(randomGmailAddress); + cy.get("#gender").click(); + cy.contains("Female").click(); // can replace with transgender but not male + cy.get("#gender").should("contain", "Female"); //male is full + // profile picture + const fileName = "1mb.png"; + cy.fixture(fileName).then((fileContent) => { + cy.get('[data-cy="imageInput"]').attachFile({ + fileContent, + fileName, + mimeType: "image/jpeg", + }); + }); + cy.get("button:nth-child(3)").click(); + cy.get("#mui-component-select-state").click(); + cy.get("li:nth-of-type(5)").click(); + cy.get("#mui-component-select-district").click(); + cy.get("#menu-district > div.MuiBackdrop-root").click(); + cy.get("#mui-component-select-district").click(); + cy.get("li:nth-of-type(3)").click(); + cy.get("#city").click(); + cy.get("#city").type("Wash.D.C"); + cy.get("#pin_code").click(); + cy.get("#pin_code").type("456001"); + cy.get("#mui-component-select-current_status").click(); + cy.get("li:nth-of-type(4)").click(); + cy.get("#mui-component-select-qualification").click(); + cy.get("li:nth-of-type(5)").click(); + cy.get("div:nth-child(7) > div > div").click(); + cy.get("div:nth-child(7) > div > div").type("99.99"); + cy.get("div:nth-child(8) > div > div").click(); + cy.get("div:nth-child(8) > div > div").type("99.99"); + cy.get("#mui-component-select-school_medium").click(); + cy.get("li:nth-of-type(3)").click(); + cy.get("#mui-component-select-caste").click(); + cy.get("li:nth-of-type(4)").click(); + cy.get("#mui-component-select-religion").click(); + cy.get("li:nth-of-type(8)").click(); + cy.get("#mui-component-select-religion").click(); + cy.get("li:nth-of-type(7)").click(); + cy.get( + "#app > div > div:nth-of-type(2) button.MuiButton-contained" + ).click(); + }); +}); diff --git a/cypress/e2e/landingPage.cy.js b/cypress/e2e/landingPage.cy.js new file mode 100644 index 00000000..2007aec1 --- /dev/null +++ b/cypress/e2e/landingPage.cy.js @@ -0,0 +1,115 @@ +/// + +// TODO: Make links dynamic +beforeEach(() => { + cy.visit("http://localhost:8080/"); +}); + +describe("Section 1: Landing page", () => { + // TS101 + it("should display the logo top left, should redirect user to home page when logo is clicked", () => { + // display logo top left + cy.get('[data-cy="logo"]') + .should("have.css", "top", "0px") + .should("have.css", "left", "0px"); + + // clicking logo should redirect user to home page + cy.visit("http://localhost:8080/test/instructions"); + cy.get('[data-cy="logo"]').click(); + + cy.url().should("equal", "http://localhost:8080/"); + }); + // TS101 + it("should have a language dropdown with the options English, Hindi and Marathi: Verify language change", () => { + cy.get(`[data-cy="lang-dropdown"]`).should("have.value", ""); + + // open the select + cy.get(`[data-cy="lang-dropdown"]`).click(); + + // select English, verify change of language + cy.get(`[data-cy="en"]`).click(); + cy.get('[data-cy="title"]').contains("Start Admisssion Test"); + + // select Hindin verify change of language + cy.get(`[data-cy="lang-dropdown"]`).click(); + cy.get(`[data-cy="hi"]`).click(); + cy.get('[data-cy="title"]').contains("परीक्षा शुरू करें"); + + // select Marathi, change language + cy.get(`[data-cy="lang-dropdown"]`).click(); + cy.get('[data-cy="ma"]').click(); + cy.get('[data-cy="title"]').contains("प्रवेश परीक्षा सुरू करा"); + }); + + // TS103 + it("should type in user's first and last names, submit form and verify move to test instructions", () => { + cy.fixture("users").then((users) => { + const user = users[0]; + + cy.get(`[data-cy="firstName-input"]`).type(user.firstName); + cy.get(`[data-cy="lastName-input"]`).type(user.lastName); + cy.get(`[data-cy="mobileNumber-input"]`).type(user.mobileNumber); + cy.get(`[data-cy="submitButton"]`).click(); + cy.url().should("include", "test/instructions"); + }); + }); + + // TS103_02 + it("should type in user's first and last names AND MIDDLE NAME, submit form and verify move to test instructions", () => { + cy.fixture("users").then((users) => { + const user = users[0]; + + cy.get(`[data-cy="firstName-input"]`).type(user.firstName); + // Middle NAME + cy.get(`[data-cy="middleName-input"]`).type(user.middleName); + + cy.get(`[data-cy="lastName-input"]`).type(user.lastName); + cy.get(`[data-cy="mobileNumber-input"]`).type(user.mobileNumber); + cy.get(`[data-cy="submitButton"]`).click(); + cy.url().should("include", "test/instructions"); + }); + }); + + // TS105 + it("should submit button with invalid data, verify notification to user, URL should not change", () => { + // get the url before submit button is clicked + cy.url().then((urlBeforeSubmit) => { + cy.get(`[data-cy=submitButton]`).click(); + + // snackbar + cy.get('[data-cy="error-bar"]'); + + // get the url after submit button is clicked + cy.url().then((urlAfterSubmit) => { + // assert the URL before and after are the same + expect(urlBeforeSubmit).to.equal(urlAfterSubmit); + }); + }); + }); + + // TS105 Submit with invalid phone number + it("should submit button with invalid data, verify notification to user, URL should not change", () => { + cy.fixture("users").then((users) => { + const user = users[0]; + + // get the url before submit button is clicked + cy.url().then((urlBeforeSubmit) => { + // enter valid inputs for, first middle and last names: Invalid # Number + cy.get(`[data-cy="firstName-input"]`).type(user.firstName); + cy.get(`[data-cy="middleName-input"]`).type(user.middleName); + cy.get(`[data-cy="lastName-input"]`).type(user.lastName); + + cy.get(`[data-cy=submitButton]`).click(); + + // snackbar + cy.get('[data-cy="error-bar"]'); + + // get the url after submit button is clicked + cy.url().then((urlAfterSubmit) => { + // assert the URL before and after are the same + expect(urlBeforeSubmit).to.equal(urlAfterSubmit); + }); + }); + }); + }); +}); diff --git a/cypress/e2e/stubForRegistration.cy.js b/cypress/e2e/stubForRegistration.cy.js new file mode 100644 index 00000000..a05009fe --- /dev/null +++ b/cypress/e2e/stubForRegistration.cy.js @@ -0,0 +1,205 @@ +/// + +import "cypress-file-upload"; +const firstNames = [ + "John", + "Jane", + "David", + "Emily", + "Michael", + "Olivia", + "Daniel", + "Sophia", + "Matthew", + "Ava", +]; +const lastNames = [ + "Wick", + "Johnson", + "Brown", + "Davis", + "Wilson", + "Anderson", + "Taylor", + "Clark", + "Walker", + "Moore", +]; + +const getRandomValueFromArray = (array) => { + const randomIndex = Math.floor(Math.random() * array.length); + return array[randomIndex]; +}; + +const getRandomFirstName = () => getRandomValueFromArray(firstNames); + +const getRandomLastName = () => getRandomValueFromArray(lastNames); + +const getRandomPhoneNumber = () => { + const randomNumber = Math.floor(Math.random() * 900000000) + 100000000; + return `9${randomNumber.toString()}`; +}; + +const getRandomGmailAddress = () => { + const randomString = Math.random().toString(36).substring(7); + return `random${randomString}@gmail.com`; +}; + +const getRandomBirthday = () => { + const currentYear = new Date().getFullYear(); + const minYear = currentYear - 28; + const maxYear = currentYear - 17; + const year = Math.floor(Math.random() * (maxYear - minYear + 1)) + minYear; + const month = Math.floor(Math.random() * 12) + 1; + const day = Math.floor(Math.random() * 28) + 1; + return `${day.toString().padStart(2, "0")}/${month + .toString() + .padStart(2, "0")}/${year}`; +}; + + +beforeEach(() => { + cy.visit("http://localhost:8080/test/studentdetails"); + + // Inputs + cy.get('[data-cy="firstName-input"]').as("firstNameInput"); + cy.get('[data-cy="middleName-input"]').as("middleNameInput"); + cy.get('[data-cy="lastName-input"]').as("lastNameInput"); + cy.get("#mui-1").as("dobDatePicker"); + cy.get('[data-cy="email-input"]').as("emailInput"); + cy.get('[data-cy="waInput"]').as("whatsAppNumberInput"); + cy.get('[data-cy="altInput"]').as("alternateNumberInput"); + cy.get('[data-cy="genderDropdown"]').as("genderDropdown"); +}); + + +describe("Stub tests for Registration", () => { + context("LandingPage", () => { + it("should not create a duplicate user and register a call", () => { + // Stubbing the request to the check_duplicate API endpoint. + cy.intercept("GET", "https://dev-join.navgurukul.org/api/check_duplicate*", { + statusCode: 200, + body: { data: { alreadyGivenTest: false } }, + }).as("checkDuplicate"); + + // Stubbing the request to the register_exotel_call API endpoint. + cy.intercept( + "GET", + "https://dev-join.navgurukul.org/api/helpline/register_exotel_call*", + { + statusCode: 200, + body: { success: true, key: "7HZP14" }, + } + ).as("registerCall"); + + cy.visit("http://localhost:8080/"); + + // Fill out the form and submit it. + cy.get("@firstNameInput").type(getRandomFirstName()); + cy.get("@lastNameInput").type(getRandomLastName()); + cy.get('[data-cy="mobileNumber-input"]').type(getRandomPhoneNumber()); + cy.get('[data-cy="submitButton"]').click(); + + // Wait for the stubbed requests to be made. + cy.wait("@checkDuplicate"); + cy.wait("@registerCall"); + + // Now you can assert that the correct things happened in the UI. + // Here, we're assuming that a success message is displayed when the requests are successful. + // replace this with the correct selector for the success message + cy.url().should("include", "/test/instructions"); + }); + + describe("stub studentDetails Page", () => { + it("Basic Deatils + Other details page", () => { + + // Stubbing the request to the check_duplicate API endpoint. + cy.intercept("POST", "https://dev-join.navgurukul.org/api/on_assessment/details/photo/7HZP14", { + statusCode: 200, + body: "1", + }).as("studentBasicDetails"); + cy.intercept("GET", "https://api.countrystatecity.in/v1/countries/IN/states/AS/cities", { + statusCode:200, + body: [{"id":57585,"name":"Abhayapuri"},{"id":57679,"name":"Amguri"},{"id":57787,"name":"Badarpur"},{"id":57820,"name":"Baksa"},{"id":57883,"name":"Barpathar"},{"id":57884,"name":"Barpeta"},{"id":57885,"name":"Barpeta Road"},{"id":58028,"name":"Bihpuriagaon"},{"id":58034,"name":"Bijni"},{"id":58053,"name":"Bilasipara"},{"id":58075,"name":"Bokajan"},{"id":58077,"name":"Bokakhat"},{"id":58081,"name":"Bongaigaon"},{"id":58150,"name":"Basugaon"},{"id":131568,"name":"Chirang"},{"id":131588,"name":"Chabua"},{"id":131604,"name":"Chapar"},{"id":131634,"name":"Cachar"},{"id":131657,"name":"Darrang"},{"id":131695,"name":"Dergaon"},{"id":131732,"name":"Dhekiajuli"},{"id":131733,"name":"Dhemaji"},{"id":131737,"name":"Dhing"},{"id":131744,"name":"Dhubri"},{"id":131762,"name":"Dibrugarh"},{"id":131766,"name":"Digboi"},{"id":131770,"name":"Dima Hasao District"},{"id":131776,"name":"Diphu"},{"id":131778,"name":"Dispur"},{"id":131795,"name":"Duliagaon"},{"id":131796,"name":"Dum Duma"},{"id":131928,"name":"Gauripur"},{"id":131967,"name":"Gohpur"},{"id":131976,"name":"Golaghat"},{"id":131977,"name":"Golakganj"},{"id":131997,"name":"Goshaingaon"},{"id":132004,"name":"Goalpara"},{"id":132039,"name":"Guwahati"},{"id":132055,"name":"Hailakandi"},{"id":132110,"name":"Hojai"},{"id":132126,"name":"Howli"},{"id":132134,"name":"Haflong"},{"id":132137,"name":"Hajo"},{"id":132291,"name":"Jogighopa"},{"id":132294,"name":"Jorhat"},{"id":132366,"name":"Kamrup Metropolitan"},{"id":132402,"name":"Karimganj"},{"id":132518,"name":"Kharupatia"},{"id":132551,"name":"Kokrajhar"},{"id":132693,"name":"Kamrup"},{"id":132718,"name":"Karbi Anglong"},{"id":132742,"name":"Lakhimpur"},{"id":132743,"name":"Lakhipur"},{"id":132786,"name":"Lumding Railway Colony"},{"id":132793,"name":"Lala"},{"id":132842,"name":"Mahur"},{"id":132853,"name":"Maibong"},{"id":132904,"name":"Mangaldai"},{"id":132937,"name":"Mariani"},{"id":132995,"name":"Morigaon"},{"id":133002,"name":"Moranha"},{"id":133064,"name":"Makum"},{"id":133110,"name":"Nagaon"},{"id":133122,"name":"Nahorkatiya"},{"id":133130,"name":"Nalbari"},{"id":133239,"name":"North Guwahati"},{"id":133240,"name":"North Lakhimpur"},{"id":133248,"name":"Numaligarh"},{"id":133266,"name":"Namrup"},{"id":133284,"name":"Nazira"},{"id":133341,"name":"Palasbari"},{"id":133584,"name":"Raha"},{"id":133608,"name":"Rangia"},{"id":133610,"name":"Rangapara"},{"id":133798,"name":"Sapatgram"},{"id":133809,"name":"Sarupathar"},{"id":133916,"name":"Sibsagar"},{"id":133934,"name":"Silapathar"},{"id":133935,"name":"Silchar"},{"id":133979,"name":"Soalkuchi"},{"id":133996,"name":"Sonitpur"},{"id":133998,"name":"Sonari"},{"id":134003,"name":"Sorbhog"},{"id":134134,"name":"Tezpur"},{"id":134175,"name":"Tinsukia"},{"id":134203,"name":"Titabar"},{"id":134252,"name":"Udalguri"}], + }).as("getCities") + cy.intercept("POST", "https://dev-join.navgurukul.org/api/on_assessment/details/7HZP14", { + statusCode: 200, + body: { + "sucess": true, + "details": { + "pin_code": "456001", + "city": "Wash.D.C", + } + }, + }).as("successful"); + cy.visit("http://localhost:8080/test/studentdetails"); + + // Fill out the form and submit it. + const fileName = "1mb.png"; + cy.fixture(fileName).then((fileContent) => { + cy.get('[data-cy="imageInput"]').attachFile({ + fileContent, + fileName, + mimeType: "image/jpeg", + }); + }); + localStorage.setItem("enrollmentKey", "37485a503134"); + cy.get("@firstNameInput").type(getRandomFirstName()); + cy.get("@lastNameInput").type(getRandomLastName()); + cy.get("@dobDatePicker").click().type(getRandomBirthday()); + cy.get("@emailInput").type(getRandomGmailAddress()); + cy.get("@whatsAppNumberInput").type(getRandomPhoneNumber()); + cy.get("@alternateNumberInput").type(getRandomPhoneNumber()); + cy.get("@genderDropdown").click(); + cy.get('[data-value="female"]').click(); + cy.get("button:nth-child(3)").click(); + // Wait for the stubbed requests to be made. + cy.wait("@studentBasicDetails"); + + // Now you can assert that the correct things happened in the UI. + // Here, we're assuming that a success message is displayed when the requests are successful. + // replace this with the correct selector for the success message + cy.get("#mui-component-select-district").should('be.visible'); + //process to next page + + // Fill out the form and submit it. + cy.get("#mui-component-select-state").click(); + cy.get("li:nth-of-type(5)").click(); + cy.get("#mui-component-select-district").click(); + cy.wait("@getCities") + cy.get("#menu-district > div.MuiBackdrop-root").click({force: true}); + cy.get("#mui-component-select-district").click(); + cy.get("li:nth-of-type(3)").click(); + cy.get("#city").click(); + cy.get("#city").type("Wash.D.C"); + cy.get("#pin_code").click(); + cy.get("#pin_code").type("456001"); + cy.get("#mui-component-select-current_status").click(); + cy.get("li:nth-of-type(4)").click(); + cy.get("#mui-component-select-qualification").click(); + cy.get("li:nth-of-type(5)").click(); + cy.get("div:nth-child(7) > div > div").click(); + cy.get("div:nth-child(7) > div > div").type("99.99"); + cy.get("div:nth-child(8) > div > div").click(); + cy.get("div:nth-child(8) > div > div").type("99.99"); + cy.get("#mui-component-select-school_medium").click(); + cy.get("li:nth-of-type(3)").click(); + cy.get("#mui-component-select-caste").click(); + cy.get("li:nth-of-type(4)").click(); + cy.get("#mui-component-select-religion").click(); + cy.get("li:nth-of-type(8)").click(); + cy.get("#mui-component-select-religion").click(); + cy.get("li:nth-of-type(7)").click(); + cy.get( + "#app > div > div:nth-of-type(2) button.MuiButton-contained" + ).click(); + // Wait for the stubbed requests to be made + cy.wait("@successful") + // Now you can assert that the correct things happened in the UI. + // Here, we're assuming that a success message is displayed when the requests are successful. + // replace this with the correct selector for the success message + cy.url().should("include", "/test/finalinstruction"); + }); + }); + }); +}); diff --git a/cypress/e2e/studentDetails.cy.js b/cypress/e2e/studentDetails.cy.js new file mode 100644 index 00000000..e980c1ec --- /dev/null +++ b/cypress/e2e/studentDetails.cy.js @@ -0,0 +1,155 @@ +/// + +beforeEach(() => { + cy.visit("http://localhost:8080/test/studentdetails"); + + // Inputs + cy.get('[data-cy="firstName-input"]').as("firstNameInput"); + cy.get('[data-cy="middleName-input"]').as("middleNameInput"); + cy.get('[data-cy="lastName-input"]').as("lastNameInput"); + cy.get('[data-cy="email-input"]').as("emailInput"); + cy.get('form > .MuiPaper-root > [tabindex="0"]').as("nextButton"); + cy.get("#mui-1").as("dobDatePicker"); + cy.get('[data-cy="waInput"]').as("whatsAppNumberInput"); + cy.get('[data-cy="altInput"]').as("alternateNumberInput"); + cy.get('[data-cy="genderDropdown"]').as("genderDropdown"); + + // Input feedback + cy.get("#FirstName-helper-text").as("firstNameFeedback"); + cy.get("#MiddleName-helper-text").as("middleNameFeedback"); + cy.get("#LastName-helper-text").as("lastNameFeedback"); + cy.get("#FirstName-helper-text").as("numberFeedback"); + cy.get("#mui-2-helper-text").as("emailFeedback"); + cy.get("#mui-1-helper-text").as("dobFeedback"); + cy.get("#whatsapp-helper-text").as("whatsAppNumFeedback"); + cy.get("#AlternateNumber-helper-text").as("altNumberFeedback"); +}); + +describe("Section 3: Basic Details", () => { + context("Form validation testing", () => { + describe("Verify error feedback", () => { + it("should verify the first name, last name & email fields with invalid inputs", () => { + // Submit form (No input) + cy.get("@nextButton").click(); + cy.get("@firstNameFeedback").contains("Enter First Name"); + cy.get("@lastNameFeedback").contains("Enter Last Name"); + cy.get("@emailFeedback").contains("Enter Email"); + }); + it("Should verify an invalid email", () => { + cy.get("@emailInput").type("invalid#gmail.com"); + cy.get("@nextButton").click(); + + cy.get("@emailInput").contains("Enter Valid Email"); + }); + }); + describe("Verify fist name, last name, & email fields", () => { + it("should verify the first name, last name & email fields with valid inputs", () => { + cy.fixture("users.json").then((users) => { + const user = users[0]; + + cy.get("@firstNameInput").type(user.firstName); + cy.get("@lastNameInput").type(user.lastName); + cy.get("@emailInput").type(user.email); + // Submit form + cy.get("@nextButton").click(); + + // Check if input is valid by checking the text value + cy.get("@firstNameInput").should("not.have.class", "Mui-error"); + cy.get("@lastNameInput").should("not.have.class", "Mui-error"); + cy.get("@emailInput").should("not.have.class", "Mui-error"); + }); + }); + }); + describe("Verify the datepicker", () => { + it("Should verify the user age is between 17 & 28 years", () => { + const now = new Date(); + const future = new Date(new Date(now).setDate(now.getDate()+Math.floor(183*Math.random() + 1))); + const [futureDate, futureMonth, futureYear] = + [future.getDate(), future.getMonth(), future.getFullYear()]; + const date = futureDate.toString().padStart(2, "0"); + const month = (futureMonth + 1).toString().padStart(2, "0"); + // Invalid age lower than 17 (16) + cy.get("@dobDatePicker").click().type(`${date}/${month}/${(futureYear - 17)}`); + cy.get("@nextButton").click(); // should not be necessary to receive error + cy.get("@dobFeedback").should("have.class", "Mui-error"); + + // valid age between 17-28 years (21) + cy.get("@dobDatePicker").click().clear().type(`${date}/${month}/${(futureYear - 22)}`); + // cy.get("@nextButton").click(); + cy.get("@dobFeedback").should("not.have.class", "Mui-error"); + + // Invalid age higher than 28 years (29) + cy.get("@dobDatePicker").click().clear().type(`${date}/${month}/${(futureYear - 30)}`); + // cy.get("@nextButton").click(); + cy.get("@dobFeedback").should("have.class", "Mui-error"); + }); + }); + describe("Verify Whatsapp and alternate number input fields", () => { + it("Should verify the number field validations are working", () => { + cy.fixture("users.json").then((users) => { + const user = users[0]; + // Valid inputs + cy.get("@whatsAppNumberInput").type(user.mobileNumber); + cy.get("@nextButton").click(); + cy.get("@whatsAppNumFeedback").should("not.have.class", "Mui-error"); + + cy.get("@alternateNumberInput").type(user.alternateNumber); + cy.get("@nextButton").click(); + cy.get("@altNumberFeedback").should("not.have.class", "Mui-error"); + + // Invalid inputs shorter than 10 digits + cy.get("@whatsAppNumberInput").clear().type("1"); + cy.get("@nextButton").click(); + cy.get("@whatsAppNumFeedback").should("have.class", "Mui-error"); + + cy.get("@alternateNumberInput").clear().type("1"); + cy.get("@nextButton").click(); + cy.get("@altNumberFeedback").should("have.class", "Mui-error"); + + // Invalid inputs greater than 10 digits + cy.get("@whatsAppNumberInput").clear().type(`${user.mobileNumber}1`); + cy.get("@nextButton").click(); + cy.get("@whatsAppNumFeedback").should("have.class", "Mui-error"); + + cy.get("@alternateNumberInput") + .clear() + .type(`${user.alternateNumber}1`); + cy.get("@nextButton").click(); + cy.get("@altNumberFeedback").should("have.class", "Mui-error"); + }); + }); + }); + describe("Verify gender dropdown functionality", () => { + it("Should verify that an empty gender dropdown does not allow the use of nextButton", () => { + cy.get("@nextButton").click(); + cy.get('[data-cy="genderFeedback"]').contains( + "Please specify your gender" + ); + }); + it("Should verify the functionality of the gender dropdown", () => { + cy.get("@genderDropdown").should("have.value", ""); + + // male dropdown input + cy.get("@genderDropdown").click(); + cy.get('[data-value="male"]').click(); + cy.get("@genderDropdown") + .find(">input") + .should("have.attr", "value", "male"); + + // female dropdown input + cy.get("@genderDropdown").click(); + cy.get('[data-value="female"]').click(); + cy.get("@genderDropdown") + .find(">input") + .should("have.attr", "value", "female"); + + // transgender dropdown input + cy.get("@genderDropdown").click(); + cy.get('[data-value="trans"]').click(); + cy.get("@genderDropdown") + .find(">input") + .should("have.attr", "value", "trans"); + }); + }); + }); +}); diff --git a/cypress/e2e/testInstructions.cy.js b/cypress/e2e/testInstructions.cy.js new file mode 100644 index 00000000..370869c6 --- /dev/null +++ b/cypress/e2e/testInstructions.cy.js @@ -0,0 +1,48 @@ +/// +beforeEach(() => { + cy.visit("http://localhost:8080/test/instructions"); +}); + +// TS201 +describe("Section 2: Test Instructions", () => { + context("Language dropdown", () => { + it("should have a language dropdown with the options English, Hindi and Marathi: Verify language change", () => { + cy.get('[data-cy="lang-dropdown"]').should("have.value", ""); + + // open the select + cy.get('[data-cy="lang-dropdown"]').click(); + + // select English, verify change of language + cy.get('[data-cy="en"]').click(); + cy.get('[data-cy="title"]').contains("Select Your Language"); + + // select Hindin verify change of language + cy.get("#mui-component-select-Language").click(); + cy.get('li[data-value="hi"]').click(); + cy.get('[data-cy="title"]').contains("अपनी भाषा चुने"); + + // select Marathi, change language + cy.get("#mui-component-select-Language").click(); + cy.get('li[data-value="ma"]').click(); + cy.get('[data-cy="title"]').contains("तुमची भाषा निवडा"); + }); + }); + + context("Next step button functionality", () => { + it("should move to next step when (Step) button is clicked", () => { + // Lets go ahead button + cy.get('[data-cy="nextStepButton"]').click(); + cy.get(`[data-cy="heading`).contains("NavGurukul Entrance Test"); + + // Im ready button + cy.get('[data-cy="nextStepButton"]').click(); + cy.get('[data-cy="heading"]').contains("Read Carefully"); + + // Start the text button + cy.get('[data-cy="startTestButton"]') + .click() + .url() + .should("contain", "/test/studentdetails"); + }); + }); +}); diff --git a/cypress/fixtures/1mb.png b/cypress/fixtures/1mb.png new file mode 100644 index 00000000..533fb8bb Binary files /dev/null and b/cypress/fixtures/1mb.png differ diff --git a/cypress/fixtures/test-image.jpg b/cypress/fixtures/test-image.jpg new file mode 100644 index 00000000..da62b411 Binary files /dev/null and b/cypress/fixtures/test-image.jpg differ diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json new file mode 100644 index 00000000..086a4003 --- /dev/null +++ b/cypress/fixtures/users.json @@ -0,0 +1,11 @@ +[ + { + "firstName": "Jane", + "middleName": "Eva", + "lastName": "Doe", + "mobileNumber": "9999999999", + "alternateNumber": "9999999999", + "email": "JaneEvaDoe@gmail.com", + "dob": "13/10/2004" + } +] diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 00000000..119ab03f --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// 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) => { ... }) diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js new file mode 100644 index 00000000..3a252243 --- /dev/null +++ b/cypress/support/e2e.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import "./commands"; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/cypress/videos/chromeRecorder.cy.js.mp4 b/cypress/videos/chromeRecorder.cy.js.mp4 new file mode 100644 index 00000000..4ff18fe4 Binary files /dev/null and b/cypress/videos/chromeRecorder.cy.js.mp4 differ diff --git a/cypress/videos/landingPage.cy.js.mp4 b/cypress/videos/landingPage.cy.js.mp4 new file mode 100644 index 00000000..e7a0c70b Binary files /dev/null and b/cypress/videos/landingPage.cy.js.mp4 differ diff --git a/cypress/videos/studentDetails.cy.js.mp4 b/cypress/videos/studentDetails.cy.js.mp4 new file mode 100644 index 00000000..43b902f8 Binary files /dev/null and b/cypress/videos/studentDetails.cy.js.mp4 differ diff --git a/cypress/videos/testInstructions.cy.js.mp4 b/cypress/videos/testInstructions.cy.js.mp4 new file mode 100644 index 00000000..1166f4b2 Binary files /dev/null and b/cypress/videos/testInstructions.cy.js.mp4 differ diff --git a/package.json b/package.json index f264d96d..9fdff84d 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "lint": "eslint src --fix", "checklint": "eslint src", "lint-staged": "lint-staged", - "prepare": "husky install" + "prepare": "husky install", + "cypress:open": "cypress open", + "cypress:run": "cypress run" }, "lint-staged": { "*.{js,jsx}": [ @@ -56,18 +58,18 @@ "axios": "^0.26.1", "date-fns": "^2.29.1", "dayjs": "^1.11.0", - "devextreme": "^21.2.6", - "devextreme-react": "^21.2.6", + "devextreme": "^24.1.4", + "devextreme-react": "^24.1.4", "dompurify": "^2.3.6", "lodash": "^4.17.21", - "mui-datatables": "^4.1.2", + "mui-datatables": "^4.3.0", "notistack": "^2.0.3", "object-hash": "^3.0.0", "react": "^17.0.2", "react-copy-to-clipboard": "^5.0.4", "react-device-detect": "^2.1.2", "react-dom": "^17.0.2", - "react-easy-edit": "^1.15.0", + "react-easy-edit": "~1.15.0", "react-epic-spinners": "^0.5.0", "react-ga": "^3.3.1", "react-google-login": "^5.2.2", @@ -77,7 +79,7 @@ "react-redux": "^7.2.6", "react-router-dom": "^6.2.2", "react-select": "^5.2.2", - "react-slick": "^0.29.0", + "react-slick": "^0.28.1", "react-spinner-material": "^1.4.0", "react-timer-hook": "^3.0.5", "react-youtube": "^7.14.0", @@ -86,9 +88,12 @@ }, "devDependencies": { "@vitejs/plugin-react": "^1.2.0", + "cypress": "^12.9.0", + "cypress-file-upload": "^5.0.8", "eslint": "^8.11.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", + "eslint-plugin-cypress": "^2.13.3", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx index b3bc413a..7b7f2b54 100644 --- a/src/components/layout/Header.jsx +++ b/src/components/layout/Header.jsx @@ -147,6 +147,7 @@ const Header = () => { > ngLogo { > Select Language )} @@ -256,7 +264,7 @@ const Header = () => { ) : ( )} diff --git a/src/components/muiTables/ServerSidePagination.jsx b/src/components/muiTables/ServerSidePagination.jsx index 7bdaef3c..8aeba6a0 100644 --- a/src/components/muiTables/ServerSidePagination.jsx +++ b/src/components/muiTables/ServerSidePagination.jsx @@ -93,9 +93,9 @@ const ServerSidePagination = ({ value === "All" ? [...newData] : [ - ...newData, - { key: keys[query], value: encodeURIComponent(value) }, - ], + ...newData, + { key: keys[query], value: encodeURIComponent(value) }, + ], }; const { filterColumns: newColumns } = newState; const newUrl = await filterColumns.reduce((cUrl, filterColumn, index) => { @@ -145,23 +145,20 @@ const ServerSidePagination = ({ let body = ""; columns.forEach((col, colInx) => { if (col.name === "donor") { - body += `"${ - nStudent.donor !== null && nStudent.donor !== undefined + body += `"${nStudent.donor !== null && nStudent.donor !== undefined ? nStudent.donor.map((donor) => donor.donor).join(", ") : "" - }",`; + }",`; } else if (colInx === columns.length - 1) - body += `"${ - !nStudent[col.name] || nStudent[col.name] === undefined + body += `"${!nStudent[col.name] || nStudent[col.name] === undefined ? " " : nStudent[col.name] - }"`; + }"`; else - body += `"${ - !nStudent[col.name] || nStudent[col.name] === undefined + body += `"${!nStudent[col.name] || nStudent[col.name] === undefined ? " " : nStudent[col.name] - }",`; + }",`; }); return body; }) @@ -242,7 +239,7 @@ const ServerSidePagination = ({ return getfilterApi(columnChanged, filterValue); } - setFilters({ filterColumns: [], url: `${baseURL}students ? ` }); + setFilters({ filterColumns: [], url: `${baseURL}students?` }); }, responsive: "vertical", rowsPerPageOptions: [10, 50, 100], diff --git a/src/components/onlineTest/Instructions/index.jsx b/src/components/onlineTest/Instructions/index.jsx index 726af581..3a34b52a 100644 --- a/src/components/onlineTest/Instructions/index.jsx +++ b/src/components/onlineTest/Instructions/index.jsx @@ -29,7 +29,7 @@ const tutorialSteps = [ }, subHadding: { // old:"Select Your Languge/ अपनी भाषा चुने", - en: "Select Your Languge", + en: "Select Your Language", hi: "अपनी भाषा चुने", ma: "तुमची भाषा निवडा", }, @@ -39,7 +39,7 @@ const tutorialSteps = [ inputField: true, button: { // old:"Aage chalein", - en: "Let's go Ahead ", + en: "Let's go Ahead", hi: "आगे बढ़ो", ma: "पुढे जा", }, @@ -217,12 +217,12 @@ const TestInstructions = () => {
- + {tutorialSteps[activeStep].heading[lang]} - + {tutorialSteps[activeStep].subHadding?.[lang]} @@ -267,14 +267,21 @@ const TestInstructions = () => { Choose your language @@ -282,6 +289,7 @@ const TestInstructions = () => { {tutorialSteps[activeStep].button ? (