├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── Jenkinsfile ├── README.md ├── cypress.config.js ├── cypress ├── e2e │ ├── components │ │ └── HeaderComponent.js │ ├── config │ │ ├── CONSTANTS.js │ │ ├── errorMessages.js │ │ └── routes.js │ ├── pages │ │ ├── AccountPage.js │ │ ├── BasePage.js │ │ ├── LoginPage.js │ │ ├── ProductDetailsPage.js │ │ ├── ProductsSearchPage.js │ │ ├── RegisterPage.js │ │ ├── ShoppingCartPage.js │ │ └── WishlistPage.js │ ├── tests │ │ ├── AddToCartTest.cy.js │ │ ├── LoginTest.cy.js │ │ ├── ProductDataTest.cy.js │ │ ├── ProductsSearchTest.cy.js │ │ ├── RegistrationTest.cy.js │ │ └── WishlistTest.cy.js │ └── utils │ │ └── ProductUtils.js ├── fixtures │ ├── product.json │ ├── productCategories.json │ ├── products.json │ └── users.json └── support │ ├── commands.js │ ├── e2e.js │ └── index.d.ts ├── doc ├── cyress-cloud-results.PNG ├── cyress-cloud-specs.PNG ├── docker.md ├── jenkins-console-output.png ├── jenkins-node.PNG ├── jenkins-pipeline-config.PNG ├── jenkins-pipieline-dashboard.PNG ├── jenkins-slack-config.PNG ├── jenkins-slack-notification.PNG ├── jenkins.md ├── jenkins.png ├── mochaawesome-report-overview.PNG ├── mochawesome-failed-test-report.png └── results-terminal-output.PNG ├── docker-compose-browsers.yml ├── docker-compose-build.yml ├── docker-compose.yml ├── jsconfig.json ├── package-lock.json ├── package.json ├── reporter-config.json └── settings ├── dev.settings.json ├── local.settings.json ├── prod.settings.json ├── qa.settings.json └── stage.settings.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es2021": true, 6 | "cypress/globals": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:cypress/recommended", 11 | "plugin:chai-friendly/recommended" 12 | ], 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "rules": { 18 | "cypress/no-assigning-return-values": "error", 19 | "cypress/no-unnecessary-waiting": "warn", 20 | "cypress/assertion-before-screenshot": "warn", 21 | "cypress/no-force": "warn", 22 | "cypress/no-async-tests": "error", 23 | "cypress/no-pause": "error" 24 | } 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | 3 | .DS_Store 4 | 5 | .vscode 6 | 7 | 8 | # Dependency directory 9 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 10 | node_modules 11 | cypress/videos 12 | cypress/screenshots 13 | cypress/reports 14 | cypress/results 15 | cypress/downloads 16 | 17 | # Values in here will overwrite conflicting environment variables in your Cypress configuration. 18 | cypress.env.json 19 | 20 | cypress_docker -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # This line specifies the base image for your Docker container. It uses an image named cypress/browsers with specific browser versions. 2 | FROM cypress/browsers:node-20.5.0-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1 3 | 4 | # Create the folder where our project will be stored inside the contanier. 5 | # RUN mkdir /cypress_chrome 6 | 7 | 8 | # We make it our work-directory inside the container. All subsequent commands will be executed in this directory. 9 | WORKDIR /cypress_docker 10 | 11 | # Let's copy the essential files that we must use to run our scripts from your local machine into the container's /cypress_docker directory 12 | COPY ./reporter-config.json . 13 | COPY ./package.json . 14 | COPY ./package-lock.json . 15 | COPY ./cypress.config.js . 16 | COPY ./cypress.env.json . 17 | COPY ./cypress ./cypress 18 | COPY ./settings ./settings 19 | 20 | 21 | # Install the Cypress dependencies in the work directory 22 | RUN npm install 23 | 24 | # Executable commands the container will use[Exec Form] 25 | # This sets the entry point for your Docker container to run the npm run command. 26 | # It means that when you start the container, it will execute the npm run command defined in the CMD instruction. 27 | ENTRYPOINT ["npm", "run"] 28 | 29 | # This is an optional command that specifies the default command to run when the container starts 30 | # In this case, it's an empty string, so it will use whatever command is specified in the npm run script when you start the container. 31 | CMD [""] -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | 3 | //The agent section specifies where the entire Pipeline, or a specific stage, 4 | //will execute in the Jenkins environment depending on where the agent section is placed. 5 | agent any 6 | 7 | //The environment directive specifies a sequence of key-value pairs which will be defined 8 | //as environment variables for all steps, or stage-specific steps, depending on where the environment directive is located within the Pipeline. 9 | /*environment { 10 | NO_COLOR = '1' 11 | }*/ 12 | 13 | //NoAnsi Color Plugin helps to avoid weird Jenkins console output and displays the console output in color format 14 | options { 15 | ansiColor('xterm') 16 | } 17 | 18 | tools { 19 | //Use Node name configured in global tools configuration for Node Jenkins Plugin 20 | nodejs "Node 20.5.0" 21 | } 22 | 23 | 24 | //The parameters directive provides a list of parameters that a user should provide when triggering the Pipeline. 25 | //The values for these user-specified parameters are made available to Pipeline steps via the params object 26 | parameters { 27 | string( 28 | name: 'TEST_SPEC', 29 | defaultValue: 'cypress/e2e/tests/*.cy.js', 30 | description: 'Enter the name of the test spec without file extension e.g. LoginTest. Default value will run all the test specs present inside cypress/e2e/tests/ directory.' 31 | ) 32 | string( 33 | name: 'RECORD_TESTS', 34 | defaultValue: '--record false', 35 | description: 'Within CI, you can pass --record argument to record the test runs to later view on cypress dashboard. Remove the false to record the tests.' 36 | ) 37 | choice( 38 | name: 'TEST_ENVIRONMENT', 39 | choices: [ 40 | 'local', 41 | 'dev', 42 | 'qa', 43 | 'stage', 44 | 'prod', 45 | ], 46 | description: 'Specify the test environment. Default will be local.' 47 | ) 48 | choice( 49 | name: 'BROWSER', 50 | choices: ['electron', 'chrome', 'edge', 'firefox'], 51 | description: 'Pick the web browser you want to use to run your scripts. Default will be electron.' 52 | ) 53 | choice( 54 | name: 'BROWSER_MODE', 55 | choices: ['headless', 'headed'], 56 | description: 'By default, Cypress will run tests headlessly.Passing --headed will force the browser to be shown.' 57 | ) 58 | choice( 59 | name: 'TAG', 60 | choices: [ 61 | '@regression', 62 | '@smoke', 63 | '@Login', 64 | '@productData', 65 | '@Search', 66 | '@Wishlist', 67 | '@Cart' 68 | ], 69 | description: 'Choose the test tag to filter your test scripts' 70 | ) 71 | } 72 | 73 | 74 | 75 | //The stage directive goes in the stages section and should contain a steps section, an optional agent section, 76 | //or other stage-specific directives. Practically speaking, all of the real work done by a Pipeline will be wrapped in one or more stage directives. 77 | stages { 78 | stage('Stage 1 - Checkout Code') { 79 | //The steps section defines a series of one or more steps to be executed in a given stage directive. 80 | steps { 81 | // Get some code from a GitHub repository 82 | /* git ([ 83 | branch: 'main', 84 | changelog: true, 85 | credentialsId: 'itkhanz', 86 | poll: false, 87 | url: 'https://github.com/itkhanz/Cypress-Framework' 88 | ]) */ 89 | 90 | echo 'Code is checked out' 91 | } 92 | } 93 | 94 | stage('Stage 2 - Installing dependencies') { 95 | steps { 96 | bat 'npm i' 97 | echo 'dependencies installed' 98 | } 99 | } 100 | 101 | //This deletes any older xml results files present in the directory 102 | stage('Stage 3 - Clearing old reports') { 103 | steps { 104 | bat "npm run report:pre" 105 | } 106 | } 107 | 108 | stage('Stage 4 - Running cypress e2e Tests') { 109 | //For recording tests on Cypress Cloud Dashboard, you need to set these environment variables 110 | environment { 111 | CYPRESS_RECORD_KEY = credentials('cypress-framework-record-key') 112 | CYPRESS_PROJECT_ID = credentials('cypress-framework-project-id') 113 | } 114 | 115 | steps { 116 | //bat "SET NO_COLOR=$NO_COLOR" //You may want to do this if ASCII characters or colors are not properly formatted in your CI. 117 | script { 118 | if (params.TEST_SPEC == "cypress/e2e/tests/*.cy.js") { 119 | echo "Running all test scripts with Browser: ${params.BROWSER}, TAG: ${params.TAG}, Environment: ${params.TEST_ENVIRONMENT}" 120 | bat "npx cypress run --${params.BROWSER_MODE} --browser ${params.BROWSER} --env environmentName=${params.TEST_ENVIRONMENT},grepTags=${params.TAG} ${params.RECORD_TESTS}" 121 | } else { 122 | echo "Running script: ${params.TEST_SPEC} with Browser: ${params.BROWSER}, TAG: ${params.TAG}, Environment: ${params.TEST_ENVIRONMENT}" 123 | bat "npx cypress run --spec cypress/e2e/tests/${params.TEST_SPEC}.cy.js --${params.BROWSER_MODE} --browser ${params.BROWSER} --env environmentName=${params.TEST_ENVIRONMENT},grepTags=${params.TAG} ${params.RECORD_TESTS}" 124 | } 125 | } 126 | 127 | } 128 | } 129 | 130 | //Mocha JUnit Reporter produces separate XML for each spec result, so we merge the test results into one XML file 131 | stage('Stage 5 - Merging JUnit reports') { 132 | steps { 133 | bat "npm run report:post" 134 | } 135 | } 136 | 137 | } 138 | 139 | post { 140 | always { 141 | //Publish the HTML report using the HTML Publisher plugin 142 | echo 'Publishing the Extent Report' 143 | publishHTML([ 144 | allowMissing: false, 145 | alwaysLinkToLastBuild: false, 146 | keepAll: true, 147 | reportDir: 'cypress/results/cypress-mochawesome-reporter', 148 | reportFiles: 'index.html', 149 | reportName: 'Cypress Mochawesome Report', 150 | reportTitles: 'Cypress Test Automation Framework', 151 | useWrapperFileDirectly: true 152 | ]) 153 | 154 | //To avoid duplicate results, we comment this, and use it within script only 155 | //junit 'cypress/results/junit/combined-report.xml' 156 | 157 | script { 158 | // Get the JUnit test results 159 | echo 'Publishing JUnit XML Results' 160 | def testResults = junit testResults: 'cypress/results/junit/combined-report.xml' 161 | 162 | //Mapping build status to slack notification colors 163 | def COLOR_MAP = [ 164 | 'SUCCESS' : '#4CAF50', //Green 165 | 'FAILURE' : '#F44336', //Red 166 | 'UNSTABLE' : '#FFC107', //Yellow 167 | 'ABORTED' : '#9E9E9E', //Grey 168 | 'NOT_BUILT' : '#2196F3', //Blue 169 | 'UNKNOWN' : '#CCCCCC' //Light Gray 170 | ] 171 | 172 | echo 'Sending Slack Notification' 173 | slackSend channel: '#cypress-framework-jenkins', 174 | color: COLOR_MAP[currentBuild.currentResult], 175 | message: "*${currentBuild.currentResult}*\n *Job*: ${env.JOB_NAME} , *Build*: ${env.BUILD_NUMBER}\n *Test Results*: \n\t Total: ${testResults.totalCount} Passed: ${testResults.passCount} Failed: ${testResults.failCount} Skipped: ${testResults.skipCount}\n *Test Run Configuration*:\n\t *Test Script(s)*: ${params.TEST_SPEC}\n\t *Browser*: ${params.BROWSER} ${params.BROWSER_MODE}\n\t *Tags*: ${params.TAG}\n\t *Environment*: ${params.TEST_ENVIRONMENT}\n\t *Dashboard Recording*: ${params.RECORD_TESTS}\n *Test Report*: ${env.BUILD_URL}Cypress_20Mochawesome_20Report/ \n *More info*: ${env.BUILD_URL}" 176 | 177 | } 178 | } 179 | 180 | success { 181 | echo 'Build Successful' 182 | } 183 | 184 | failure { 185 | echo 'Build Failed' 186 | } 187 | 188 | unstable { 189 | echo 'Build unstable' 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cypress Test Automation Framework 2 | 3 | [![cypress-framework](https://img.shields.io/endpoint?url=https://cloud.cypress.io/badge/detailed/sh3aqa/main&style=flat&logo=cypress)](https://cloud.cypress.io/projects/sh3aqa/runs) 4 | [![cypress-framework](https://img.shields.io/endpoint?url=https://cloud.cypress.io/badge/count/sh3aqa/main&style=flat&logo=cypress)](https://cloud.cypress.io/projects/sh3aqa/runs) 5 | 6 | Cypress test automation framework built with JavaScript (JS) that follows the Page Object Model (POM) design pattern to implement the UI tests for OpenCart E-commerce store. 7 | 8 | > If you are a beginner to Cypress, refer to my other repo to refresh your Cypress knowledge which will serve as a starting-point to Cypress testing: 9 | [Cypress-E2E-Web-and-API-Testing](https://github.com/itkhanz/Cypress-E2E-Web-and-API-Testing) 10 | 11 | Application Under Test (AUT): 12 | https://naveenautomationlabs.com/opencart/index.php 13 | 14 | ## Features 💡 15 | 16 | * Atomic and Independent test cases 17 | * Robust Locator strategies to target elements 18 | * No hard coded strings and test data in spec files 19 | * Hooks to perform the repeated steps for all the tests inside spec 20 | * Loading test data from external fixtures files i.e. JSON 21 | * Generate random test data with faker library 22 | * Loading environment specific configuration and environment variables per environment i.e. dev, stage, prod 23 | * Ability to filter and run tests with specific tags i.e. regression, smoke 24 | * Pass browser and mode as environment variable 25 | * Configure routes (URL endpoints) in a constant config file 26 | * Usage of OOP Inheritance to extend all the pages from BasePage 27 | * Allows to load header and footer components from BasePage constructor 28 | * Call the `cy.visit()` from BasePage with specified path 29 | * Test Retries for failing tests 30 | * Custom commands for login and validation in `cypress/support/commands.js` 31 | * Intellisense for custom commands in `cypress/support/index.d.ts` 32 | * Reusable test utilities functions inside `cypress/e2e/utils` 33 | * Support for Cypress Cloud (Dashboard) 34 | * Multiple reporters configuration (JUnit XML, cpress-mochawesome-reporter HTML) 35 | * Cypress with Docker using Dockerfile and docker-compose 36 | * Continuous Integration CI with Jenkins 37 | 38 | 39 | ## Setup 🛠️ 40 | 41 | ### Pre-requisites 42 | 43 | * Install NodeJS and NPM pakcage manager. 44 | * Code Editor of your choice e.g. Visual Studio Code 45 | * Install [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) VS Code extension from Microsoft to make linting work in IDE 46 | * GIT Client (for remote tracking) 47 | * GIT Bash terminal (for Windows) 48 | 49 | #### Optional 50 | * Install [Cypress Snippets](https://marketplace.visualstudio.com/items?itemName=CliffSu.cypress-snippets) VS Code Extension that includes the most common cypress snippets. 51 | 52 | ### Setup from Scratch 53 | 54 | * `npm init` to setup node project with package.json 55 | * `npm install --save-dev cypress` to install cypress as dev dependency 56 | * `npx cypress open` to open the cypress test runner and choose `E2E Testing` which will create cypress config, support and fixture folders. 57 | * Choose browser of your choice, and scaffold examples which will create boilerplate specs within e2e folder. 58 | * Remove the default boilerplate specs from `cypress/e2e` folder 59 | * Add `.gitignore` to exclude files and folders from GIT 60 | * Add `README.md` to document 61 | * Start with writing tests under `cypress/e2e` directory. 62 | 63 | #### Integrating ESLint 64 | 65 | * Install [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) VS Code extension from Microsoft to make linting work in IDE 66 | * Install eslint in project `npm init @eslint/config --save-dev` 67 | * Install [Cypress ESLint Plugin](https://www.npmjs.com/package/eslint-plugin-cypress) with `npm install eslint-plugin-cypress --save-dev` 68 | * Configure the `.eslintsrc.json` to use recommended settings or add custom rules: 69 | ```json 70 | { 71 | "extends": [ 72 | "plugin:cypress/recommended" 73 | ] 74 | } 75 | ``` 76 | * [Linting Cypress code using ESLint and eslint-plugin-cypress](https://www.youtube.com/watch?v=-YgitwmwFo0) 77 | 78 | ### Using existing framework 79 | 80 | * Clone git repo 81 | * Navigate to folder and open terminal 82 | * Run `npm install` to install the framework dependencies 83 | 84 | #### Pre-requistes 85 | 86 | * Register with a new user on the website manually for first time. 87 | * Save the login credentials for registered user under `cypress/fixtures/users.json` 88 | * Add the `projectId` for Cypress Cloud in `cypress.config.js` 89 | 90 | > To add IntelliSense for IDE like VS Code, add the [Triple slash directives](https://docs.cypress.io/guides/tooling/IDE-integration#Triple-slash-directives) to the head of test file or use the [Refernce type declaration via jsconfig.json](https://docs.cypress.io/guides/tooling/IDE-integration#Triple-slash-directives) 91 | 92 | 93 | --- 94 | 95 | ## Configurarion ⚙️ 96 | 97 | * Project specific constants are defined under `cypress/config/constants.js` 98 | * URL routes for pages are defined under `cypress/config/routes.js`. This allows to open the page directly by calling the `open()` method of page. 99 | * Environment specific Cypress settings are placed inside JSON files per enironment in `settings/{environmentName}.settings.json` file. This allows to load separaete settings per environment. 100 | * [Configuration](https://docs.cypress.io/guides/references/configuration) 101 | * [Cypress.config](https://docs.cypress.io/api/cypress-api/config) 102 | * [How To Load Cypress Settings Per Environment](https://glebbahmutov.com/blog/load-cypress-env-settings/) 103 | * [Cypress basics: Using baseUrl](https://filiphric.com/cypress-basics-using-baseurl) 104 | * 105 | 106 | --- 107 | 108 | ## Running tests ⚡ 109 | 110 | * [Command Line](https://docs.cypress.io/guides/guides/command-line) 111 | * [Environment Variables](https://docs.cypress.io/guides/guides/environment-variables) 112 | * `npx cypress open` will open the cypress test runner so you can run the tests from it 113 | * `npx cypress run` will run all the test spec files located within `cypress/e2e` folder. By default test are run in headless mode on electron browser. 114 | * If you want to run test on specific browser, you can provide with `--browser` argument like `--browser chrome`. 115 | * To run tests in headed mode, pass argument `--headed` 116 | * To run a specific test spec, use the following cmd syntax: 117 | `npx cypress run --spec cypress/e2e/tests/AddToCartTest.cy.js --headed --browser chrome` will run the tests from AddToCartTest spec on chrome browser in headed mode. 118 | * Following custom test scripts are setup in `package.json` to run specific test suites in headless format: 119 | * `npm run test:registration` 120 | * `npm run test:login` 121 | * `npm run test:productData` 122 | * `npm run test:addToCart` 123 | * `npm run test:wishlist` 124 | * `npm run test:productSearch` 125 | * You can also filter the tests based on tag by providing `--env grepTags=""` 126 | * For example, To run the tests that are tagged as `@smoke`: 127 | * `npx cypress run --spec cypress/e2e/tests/AddToCartTest.cy.js --env grepTags="@smoke"` 128 | * To use the command line args with npm run scripts, append an extra `--` 129 | * `npm run test:addToCart -- --env grepTags="@smoke"` 130 | * Similarly you can filter the test based on its title by providing ` --env grep="` 131 | * To run the tests on any specific browser in headed mode: 132 | * For example, `npm run test:addToCart -- --env grepTags="@smoke" --headed --browser chrome` will run the smoke tests from addToCart spec on chrome browser in headed mode 133 | * You can also change baseUrl, configuration files and enironment variables during test execution by passing the `environmentName` enviornment variable. 134 | * Summing up all the above configuration, here is an example: 135 | * `npm run test:registration -- --env environmentName="stage",grepTags="@smoke" --headed --browser chrome` will run tests with following configurations: 136 | * **spec** `RegistrationTest.cy.js` 137 | * **browser** `chrome` 138 | * **mode** `headed` 139 | * **tag** `smoke` 140 | * **environmentName** `stage` 141 | * **baseUrl** `https://stage.naveenautomationlabs.com/opencart/index.php` is automatically teken from `environmentName` 142 | 143 | * You can add cloud execution and test recording to the existing scripts by appending `-- --record --key ` to the end of npm test scripts. For example, `npm run test:registration -- --env environmentName="local",grepTags="@smoke" --record --key ` 144 | * Terminal output shows the results summary as: 145 | 146 | 147 | 148 | --- 149 | 150 | ## Test Reporting 📑 151 | 152 | * This framework uses [cypress-mochawesome-reporter](https://www.npmjs.com/package/cypress-mochawesome-reporter) to generate HTML test reports. 153 | * Add the following options to `cypress.config.js` 154 | ```js 155 | //cypress-mochawesome-reporter 156 | reporter: 'cypress-mochawesome-reporter', 157 | reporterOptions: { 158 | charts: true, //Genarates Chart in HTML report 159 | reportPageTitle: 'OpenCart Test Report', //Report title will be set to the mentioned string 160 | embeddedScreenshots: true, //Screenshot will be embedded within the report 161 | inlineAssets: true, //No separate assets folder will be created 162 | videoOnFailOnly: false, //If Videos are recorded and added to the report, setting this to true will add the videos only to tests with failures. 163 | }, 164 | ``` 165 | * HTML Reports are generated by default in `root/cypress/reports` folder. 166 | * It embeds the screenshots of tests on failure automatically to the report, and also attaches the videos to report. 167 | * It also allows the customization of report with better control of how and where report is generated. 168 | * This is how the report dashboard looks like: 169 | 170 | 171 | 172 | * Some ther reporting possibilities are: 173 | * [mochawesome](https://www.npmjs.com/package/mochawesome) 174 | * [cypress-allure-plugin](https://www.npmjs.com/package/@shelex/cypress-allure-plugin) 175 | 176 | * If a test case is failed, then the assertion error, screenshot and video gets attached to report automatically. 177 | * When a test retries, Cypress will continue to take screenshots for each failed attempt or cy.screenshot() and suffix each new screenshot with (attempt n), corresponding to the current retry attempt number. 178 | 179 | 180 | 181 | 182 | ## Multiple Reporters 183 | 184 | * [Configure Multiple Reports](https://docs.cypress.io/guides/tooling/reporters) 185 | * Oftentimes we see users wanting the ability to use multiple reporters. When running in CI, you might want to generate a report for junit and perhaps a json report. This is great, but by setting this reporter you won't receive any additional feedback while the tests are running! 186 | * The framework is configured to report JUnit XML reports, as well as mochawesome HTML reports. 187 | * Install additional dependencies with `npm install --save-dev cypress-multi-reporters mocha-junit-reporter` 188 | * Specify your reporter and reporterOptions in your Cypress configuration: 189 | ``` 190 | reporter: 'cypress-multi-reporters', 191 | reporterOptions: { 192 | configFile: 'reporter-config.json', 193 | }, 194 | ``` 195 | * Then add the separate `reporter-config.json` file (defined in your configuration) to enable mochawesome and junit reporters and direct the junit reporter to save separate XML files. 196 | ```json 197 | { 198 | "reporterEnabled": "cypress-mochawesome-reporter, mocha-junit-reporter", 199 | "mochaJunitReporterReporterOptions": { 200 | "mochaFile": "cypress/results/junit/results-[hash].xml" 201 | }, 202 | "cypressMochawesomeReporterReporterOptions": { 203 | "reportDir": "cypress/results/cypress-mochawesome-reporter", 204 | "charts": true, 205 | "reportPageTitle": "OpenCart Test Report", 206 | "embeddedScreenshots": true, 207 | "inlineAssets": true, 208 | "videoOnFailOnly": false 209 | } 210 | } 211 | ``` 212 | * Test results are generated under directory: 213 | * `cypress/results/junit` for JUnit XML reports 214 | * `cypress/results/cypress-mochawesome-reporter` for mochawesome HTML reports 215 | * In case you want to combine generated XML files into a single one, [junit-report-merger](https://www.npmjs.com/package/junit-report-merger) can be added. 216 | * Add the scripts in `package.json` to delete the reports before test run, and combine the JUnit XML reports into single XML report after test finishes. 217 | ```json 218 | { 219 | "results-junit:delete": "rm -rf cypress/results/junit/* || true", 220 | "results-junit:combine": "jrm cypress/results/junit/combined-report.xml \"cypress/results/junit/*.xml\"", 221 | "report:pre": "npm run results-junit:delete", 222 | "report:post": "npm run results-junit:combine", 223 | "test:report": "npm run report:pre && npx cypress run && npm run report:post" 224 | } 225 | ``` 226 | * `rm` and `true` are native shell commands and so the above scripts will not run in poweshell terminal, therefore use GIT Bash terminal if you are on windows. 227 | 228 | 229 | --- 230 | 231 | ## Cypress Cloud ☁️ 232 | 233 | * Record a run to see your test results in Cypress Cloud. You can then optimize your test suite, debug failing and flaky tests, Test Replay, watch video, view console output and screenshots, and integrate with your favorite tools. 234 | * [Cypress Cloud documentation](https://docs.cypress.io/guides/cloud/introduction) 235 | * [Cypress Project ID and Record Key](https://docs.cypress.io/guides/cloud/account-management/projects) 236 | * [Record tests](https://docs.cypress.io/guides/continuous-integration/introduction#Record-tests) 237 | * Once you set up your project to record, Cypress generates a unique projectId for your project and automatically insert it into your Cypress configuration file. The projectId is a 6 character string in your Cypress configuration. 238 | * The record key is used to authenticate that your project is allowed to record tests to Cypress Cloud. As long as your record key stays private, no one will be able to record test runs for your project - even if they have your projectId. 239 | * Create `cypress.env.json` in root directory that Cypress will automatically check. Values in here will overwrite conflicting environment variables in your Cypress configuration.This strategy is useful because if you add cypress.env.json to your `.gitignore` file, the values in here can be different for each developer machine. 240 | ```json 241 | { 242 | "projectId": "" 243 | } 244 | ``` 245 | 246 | * Run the test via `npx cypress run --record --key ` 247 | 248 | 249 | 250 | 251 | 252 | --- 253 | 254 | ## Docker 🐋 255 | 256 | If you want to execute the tests using Docker, you can do the following in your terminal (Powershell) at the workspace project.- 257 | 258 | ```bash 259 | # Without docker-compose 260 | 261 | # Build the docker image from Dockerfile 262 | > docker build -t cypress_docker . 263 | 264 | # Run the following command to run all tests with test:all script inside docker 265 | > docker run -i -v ${PWD}:/cypress_docker -t cypress_docker:latest test:all 266 | 267 | # You can also pass command line parameters optionally to further narrow down your tests 268 | > docker run -i -v ${PWD}:/cypress_docker -t cypress_docker:latest test:registration -- --env environmentName="local",grepTags="@smoke" --headed --browser chrome 269 | ``` 270 | 271 | 272 | ```bash 273 | # With docker-compose 274 | 275 | # Build the docker image from Dockerfile 276 | > docker build -t cypress_docker . 277 | 278 | # Execute the following command to compile the file. may be any value you want 279 | > docker build -t cypress_docker: . 280 | 281 | # Then, execute the following command to run the tests inside of the container 282 | > docker-compose up 283 | 284 | # To run cross browser tests on multiple browsers 285 | > docker-compose -f docker-compose-browsers up 286 | 287 | # To stop the docker compose execution 288 | > docker-compose -f docker-compose-browsers.yml down --volumes --remove-orphans 289 | 290 | # You can also combine the building of image and running of test in single step 291 | > docker-compose -f docker-compose-build.yml up --build 292 | ``` 293 | 294 | 295 | 296 | * For more detail on cypress with docker, read [cypress docker](./doc/docker.md) 297 | 298 | ## Continuous Integration CI 🚀 299 | 300 | The Project is configured to run Cypress in Continuous Integration with multiple CI Providers. 301 | 302 | ### Jenkins Integration 303 | 304 | 305 | 306 | * This framework supports the Continous Integration CI with Jenkins, and offers fully customize execution with support for: 307 | * Jenkinsfile Pipeline Script 308 | * Build with Parameters 309 | * Allows to select test specs, browser, browser mode, tags, environment, dashboard recording. 310 | * Publishes Mochawesome HTML report via HTML Publisher Plugin 311 | * Parses JUnit XML results to display a summary and trends of test results 312 | * Sends a Slack Notification with test results summary, link to test report, and job. 313 | * See the [Jenkinsfile](./Jenkinsfile) to see how the pipeline is configured. 314 | * For more detail on cypress with Jenkins, read [Cypress Jenkins](./doc/jenkins.md) 315 | 316 | 317 | 318 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************************** 2 | * This file is used to store any configuration specific to Cypress. 3 | * https://docs.cypress.io/guides/references/configuration 4 | * 5 | * Cypress gives you the option to dynamically alter configuration options. 6 | * This is helpful when running Cypress in multiple environments and on multiple developer machines. 7 | * https://docs.cypress.io/guides/references/configuration#Overriding-Options 8 | * 9 | * Overriding Individual Options 10 | * When running Cypress from the command line you can pass a --config flag to override individual config options. 11 | * 12 | * Specifying an Alternative Config File 13 | * In the Cypress CLI, you can change which config file Cypress will use with the --config-file flag. 14 | * 15 | * https://docs.cypress.io/guides/guides/environment-variables 16 | * https://docs.cypress.io/api/cypress-api/env 17 | * Configuration options can be overridden with environment variables. 18 | * export CYPRESS_VIEWPORT_WIDTH=800 19 | * This is especially useful in Continuous Integration or when working locally. 20 | * This gives you the ability to change configuration options without modifying any code or build scripts. 21 | * 22 | * https://docs.cypress.io/guides/references/configuration#Test-Configuration 23 | * Cypress provides two options to override the configuration while your test are running, 24 | * Cypress.config() and suite-specific or test-specific configuration overrides. 25 | * 26 | * Cypress.config() 27 | * https://docs.cypress.io/api/cypress-api/config 28 | * You can also override configuration values within your test using Cypress.config(). 29 | * 30 | * Test-specific Configuration 31 | * To apply specific Cypress configuration values to a suite or test, 32 | * pass a configuration object to the test or suite function as the second argument. 33 | * 34 | ***********************************************************************************/ 35 | 36 | const { defineConfig } = require("cypress"); 37 | 38 | module.exports = defineConfig({ 39 | 40 | //Cypress uses your projectId and Record Key together to uniquely identify projects. 41 | //projectId: '', 42 | 43 | trashAssetsBeforeRuns : true, //Whether Cypress will trash assets within the downloadsFolder, screenshotsFolder, and videosFolder before tests run with cypress run. 44 | 45 | //Folders/ Files 46 | downloadsFolder : 'cypress/downloads', 47 | fixturesFolder : 'cypress/fixtures', 48 | 49 | //Screenshots 50 | screenshotsFolder : 'cypress/screenshots', 51 | screenshotOnRunFailure : true, //Whether Cypress will take a screenshot when a test fails during cypress run. 52 | 53 | //Videos 54 | video : true, //Whether Cypress will capture a video of the tests run with cypress run. 55 | videosFolder : 'cypress/videos', 56 | videoCompression : false, //The quality setting for the video compression, in Constant Rate Factor (CRF). 57 | 58 | //Viewport (Override with cy.viewport() command) 59 | viewportHeight : 800, //Default height in pixels for the application under tests' viewport. 60 | viewportWidth : 1200, //Default width in pixels for the application under tests' viewport. 61 | 62 | //Timeouts 63 | defaultCommandTimeout : 5000, //Time, in milliseconds, to wait until most DOM based commands are considered timed out. 64 | 65 | 66 | //reports configuration 67 | reporter: 'cypress-multi-reporters', 68 | reporterOptions: { 69 | configFile: 'reporter-config.json', 70 | }, 71 | 72 | //The number of times to retry a failing test. Can be configured to apply to cypress run or cypress open separately. 73 | //If you want to configure retry attempts on a specific test or suite, you can set this by using the test's/suite's configuration. 74 | retries : { 75 | // Configure retry attempts for `cypress run` 76 | // Default is 0 77 | runMode: 1, 78 | // Configure retry attempts for `cypress open` 79 | // Default is 0 80 | openMode : 1 81 | }, 82 | 83 | 84 | //configuration options for e2e configuration object 85 | e2e: { 86 | 87 | //URL used as prefix for cy.visit() or cy.request() command's URL. 88 | //baseUrl: 'https://naveenautomationlabs.com/opencart/index.php', 89 | 90 | //A String or Array of glob patterns of the test files to load. 91 | specPattern : 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', 92 | 93 | //Path to file to load before spec files load. This file is compiled and bundled. 94 | supportFile : 'cypress/support/e2e.{js,jsx,ts,tsx}', 95 | 96 | setupNodeEvents(on, config) { 97 | // implement node event listeners here 98 | 99 | //Load the testing configuration and environment variables from separate JSON files. 100 | //we put the baseUrl and envionment specific config settings in settings/env.settings.json 101 | const environmentName = config.env.environmentName || 'local'; 102 | const environmentFilename = `./settings/${environmentName}.settings.json`; 103 | console.log('loading %s', environmentFilename); 104 | const settings = require(environmentFilename); 105 | 106 | //overwriting the baseUrl from settings file to config 107 | if (settings.baseUrl) { 108 | config.baseUrl = settings.baseUrl 109 | } 110 | 111 | // Megring the configuration settings. 112 | // If there are properties with the same name in both objects, the ones from settings.env will overwrite those in config.env. 113 | // If there are unique properties in either object, they will also be included in the merged config.env. 114 | if (settings.env) { 115 | config.env = { 116 | ...config.env, 117 | ...settings.env, 118 | } 119 | } 120 | console.log('loaded settings for environment %s', environmentName); 121 | 122 | //we save the projectId as env variable in cypress.env.json that is loaded automatically by cypress 123 | if(config.env.projectId) { 124 | config.projectId = config.env.projectId; 125 | } 126 | 127 | //cypress-mochawesome-reporter 128 | require('cypress-mochawesome-reporter/plugin')(on); 129 | 130 | //cypress grep plugin config for tags 131 | require('@cypress/grep/src/plugin')(config); 132 | 133 | //It is very important to return the updated config object to the caller, so Cypress knows to use the changes configuration. 134 | return config; 135 | }, 136 | 137 | //Any key/value you set in your Cypress configuration under the env key will become an environment variable. 138 | //When your tests are running, you can use the Cypress.env function to access the values of your environment variables. 139 | env : { 140 | URL : 'https://naveenautomationlabs.com/opencart/index.php' 141 | } 142 | 143 | }, 144 | }); 145 | -------------------------------------------------------------------------------- /cypress/e2e/components/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | export default class HeaderComponent { 2 | 3 | get searchInput() { return cy.get('#search input[name="search"]'); } 4 | get searchBtn() { return cy.get('#search button'); } 5 | get myAccountDropdown() { return cy.get('#top-links a[title="My Account"]'); } 6 | get shoppingCart() { return cy.get('#top-links a[title="Shopping Cart"]'); } 7 | get logoutLink() { return cy.get('#top-links a').contains('Logout'); } 8 | get wishListMenu() { return cy.get('#top-links #wishlist-total'); } 9 | 10 | searchProduct(product) { 11 | this.searchInput.clear() 12 | this.searchInput.type(product); 13 | this.searchBtn.click(); 14 | } 15 | 16 | performLogout() { 17 | this.myAccountDropdown.click(); 18 | this.logoutLink.click(); 19 | } 20 | 21 | openShoppingCart() { 22 | this.shoppingCart.click() 23 | } 24 | 25 | openWishlist() { 26 | this.wishListMenu.click(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /cypress/e2e/config/CONSTANTS.js: -------------------------------------------------------------------------------- 1 | //Add all the constants here 2 | export const PRODUCT_TO_TEST = 'MacBook'; 3 | export const PRODUCT_ID_TO_TEST = '43'; //product ID for MacBook (useful to open product details page directly) 4 | export const ENDPOINT_PREFIX = '?route='; //url prefix 5 | 6 | export const SORTING_CRITERIA = { 7 | PRICE_ASC : 'Price (Low > High)', 8 | PRICE_DESC : 'Price (High > Low)', 9 | NAME_ASC : 'Name (A - Z)', 10 | NAME_DESC : 'Name (Z - A)', 11 | }; 12 | -------------------------------------------------------------------------------- /cypress/e2e/config/errorMessages.js: -------------------------------------------------------------------------------- 1 | //This file stores the error messages for form validation for Register Account page 2 | export const validationMessages = { 3 | FIRSTNAME : 'First Name must be between 1 and 32 characters!', 4 | LASTNAME : 'Last Name must be between 1 and 32 characters!', 5 | EMAIL : 'E-Mail Address does not appear to be valid!', 6 | TELEPHONE : 'Telephone must be between 3 and 32 characters!', 7 | PASSWORD : 'Password must be between 4 and 20 characters!', 8 | PRIVACY_POLICY : 'Warning: You must agree to the Privacy Policy!', 9 | } -------------------------------------------------------------------------------- /cypress/e2e/config/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | REGISTRATION_ENDPOINT : 'account/register', 3 | LOGIN_ENDPOINT : 'account/login', 4 | ACCOUNT_ENDPOINT : 'account/account', 5 | PRODUCT_SEARCH_ENDPOINT : 'product/search', 6 | PRODUCT_DETAILS_ENDPOINT : 'product/product&product_id=', 7 | CART_ENDPOINT : 'checkout/cart', 8 | WISHLIST_ENDPOINT : 'account/wishlist', 9 | } -------------------------------------------------------------------------------- /cypress/e2e/pages/AccountPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class AccountPage extends BasePage{ 6 | 7 | //refer to the elements in tests as AccountPage.elements.h1Heading() 8 | /* elements = { 9 | h1Heading : () => cy.get('#content h1') , 10 | h2Heading : () => cy.get('#content h2'), 11 | } */ 12 | 13 | //Alternative better approach is to define the getters for elements and refer to them in tests directly as AccountPage.h1Heading 14 | // In JavaScript, getters are special methods that allow you to access an object's properties like they were regular properties, 15 | //but you can also execute code when they are accessed. 16 | //The advantage of using getter and setter is that we can use them as properties instead of functions. 17 | get h1Heading() {return cy.get('#content h1')}; 18 | get h2Heading() {return cy.get('#content h2')}; 19 | 20 | open() { 21 | return super.open(ENDPOINT_PREFIX + routes.ACCOUNT_ENDPOINT) 22 | } 23 | 24 | } 25 | 26 | 27 | export default new AccountPage(); 28 | 29 | -------------------------------------------------------------------------------- /cypress/e2e/pages/BasePage.js: -------------------------------------------------------------------------------- 1 | import HeaderComponent from "../components/HeaderComponent"; 2 | 3 | class BasePage { 4 | 5 | constructor() { 6 | this.header = new HeaderComponent(); 7 | } 8 | 9 | open(path) { 10 | return cy.visit(path) 11 | } 12 | 13 | } 14 | 15 | export default BasePage; 16 | -------------------------------------------------------------------------------- /cypress/e2e/pages/LoginPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class LoginPage extends BasePage{ 6 | 7 | get continueBtn() { return cy.get('a').contains('Continue'); } 8 | get loginInput() { return cy.get('#input-email'); } 9 | get passwordInput() { return cy.get('#input-password'); } 10 | get loginBtn() { return cy.get("input[value='Login']"); } 11 | get alertMsg() { return cy.get('#account-login .alert'); } 12 | 13 | open() { 14 | //cy.visit('?route=account/login'); //Prefixes the baseUrl 15 | //cy.visit(Cypress.env('URL')); //loads the URL from env object in cypress.config.js 16 | return super.open(ENDPOINT_PREFIX + routes.LOGIN_ENDPOINT) 17 | } 18 | 19 | openRegistrationPage() { 20 | this.open(); 21 | this.continueBtn.click(); 22 | } 23 | 24 | loginWithUI(email, password) { 25 | this.open(); 26 | this.loginInput.type(email) 27 | this.passwordInput.type(password) 28 | this.loginBtn.click() 29 | } 30 | 31 | } 32 | 33 | 34 | export default new LoginPage(); 35 | 36 | -------------------------------------------------------------------------------- /cypress/e2e/pages/ProductDetailsPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class ProductDetailsPage extends BasePage{ 6 | 7 | get addToWishlistBtn() { return cy.get('[data-original-title="Add to Wish List"]').first(); } 8 | get addToCartBtn() { return cy.get('#button-cart'); } 9 | get alert() { return cy.get('#product-product .alert'); } 10 | 11 | get productName() { return cy.get('#content h1'); } 12 | get productPrice() { return cy.get('#content #product').prev('ul').find('h2'); } 13 | get productDescription() { return cy.get('#content .intro'); } 14 | 15 | 16 | open(productID) { 17 | return super.open(ENDPOINT_PREFIX + routes.PRODUCT_DETAILS_ENDPOINT + productID) 18 | } 19 | 20 | addProductToCart() { 21 | this.addToCartBtn.click(); 22 | } 23 | 24 | addProductToWishlist() { 25 | this.addToWishlistBtn.click(); 26 | } 27 | 28 | } 29 | 30 | 31 | export default new ProductDetailsPage(); 32 | 33 | -------------------------------------------------------------------------------- /cypress/e2e/pages/ProductsSearchPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class ProductsSearchPage extends BasePage{ 6 | 7 | get productCard() { return (productName) => cy.get(`#content .product-thumb img[title='${productName}']`).parents('.product-thumb'); } 8 | get alert() { return cy.get('#product-search .alert'); } 9 | 10 | get productName() { return (productName) => this.productCard(productName).find('.caption h4 a')} 11 | get productDescription() { return (productName) => this.productCard(productName).find('.caption p').first()} 12 | get productPrice() { return (productName) => this.productCard(productName).find('.caption .price')} 13 | 14 | get sortDropdown() { return cy.get('#input-sort'); } 15 | get allProductsPrices() { return cy.get('.price'); } 16 | get allProductNames() { return cy.get('.caption h4 a'); } 17 | 18 | get catrgoryDropdown() { return cy.get('select[name="category_id"]'); } 19 | get searchBtn() { return cy.get('#button-search'); } 20 | 21 | open() { 22 | return super.open(ENDPOINT_PREFIX + routes.PRODUCT_SEARCH_ENDPOINT) 23 | } 24 | 25 | openProduct(productName) { 26 | this.productCard(productName).find('.caption h4 a').click(); 27 | } 28 | 29 | addProductToCart(productName) { 30 | this.productCard(productName).find('button').contains('Add to Cart').click({force: true}); 31 | } 32 | 33 | sortSearchResultsBy(sortingCriteria) { 34 | this.sortDropdown.select(sortingCriteria); 35 | } 36 | 37 | filterSearchResultsByCategory(category) { 38 | this.catrgoryDropdown.select(category); 39 | } 40 | 41 | applyFilter() { 42 | this.searchBtn.click(); 43 | } 44 | 45 | getAllProductNames() { 46 | const productNamesArr = [] 47 | this.allProductNames.each(($productName) => productNamesArr.push($productName.text())); 48 | 49 | //This will evaluate to true if value is not: 50 | //null, undefined, NaN, empty String(""), 0 or false 51 | //Helps in validation that array returned is not empty or 52 | if(productNamesArr) { 53 | return productNamesArr; 54 | } 55 | return null; 56 | } 57 | 58 | } 59 | 60 | 61 | export default new ProductsSearchPage(); 62 | 63 | -------------------------------------------------------------------------------- /cypress/e2e/pages/RegisterPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class RegisterPage extends BasePage{ 6 | 7 | 8 | get firstnameInput() { return cy.get('#input-firstname'); } 9 | get lastnameInput() { return cy.get('#input-lastname'); } 10 | get emailInput() { return cy.get('#input-email'); } 11 | get telephoneInput() { return cy.get('#input-telephone'); } 12 | get passwordInput() { return cy.get('#input-password'); } 13 | get passwordConfirmInput() { return cy.get('#input-confirm'); } 14 | get policyCheckbox() { return cy.get('input[type="checkbox"][name="agree"]'); } 15 | get continueBtn() { return cy.get('input[type="submit"][value="Continue"]'); } 16 | 17 | get inputValidationErr() { return (inputField) => cy.wrap(inputField).next('.text-danger'); } 18 | 19 | get alertMsg() { return cy.get('#account-register .alert'); } 20 | 21 | open() { 22 | return super.open(ENDPOINT_PREFIX + routes.REGISTRATION_ENDPOINT) 23 | } 24 | 25 | 26 | enterfirstName(username) { 27 | this.firstnameInput.type(username); 28 | return this; 29 | } 30 | 31 | enterlastName(lastname) { 32 | this.lastnameInput.type(lastname); 33 | return this; 34 | } 35 | 36 | enterPassword(password) { 37 | this.passwordInput.type(password); 38 | return this; 39 | } 40 | 41 | enterConfirmPassword(password) { 42 | this.passwordConfirmInput.type(password); 43 | return this; 44 | } 45 | 46 | enterEmail(email) { 47 | this.emailInput.type(email); 48 | return this; 49 | } 50 | 51 | enterTelephone(telephone) { 52 | this.telephoneInput.type(telephone); 53 | return this; 54 | } 55 | 56 | confirmPolicy(value) { 57 | 58 | if(value) { 59 | this.policyCheckbox.check(); 60 | } else { 61 | this.policyCheckbox.uncheck(); 62 | } 63 | return this; 64 | } 65 | 66 | submitRegistraion() { 67 | this.continueBtn.click(); 68 | } 69 | 70 | 71 | } 72 | 73 | 74 | export default new RegisterPage(); 75 | 76 | -------------------------------------------------------------------------------- /cypress/e2e/pages/ShoppingCartPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class ShoppingCartPage extends BasePage{ 6 | 7 | get cartItems() { return cy.get('form table>tbody>tr'); } 8 | get checkoutBtn() { return cy.get('a').contains('Checkout'); } 9 | 10 | open() { 11 | return super.open(ENDPOINT_PREFIX + routes.CART_ENDPOINT) 12 | } 13 | 14 | performCheckout() { 15 | this.checkoutBtn.click(); 16 | } 17 | 18 | getItemsAddedToCart() { 19 | let cartItems = []; 20 | 21 | this.cartItems.each( 22 | ($row, index, $rows) => { 23 | 24 | //within() scopes all subsequent cy commands to within this element. 25 | cy.wrap($row).within( () => { 26 | 27 | cy.get("td:nth-of-type(2) a").each(($col, index, $cols) => { 28 | cy.log($col.text()) 29 | cartItems.push($col.text()) 30 | }) 31 | }) 32 | 33 | }) 34 | 35 | return cy.wrap(cartItems); //Wrap elements to continue executing commands 36 | } 37 | 38 | } 39 | 40 | 41 | export default new ShoppingCartPage(); 42 | 43 | -------------------------------------------------------------------------------- /cypress/e2e/pages/WishlistPage.js: -------------------------------------------------------------------------------- 1 | import BasePage from "./BasePage"; 2 | const routes = require('../config/routes'); 3 | import { ENDPOINT_PREFIX } from "../config/constants"; 4 | 5 | class WishlistPage extends BasePage{ 6 | 7 | get wishlistItems() { return cy.get('h2 + div>table>tbody>tr'); } 8 | get removeFromWishlistBtn() { return cy.get('[data-original-title="Remove"]'); } 9 | get emptyWishlistDesc() { return cy.get('#content>h2+p'); } 10 | 11 | get productNameCol() { return cy.contains('table thead td', 'Product Name'); } 12 | 13 | open() { 14 | return super.open(ENDPOINT_PREFIX + routes.WISHLIST_ENDPOINT) 15 | } 16 | 17 | getItemsAddedToWishlist() { 18 | let wishlistItemsArr = []; 19 | 20 | //First grab a column index with Product Name header 21 | this.productNameCol 22 | .invoke('index') 23 | .should('be.a', 'number') 24 | .then((columnIndex) => { 25 | //Iterate over all rows in table 26 | this.wishlistItems.each( ($wishlistItem) => { 27 | //Now find the table cell using column index 28 | cy.wrap($wishlistItem) 29 | .find('td a') 30 | .eq(columnIndex) 31 | .invoke('text').then((productName) => { 32 | wishlistItemsArr.push(productName) 33 | }); 34 | 35 | }) 36 | 37 | }) 38 | 39 | return cy.wrap(wishlistItemsArr); //Wrap elements to continue executing commands 40 | } 41 | 42 | removeItemsFromWishlist() { 43 | 44 | //Since the page refreshes after button click() so we can't use cypress.each() method to iterate over the list of buttons 45 | /* this.removeFromWishlistBtn.each(($button) => { 46 | cy.wrap($button).click(); 47 | }) */ 48 | 49 | 50 | //To work around this issue, we are looping over the length of buttons, and then calling the click() method in loop 51 | this.removeFromWishlistBtn.then($buttons => { 52 | const buttonCount = $buttons.length; 53 | for (let i = 0; i < buttonCount; i++) { 54 | this.removeFromWishlistBtn.eq(0).click(); 55 | } 56 | }) 57 | 58 | 59 | } 60 | 61 | } 62 | 63 | 64 | export default new WishlistPage(); 65 | 66 | -------------------------------------------------------------------------------- /cypress/e2e/tests/AddToCartTest.cy.js: -------------------------------------------------------------------------------- 1 | import BasePage from "../pages/BasePage"; 2 | import ProductDetailsPage from "../pages/ProductDetailsPage"; 3 | import ProductsSearchPage from "../pages/ProductsSearchPage"; 4 | import ShoppingCartPage from "../pages/ShoppingCartPage"; 5 | // import * as constants from "../config/constants"; 6 | import { PRODUCT_TO_TEST as PRODUCT, PRODUCT_ID_TO_TEST as PRODUCT_ID } from "../config/constants"; 7 | 8 | describe("adding products to cart", { tags: ['@Cart', '@regression'] }, () => { 9 | 10 | let basePage; 11 | 12 | before(() => { 13 | basePage = new BasePage(); 14 | }) 15 | 16 | beforeEach(() => { 17 | cy.login(); //login via custom command 18 | basePage.header.searchProduct(PRODUCT); 19 | }) 20 | 21 | it("should add product to the cart from products search page", {tags: '@smoke'}, function () { 22 | 23 | ProductsSearchPage 24 | .addProductToCart(PRODUCT); 25 | 26 | ProductsSearchPage.alert 27 | .should('contains.text', `Success: You have added ${PRODUCT} to your shopping cart!`) 28 | }) 29 | 30 | 31 | it("should add product to the cart from product details page", function () { 32 | 33 | 34 | //open product detais page directly with URL 35 | //ProductDetailsPage.open(PRODUCT_ID) 36 | 37 | ProductsSearchPage 38 | .openProduct(PRODUCT); 39 | 40 | ProductDetailsPage 41 | .addProductToCart(); 42 | 43 | ProductDetailsPage.alert 44 | .should('contains.text', `Success: You have added ${PRODUCT} to your shopping cart!`) 45 | }) 46 | 47 | it("should validate the presence of product in cart", function () { 48 | 49 | ProductsSearchPage 50 | .addProductToCart(PRODUCT); 51 | 52 | basePage.header.openShoppingCart(); 53 | 54 | ShoppingCartPage.getItemsAddedToCart().should('include', PRODUCT); 55 | 56 | }) 57 | }) -------------------------------------------------------------------------------- /cypress/e2e/tests/LoginTest.cy.js: -------------------------------------------------------------------------------- 1 | import AccountPage from "../pages/AccountPage"; 2 | import BasePage from "../pages/BasePage"; 3 | import LoginPage from "../pages/LoginPage"; 4 | 5 | describe("Success and Fail login flow", { tags: ['@Login', '@regression'] }, () => { 6 | 7 | let basePage; 8 | 9 | before(() => { 10 | basePage = new BasePage(); 11 | }) 12 | 13 | //Mocha automatically shares contexts for us across all applicable hooks for each test. 14 | //Additionally these aliases and properties are automatically cleaned up after each test. 15 | beforeEach(() => { 16 | 17 | //Aliasing cy.fixture() data and then using this to access it via the alias. 18 | //Note the use of the standard function syntax. 19 | //Using arrow functions to access aliases via this won't work because of the lexical binding of this. 20 | 21 | cy.fixture('users.json').as('users') 22 | }) 23 | 24 | 25 | it("should login successfully with valid credentials", {tags: '@smoke'}, function () { 26 | 27 | LoginPage 28 | .loginWithUI(this.users.validUser.email, this.users.validUser.password) 29 | 30 | AccountPage.h2Heading 31 | .should('contains.text', 'My Account'); 32 | }) 33 | 34 | it("should fail to login with invalid credentials", {tags: '@smoke'}, function () { 35 | 36 | LoginPage 37 | .loginWithUI(this.users.invalidUser.email, this.users.invalidUser.password) 38 | 39 | LoginPage.alertMsg 40 | .should('contains.text', 'Warning'); 41 | }) 42 | 43 | it("should perform login and logout", function () { 44 | 45 | cy.login(); //login via custom command 46 | 47 | basePage.header.performLogout(); 48 | 49 | AccountPage.h1Heading 50 | .should('contains.text', 'Account Logout'); 51 | }) 52 | }) -------------------------------------------------------------------------------- /cypress/e2e/tests/ProductDataTest.cy.js: -------------------------------------------------------------------------------- 1 | import BasePage from "../pages/BasePage"; 2 | import ProductDetailsPage from "../pages/ProductDetailsPage"; 3 | import ProductsSearchPage from "../pages/ProductsSearchPage"; 4 | 5 | describe('product details and search', { tags: ['@Product', '@regression'] }, () => { 6 | 7 | let basePage; 8 | 9 | before(() => { 10 | basePage = new BasePage(); 11 | }) 12 | 13 | 14 | beforeEach(() => { 15 | cy.visit(''); 16 | // alias the product fixtures 17 | cy.fixture('product.json').as('productData') 18 | }); 19 | 20 | it('should validate all the products presence in store', {tags: '@smoke'}, () => { 21 | 22 | //This is a demonstration for data-driven testing using fixtures 23 | //Test will be executed for all the products in products.json file 24 | cy.fixture('products').then((products) => { 25 | 26 | products.forEach(product => { 27 | 28 | cy.log(product.name); 29 | 30 | basePage 31 | .header.searchProduct(product.name); 32 | 33 | ProductsSearchPage 34 | .productName(product.name) 35 | .should('have.text', product.name); 36 | 37 | }); 38 | }) 39 | }); 40 | 41 | it('should validate the product data on products search page', () => { 42 | 43 | // use the special '@' syntax to access aliases which avoids the use of 'this' 44 | cy.get('@productData').then((productData) => { 45 | basePage 46 | .header.searchProduct(productData.name); 47 | 48 | // we pass the product name to filter the product card for specific product 49 | // which gives us the name, price and description based on product name 50 | ProductsSearchPage 51 | .productName(productData.name) 52 | .should('have.text', productData.name); 53 | 54 | ProductsSearchPage 55 | .productDescription(productData.name) 56 | .should('contains.text', productData.description); 57 | 58 | ProductsSearchPage 59 | .productPrice(productData.name).then((price) => { 60 | const actualPrice = price.text().split("Ex Tax:")[0].split("$")[1].trim(); 61 | expect(actualPrice).to.be.eq(productData.price); 62 | }) 63 | 64 | }) 65 | }); 66 | 67 | it('should validate the product data on product details page', () => { 68 | 69 | // use the special '@' syntax to access aliases which avoids the use of 'this' 70 | cy.get('@productData').then((productData) => { 71 | basePage 72 | .header.searchProduct(productData.name); 73 | 74 | ProductsSearchPage.openProduct(productData.name); 75 | 76 | ProductDetailsPage 77 | .productName 78 | .should('have.text', productData.name); 79 | 80 | ProductDetailsPage 81 | .productDescription 82 | .should('contains.text', productData.description); 83 | 84 | ProductDetailsPage 85 | .productPrice.then((price) => { 86 | const actualPrice = price.text().split("$")[1].trim(); 87 | expect(actualPrice).to.be.eq(productData.price); 88 | }) 89 | 90 | }) 91 | }); 92 | }); -------------------------------------------------------------------------------- /cypress/e2e/tests/ProductsSearchTest.cy.js: -------------------------------------------------------------------------------- 1 | import { SORTING_CRITERIA } from "../config/constants"; 2 | import { default as ProductsSearchPage } from "../pages/ProductsSearchPage"; 3 | import { extractActualPrices, extractProductsName } from "../utils/ProductUtils"; 4 | import productCategories from "../../fixtures/productCategories.json"; 5 | 6 | describe('Products meeting the search criteria', { tags: ['@Search', '@regression'] }, () => { 7 | 8 | beforeEach(() => { 9 | ProductsSearchPage.open(); 10 | //search with a space to show all the products in inventory 11 | ProductsSearchPage.header.searchProduct(" "); 12 | }); 13 | 14 | context('Sorting', () => { 15 | 16 | it('should check that the products are sorted by Price (Low > High)', {tags: '@smoke'}, () => { 17 | ProductsSearchPage.sortSearchResultsBy(SORTING_CRITERIA.PRICE_ASC); 18 | 19 | ProductsSearchPage.allProductsPrices 20 | .should('not.be.empty') 21 | .then(($prices) => { 22 | const prices = extractActualPrices($prices); 23 | const sorted = Cypress._.sortBy(prices) 24 | expect(sorted).to.deep.equal(prices) 25 | }) 26 | }); 27 | 28 | it('should check that the products are sorted by Price (High > Low)', () => { 29 | ProductsSearchPage.sortSearchResultsBy(SORTING_CRITERIA.PRICE_DESC); 30 | 31 | ProductsSearchPage.allProductsPrices 32 | .should('not.be.empty') 33 | .then(($prices) => { 34 | const prices = extractActualPrices($prices); 35 | const sorted = Cypress._.sortBy(prices).reverse(); 36 | expect(sorted).to.deep.equal(prices) 37 | }) 38 | }); 39 | 40 | it('should check that the products are sorted by Name (A - Z)', () => { 41 | ProductsSearchPage.sortSearchResultsBy(SORTING_CRITERIA.NAME_ASC); 42 | 43 | //Approach 1 44 | const productNames = ProductsSearchPage.getAllProductNames(); 45 | const sortedNames = productNames.sort(); 46 | cy.wrap(sortedNames) 47 | .should('deep.equal', productNames) 48 | //.and('not.be.empty') 49 | //.and('not.be.undefined') 50 | //.and('not.be.null') 51 | 52 | //Approach 2 53 | /* ProductsSearchPage.allProductNames 54 | .should('not.be.empty') 55 | .then(($names) => { 56 | const productNames = extractProductsName($names); 57 | const sortedNames = Cypress._.orderBy(productNames, [name => name.toLowerCase()]) 58 | expect(sortedNames).to.deep.equal(productNames) 59 | }) */ 60 | }); 61 | 62 | it('should check that the products are sorted by Price Name (Z - A)', () => { 63 | ProductsSearchPage.sortSearchResultsBy(SORTING_CRITERIA.NAME_DESC); 64 | 65 | //Approach 1 66 | const productNames = ProductsSearchPage.getAllProductNames(); 67 | const sortedNames = productNames.sort().reverse(); 68 | cy.wrap(sortedNames) 69 | .should('deep.equal', productNames); 70 | 71 | //Approaach 2 72 | /* ProductsSearchPage.allProductNames 73 | .should('not.be.empty') 74 | .then(($names) => { 75 | const names = extractProductsName($names); 76 | const sorted = Cypress._.orderBy(names, [name => name.toLowerCase()], ['desc']) 77 | expect(sorted).to.deep.equal(names) 78 | }) */ 79 | 80 | }); 81 | }); 82 | 83 | context('Categories', () => { 84 | 85 | /************ This approach creates tests dynamically based on loaded test data ************/ 86 | productCategories.categories.forEach(category => { 87 | 88 | it(`should filter the search results by category: ${category.categoryName}`, {tags: '@smoke'}, () => { 89 | ProductsSearchPage.filterSearchResultsByCategory(category.categoryName); 90 | ProductsSearchPage.applyFilter(); 91 | 92 | //Approach 1 93 | const productNames = ProductsSearchPage.getAllProductNames(); 94 | cy.wrap(productNames) 95 | .should('include.members', category.products); 96 | 97 | //Approach 2 98 | /* ProductsSearchPage.allProductNames 99 | .then(($names) => { 100 | const productNamesList = extractProductsName($names); 101 | expect(productNamesList).to.include.members(category.products) 102 | }) */ 103 | 104 | }); 105 | 106 | }); 107 | 108 | 109 | /************ This approach considers it the same test and run 2 times with different data ***************/ 110 | /* beforeEach(() => { 111 | cy.fixture('productCategories.json').as('productCategories'); 112 | }); 113 | 114 | it('should filter the search results by category', {tags: '@smoke'}, () => { 115 | cy.get('@productCategories').then((productCategories) => { 116 | 117 | cy.wrap(productCategories.categories).each(category => { 118 | cy.log(`********* Applying category fiter: ${category.categoryName} *********`) 119 | ProductsSearchPage.filterSearchResultsByCategory(category.categoryName); 120 | ProductsSearchPage.applyFilter(); 121 | 122 | const productNames = ProductsSearchPage.getAllProductNames(); 123 | cy.wrap(productNames) 124 | .should('include.members', category.products); 125 | 126 | }); 127 | }) 128 | }); */ 129 | }); 130 | 131 | 132 | }); -------------------------------------------------------------------------------- /cypress/e2e/tests/RegistrationTest.cy.js: -------------------------------------------------------------------------------- 1 | import { validationMessages } from "../config/errorMessages"; 2 | import AccountPage from "../pages/AccountPage"; 3 | import LoginPage from "../pages/LoginPage"; 4 | import RegisterPage from "../pages/RegisterPage" 5 | 6 | import { faker } from '@faker-js/faker'; 7 | 8 | describe("Account Registration", { tags: ['@Register', '@regression'] }, () => { 9 | 10 | beforeEach(() => { 11 | LoginPage.openRegistrationPage(); 12 | }); 13 | 14 | //Unique credentials are required for registration, so we use faker library to generate test data 15 | it("should register the new user", ()=> { 16 | 17 | let password = faker.internet.password(); 18 | 19 | RegisterPage 20 | .enterfirstName(faker.person.firstName()) 21 | .enterlastName(faker.person.lastName()) 22 | .enterEmail(faker.internet.email()) 23 | .enterTelephone(faker.phone.number()) 24 | .enterPassword(password) 25 | .enterConfirmPassword(password) 26 | .confirmPolicy(true) 27 | .submitRegistraion() 28 | ; 29 | 30 | AccountPage.h1Heading 31 | .should('have.text', 'Your Account Has Been Created!'); 32 | 33 | }) 34 | 35 | it('should validate the error messages for missing input fields', {tags: '@smoke'}, () => { 36 | RegisterPage.submitRegistraion(); 37 | 38 | //Instead of repeating the below code for querying and asserting each input field repeatedly 39 | /* RegisterPage.firstnameInput 40 | .then(($input) => RegisterPage.inputValidationErr($input)) 41 | .should('be.visible') 42 | .and('have.text', validationMessages.FIRSTNAME) */ 43 | 44 | //Better approach is to write a custom command and call it with input field parameters to validate 45 | cy.validateFormField(RegisterPage.firstnameInput, validationMessages.FIRSTNAME); 46 | cy.validateFormField(RegisterPage.lastnameInput, validationMessages.LASTNAME); 47 | cy.validateFormField(RegisterPage.emailInput, validationMessages.EMAIL); 48 | cy.validateFormField(RegisterPage.telephoneInput, validationMessages.TELEPHONE); 49 | cy.validateFormField(RegisterPage.passwordInput, validationMessages.PASSWORD); 50 | 51 | 52 | RegisterPage.alertMsg 53 | .should('be.visible') 54 | .and('contain.text', validationMessages.PRIVACY_POLICY); 55 | 56 | }); 57 | }) -------------------------------------------------------------------------------- /cypress/e2e/tests/WishlistTest.cy.js: -------------------------------------------------------------------------------- 1 | import { PRODUCT_ID_TO_TEST, PRODUCT_TO_TEST } from "../config/constants"; 2 | import { default as ProductDetailsPage } from "../pages/ProductDetailsPage"; 3 | import WishlistPage from "../pages/WishlistPage"; 4 | 5 | describe('adding and removing products from wishlist', { tags: ['@Wishlist', '@regression'] }, () => { 6 | context('logged-in user', () => { 7 | beforeEach(() => { 8 | cy.login(); 9 | ProductDetailsPage.open(PRODUCT_ID_TO_TEST); 10 | ProductDetailsPage.addProductToWishlist(); 11 | }); 12 | 13 | after(() => { 14 | //Clear the wishlist after the test suite is finished 15 | //so each test starts with fresh state and empty wishlist 16 | WishlistPage 17 | .removeItemsFromWishlist(); 18 | 19 | WishlistPage 20 | .emptyWishlistDesc 21 | .should('be.visible') 22 | .and('have.text', 'Your wish list is empty.'); 23 | }); 24 | 25 | it('should validate the success message for product added to wishlist', () => { 26 | 27 | ProductDetailsPage 28 | .alert 29 | .should('contains.text', `Success: You have added ${PRODUCT_TO_TEST} to your wish list!`); 30 | }); 31 | 32 | it('should validate the presence of product in wishlist', {tags: '@smoke'}, () => { 33 | 34 | ProductDetailsPage.header.openWishlist(); 35 | 36 | WishlistPage 37 | .getItemsAddedToWishlist() 38 | .should('include', PRODUCT_TO_TEST) 39 | 40 | }); 41 | 42 | }); 43 | 44 | context('logged-out user', () => { 45 | beforeEach(() => { 46 | ProductDetailsPage.open(PRODUCT_ID_TO_TEST); 47 | ProductDetailsPage.addProductToWishlist(); 48 | }); 49 | 50 | it('should validate the error message for product added to wishlist', () => { 51 | 52 | ProductDetailsPage 53 | .alert 54 | .should('contains.text', `You must login or create an account to save ${PRODUCT_TO_TEST} to your wish list!`); 55 | }); 56 | 57 | }); 58 | 59 | 60 | }); -------------------------------------------------------------------------------- /cypress/e2e/utils/ProductUtils.js: -------------------------------------------------------------------------------- 1 | //https://docs.cypress.io/api/utilities/_ 2 | //https://lodash.com/docs/4.17.15#map 3 | //https://lodash.com/docs/4.17.15#orderBy 4 | //https://lodash.com/docs/4.17.15#sortBy 5 | //https://glebbahmutov.com/cypress-examples/recipes/sorted-list.html 6 | 7 | 8 | 9 | //It loops over the list of jQuery prices elements and return the actual prices list 10 | export const extractActualPrices = ($prices) => { 11 | const innerText = (el) => el.innerText; 12 | const firstWord = (text) => text.split(' ')[0]; 13 | const justDigits = (str) => str.replace(/[^0-9.]/g, ''); 14 | 15 | const processedPricesList = Cypress._.map($prices, (el) => 16 | parseFloat(justDigits(firstWord(innerText(el)))), 17 | ) 18 | 19 | return processedPricesList; 20 | }; 21 | 22 | //It returns the text content of the list elements 23 | export const extractProductsName = ($names) => { 24 | const productNamesList = Cypress._.map($names, (name) => name.innerText); 25 | return productNamesList; 26 | }; 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /cypress/fixtures/product.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "iPhone", 3 | "brand" : "Apple", 4 | "price" : "123.20", 5 | "description" : "iPhone is a revolutionary new mobile phone" 6 | } -------------------------------------------------------------------------------- /cypress/fixtures/productCategories.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "categoryName": "Cameras", 5 | "products": [ 6 | "Canon EOS 5D", 7 | "Nikon D300" 8 | ] 9 | }, 10 | { 11 | "categoryName": "Monitors", 12 | "products": [ 13 | "Apple Cinema 30\"", 14 | "Samsung SyncMaster 941BW" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /cypress/fixtures/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "iPhone", 4 | "brand" : "Apple", 5 | "price" : "123.20", 6 | "description" : "iPhone is a revolutionary new mobile phone" 7 | }, 8 | { 9 | "name" : "MacBook", 10 | "brand" : "Apple", 11 | "price" : "602.00", 12 | "description" : "Intel Core 2 Duo processor" 13 | }, 14 | { 15 | "name" : "Sony VAIO", 16 | "brand" : "Sony", 17 | "price" : "1,202.00", 18 | "description" : "Unprecedented power. The next generation of processing technology has arrived." 19 | } 20 | ] -------------------------------------------------------------------------------- /cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "validUser": { 3 | "email": "testuser@test.com", 4 | "password": "test123" 5 | }, 6 | "invalidUser": { 7 | "email": "invalid@test.com", 8 | "password": "invalidpassword" 9 | } 10 | } -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | 27 | import LoginPage from "../e2e/pages/LoginPage" 28 | import RegisterPage from "../e2e/pages/RegisterPage"; 29 | 30 | Cypress.Commands.add('login', () => { 31 | 32 | cy.fixture('users.json').then((users) => { 33 | 34 | LoginPage.loginWithUI(users.validUser.email, users.validUser.password); 35 | }) 36 | 37 | }) 38 | 39 | Cypress.Commands.add('validateFormField', (inputField, message) => { 40 | return inputField.then(($input) => RegisterPage.inputValidationErr($input)) 41 | .should('be.visible') 42 | .and('have.text', message) 43 | }) 44 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import 'cypress-mochawesome-reporter/register'; 23 | 24 | const registerCypressGrep = require('@cypress/grep') 25 | registerCypressGrep() 26 | 27 | // ignore all console errors in app 28 | //prevents failure of test on console errors while loading website 29 | // eslint-disable-next-line no-unused-vars 30 | Cypress.on('uncaught:exception', (err, runnable) => { 31 | // returning false here prevents Cypress from 32 | // failing the test 33 | return false 34 | }) -------------------------------------------------------------------------------- /cypress/support/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Cypress { 2 | interface Chainable { 3 | /** 4 | * Custom command to login via UI 5 | * It reads the valid credentials from fixtures and call LoginPage.loginWithUI(email, password) 6 | * @example cy.login() 7 | */ 8 | login(): Cypress.Chainable; 9 | 10 | /** 11 | * Custom Cypress command to validate a form field. 12 | * @param {Chainable>} inputField - The input field element. 13 | * @param {string} message - The expected validation message. 14 | * @example 15 | * cy.validateFormField(RegisterPage.firstnameInput, 'Invalid input provided'); 16 | */ 17 | validateFormField(inputField: Chainable>, message: string): Cypress.Chainable>; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /doc/cyress-cloud-results.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/cyress-cloud-results.PNG -------------------------------------------------------------------------------- /doc/cyress-cloud-specs.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/cyress-cloud-specs.PNG -------------------------------------------------------------------------------- /doc/docker.md: -------------------------------------------------------------------------------- 1 | # Run Cypress Tests on Docker 2 | 3 | ## Docker Setup 4 | 5 | * Download the Docker Desktop 6 | * Before Installing the Docker Desktop, Your Windows machine must meet the following [requirements](https://docs.docker.com/desktop/install/windows-install/#system-requirements) to successfully install Docker Desktop. 7 | * Make sure the system RAM, windows version,processor and the [BIOS virtualization](https://docs.docker.com/desktop/troubleshoot/topics/#virtualization) is enabled. 8 | * [How to install Linux on Windows with WSL](https://learn.microsoft.com/en-us/windows/wsl/install) 9 | * Run `wsl --install` in PowerShell to install WSL that will download and install Ubuntu latest version. It may ask to restart the PC. 10 | * Now install the Docker-Desktop. After Installation, check by running the following command to see if the Docker is successfully installed `docker -v` 11 | * Optionally also Install Docker extension from Microsoft in VS Code. 12 | 13 | ## Dockerfile 14 | 15 | * Create `Dockerfile` in your project root. 16 | * We named our docker image as `cypress_docker` in Dockerfile so we use the same name later to build the image, and run containerized tests. 17 | 18 | ## Run Tests 19 | 20 | ### Without docker-compose 21 | 22 | * Execute the following command to build the docker image `docker build -t .` 23 | * `docker run -i -v ${PWD}:/cypress_docker -t cypress_docker:latest test:all` is equivalent of running `npx cypress run` locally which will run all test specs inside the docker container. 24 | * Run the `docker run -i -v ${PWD}:/cypress_docker -t cypress_docker:latest test:registration -- --env environmentName="local",grepTags="@smoke" --headed chrome` which will run the cypress tests inside docker container. 25 | * It mounts the current local directory to cypress_docker directory inside container so that videos and reports can be exported. 26 | * Since we gave the ENTRYPOINT as `npm run`, so we can call any script from our package.json 27 | * For example, the above command will run `test:registration` script with additional environment variables supplied to it for environment, tags, and browser. 28 | 29 | ### With docker-compose 30 | 31 | * Add the following lines of code to `docker-compose.yml` 32 | ```yaml 33 | version: '3' 34 | services: 35 | cypress: 36 | image: cypress_docker:latest # Replace with the name and tag of your Docker image 37 | volumes: 38 | - .:/cypress_docker 39 | working_dir: /cypress_docker 40 | command: test:all 41 | ``` 42 | * `docker-compose up` command will run all the cypress tests inside docker. 43 | * Alternatively, if you wish to combine the build of your Docker image and the execution of Docker Compose in a single command by using the `docker-compose build` and `docker-compose up` commands together. Here's how you can do it 44 | * Update your `docker-compose.yml` file to include the Dockerfile build configuration. We name the new file as `docker-compose-build.yml` 45 | ```yaml 46 | version: '3' 47 | services: 48 | cypress: 49 | build: 50 | context: . 51 | dockerfile: Dockerfile # Specify the path to your Dockerfile 52 | volumes: 53 | - .:/cypress_docker 54 | working_dir: /cypress_docker 55 | command: test:all 56 | ``` 57 | * In this updated configuration: 58 | * `build` specifies that you want to build an image. 59 | * `context` specifies the build context, which is set to . to use the current directory. 60 | * `dockerfile` specifies the path to your Dockerfile, which should be in the same directory as your docker-compose.yml. 61 | * Run both the build and the service using a single docker-compose command: `docker-compose -f docker-compose-build.yml up --build` 62 | * This command tells Docker Compose to use the `docker-compose-build.yml` file instead of the default `docker-compose.yml` 63 | * The `--build` flag tells Docker Compose to rebuild the service's image before starting it. This way, Docker Compose will first build the Docker image based on your Dockerfile, and then it will start the service, executing the specified command (`npm run test:all`) inside the container. 64 | * To run cypress tests on multiple browsers, use `docker-compose -f docker-compose-browsers.yml up` command to spin separate containers that will run tests on chrome, firefox, and edge. 65 | * Inspect the container logs in `Docker Desktop` to see the output clearly 66 | * To save the report, and media seprately for each container, configure different WORKDIR for each service with a a different Dockerfile 67 | 68 | ## Resources 69 | 70 | * [Cypress Docker](https://docs.cypress.io/examples/docker) 71 | * [Cypress Docker GitHub](https://github.com/cypress-io/cypress-docker-images) 72 | * [Cypress Docer hub](https://hub.docker.com/u/cypress) 73 | * [How to Run Cypress in Docker With a Single Command](https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command/) 74 | * [Cypress Docker Tutorial: A Step-by-Step Guide With Examples](https://www.lambdatest.com/learning-hub/cypress-docker) 75 | -------------------------------------------------------------------------------- /doc/jenkins-console-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-console-output.png -------------------------------------------------------------------------------- /doc/jenkins-node.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-node.PNG -------------------------------------------------------------------------------- /doc/jenkins-pipeline-config.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-pipeline-config.PNG -------------------------------------------------------------------------------- /doc/jenkins-pipieline-dashboard.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-pipieline-dashboard.PNG -------------------------------------------------------------------------------- /doc/jenkins-slack-config.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-slack-config.PNG -------------------------------------------------------------------------------- /doc/jenkins-slack-notification.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins-slack-notification.PNG -------------------------------------------------------------------------------- /doc/jenkins.md: -------------------------------------------------------------------------------- 1 | # Running Cypress Tests in Jenkins CI 2 | 3 | ## Prerequisites 4 | 5 | * Ensure you have Java Development Kit (JDK) installed on your Windows machine. 6 | * Download the Jenkins .war file from the official website. 7 | * GIT and GIT BASH 8 | 9 | ## Setup 10 | 11 | * Install Java: 12 | * Download and install the latest version of the JDK for Windows. 13 | Set up the JAVA_HOME environment variable to point to your JDK installation directory. 14 | * Download Jenkins .war File: 15 | * Visit the Jenkins website (https://www.jenkins.io/download/) and download the latest stable .war file. 16 | * Launch Jenkins: 17 | * Open Command Prompt (CMD) or PowerShell. 18 | * Navigate to the directory where you downloaded the Jenkins .war file. 19 | * Start Jenkins: 20 | * Launch Jenkins by running the following command: 21 | ```bash 22 | java -Dfile.encoding=UTF-8 -jar jenkins.war 23 | ``` 24 | 25 | > We specify the UTF-8 file encoding to avoid weird output formatting in jenkins job console output. Alternatively you can alse set it in jenkins xml file. 26 | > 27 | 28 | * After Jenkins has started, open a web browser and go to http://localhost:8080 (or the port you specified). 29 | 30 | * Complete the one-time necessary configiuration steps like installing plugins, creating admin user etc. 31 | 32 | ## Configuring Jenkins to run Cypress Tests 33 | 34 | * Install following plugins 35 | * [Jenkins ANI Color Plugin](https://plugins.jenkins.io/ansicolor/) to avoid passing explicitly `NO_COLOR` environment variable to format the cypress output in jenkins console. 36 | * In `Job Configuration -> Build Environment -> Color ANSI Console Output -> ANSI Color Map xterm` 37 | * or in Pipeline cnfigure as options in pipeline 38 | ```groovy 39 | options { 40 | ansiColor('xterm') 41 | } 42 | ``` 43 | * If you choose not to use ANSI color plugin, then you need to define `NO_COLOR` in pipeline 44 | ```groovy 45 | environment { 46 | NO_COLOR = '1' 47 | } 48 | ``` 49 | * if you are on unix you can simply run `sh "NO_COLOR=1" npm run test:all` 50 | * On Windows, you must run `bat "SET NO_COLOR=1" && npm run test:all` 51 | * if you are using freestyle project, then you have to declare it in jenkins global environment variables. 52 | * 53 | * [NodeJS Plugin](https://plugins.jenkins.io/nodejs/) 54 | * Manage Jenkins -> Global Tools Confioguration -> NodeJS Installation, choose the NODE JS version, and give a name to it, which will then be used in pipeline. 55 | 56 | 57 | 58 | * [HTML Publisher Plugin](https://plugins.jenkins.io/htmlpublisher/) 59 | * This will allow us to publish Mochawesome HTML reports 60 | * Jenkins does not allow CSS, and JS in reports so we need to run following groovy script in jenkins when we start the server, so we can view the HTML reports. 61 | * Manage Jenkins -> Tools and Actions -> Script Console 62 | ```groovy 63 | System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "") 64 | ``` 65 | * [JUnit Plugin](https://plugins.jenkins.io/junit/) will allow us to display the test results summary, status, and trends in graphical format within Jenkins. 66 | * It will parse the combined-result.xml file that is generated in our post report script. 67 | * Moreover it will allows us to extract tests count based on the status. 68 | * [Slack Notification Plugin](https://plugins.jenkins.io/slack/) will enable us to send a slack message 69 | * Create a Slack Account 70 | * Create a Workspace 71 | * In Workspace, click on members, and choose App Integration 72 | * Select Jenkins App. After Installation 73 | * We need the following to setup Slack within Jenkins: 74 | * `Team Subdomain` 75 | * `Integration Token Credential ID` 76 | * Go to Manage Jenkins -> Global Configuration, and add configuration for your slack channel, workspace, and add the token already added from secre text credentials. 77 | 78 | 79 | 80 | * In our test scripts, we need to execute some shell commands that are not available in windows command line or powershell and only available in Bash terminals, so we need to change the default terminal for Jenkins. 81 | * Go to Manage Jenkins -> Configure System -> Shell executable and specify path to your executable e.g. `C:\Program Files\Git\usr\bin\sh.exe` 82 | 83 | 84 | ## Add Credentials for Cypress Dashboard in Jenkins 85 | 86 | * If we are adding support for recording tests in Cyprss Dashboard, then we need to pass certain environment variables through CI. 87 | * In the "Manage Jenkins" page, click on "Manage Credentials" from the left-hand menu. 88 | * In the "Credentials" page, click on the "Global credentials (unrestricted)" link. 89 | * In the "Global credentials" page, click on the "Add Credentials" link on the left. 90 | * Enter the credentials as `Secret Text` for: 91 | * `CYPRESS_RECORD_KEY` 92 | * `CYPRESS_PROJECT_ID` 93 | 94 | * [Projects](https://docs.cypress.io/guides/cloud/account-management/projects) 95 | * [Set up a project to record](https://docs.cypress.io/guides/cloud/getting-started) 96 | * [cypress ci](https://docs.cypress.io/guides/references/error-messages#You-passed-the---record-flag-but-did-not-provide-us-your-Record-Key) 97 | 98 | ## Jenkins Pipeline Job 99 | 100 | * Choose `Pipeline` as the job type and give it a name. 101 | * In the job configuration, under the `Pipeline` section, select `Pipeline script from SCM` as the Definition. 102 | * Choose your version control system (e.g., Git) and provide the repository URL. 103 | * Specify the branch (e.g., main or master) where your Jenkinsfile is located. 104 | * Add Credentials 105 | * Specify Path to your Jenkinsfile e.g. `Jenkinsfile` since in our case it is located in root directory of our GIT repo. 106 | 107 | > First time running the parameterized jenkins script may fail and does not show the `Build with Parameter` options because we only declared them in script. After the first build, Jenkins will update the configuration to add them automatically and displaz build with parameter options. 108 | 109 | * Here is how the Jenkins Console Output will look like: 110 | 111 | 112 | 113 | ## Resources 114 | 115 | * [Cypress CI](https://docs.cypress.io/guides/continuous-integration/introduction) 116 | * [Cypress Jenkins CI](https://docs.cypress.io/guides/continuous-integration/ci-provider-examples#Jenkins) 117 | 118 | ### Blogs 119 | 120 | * [How To Run Cypress Tests In Jenkins Pipeline [Jenkins and Cypress Tutorial] 121 | ](https://www.lambdatest.com/blog/jenkins-and-cypress-tutorial/) 122 | * [Integrate Cypress Dashboard with Jenkins and Execute the Test Cases 123 | ](https://qaautomationlabs.com/integrate-cypress-dashboard-with-jenkins-and-execute-the-test-cases/) 124 | * [Cypress e2e Testing in the Jenkins Pipeline 125 | ](https://ronnieschaniel.medium.com/cypress-e2e-testing-in-the-jenkins-pipeline-cc0a0df29fb6) 126 | 127 | ### YouTube 128 | 129 | * [Cypress Jenkins JoanMedia](https://www.youtube.com/playlist?list=PLYDwWPRvXB89p_NxZJSjsldRR0vUdv3-g 130 | ) 131 | 132 | ### Example Jenkinsfile 133 | 134 | * https://github.com/rschaniel/angular_cypress_example/blob/master/Jenkinsfile 135 | * https://github.com/helenanull/cypress-example/blob/main/Jenkinsfile 136 | * https://github.com/JoanEsquivel/cypress-demo-framework/blob/main/Jenkinsfile 137 | * https://github.com/abhinaba-ghosh/cypress-e2e-boilerplate/blob/master/Jenkinsfile 138 | * https://github.com/cypress-io/cypress-example-kitchensink/blob/master/basic/* Jenkinsfile 139 | * https://github.com/cypress-io/cypress-example-kitchensink/blob/master/Jenkinsfile 140 | 141 | ### Stack Overflow Issues 142 | 143 | * [Change Windows shell in Jenkins (from Cygwin to Git Bash/msys)](https://stackoverflow.com/questions/35043665/change-windows-shell-in-jenkins-from-cygwin-to-git-bash-msys) 144 | * [Jenkins: console output characters](https://stackoverflow.com/questions/27960996/jenkins-console-output-characters) 145 | * [Jenkins Console Print Encoded Characters](https://stackoverflow.com/questions/51993828/jenkins-console-print-encoded-characters) 146 | * [Show mochaawesome html report in jenkins](https://github.com/adamgruber/mochawesome/issues/180) 147 | * [Cypress CLI console output not very readable](https://stackoverflow.com/questions/48634725/cypress-cli-console-output-not-very-readable) 148 | * [Cypress CI Colors](https://docs.cypress.io/guides/continuous-integration/introduction#Colors) 149 | * [How do I set a default choice in jenkins pipeline?](https://stackoverflow.com/questions/47873401/how-do-i-set-a-default-choice-in-jenkins-pipeline) 150 | * [Cannot define variable in pipeline stage](https://stackoverflow.com/questions/39832862/cannot-define-variable-in-pipeline-stage) 151 | * [Jenkins - HTML Publisher Plugin - No CSS is displayed when report is viewed in Jenkins Server](https://stackoverflow.com/questions/35783964/jenkins-html-publisher-plugin-no-css-is-displayed-when-report-is-viewed-in-j) 152 | * [How to create JUnit Report in Jenkins](https://qaautomation.expert/2022/12/14/how-to-create-junit-report-in-jenkins/) 153 | * [How to access Junit test counts in Jenkins Pipeline project](https://stackoverflow.com/questions/39920437/how-to-access-junit-test-counts-in-jenkins-pipeline-project) 154 | * [Test results are displayed with duplicate entries for failures in Jenkins](https://stackoverflow.com/questions/42427266/test-results-are-displayed-with-duplicate-entries-for-failures-in-jenkins) 155 | -------------------------------------------------------------------------------- /doc/jenkins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/jenkins.png -------------------------------------------------------------------------------- /doc/mochaawesome-report-overview.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/mochaawesome-report-overview.PNG -------------------------------------------------------------------------------- /doc/mochawesome-failed-test-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/mochawesome-failed-test-report.png -------------------------------------------------------------------------------- /doc/results-terminal-output.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itkhanz/Cypress-Framework/e3ad1b08bfd2651a87fa60938a872e807b2a28d1/doc/results-terminal-output.PNG -------------------------------------------------------------------------------- /docker-compose-browsers.yml: -------------------------------------------------------------------------------- 1 | # Currently it replaces the media and report for previous browser execution because work and report dir is same for all the images 2 | # Workaround is to use a different image with different WORKDIR for each service so the media will be saved in different directory 3 | 4 | version: '3' 5 | services: 6 | cypress-chrome: 7 | image: cypress_docker:latest 8 | volumes: 9 | - .:/cypress_docker 10 | working_dir: /cypress_docker 11 | command: test:chrome 12 | 13 | cypress-edge: 14 | image: cypress_docker:latest 15 | volumes: 16 | - .:/cypress_docker 17 | working_dir: /cypress_docker 18 | command: test:edge 19 | 20 | cypress-firefox: 21 | image: cypress_docker:latest 22 | shm_size: '2g' # Set the shared memory size to 2 gigabytes 23 | volumes: 24 | - .:/cypress_docker 25 | working_dir: /cypress_docker 26 | command: test:firefox 27 | -------------------------------------------------------------------------------- /docker-compose-build.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cypress: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - .:/cypress_docker 9 | working_dir: /cypress_docker 10 | command: ["test:all"] 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | cypress: 4 | image: cypress_docker:latest # Replace with the name and tag of your Docker image 5 | volumes: 6 | - .:/cypress_docker 7 | working_dir: /cypress_docker 8 | command: test:all 9 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["cypress", "./cypress/support/index.d.ts"] 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-framework", 3 | "version": "1.0.0", 4 | "description": "cypress test automation framework", 5 | "main": "index.js", 6 | "scripts": { 7 | "results-junit:delete": "rm -rf cypress/results/junit/* || true", 8 | "results-junit:combine": "jrm cypress/results/junit/combined-report.xml \"cypress/results/junit/*.xml\"", 9 | "report:pre": "npm run results-junit:delete", 10 | "report:post": "npm run results-junit:combine", 11 | "test:report": "npm run report:pre && npx cypress run && npm run report:post", 12 | "test:all": "npx cypress run", 13 | "test:chrome": "npx cypress run --browser chrome", 14 | "test:firefox": "npx cypress run --browser firefox", 15 | "test:edge": "npx cypress run --browser edge", 16 | "test:regression": "npx cypress run --env grepTags=@regression", 17 | "test:smoke": "npx cypress run --env grepTags=@smoke", 18 | "test:local": "npx cypress run --env environmentName=\"local\"", 19 | "test:dev": "npx cypress run --env environmentName=\"dev\"", 20 | "test:qa": "npx cypress run --env environmentName=\"qa\"", 21 | "test:stage": "npx cypress run --env environmentName=\"stage\"", 22 | "test:prod": "npx cypress run --env environmentName=\"prod\"", 23 | "test:registration": "npx cypress run --spec cypress/e2e/tests/RegistrationTest.cy.js", 24 | "test:login": "npx cypress run --spec cypress/e2e/tests/LoginTest.cy.js", 25 | "test:productData": "npx cypress run --spec cypress/e2e/tests/ProductDataTest.cy.js", 26 | "test:addToCart": "npx cypress run --spec cypress/e2e/tests/AddToCartTest.cy.js", 27 | "test:wishlist": "npx cypress run --spec cypress/e2e/tests/WishlistTest.cy.js", 28 | "test:productSearch": "npx cypress run --spec cypress/e2e/tests/ProductsSearchTest.cy.js" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/itkhanz/Cypress-Framework.git" 33 | }, 34 | "keywords": [ 35 | "cypress" 36 | ], 37 | "author": "itkhanz", 38 | "license": "ISC", 39 | "bugs": { 40 | "url": "https://github.com/itkhanz/Cypress-Framework/issues" 41 | }, 42 | "homepage": "https://github.com/itkhanz/Cypress-Framework#readme", 43 | "devDependencies": { 44 | "@cypress/grep": "^3.1.5", 45 | "@faker-js/faker": "^8.0.2", 46 | "cypress": "^13.0.0", 47 | "cypress-mochawesome-reporter": "^3.5.1", 48 | "cypress-multi-reporters": "^1.6.3", 49 | "eslint": "^8.48.0", 50 | "eslint-config-standard": "^17.1.0", 51 | "eslint-plugin-chai-friendly": "^0.7.2", 52 | "eslint-plugin-cypress": "^2.14.0", 53 | "eslint-plugin-import": "^2.28.1", 54 | "eslint-plugin-n": "^16.0.2", 55 | "eslint-plugin-promise": "^6.1.1", 56 | "junit-report-merger": "^6.0.2", 57 | "mocha-junit-reporter": "^2.2.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /reporter-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "cypress-mochawesome-reporter, mocha-junit-reporter", 3 | "mochaJunitReporterReporterOptions": { 4 | "mochaFile": "cypress/results/junit/results-[hash].xml" 5 | }, 6 | "cypressMochawesomeReporterReporterOptions": { 7 | "reportDir": "cypress/results/cypress-mochawesome-reporter", 8 | "charts": true, 9 | "reportPageTitle": "OpenCart Test Report", 10 | "embeddedScreenshots": true, 11 | "inlineAssets": true, 12 | "videoOnFailOnly": false 13 | } 14 | } -------------------------------------------------------------------------------- /settings/dev.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://dev.naveenautomationlabs.com/opencart/index.php", 3 | "env": { 4 | "environment": "development" 5 | }, 6 | "execTimeout": 60000, 7 | "defaultCommandTimeout": 4000, 8 | "requestTimeout": 5000, 9 | "pageLoadTimeout": 60000, 10 | "responseTimeout": 30000, 11 | "viewportWidth": 1200, 12 | "viewportHeight": 800, 13 | "retries": { 14 | "runMode": 1, 15 | "openMode": 0 16 | } 17 | } -------------------------------------------------------------------------------- /settings/local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://naveenautomationlabs.com/opencart/index.php", 3 | "env": { 4 | "environment": "local" 5 | }, 6 | "execTimeout": 60000, 7 | "defaultCommandTimeout": 4000, 8 | "requestTimeout": 5000, 9 | "pageLoadTimeout": 60000, 10 | "responseTimeout": 30000, 11 | "viewportWidth": 1200, 12 | "viewportHeight": 800, 13 | "retries": { 14 | "runMode": 1, 15 | "openMode": 1 16 | } 17 | } -------------------------------------------------------------------------------- /settings/prod.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://prod.naveenautomationlabs.com/opencart/index.php", 3 | "env": { 4 | "environment": "production" 5 | }, 6 | "execTimeout": 60000, 7 | "defaultCommandTimeout": 4000, 8 | "requestTimeout": 5000, 9 | "pageLoadTimeout": 60000, 10 | "responseTimeout": 30000, 11 | "viewportWidth": 1200, 12 | "viewportHeight": 800, 13 | "retries": { 14 | "runMode": 1, 15 | "openMode": 0 16 | } 17 | } -------------------------------------------------------------------------------- /settings/qa.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://qa.naveenautomationlabs.com/opencart/index.php", 3 | "env": { 4 | "environment": "qa" 5 | }, 6 | "execTimeout": 60000, 7 | "defaultCommandTimeout": 4000, 8 | "requestTimeout": 5000, 9 | "pageLoadTimeout": 60000, 10 | "responseTimeout": 30000, 11 | "viewportWidth": 1200, 12 | "viewportHeight": 800, 13 | "retries": { 14 | "runMode": 1, 15 | "openMode": 0 16 | } 17 | } -------------------------------------------------------------------------------- /settings/stage.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://stage.naveenautomationlabs.com/opencart/index.php", 3 | "env": { 4 | "environment": "stage" 5 | }, 6 | "execTimeout": 60000, 7 | "defaultCommandTimeout": 4000, 8 | "requestTimeout": 5000, 9 | "pageLoadTimeout": 60000, 10 | "responseTimeout": 30000, 11 | "viewportWidth": 1200, 12 | "viewportHeight": 800, 13 | "retries": { 14 | "runMode": 1, 15 | "openMode": 0 16 | } 17 | } --------------------------------------------------------------------------------