├── cypress ├── support │ ├── eyes-index.d.ts │ ├── commands.ts │ ├── index.ts │ ├── filterTests.js │ ├── e2e.js │ └── commands.js ├── fixtures │ ├── profile.json │ ├── users.json │ ├── read-users.json │ ├── read-writeusers.json │ ├── example.json │ ├── fixtures-demo │ │ └── sauceCredentials.json │ ├── read-write │ │ └── read-write.json │ ├── intercept │ │ └── interceptFixture.json │ └── data-driven │ │ └── sauceUsers.json ├── jsconfig.json ├── e2e │ ├── sample_spec.js │ ├── more-commands │ │ ├── cypress-arch.spec.js │ │ ├── cypress-dom.spec.js │ │ ├── cypress-browser.spec.js │ │ └── cypress-keyboard.js │ ├── tags │ │ ├── buttons.spec.js │ │ ├── readme.text │ │ ├── homeSauceFixture.spec.js │ │ └── fixtures.spec.js │ ├── lighthouse │ │ └── performance.spec.js │ ├── typescript │ │ └── ts-demo.ts │ ├── plugins │ │ └── applitools-visual-testing.spec.js │ ├── cypress-studio-test │ │ └── studio-demo.spec.js │ ├── read-write-fixture │ │ └── readWrite.spec.js │ ├── data-driven │ │ └── json-iteration.spec.js │ ├── invoke │ │ └── invoke-test.spec.js │ ├── pom │ │ ├── homeSauceFixture.spec.js │ │ ├── homeSauceFixtureXpath.spec.js │ │ └── homeSauce.spec.js │ ├── location │ │ └── location.spec.js │ ├── commands │ │ └── commands.spec.js │ ├── locators │ │ └── locators.spec.js │ ├── request │ │ ├── apiRequests.spec.js │ │ ├── apiTestDemo.spec.js │ │ └── dummy-api.spec.js │ ├── applitools │ │ └── visual-testing.js │ ├── intercept │ │ └── mockApi.js │ ├── structure │ │ ├── hooks.spec.js │ │ └── test_structure.js │ ├── fixture-demo │ │ └── fixtures.spec.js │ ├── database-testing │ │ └── db-testing.spec.js │ ├── assertions │ │ └── assert.spec.js │ ├── variables-aliases │ │ └── var-alias.spec.js │ ├── xpath │ │ └── xpath-explanation.js │ └── retry-ability │ │ └── retry-ability.spec.js ├── tsconfig.json ├── pages │ └── saucedemo │ │ ├── inventoryPage.js │ │ ├── homeSaucePage.js │ │ └── homeSaucePageXpath.js └── plugins │ └── index.js ├── dockerReadme.txt ├── Dockerfile ├── applitools.config.js ├── .gitignore ├── cypress.config.ts ├── package.json ├── README.md └── Jenkinsfile /cypress/support/eyes-index.d.ts: -------------------------------------------------------------------------------- 1 | import "@applitools/eyes-cypress" -------------------------------------------------------------------------------- /cypress/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /cypress/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "myUser", 4 | "password": "myPassword" 5 | } 6 | ] -------------------------------------------------------------------------------- /cypress/fixtures/read-users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "myUser", 4 | "password": "myPassword" 5 | } 6 | ] -------------------------------------------------------------------------------- /cypress/fixtures/read-writeusers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "myUser", 4 | "password": "myPassword" 5 | } 6 | ] -------------------------------------------------------------------------------- /cypress/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "types": ["cypress"] 5 | }, 6 | "include": ["**/*.*"] 7 | } -------------------------------------------------------------------------------- /cypress/e2e/sample_spec.js: -------------------------------------------------------------------------------- 1 | describe('My First Test', () => { 2 | it('Does not do much!', () => { 3 | expect(true).to.equal(false) 4 | }) 5 | }) -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["es5", "dom"], 5 | "types": ["cypress"] 6 | }, 7 | "include": ["**/*.ts"] 8 | } -------------------------------------------------------------------------------- /cypress/pages/saucedemo/inventoryPage.js: -------------------------------------------------------------------------------- 1 | class inventoryPage { 2 | elements = { 3 | titleSpan: () => cy.get('.title') 4 | } 5 | } 6 | 7 | module.exports = new inventoryPage(); -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add('typeLogin', (username, password) => { 2 | cy.get('[data-test="username"]').type(username); 3 | cy.get('#password').type(password); 4 | cy.get('#login-button').click(); 5 | }) -------------------------------------------------------------------------------- /cypress/fixtures/fixtures-demo/sauceCredentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "standardUsername": "standard_user", 3 | "systemPassword": "secret_sauce", 4 | "lockedUsername": "locked_out_user", 5 | "dummyUsername": "dummy-test", 6 | "dummyPassword": "dummy-password" 7 | } -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // in cypress/support/index.ts 2 | // load type definitions that come with Cypress module 3 | /// 4 | 5 | declare namespace Cypress { 6 | interface Chainable { 7 | typeLogin: (username: string, password:string) => void; 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /cypress/fixtures/read-write/read-write.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "email": "myUser", 4 | "password": "myPassword" 5 | }, 6 | { 7 | "title": "test", 8 | "completed": true, 9 | "id": "1" 10 | }, 11 | { 12 | "title": "wash dishes", 13 | "completed": false, 14 | "id": "2" 15 | } 16 | ] -------------------------------------------------------------------------------- /cypress/e2e/more-commands/cypress-arch.spec.js: -------------------------------------------------------------------------------- 1 | //https://docs.cypress.io/api/cypress-api/arch 2 | 3 | describe('4 commands you did not know', () => { 4 | 5 | 6 | //Cypress.arch returns you the CPU architecture name of the underlying OS, as returned from Node's os.arch(). 7 | 8 | it('Cypress.arch Demo', () => { 9 | cy.log(Cypress.arch) 10 | }); 11 | 12 | }); -------------------------------------------------------------------------------- /cypress/fixtures/intercept/interceptFixture.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Cypress Assertions Video", 4 | "completed": false, 5 | "id": "1" 6 | }, 7 | { 8 | "title": "Page Object Model Cypress", 9 | "completed": false, 10 | "id": "2" 11 | }, 12 | { 13 | "title": "Intercept Cypress", 14 | "completed": false, 15 | "id": "3" 16 | } 17 | ] -------------------------------------------------------------------------------- /cypress/support/filterTests.js: -------------------------------------------------------------------------------- 1 | const TestFilters = (givenTags, runTest) => { 2 | if (Cypress.env('tags')) { 3 | const tags = Cypress.env('tags').split(',') 4 | const isFound = givenTags.some((givenTag) => tags.includes(givenTag)) 5 | 6 | if (isFound) { 7 | runTest() 8 | } 9 | } else { 10 | runTest() 11 | } 12 | }; 13 | 14 | export default TestFilters -------------------------------------------------------------------------------- /cypress/e2e/tags/buttons.spec.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Button Actions (regression)', { tags: '@regressionTag' },() => { 3 | 4 | beforeEach(() => { 5 | cy.visit('https://demoqa.com/buttons') 6 | }) 7 | 8 | it('Double Click', () => { 9 | 10 | cy.get('#doubleClickBtn').dblclick() 11 | cy.get('#doubleClickMessage').should('have.text', 'You have done a double click') 12 | 13 | }); 14 | 15 | it('Right Click', () => { 16 | 17 | cy.get('#rightClickBtn').rightclick() 18 | cy.get('#rightClickMessage').should('have.text', 'You have done a right click') 19 | 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /dockerReadme.txt: -------------------------------------------------------------------------------- 1 | To construct the docker image 2 | 3 | With Docker, we can create images setting the parameters we need in a dockerfile. 4 | 5 | 6 | ENTRYPOINT will specify the command the container will be using, 7 | and CMD will be the parameters we are going to use with the executable. 8 | 9 | docker build -t my-cypress-image:1.0.0 . 10 | 11 | 12 | Run the specific command using 13 | docker run -i -v "%cd%":/my-cypress-project -t my-cypress-image:1.0.0 --spec cypress/integration/pom/*.spec.js 14 | 15 | 16 | Additional material: Entry point & CMD difference -> https://www.youtube.com/watch?v=OYbEWUbmk90&t=437s&ab_channel=KodeKloud -------------------------------------------------------------------------------- /cypress/pages/saucedemo/homeSaucePage.js: -------------------------------------------------------------------------------- 1 | class homeSaucePage{ 2 | 3 | elements = { 4 | usernameInput: () => cy.get('#user-name'), 5 | passwordInput: () => cy.get('#password'), 6 | loginBtn: () => cy.get('#login-button'), 7 | errorMessage: () => cy.get('h3[data-test="error"]') 8 | } 9 | 10 | typeUsername(username){ 11 | this.elements.usernameInput().type(username); 12 | } 13 | 14 | typePassword(password){ 15 | this.elements.passwordInput().type(password); 16 | } 17 | 18 | clickLogin(){ 19 | this.elements.loginBtn().click(); 20 | } 21 | 22 | 23 | } 24 | 25 | module.exports = new homeSaucePage(); -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #Base image taken from:https://github.com/cypress-io/cypress-docker-images 2 | FROM cypress/browsers:node14.17.0-chrome91-ff89 3 | #Create the folder where our project will be stored 4 | RUN mkdir /my-cypress-project 5 | #We make it our workdirectory 6 | WORKDIR /my-cypress-project 7 | #Let's copy the essential files that we MUST use to run our scripts. 8 | COPY ./package.json . 9 | COPY ./cypress.json . 10 | COPY ./cypress ./cypress 11 | #Install the cypress dependencies in the work directory 12 | RUN npm install 13 | #Executable commands the container will use[Exec Form] 14 | ENTRYPOINT ["npx","cypress","run"] 15 | #With CMD in this case, we can specify more parameters to the last entrypoint. 16 | CMD [""] -------------------------------------------------------------------------------- /cypress/e2e/lighthouse/performance.spec.js: -------------------------------------------------------------------------------- 1 | it("Lighthouse Testing", () => { 2 | cy.visit("https://www.google.com/"); 3 | // set your treshold 4 | // Thresholds are pass/fail criteria that specify the performance expectations of the system under test. 5 | cy.lighthouse( 6 | { 7 | performance: 10, 8 | accessibility: 79, 9 | "best-practices": 80, 10 | seo: 80, 11 | }, 12 | { 13 | formFactor: "desktop", 14 | screenEmulation: { 15 | mobile: false, 16 | disable: false, 17 | width: Cypress.config("viewportWidth"), 18 | height: Cypress.config("viewportHeight"), 19 | deviceScaleRatio: 1, 20 | }, 21 | } 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/pages/saucedemo/homeSaucePageXpath.js: -------------------------------------------------------------------------------- 1 | class homeSaucePage{ 2 | 3 | elements = { 4 | usernameInput: () => cy.xpath(`//input[@data-test='username']`), 5 | passwordInput: () => cy.xpath(`//*[@data-test='password' or @id='password']`), 6 | loginBtn: () => cy.xpath(`//*[@value='Login']`), 7 | errorMessage: () => cy.xpath('//h3') 8 | } 9 | 10 | typeUsername(username){ 11 | this.elements.usernameInput().type(username); 12 | } 13 | 14 | typePassword(password){ 15 | this.elements.passwordInput().type(password); 16 | } 17 | 18 | clickLogin(){ 19 | this.elements.loginBtn().click(); 20 | } 21 | 22 | 23 | } 24 | 25 | module.exports = new homeSaucePage(); -------------------------------------------------------------------------------- /cypress/e2e/typescript/ts-demo.ts: -------------------------------------------------------------------------------- 1 | let expectedUrl: string = 'https://www.saucedemo.com/inventory.html' 2 | 3 | describe('Login page', () => { 4 | beforeEach(()=>{ 5 | cy.visit('https://www.saucedemo.com/') 6 | }) 7 | 8 | it('Sucess Login without custom command', () => { 9 | cy.get('#user-name').type('standard_user'); 10 | cy.get('#password').type('secret_sauce') 11 | cy.get('#login-button').click() 12 | 13 | cy.url().should('eq', 'https://www.saucedemo.com/inventory.html') 14 | }); 15 | 16 | it('Sucess Login with custom command', () => { 17 | cy.typeLogin('standard_user', 'secret_sauce') 18 | 19 | cy.url().should('eq', 'https://www.saucedemo.com/inventory.html') 20 | }); 21 | }); -------------------------------------------------------------------------------- /applitools.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testConcurrency: 1, 3 | apiKey: 'Nl7efgZhAZgM91E972UoXUJQnrf108UyXU9OH2vYPDKNRQ110', 4 | browser: [ 5 | // Add browsers with different viewports 6 | {width: 800, height: 600, name: 'chrome'}, 7 | {width: 700, height: 500, name: 'firefox'}, 8 | {width: 1600, height: 1200, name: 'ie11'}, 9 | {width: 1024, height: 768, name: 'edgechromium'}, 10 | {width: 800, height: 600, name: 'safari'}, 11 | // Add mobile emulation devices in Portrait mode 12 | {deviceName: 'iPhone X', screenOrientation: 'portrait'}, 13 | {deviceName: 'Pixel 2', screenOrientation: 'portrait'} 14 | ], 15 | // set batch name to the configuration 16 | batchName: 'Ultrafast Batch' 17 | } -------------------------------------------------------------------------------- /cypress/e2e/more-commands/cypress-dom.spec.js: -------------------------------------------------------------------------------- 1 | //https://docs.cypress.io/api/cypress-api/dom#Syntax 2 | 3 | //Cypress.dom.method() is a collection of DOM related helper methods. 4 | 5 | describe('4 Commands Probably You Did NOT Know', () => { 6 | 7 | it('isVisible DEMO', () => { 8 | 9 | cy.visit('https://demoqa.com/accordian') 10 | 11 | cy.get('.collapse').eq(6).then(($element)=>{ 12 | cy.log(`Collapse content as soon as the website is loaded: ${Cypress.dom.isVisible($element)}`) 13 | }) 14 | 15 | cy.get('#section1Heading').click(); 16 | 17 | cy.get('.collapse').eq(6).then(($element)=>{ 18 | cy.log(`Collapse content as soon as I click on the card: ${Cypress.dom.isVisible($element)}`) 19 | }) 20 | 21 | 22 | }); 23 | 24 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | -------------------------------------------------------------------------------- /cypress/e2e/plugins/applitools-visual-testing.spec.js: -------------------------------------------------------------------------------- 1 | //Steps to install the plugin 2 | //1- npm install @applitools/eyes-cypress@3 --save-dev 3 | //2- npx eyes-setup 4 | 5 | //How it works https://applitools.com/tutorials/cypress.html#applitools-eyes-cypress-sdk 6 | 7 | describe('Demo - Applitools', () => { 8 | 9 | it('Sign In Page Validation', () => { 10 | 11 | cy.visit('https://www.saucedemo.com/') 12 | 13 | cy.eyesOpen({ 14 | appName: 'App Demo', 15 | batchName: 'Batch Name #1', 16 | browser: [ 17 | {deviceName: 'iPhone X', screenOrientation: 'portrait'}, 18 | {name: 'chrome', width: 1024, height: 768} 19 | ] 20 | }) 21 | 22 | 23 | cy.eyesCheckWindow('Sign in page') 24 | 25 | cy.eyesClose(); 26 | 27 | 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /cypress/e2e/cypress-studio-test/studio-demo.spec.js: -------------------------------------------------------------------------------- 1 | /* === Test Created with Cypress Studio === */ 2 | it('Web-tables-demo', function() { 3 | /* ==== Generated with Cypress Studio ==== */ 4 | cy.visit('https://demoqa.com/webtables'); 5 | cy.get('#addNewRecordButton').click(); 6 | cy.get('#firstName').clear(); 7 | cy.get('#firstName').type('Joan'); 8 | cy.get('#lastName').clear(); 9 | cy.get('#lastName').type('Test'); 10 | cy.get('#userEmail').clear(); 11 | cy.get('#userEmail').type('joan@test.com'); 12 | cy.get('#age').clear(); 13 | cy.get('#age').type('50'); 14 | cy.get('#salary').clear(); 15 | cy.get('#salary').type('5000'); 16 | cy.get('#department').clear(); 17 | cy.get('#department').type('Sales'); 18 | /* ==== End Cypress Studio ==== */ 19 | /* ==== Generated with Cypress Studio ==== */ 20 | cy.get('#submit').click(); 21 | /* ==== End Cypress Studio ==== */ 22 | }); -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | chromeWebSecurity: false, 5 | projectId: 'ioceso', 6 | screenshotOnRunFailure: true, 7 | reporter: 'cypress-mochawesome-reporter', 8 | reporterOptions: { 9 | reportDir: 'cypress/report', 10 | charts: true, 11 | reportPageTitle: 'Joan Media Demo Report', 12 | embeddedScreenshots: true, 13 | }, 14 | env: { 15 | db: { 16 | host: 'localhost', 17 | user: 'root', 18 | password: '', 19 | database: 'cypress_test', 20 | }, 21 | }, 22 | e2e: { 23 | // We've imported your old cypress plugins here. 24 | // You may want to clean this up later by importing these. 25 | setupNodeEvents(on, config) { 26 | return require('./cypress/plugins/index.js')(on, config) 27 | }, 28 | specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', 29 | }, 30 | }) 31 | -------------------------------------------------------------------------------- /cypress/e2e/read-write-fixture/readWrite.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Write Fixture Demo', () => { 4 | 5 | it('Write static-schema response in a Json File', () => { 6 | 7 | cy.request('GET', 'http://localhost:3000/accounts').then(response => { 8 | 9 | cy.writeFile('cypress/fixtures/read-write/read-write.json', response.body) 10 | 11 | }) 12 | 13 | }); 14 | 15 | it('Write more objects in the array', () => { 16 | 17 | const fileName = 'cypress/fixtures/read-write/read-write.json' 18 | 19 | cy.request('GET', 'http://localhost:3000/todos') 20 | .its('body') 21 | .each($object => { 22 | cy.readFile(fileName).then((list) => { 23 | list.push($object) 24 | // write the merged array 25 | cy.writeFile(fileName, list) 26 | }) 27 | }) 28 | 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /cypress/e2e/data-driven/json-iteration.spec.js: -------------------------------------------------------------------------------- 1 | //Data Driven Testing using data from a JSON file. 2 | 3 | const tests = require('../../fixtures/data-driven/sauceUsers.json'); 4 | 5 | describe('Data Driven Test reading data from a JSON file', function(){ 6 | 7 | beforeEach(function(){ 8 | cy.visit('https://www.saucedemo.com/'); 9 | }); 10 | 11 | tests.forEach(test => { 12 | 13 | it(test.name, function(){ 14 | 15 | cy.get('[data-test="username"]').type(test.username); 16 | cy.get('[data-test="password"]').type(test.password); 17 | cy.get('[data-test="login-button"]').click(); 18 | 19 | if(test.name === 'Standard User'){ 20 | cy.get('.title').should('contain.text', test.expected); 21 | }else{ 22 | cy.get('[data-test="error"]').should('contain.text', test.expected); 23 | } 24 | 25 | }); 26 | 27 | }); 28 | }); -------------------------------------------------------------------------------- /cypress/e2e/invoke/invoke-test.spec.js: -------------------------------------------------------------------------------- 1 | //https://api.jquery.com/category/manipulation/general-attributes/ 2 | 3 | describe('Invoke Test Scripts', function(){ 4 | beforeEach(function(){ 5 | cy.visit('https://demoqa.com/links') 6 | }) 7 | 8 | it('Invoke to remove target attribute', ()=> { 9 | cy.get('#simpleLink').invoke('removeAttr', 'target') 10 | cy.get('#simpleLink').click() 11 | cy.location().then(yieldedObject => cy.log(yieldedObject.href)) 12 | }) 13 | 14 | it('Invoke to get an attribute', ()=> { 15 | cy.get('#simpleLink').invoke('attr', 'target').then(target => cy.log(target)) 16 | 17 | //There is a direct way to validate this with an assertion 18 | cy.get('#simpleLink').should('have.attr', 'target', '_blank') 19 | 20 | }) 21 | 22 | it('Invoke to get text from an element', ()=> { 23 | cy.get('.main-header').invoke('text').then(text => cy.log(text)) 24 | }) 25 | }) -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.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 | 18 | import '@applitools/eyes-cypress/commands' 19 | 20 | import './commands' 21 | import 'cypress-mochawesome-reporter/register' 22 | import '@cypress-audit/lighthouse/commands' 23 | 24 | // Alternatively you can use CommonJS syntax: 25 | // require('./commands') 26 | require('cypress-grep')() 27 | require('cypress-xpath') 28 | -------------------------------------------------------------------------------- /cypress/e2e/pom/homeSauceFixture.spec.js: -------------------------------------------------------------------------------- 1 | import homeSaucePage from '../../pages/saucedemo/homeSaucePage' 2 | import inventoryPage from '../../pages/saucedemo/inventoryPage' 3 | 4 | const tests = require('../../fixtures/data-driven/sauceUsers.json'); 5 | 6 | describe('Home Sauce Demo', () => { 7 | 8 | beforeEach(function(){ 9 | cy.visit('https://www.saucedemo.com/'); 10 | }); 11 | 12 | tests.forEach(test => { 13 | 14 | it(test.name, function(){ 15 | 16 | homeSaucePage.typeUsername(test.username); 17 | homeSaucePage.typePassword(test.password); 18 | homeSaucePage.clickLogin(); 19 | 20 | if(test.name === 'should login to inventory page'){ 21 | inventoryPage.elements.titleSpan().should('have.text', test.expected) 22 | }else{ 23 | homeSaucePage.elements.errorMessage().should('have.text', test.expected) 24 | } 25 | 26 | }); 27 | 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /cypress/e2e/more-commands/cypress-browser.spec.js: -------------------------------------------------------------------------------- 1 | //https://docs.cypress.io/api/cypress-api/browser#Log-browser-information 2 | 3 | describe('4 Commands Probably You Did NOT Know', () => { 4 | 5 | beforeEach(() => { 6 | cy.visit('https://www.whatismybrowser.com/es/') 7 | }); 8 | 9 | it('Log Web Browser Information', () => { 10 | cy.log(Cypress.browser.name) 11 | cy.log(Cypress.browser.family) 12 | cy.log(Cypress.browser.isHeaded) 13 | cy.log(Cypress.browser.isHeadless) 14 | cy.log(Cypress.browser.path) 15 | cy.log(Cypress.browser.version) 16 | }); 17 | 18 | it('Check text depending on the browser', () => { 19 | if(Cypress.browser.name==='chrome'){ 20 | cy.get('div[class="string-major"] a').should('have.text','Chrome 91 on Windows 10') 21 | }else if(Cypress.browser.name === 'firefox'){ 22 | cy.get('div[class="string-major"] a').should('have.text','Firefox 90 on Windows 10') 23 | } 24 | }); 25 | 26 | }); -------------------------------------------------------------------------------- /cypress/fixtures/data-driven/sauceUsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "should login to inventory page", 4 | "username": "standard_user", 5 | "password": "secret_sauce", 6 | "expected": "Products" 7 | }, 8 | 9 | { 10 | "name": "should display incorrect username message", 11 | "username": "incorrect_user", 12 | "password": "secret_sauce", 13 | "expected": "Epic sadface: Username and password do not match any user in this service" 14 | }, 15 | 16 | { 17 | "name": "should display incorrect password message", 18 | "username": "standard_user", 19 | "password": "incorrect_password", 20 | "expected": "Epic sadface: Username and password do not match any user in this service" 21 | }, 22 | 23 | { 24 | "name": "should display locked out message", 25 | "username": "locked_out_user", 26 | "password": "secret_sauce", 27 | "expected": "Epic sadface: Sorry, this user has been locked out." 28 | } 29 | ] -------------------------------------------------------------------------------- /cypress/e2e/tags/readme.text: -------------------------------------------------------------------------------- 1 | npm i -D cypress-grep 2 | Support File -> require('cypress-grep')() 3 | Plugin -> --- 4 | // cypress/plugins/index.js 5 | module.exports = (on, config) => { 6 | // optional: register cypress-grep plugin code 7 | // https://github.com/bahmutov/cypress-grep 8 | require('cypress-grep/src/plugin')(config) 9 | } 10 | ------ 11 | 12 | it('works as an array', { tags: ['config', 'some-other-tag'] }, () => { 13 | expect(true).to.be.true 14 | }) 15 | 16 | it('works as a string', { tags: 'config' }, () => { 17 | expect(true).to.be.true 18 | }) 19 | 20 | By Describe / Test Title 21 | npx cypress run --spec cypress/integration/tags/*.js --env grep="regression" 22 | npx cypress run --spec cypress/integration/tags/*.js --env grep="-regression" 23 | By Tags 24 | npx cypress run --spec cypress/integration/tags/*.js --env grepTags=@regressionTag 25 | npx cypress run --spec cypress/integration/tags/*.js --env grepTags=-@regressionTag 26 | AND npx cypress run --spec cypress/integration/tags/*.js --env grepTags='@regressionTag+@sanityTag' 27 | -------------------------------------------------------------------------------- /cypress/e2e/tags/homeSauceFixture.spec.js: -------------------------------------------------------------------------------- 1 | import homeSaucePage from '../../pages/saucedemo/homeSaucePage' 2 | import inventoryPage from '../../pages/saucedemo/inventoryPage' 3 | 4 | const tests = require('../../fixtures/data-driven/sauceUsers.json'); 5 | 6 | 7 | describe('Home Sauce Demo (regression,sanity)', { tags: ['@regressionTag', '@sanityTag'] }, () => { 8 | 9 | beforeEach(function () { 10 | cy.visit('https://www.saucedemo.com/'); 11 | }); 12 | 13 | tests.forEach(test => { 14 | 15 | it(test.name, function () { 16 | 17 | homeSaucePage.typeUsername(test.username); 18 | homeSaucePage.typePassword(test.password); 19 | homeSaucePage.clickLogin(); 20 | 21 | if (test.name === 'should login to inventory page') { 22 | inventoryPage.elements.titleSpan().should('have.text', test.expected) 23 | } else { 24 | homeSaucePage.elements.errorMessage().should('have.text', test.expected) 25 | } 26 | 27 | }); 28 | 29 | }); 30 | 31 | }) 32 | -------------------------------------------------------------------------------- /cypress/e2e/location/location.spec.js: -------------------------------------------------------------------------------- 1 | describe('Location Demo', function(){ 2 | 3 | beforeEach(function(){ 4 | cy.visit('https://www.saucedemo.com/'); 5 | }); 6 | 7 | it('should have title tag with value Swag Labs', function(){ 8 | cy.title().should('eq','Swag Labs'); 9 | }); 10 | 11 | it('URL should be https://www.saucedemo.com/', function(){ 12 | cy.url().should('eq', 'https://www.saucedemo.com/'); 13 | }) 14 | 15 | it('should be HTTPS',function(){ 16 | cy.location('protocol').should('contains', 'https'); 17 | }); 18 | 19 | it('the hostname should be www.saucedemo.com', function(){ 20 | cy.location('hostname').should('eq', 'www.saucedemo.com'); 21 | }); 22 | 23 | it('should redirect /inventory.html', function(){ 24 | cy.get('[data-test="username"]').type('standard_user'); 25 | cy.get('[data-test="password"]').type('secret_sauce'); 26 | cy.get('[data-test="login-button"]').click(); 27 | 28 | cy.location('pathname').should('eq', '/inventory.html') 29 | }) 30 | 31 | }) -------------------------------------------------------------------------------- /cypress/e2e/pom/homeSauceFixtureXpath.spec.js: -------------------------------------------------------------------------------- 1 | // import homeSaucePage from '../../pages/saucedemo/homeSaucePage' 2 | const homeSaucePage = require('../../pages/saucedemo/homeSaucePageXpath') 3 | import inventoryPage from '../../pages/saucedemo/inventoryPage' 4 | 5 | const tests = require('../../fixtures/data-driven/sauceUsers.json'); 6 | 7 | describe('Home Sauce Demo', () => { 8 | 9 | beforeEach(function(){ 10 | cy.visit('https://www.saucedemo.com/'); 11 | }); 12 | 13 | tests.forEach(test => { 14 | 15 | it(test.name, function(){ 16 | 17 | homeSaucePage.typeUsername(test.username); 18 | homeSaucePage.typePassword(test.password); 19 | homeSaucePage.clickLogin(); 20 | 21 | if(test.name === 'should login to inventory page'){ 22 | inventoryPage.elements.titleSpan().should('have.text', test.expected) 23 | }else{ 24 | homeSaucePage.elements.errorMessage().should('have.text', test.expected) 25 | } 26 | 27 | }); 28 | 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-e2e-framework", 3 | "version": "1.0.0", 4 | "description": "E2E Framework Testing Cypress", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "test", 8 | "cypress:open": "cypress open --browser chrome --e2e", 9 | "cypress:runDefault": "cypress run --spec cypress/integration/pom/*.spec.js", 10 | "cypress:chrome": "cypress run --browser chrome --spec cypress/integration/pom/*.spec.js", 11 | "cypress:edge": "cypress run --browser edge --spec cypress/integration/pom/*.spec.js", 12 | "cypress:runChromeHeadless": "cypress run --headless --browser chrome --spec cypress/integration/pom/*.spec.js --record --key 4bc50df7-5eee-4147-8eb3-ccea611c8d4b" 13 | }, 14 | "keywords": [ 15 | "e2e", 16 | "automation", 17 | "framework", 18 | "cypress" 19 | ], 20 | "author": "joanmedia", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "@applitools/eyes-cypress": "^3.25.5", 24 | "@cypress-audit/lighthouse": "^1.3.0", 25 | "cypress": "^10.0.2", 26 | "cypress-grep": "^2.5.3", 27 | "cypress-mochawesome-reporter": "^3.0.1", 28 | "cypress-xpath": "^1.6.2", 29 | "mysql": "^2.18.1", 30 | "typescript": "^4.5.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cypress/e2e/commands/commands.spec.js: -------------------------------------------------------------------------------- 1 | // Cypress comes with its own API for creating custom commands 2 | // and overwriting existing commands. The built in Cypress commands 3 | // use the very same API that's defined below. 4 | 5 | //First step is creating a new command file under /support/commands.js 6 | //Why there? since it is loaded before any test files are evaluated via 7 | //an import statement in your supportFile (cypress/support/index.js by default). 8 | 9 | 10 | describe('Commands Example', function(){ 11 | 12 | beforeEach(function(){ 13 | cy.visit('https://www.saucedemo.com/') 14 | 15 | }) 16 | 17 | it('Success Login Test', function(){ 18 | cy.typeLogin('standard_user','secret_sauce') 19 | cy.get('.title').should('contain.text', 'Products') 20 | cy.logout(); 21 | cy.url().should('eq', 'https://www.saucedemo.com/') 22 | }) 23 | 24 | it('Failed Login Test', function(){ 25 | cy.typeLogin('standard_user', 'DummyPassword'); 26 | cy.get('.error').should('contain.text','Epic sadface: Username and password do not match any user in this service'); 27 | }) 28 | 29 | 30 | }) -------------------------------------------------------------------------------- /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 | Cypress.Commands.add('typeLogin', (username, password) => { 28 | cy.get('[data-test="username"]').type(username); 29 | cy.get('#password').type(password); 30 | cy.get('#login-button').click(); 31 | }) 32 | 33 | Cypress.Commands.add('logout', () => { 34 | cy.get('#react-burger-menu-btn').click(); 35 | cy.get('#logout_sidebar_link').click(); 36 | }) -------------------------------------------------------------------------------- /cypress/e2e/locators/locators.spec.js: -------------------------------------------------------------------------------- 1 | let username = "standard_user"; 2 | let password = "secret_sauce"; 3 | 4 | describe('Locators in Cypress', function(){ 5 | 6 | beforeEach(function(){ 7 | cy.visit('https://www.saucedemo.com/'); 8 | }); 9 | 10 | it('GET Method', function(){ 11 | cy.get('#user-name').type(username); 12 | cy.get('input#password').type(password); 13 | cy.get('[data-test="login-button"]').click(); 14 | }); 15 | 16 | it('EQ|FIRST|LAST Method', function(){ 17 | cy.get('input').first().type(username); 18 | cy.get('input').eq(1).type(password); 19 | cy.get('input').last().click(); 20 | }); 21 | 22 | it('FILTER Method',function(){ 23 | cy.get('input').filter('[type="text"]').type(username); 24 | cy.get('input').filter('[type="password"]').type(password); 25 | cy.get('input').filter('[type="submit"]').click(); 26 | 27 | }) 28 | 29 | it('FIND Method', function(){ 30 | cy.get('form').find('input').eq(0).type(username); 31 | cy.get('form').find('input').eq(1).type(password); 32 | cy.get('form').find('input').eq(2).click(); 33 | }) 34 | 35 | it('PARENT Method', function(){ 36 | cy.get('form').parent().should('have.class','login-box'); 37 | }) 38 | 39 | }); -------------------------------------------------------------------------------- /cypress/e2e/request/apiRequests.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const endPoint = 'http://localhost:3000/todos' 3 | 4 | const addTodo = item => { 5 | cy.request('POST', `${endPoint}`, item) 6 | } 7 | 8 | const deleteTodo = item => { 9 | cy.request('DELETE', `${endPoint}/${item.id}`) 10 | } 11 | 12 | const patchTodo = item => { 13 | cy.request('PATCH', `${endPoint}/${item.id}`, item) 14 | } 15 | 16 | const todoObject = { 17 | "title": "NewTodoFromPost", 18 | "completed": false, 19 | "id": "3" 20 | } 21 | 22 | const patchObject = { 23 | "title": "NewTodoFromPost", 24 | "completed": true, 25 | "id": "3" 26 | } 27 | 28 | 29 | describe('API DEMO', () => { 30 | 31 | it('Add a new todo', () => { 32 | 33 | addTodo(todoObject) 34 | 35 | cy.request('GET', `${endPoint}/${todoObject.id}`) 36 | .its('body') 37 | .should('deep.eq', todoObject) 38 | 39 | }) 40 | 41 | it('Update a todo', () => { 42 | 43 | patchTodo(patchObject) 44 | 45 | cy.request('GET', `${endPoint}/${todoObject.id}`) 46 | .its('body') 47 | .should('deep.eq', patchObject) 48 | 49 | 50 | }); 51 | 52 | it('Delete a todo', () => { 53 | deleteTodo(todoObject) 54 | 55 | cy.request('GET', endPoint) 56 | .its('body') 57 | .should('have.length', 2) 58 | 59 | }); 60 | 61 | 62 | }); -------------------------------------------------------------------------------- /cypress/e2e/more-commands/cypress-keyboard.js: -------------------------------------------------------------------------------- 1 | describe('4 Commands Probably You Did NOT Know', () => { 2 | 3 | //Cypress KEYBOARD 4 | //The Keyboard API allows you set the default values for how 5 | //the .type() command is executed. 6 | //Change the keystrokeDelay 7 | //https://docs.cypress.io/api/cypress-api/keyboard-api 8 | 9 | /* 10 | You can specify this as a global change at Cypress/Support/Index.js 11 | Cypress.Keyboard.defaults({ 12 | keystrokeDelay: 20, 13 | }) 14 | */ 15 | 16 | it('Cypress Keyboard - Slow Type', { keystrokeDelay: 100 }, () => { 17 | 18 | cy.visit('https://www.saucedemo.com/') 19 | 20 | cy.get('#user-name').type('user name test') 21 | 22 | 23 | }); 24 | 25 | it('Cypress Keyboard - Standard', { keystrokeDelay: 10 }, () => { 26 | //You can specify this as a global change at Cypress/Support/Index.js 27 | 28 | cy.visit('https://www.saucedemo.com/') 29 | 30 | cy.get('#user-name').type('user name test') 31 | 32 | 33 | }); 34 | 35 | it('Cypress Keyboard - Fast Type', { keystrokeDelay: 0 }, () => { 36 | //You can specify this as a global change at Cypress/Support/Index.js 37 | 38 | cy.visit('https://www.saucedemo.com/') 39 | 40 | cy.get('#user-name').type('user name test') 41 | 42 | 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /cypress/e2e/applitools/visual-testing.js: -------------------------------------------------------------------------------- 1 | //https://applitools.com/tutorials/cypress.html#applitools-eyes-cypress-sdk 2 | 3 | //npm i -D @applitools/eyes-cypress 4 | 5 | //npx eyes-setup -> The above command will add the necessary imports to your cypress pluginsFile and supportFile (and create the TypeScript definitions file), as described in the manual configuration below. 6 | 7 | //Add the "applitools.config.js" with configurations in the root 8 | 9 | describe("AppTest", () => { 10 | 11 | it(`ultraFastTest`, function () { 12 | // Navigate to the url we want to test 13 | // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run. 14 | // but then change the above URL to https://demo.applitools.com/index_v2.html 15 | // (for the 2nd run) 16 | cy.visit('http://localhost:3000/'); 17 | 18 | // Call Open on eyes to initialize a test session 19 | cy.eyesOpen({ 20 | appName: 'To Dos App', 21 | testName: 'To Do Testing', 22 | }) 23 | 24 | // check the login page with fluent api, see more info here 25 | // https://applitools.com/docs/topics/sdk/the-eyes-sdk-check-fluent-api.html 26 | cy.eyesCheckWindow({ 27 | tag: "To do Window", 28 | target: 'window', 29 | fully: true 30 | }); 31 | 32 | // Call Close on eyes to let the server know it should display the results 33 | cy.eyesClose() 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cypress-demo-framework 2 | 3 | exemplo imagem 4 | 5 | > Cypress IO demo framework built over Youtube videos to teach the framework basics to anyone interested in this technology. 6 | 7 | ### 💻 Topics 8 | 9 | We reviewed topics like: 10 | 11 | - [x] Mocha Structure 12 | - [x] Mocha Hooks 13 | - [x] Retry Ability 14 | - [X] Cy.Request(For API testing) 15 | - [X] Write and Read a file. 16 | - [X] Page Object Model 17 | - [X] Browser, Arch, and DOM commands. 18 | - [X] Locators 19 | - [X] Location Methods(URL Validation) 20 | - [X] Cy.Intercept(To mock and stub APIs) 21 | - [X] Fixtures 22 | - [X] Data Driven Testing 23 | - [X] Cypress Studio Review 24 | - [X] Cypress Custom Commands 25 | - [X] Cypress Assertions 26 | - [X] Cypress variables and aliases. 27 | - [X] Plugins: Browserstack, Mochawesome Reporter, Cypress Grep, Xpath 28 | - [X] Dockerfile added. 29 | - [X] Jenkinsfile added. 30 | - [X] Github Actions & Gitlab CI/CD yml files. 31 | - [X] Typescript Configuration 32 | - [X] Visual Testing with Applitools 33 | - [X] Database Testing using SQL 34 | - [X] Lighthouse integration 35 | 36 | 37 | - ## 💻 Pre-requisites 38 | 39 | Before you use this project you only need to have Node Js installed in your computer. 40 | 41 | https://nodejs.org/es/download/ 42 | 43 | ## 🚀 Install the project 44 | 45 | Install project dependencies with: npm i 46 | -------------------------------------------------------------------------------- /cypress/e2e/intercept/mockApi.js: -------------------------------------------------------------------------------- 1 | 2 | //Mock API responses may be useful during development and testing when live data is either unavailable or unreliable. 3 | // While designing an API, you can use mock APIs to work concurrently on the front and back-end, as well as to gather feedback from developers. 4 | 5 | //Website REPO: https://github.com/filiphric/testing-lists 6 | 7 | describe('Intercept Demo', () => { 8 | 9 | it('Intitial Validation', () => { 10 | cy.visit('http://localhost:3000/') 11 | cy.get('#todo-list li') 12 | .should('have.length', 2) 13 | .and('contain', 'test') 14 | .and('contain', 'wash dishes') 15 | }); 16 | 17 | it('Mocked API Response', ()=> { 18 | 19 | cy.intercept('GET', '/todos', { 20 | fixture: 'intercept/interceptFixture.json' 21 | }).as('getTodos-Fixture') 22 | 23 | cy.visit('http://localhost:3000/') 24 | 25 | cy.get('#todo-list li') 26 | .should('have.length', 3) 27 | .and('contain', 'Cypress Assertions Video') 28 | .and('contain', 'Page Object Model Cypress') 29 | .and('contain', 'Intercept Cypress') 30 | 31 | }) 32 | 33 | it('Mocked a ready todo item', () => { 34 | 35 | const stubExample = [{ 36 | "title": "Mock API Response", 37 | "completed": true, 38 | "id":1 39 | }] 40 | 41 | cy.intercept('GET', '/todos', { 42 | body: stubExample 43 | }) 44 | 45 | cy.visit('http://localhost:3000/') 46 | 47 | cy.get('div label').should('have.css', 'text-decoration', 'line-through solid rgb(217, 217, 217)') 48 | 49 | }); 50 | 51 | 52 | 53 | }); -------------------------------------------------------------------------------- /cypress/e2e/structure/hooks.spec.js: -------------------------------------------------------------------------------- 1 | //Hooks are also borrowed from mocha 2 | //These are helpful to set conditions that you want to run before a set of tests or before each test. They are also helpful 3 | //to clean up conditions after a set of tests or after each test. 4 | 5 | /* 6 | Order: 7 | before() -> It is run once 8 | beforeEach() -> It is run before any test execution 9 | -- Test Execution -- 10 | afterEach() -> It is run after any test execution 11 | after() -> It is run after all the test suite execution once. 12 | */ 13 | 14 | console.log('Test') 15 | 16 | 17 | describe('Hooks Demo', function (){ 18 | 19 | before(function(){ 20 | cy.log('This code will be executed once before any test execution') 21 | }); 22 | 23 | beforeEach(function(){ 24 | cy.log('This code is executed before any test execution'); 25 | }); 26 | 27 | context('Context #1', function(){ 28 | it('should be a #1 dummy test using IT',function(){ 29 | console.log('#1 Test'); 30 | }); 31 | }); 32 | context('Context #2', function(){ 33 | specify.skip('should be a #2 dummy test using SPECIFY', function(){ 34 | console.log('#2 Test') 35 | }); 36 | }); 37 | 38 | 39 | context('Context #3', function(){ 40 | specify.only('should be a #3 dummy test using SPECIFY', function(){ 41 | console.log('#3 Test') 42 | }); 43 | }); 44 | 45 | afterEach(function(){ 46 | cy.log('This code is executed after any test execution'); 47 | }); 48 | 49 | after(function(){ 50 | cy.log('This code will be executed once all test execution is done'); 51 | }); 52 | }); 53 | 54 | -------------------------------------------------------------------------------- /cypress/e2e/pom/homeSauce.spec.js: -------------------------------------------------------------------------------- 1 | import homeSaucePage from '../../pages/saucedemo/homeSaucePage' 2 | import inventoryPage from '../../pages/saucedemo/inventoryPage' 3 | 4 | describe('POM Implementation', () => { 5 | 6 | beforeEach(() => { 7 | cy.visit('https://www.saucedemo.com/') 8 | }); 9 | 10 | it('should login to inventory page', () => { 11 | homeSaucePage.typeUsername('standard_user'); 12 | homeSaucePage.typePassword('secret_sauce'); 13 | homeSaucePage.clickLogin(); 14 | inventoryPage.elements.titleSpan().should('have.text', 'Productss') 15 | }); 16 | 17 | it('should display locked out message', () => { 18 | homeSaucePage.typeUsername('locked_out_user'); 19 | homeSaucePage.typePassword('secret_sauce'); 20 | homeSaucePage.clickLogin(); 21 | homeSaucePage.elements.errorMessage().should('have.text', 'Epic sadface: Sorry, this user has been locked out.') 22 | }); 23 | 24 | it('should display incorrect username message', () => { 25 | homeSaucePage.typeUsername('dummyUsername'); 26 | homeSaucePage.typePassword('secret_sauce'); 27 | homeSaucePage.clickLogin(); 28 | homeSaucePage.elements.errorMessage().should('have.text', 'Epic sadface: Username and password do not match any user in this service') 29 | }); 30 | 31 | it('should display incorrect password message', () => { 32 | homeSaucePage.typeUsername('locked_out_user'); 33 | homeSaucePage.typePassword('dummyPassword'); 34 | homeSaucePage.clickLogin(); 35 | homeSaucePage.elements.errorMessage().should('have.text', 'Epic sadface: Username and password do not match any user in this service') 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /cypress/e2e/structure/test_structure.js: -------------------------------------------------------------------------------- 1 | //Cypress is built on top of Mocha and Chai. And Cypress support chai's BDD and TDD assertion styles. 2 | //The interface is borrowed from Mocha and we use describe, context, it, and specify 3 | 4 | //-- Dummy aplication code 5 | let add = (a,b) => a+b; 6 | let subtract = (a,b) => a-b; 7 | let divide = (a,b) => a/b; 8 | let multiply = (a,b) => a*b; 9 | 10 | //-- Cypress Test 11 | 12 | //Describe -> Simply a way to group our tests in Mocha. We can nest our test in groups as deep as we need. It takes to arguments, 13 | //the first is the name and then the call back function. 14 | 15 | //!Context is just an alias for describe() and behaves the same way. 16 | 17 | 18 | describe('Unit testing for our dummy application', () =>{ 19 | 20 | //It -> Used for an individual test cases. Receives two parameters, one the name as string explaining what the test will do, 21 | //and the second one is the call back function 22 | 23 | //Specify is also an alias of it, and we can use it in the same way. 24 | 25 | context('math with POSITIVE numbers', () => { 26 | 27 | it('Should add Numbers', ()=>{ 28 | expect(add(1,2)).to.eq(3); 29 | }); 30 | 31 | it('Should Subtract Numbers', ()=>{ 32 | expect(subtract(2,1)).to.eq(1); 33 | }); 34 | 35 | it('Should Multiply Numbers', ()=>{ 36 | expect(multiply(2,2)).to.eq(4); 37 | }); 38 | 39 | it('Should Divide Numbers', ()=>{ 40 | expect(divide(4,2)).to.eq(2); 41 | }); 42 | }); 43 | 44 | context('math with DECIMAL numbers', () => { 45 | 46 | it('Should Add Numbers', ()=>{ 47 | expect(add(2.2,2.2)).to.eq(4.4); 48 | }); 49 | 50 | it('Should Subtract Numbers', ()=>{ 51 | expect(subtract(4.4, 2.2)).to.eq(2.2); 52 | }); 53 | }); 54 | 55 | }); -------------------------------------------------------------------------------- /cypress/e2e/fixture-demo/fixtures.spec.js: -------------------------------------------------------------------------------- 1 | describe('Fixtures Demo', function(){ 2 | beforeEach(function(){ 3 | cy.visit('https://www.saucedemo.com/'); 4 | 5 | cy.fixture('fixtures-demo/sauceCredentials') 6 | .then(credentials => { 7 | this.credentials = credentials; 8 | }) 9 | }); 10 | 11 | it('Standard User', function(){ 12 | cy.get('[data-test="username"]').type(this.credentials.standardUsername); 13 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 14 | cy.get('[data-test="login-button"]').click(); 15 | 16 | cy.get('.title').should('contain.text', 'Products') 17 | }); 18 | 19 | it('Incorrect Username', function(){ 20 | cy.get('[data-test="username"]').type(this.credentials.dummyUsername); 21 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 22 | cy.get('[data-test="login-button"]').click(); 23 | 24 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Username and password do not match any user in this service') 25 | }); 26 | 27 | it('Incorrect Password', function(){ 28 | cy.get('[data-test="username"]').type(this.credentials.standardUsername); 29 | cy.get('[data-test="password"]').type(this.credentials.dummyPassword); 30 | cy.get('[data-test="login-button"]').click(); 31 | 32 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Username and password do not match any user in this service') 33 | }); 34 | 35 | it('Incorrect Password', function(){ 36 | cy.get('[data-test="username"]').type(this.credentials.lockedUsername); 37 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 38 | cy.get('[data-test="login-button"]').click(); 39 | 40 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Sorry, this user has been locked out.') 41 | }); 42 | }) -------------------------------------------------------------------------------- /cypress/e2e/tags/fixtures.spec.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Fixtures Demo (sanity)', { tags: '@sanityTag' }, function () { 3 | beforeEach(function () { 4 | cy.visit('https://www.saucedemo.com/'); 5 | 6 | cy.fixture('fixtures-demo/sauceCredentials') 7 | .then(credentials => { 8 | this.credentials = credentials; 9 | }) 10 | }); 11 | 12 | it('Standard User', function () { 13 | cy.get('[data-test="username"]').type(this.credentials.standardUsername); 14 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 15 | cy.get('[data-test="login-button"]').click(); 16 | 17 | cy.get('.title').should('contain.text', 'Products') 18 | }); 19 | 20 | it('Incorrect Username', function () { 21 | cy.get('[data-test="username"]').type(this.credentials.dummyUsername); 22 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 23 | cy.get('[data-test="login-button"]').click(); 24 | 25 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Username and password do not match any user in this service') 26 | }); 27 | 28 | it('Incorrect Password', function () { 29 | cy.get('[data-test="username"]').type(this.credentials.standardUsername); 30 | cy.get('[data-test="password"]').type(this.credentials.dummyPassword); 31 | cy.get('[data-test="login-button"]').click(); 32 | 33 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Username and password do not match any user in this service') 34 | }); 35 | 36 | it('Incorrect Password', function () { 37 | cy.get('[data-test="username"]').type(this.credentials.lockedUsername); 38 | cy.get('[data-test="password"]').type(this.credentials.systemPassword); 39 | cy.get('[data-test="login-button"]').click(); 40 | 41 | cy.get('[data-test="error"]').should('contain.text', 'Epic sadface: Sorry, this user has been locked out.') 42 | }); 43 | }) 44 | -------------------------------------------------------------------------------- /cypress/e2e/request/apiTestDemo.spec.js: -------------------------------------------------------------------------------- 1 | ////Website REPO: https://github.com/filiphric/testing-lists 2 | 3 | //-> https://www.cypress.io/blog/2017/11/07/add-gui-to-your-e2e-api-tests/ 4 | 5 | //-> The intention of cy.request() is to be used for checking endpoints on an actual, running server without having to start the front end application. 6 | 7 | // -> These are tests that do not modify the server state. 8 | 9 | describe('Basic API Testing - Part #1', () => { 10 | 11 | beforeEach(function() { 12 | cy.request('GET','http://localhost:3000/todos').as('getTodos') 13 | }); 14 | 15 | it('Body Length - Test', () => { 16 | 17 | cy.request('http://localhost:3000/todos') 18 | .its('body') 19 | .should('have.length', 2) 20 | 21 | }); 22 | 23 | it('Request Status - Test', () => { 24 | cy.request('http://localhost:3000/todos') 25 | .its('status') 26 | .should('eq', 200) 27 | }); 28 | 29 | it('header/content type - test', () => { 30 | 31 | cy.request('http://localhost:3000/todos') 32 | .its('headers') 33 | .its('content-type') 34 | .should('include', 'application/json') 35 | .should('include', 'charset=utf-8') 36 | 37 | }); 38 | 39 | const apiItems = [ 40 | { 41 | "title": "test", 42 | "completed": true, 43 | "id": "1" 44 | }, 45 | { 46 | "title": "wash dishes", 47 | "completed": false, 48 | "id": "2" 49 | } 50 | ] 51 | 52 | it('Loading of initial items - test', () => { 53 | 54 | cy.request('http://localhost:3000/todos') 55 | .its('body') 56 | .should('deep.eq', apiItems) 57 | 58 | }); 59 | 60 | it('JSON Schema Validation', () => { 61 | cy.request('http://localhost:3000/todos') 62 | .its('body') 63 | .each(object => { 64 | expect(object).to.have.all.keys('title', 'completed', 'id') 65 | }) 66 | }); 67 | 68 | it('Using Alias Request', function(){ 69 | 70 | cy.get('@getTodos').should(response => { 71 | expect(response.body).to.have.length(2) 72 | expect(response).to.have.property('headers') 73 | expect(response).to.have.property('duration') 74 | }) 75 | 76 | }) 77 | 78 | 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /cypress/e2e/database-testing/db-testing.spec.js: -------------------------------------------------------------------------------- 1 | //https://testersdock.com/cypress-database-testing/ 2 | //https://phoenixnap.com/kb/how-to-create-a-table-in-mysql#:~:text=The%20general%20syntax%20for%20creating,an%20identical%20table%20already%20exists. 3 | describe("DB Testing", function () { 4 | it("Create a movie table", function () { 5 | cy.task( 6 | "queryDb", 7 | "CREATE TABLE movies(title VARCHAR(50) NOT NULL,genre VARCHAR(30) NOT NULL,director VARCHAR(60) NOT NULL,release_year INT NOT NULL,PRIMARY KEY(title));" 8 | ).then((result) => { 9 | expect(result.message).to.equal(""); 10 | }); 11 | }); 12 | it("Insert a movie", function () { 13 | cy.task( 14 | "queryDb", 15 | `INSERT INTO movies VALUES ("Joker", "psychological thriller", "Todd Phillips", 2019),("The Batman", "action", "Matt Reeves", 2022);` 16 | ).then((result) => { 17 | expect(result.affectedRows).to.equal(2); 18 | }); 19 | }); 20 | it("Select all movies", function () { 21 | cy.task("queryDb", `SELECT * FROM movies;`).then((result) => { 22 | cy.log("First row validation").then(() => { 23 | expect(result[0].director).to.equal("Todd Phillips"); 24 | expect(result[0].genre).to.equal("psychological thriller"); 25 | expect(result[0].release_year).to.equal(2019); 26 | expect(result[0].title).to.equal("Joker"); 27 | }); 28 | cy.log("Second row validation").then(() => { 29 | expect(result[1].director).to.equal("Matt Reeves"); 30 | expect(result[1].genre).to.equal("action"); 31 | expect(result[1].release_year).to.equal(2022); 32 | expect(result[1].title).to.equal("The Batman"); 33 | }); 34 | }); 35 | }); 36 | it("Update a movie", function () { 37 | cy.task( 38 | "queryDb", 39 | `UPDATE movies SET genre = "test genre" WHERE title="Joker"` 40 | ).then((result) => { 41 | expect(result.changedRows).to.equal(1); 42 | }); 43 | cy.task("queryDb", `SELECT genre FROM movies WHERE title="Joker"`).then( 44 | (result) => { 45 | expect(result[0].genre).to.equal("test genre"); 46 | } 47 | ); 48 | }); 49 | it("Delete the movie table", function () { 50 | cy.task("queryDb", `DROP TABLE movies`).then((result) => { 51 | expect(result.message).to.equal(""); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | }; 22 | 23 | // cypress/plugins/index.js 24 | module.exports = (on, config) => { 25 | // optional: register cypress-grep plugin code 26 | // https://github.com/bahmutov/cypress-grep 27 | require("cypress-grep/src/plugin")(config); 28 | }; 29 | 30 | module.exports = (on, config) => { 31 | // optional: register cypress-grep plugin code 32 | // https://github.com/bahmutov/cypress-grep 33 | require("cypress-mochawesome-reporter/plugin")(on); 34 | }; 35 | 36 | require("@applitools/eyes-cypress")(module); 37 | 38 | const mysql = require("mysql"); 39 | function queryTestDb(query, config) { 40 | // creates a new mysql connection using credentials from cypress.json env's 41 | const connection = mysql.createConnection(config.env.db); 42 | // start connection to db 43 | connection.connect(); 44 | // exec query + disconnect to db as a Promise 45 | return new Promise((resolve, reject) => { 46 | connection.query(query, (error, results) => { 47 | if (error) reject(error); 48 | else { 49 | connection.end(); 50 | return resolve(results); 51 | } 52 | }); 53 | }); 54 | } 55 | 56 | module.exports = (on, config) => { 57 | on("task", { 58 | queryDb: (query) => { 59 | return queryTestDb(query, config); 60 | }, 61 | }); //For running sql query 62 | }; 63 | 64 | const { lighthouse, prepareAudit } = require('@cypress-audit/lighthouse') 65 | 66 | module.exports = (on, config) => { 67 | on('before:browser:launch', (browser = {}, launchOptions) => { 68 | prepareAudit(launchOptions) 69 | }) 70 | 71 | on('task', { 72 | lighthouse: lighthouse(), // calling the function is important 73 | }) 74 | } 75 | 76 | -------------------------------------------------------------------------------- /cypress/e2e/assertions/assert.spec.js: -------------------------------------------------------------------------------- 1 | //Assertions are the validation steps that determine whether the specified step of the automated test case succeeded or not. 2 | // In actual, Assertions validates the desired state of your elements, objects, or application under test 3 | 4 | // Cypress bundles the popular Chai assertion library, 5 | // as well as helpful extensions for Sinon and jQuery, bringing you dozens of powerful assertions for free. 6 | // --- BDD & TDD Assertions 7 | 8 | 9 | describe('Assertion Demo', () => { 10 | 11 | beforeEach(() => { 12 | cy.visit('https://demoqa.com/radio-button') 13 | }); 14 | 15 | it('TDD Assertions', () => { 16 | 17 | cy.log('-- Length Check'); 18 | cy.get('input[type="radio"]').should('have.length', 3); 19 | 20 | cy.log('-- Class Check'); 21 | cy.get('input[type="radio"]').eq(2).should('have.class', 'disabled'); 22 | 23 | //Negative Assertions 24 | // We recommend using negative assertions to verify that a specific condition 25 | // is no longer present after the application performs an action. For example, 26 | // when a previously completed item is unchecked, we might verify that a CSS class is removed. 27 | cy.log('-- Exist Check') 28 | cy.get('.mt-3').should('not.exist') 29 | 30 | //Multiple Assertions 31 | cy.log('-- Text Check'); 32 | cy.get('input[type="radio"]').eq(0).click({force: true}); 33 | cy.get('.mt-3') 34 | .should('have.text', 'You have selected Yes') 35 | .and('include.text', 'Yes') 36 | .and('not.contain', 'Test Word') 37 | 38 | cy.log('-- CSS check'); 39 | cy.get('.text-success').should('have.css', 'color', 'rgb(40, 167, 69)') 40 | 41 | }); 42 | 43 | it('BDD Assertions', () => { 44 | 45 | //Should call back & Multiple assertions 46 | cy.log('-- Length & Class Check') 47 | cy.get('input[type="radio"]').should($element => { 48 | expect($element).to.have.lengthOf(3) 49 | expect($element).to.have.class('disabled') 50 | }) 51 | 52 | cy.log('-- Text Check'); 53 | cy.get('input[type="radio"]').eq(0).click({force: true}); 54 | cy.get('.mt-3').should($element => { 55 | expect($element).to.have.text('You have selected Yes') 56 | expect($element).to.include.text('Yes') 57 | expect($element).to.not.include.text('No') 58 | }) 59 | 60 | 61 | }); 62 | 63 | }); -------------------------------------------------------------------------------- /cypress/e2e/variables-aliases/var-alias.spec.js: -------------------------------------------------------------------------------- 1 | describe('Variables & Aliases DEMO', () => { 2 | 3 | beforeEach(function(){ 4 | cy.visit('https://demoqa.com/modal-dialogs') 5 | }) 6 | 7 | it('Return Variables Misconception', () => { 8 | 9 | //You cannot assign or work with the return values of any Cypress command. Commands are enqueued and run asynchronously. 10 | 11 | const smallModalText = cy.get('#showSmallModal').text(); 12 | 13 | cy.log(smallModalText) 14 | 15 | }); 16 | 17 | it('CLOSURES & VARIABLES', () => { 18 | 19 | //If you're familiar with native Promises the Cypress .then() works the same way. You can continue to nest more Cypress commands inside of the .then(). 20 | 21 | //Each nested command has access to the work done in previous commands. 22 | 23 | cy.get('#showSmallModal').then($modalButon => { 24 | 25 | //By using callback functions we've created a closure. Closures enable us to keep references around to refer to work done in previous commands. 26 | const smallModalText = $modalButon.text(); 27 | cy.log(smallModalText) 28 | 29 | $modalButon.click(); 30 | 31 | cy.get('#example-modal-sizes-title-sm').contains(smallModalText, {matchCase: false}) 32 | 33 | //This rule is when you are dealing with mutable objects (that change state). 34 | // When things change state you often want to compare an object's previous value to the next value. 35 | 36 | //The commands outside of the .then() will not run until all of the nested commands finish. 37 | 38 | }) 39 | 40 | }); 41 | 42 | it('Context Misconception', () =>{ 43 | cy.log(smallModalText) 44 | }) 45 | 46 | //Sharing context is the simplest way to use aliases. 47 | 48 | //To alias something you'd like to share use the .as() command. 49 | it('ALIASES', function() { 50 | 51 | cy.get('#showSmallModal').invoke('text').as('invoqueText') 52 | 53 | cy.get('#showSmallModal').then($modalButon => { 54 | 55 | //By using callback functions we've created a closure. Closures enable us to keep references around to refer to work done in previous commands. 56 | const smallModalText = $modalButon.text(); 57 | cy.log(smallModalText) 58 | 59 | cy.wrap(smallModalText).as('wrapText') 60 | 61 | }) 62 | }); 63 | 64 | it('Sharing Context', function() { 65 | cy.log(this.invoqueText) 66 | cy.log(this.wrapText) 67 | 68 | }); 69 | 70 | }); -------------------------------------------------------------------------------- /cypress/e2e/request/dummy-api.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const postRequest = (resource, endpoint) => { 4 | cy.request('POST', endpoint, resource) 5 | } 6 | 7 | 8 | describe('Scheduled Searches - Endpoint API Test - DEMO', function () { 9 | 10 | 11 | it('Create a scheduled Search', function () { 12 | 13 | cy.request('POST','test', ss) 14 | .should(response => { 15 | expect(response.status).to.equal(200) 16 | expect(response.statusText).to.equal('OK') 17 | expect(response.headers['content-type']).to.include('application/json') 18 | expect(response.body).to.have.any.keys('id', 'type', 'active', 'name', 'range', 'schedule', 'userId', 'timezone', 'lastChangeDate','createdAt', 'data', 'type', 'start', 'end','occurrences', 'templateSearchId', 'overrideResults', 'dateTimeFormat') 19 | 20 | //This can be handled in a custom command. 21 | //Save the ID for further usage in a fixture file. 22 | cy.readFile("cypress/fixtures/api-demo/schedule-data.json", (err, data) => { 23 | if (err) { 24 | return console.error(err); 25 | }; 26 | }).then((data) => { 27 | data.id = response.body["id"] //save the New Value of Unique ID 28 | cy.writeFile("cypress/fixtures/api-demo/schedule-data.json", JSON.stringify(data)) //Write it to the fixtures file 29 | }) 30 | 31 | //End of the custom command. 32 | 33 | }) 34 | }) 35 | 36 | it.skip('Update a scheduled Search', ()=>{ 37 | 38 | cy.fixture('api-demo/schedule-data').then(data=>{ 39 | 40 | cy.request('PUT',`https://test/${data.id}`, ssUpdated) 41 | .should(response => { 42 | expect(response.status).to.equal(200) 43 | expect(response.statusText).to.equal('OK') 44 | expect(response.headers['content-type']).to.include('application/json') 45 | expect(response.body["active"]).to.equal(false) 46 | expect(response.body["name"]).to.equal('Test1-Changed') 47 | }) 48 | 49 | }) 50 | 51 | }) 52 | 53 | it('Delete a schheduled search', function(){ 54 | cy.fixture('api-demo/schedule-data').then(data=>{ 55 | 56 | cy.request('DELETE',`test/${data.id}`) 57 | .should(response => { 58 | expect(response.status).to.equal(200) 59 | expect(response.statusText).to.equal('OK') 60 | expect(response.headers['content-type']).to.include('application/json') 61 | expect(response.body).to.equal(data.id) 62 | }) 63 | 64 | }) 65 | 66 | }) 67 | 68 | }) 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /cypress/e2e/xpath/xpath-explanation.js: -------------------------------------------------------------------------------- 1 | // To install Xpath Plugin -> npm install -D cypress-xpath 2 | // Include the following command at support/index.js require('cypress-xpath') 3 | 4 | //Xpath -> XPath is defined as XML path. It is a syntax or 5 | //language for finding any element on the web page using the 6 | //XML path expression. XPath is used to find the location of 7 | //any element on a webpage using HTML DOM structure. 8 | 9 | //Common Xpath Structure 10 | /* //tagName[@attribute='value'] */ 11 | /* 12 | // -> Select Current Node 13 | Tagname -> Tagname of the particular node. 14 | @ -> Select attribute 15 | Attribute -> Attribute name of the node 16 | Value -> Value of the attribute 17 | */ 18 | 19 | //Types of Xpath 20 | /* 21 | Absolute Xpath 22 | Absolute XPath is the direct way to find the element. 23 | But the disadvantage of the absolute XPath is that if 24 | there are any changes made in the path of the element then that XPath fails. 25 | The key characteristic of XPath is that it begins with the single forward slash(/) 26 | ,which means you can select the element from the root node. 27 | 28 | Site: https://www.saucedemo.com/ 29 | Example: /html/body/div/div/div/div/div/div/form/div/input 30 | 31 | Relativa Xpath 32 | For Relative XPath, the path starts from the middle of the HTML DOM structure 33 | . It starts with the double forward slash (//), which means it can search the 34 | element anywhere at the webpage. 35 | 36 | Example: //div[@class='login-box']/form/div/input[@id='user-name'] 37 | 38 | //Dynamic Xpath Strategies 39 | 1- Basic one: XPath expression selects nodes or lists of nodes on the basis of 40 | attributes like ID, name, classname, etc. from the XML document as illustrated below. 41 | //input[@name='user-name'] 42 | 43 | 2- Contains: The contain feature has an ability to find the element with partial text 44 | as shown in the below example. 45 | //*[contains(@placeholder, 'User')] 46 | 47 | 3- Using OR & AND: In OR expression, two conditions are used, 48 | whether the first condition OR second condition should be true. 49 | //*[@data-test='username' or @data-test='password'] 50 | 51 | In AND expression, two conditions are used. Both conditions 52 | should be true to find the element. It fails to find the element 53 | if any one condition is false. 54 | //*[@data-test='username' and @name='user-name'] 55 | 56 | 4- Starts-With Function: 57 | Starts-with function finds the element whose attribute value changes on refresh or 58 | any operation on the webpage. In this expression, match the starting text of the 59 | attribute used to find the element whose attribute changes dynamically. You can also 60 | find the element whose attribute value is static (does not change). 61 | 62 | For example, suppose the ID of a particular element changes dynamically like: 63 | Id=" message12" 64 | Id=" message345" 65 | Id=" message8769" 66 | And so on. But the initial text is same. In this case, we use Start-with expression “message.” 67 | //input[starts-with(@class,'input')] 68 | 69 | 5- Text(): In this expression, with text function, we find the element with the exact text match 70 | as shown below. In this case, we find the element with text "Username or email." 71 | 72 | // //*[text()='Password for all users:'] 73 | 74 | 6- Using Index: This approach comes in use when you wish to specify a given tag name in terms of 75 | the index value you wish to locate. 76 | 77 | (//input[starts-with(@class,'input')])[1] 78 | 79 | */ -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonOutput 2 | 3 | def COLOR_MAP = [ 4 | 'SUCCESS': 'good', 5 | 'FAILURE': 'danger', 6 | ] 7 | 8 | def getBuildUser() { 9 | return currentBuild.rawBuild.getCause(Cause.UserIdCause).getUserId() 10 | } 11 | 12 | 13 | pipeline { 14 | //The agent section specifies where the entire Pipeline, or a specific stage, 15 | //will execute in the Jenkins environment depending on where the agent section is placed. 16 | agent any 17 | 18 | //The environment directive specifies a sequence of key-value pairs which will be defined 19 | //as environment variables for all steps, or stage-specific steps, depending on where the environment directive is located within the Pipeline. 20 | environment { 21 | BUILD_USER = '' 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, see 26 | //the Parameters, Declarative Pipeline for its specific usage. 27 | parameters { 28 | string(name: 'SPEC', defaultValue: 'cypress/integration/**/**', description: 'Ej: cypress/integration/pom/*.spec.js') 29 | choice(name: 'BROWSER', choices: ['chrome', 'edge', 'firefox'], description: 'Pick the web browser you want to use to run your scripts') 30 | } 31 | 32 | //The options directive allows configuring Pipeline-specific options from within the Pipeline itself. 33 | //Pipeline provides a number of these options, such as buildDiscarder, but they may also be provided by 34 | //plugins, such as timestamps. Ex: retry on failure 35 | options { 36 | ansiColor('xterm') 37 | } 38 | 39 | //The stage directive goes in the stages section and should contain a steps section, an optional agent section, 40 | //or other stage-specific directives. Practically speaking, all of the real work done by a Pipeline will be wrapped 41 | //in one or more stage directives. 42 | stages { 43 | 44 | stage('Build'){ 45 | //The steps section defines a series of one or more steps to be executed in a given stage directive. 46 | steps { 47 | echo "Building the application" 48 | } 49 | } 50 | 51 | stage('Testing') { 52 | steps { 53 | bat "npm i" 54 | bat "npx cypress run --browser ${BROWSER} --spec ${SPEC}" 55 | } 56 | } 57 | 58 | stage('Deploy'){ 59 | steps { 60 | echo "Deploying" 61 | } 62 | } 63 | } 64 | 65 | post { 66 | always { 67 | //The script step takes a block of Scripted Pipeline and executes that in the Declarative Pipeline. 68 | //For most use-cases, the script step should be unnecessary in Declarative Pipelines, but it can provide 69 | //a useful "escape hatch." script blocks of non-trivial size and/or complexity should be moved into Shared Libraries instead. 70 | script { 71 | BUILD_USER = getBuildUser() 72 | } 73 | 74 | slackSend channel: '#jenkins-example', 75 | color: COLOR_MAP[currentBuild.currentResult], 76 | message: "*${currentBuild.currentResult}:* Job ${env.JOB_NAME} build ${env.BUILD_NUMBER} by ${BUILD_USER}\n Tests:${SPEC} executed at ${BROWSER} \n More info at: ${env.BUILD_URL}HTML_20Report/" 77 | 78 | publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'cypress/report', reportFiles: 'index.html', reportName: 'HTML Report', reportTitles: '']) 79 | deleteDir() 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cypress/e2e/retry-ability/retry-ability.spec.js: -------------------------------------------------------------------------------- 1 | //What is the retry ability in Cypress 2 | //-> The retry-ability allows the tests to complete each command as soon as the assertion passes, 3 | //without hard-coding waits. If your application takes a few milliseconds or even seconds to render each DOM element - no big deal, 4 | //the test does not have to change at all. 5 | 6 | describe('Retry-Ability Session', function(){ 7 | 8 | beforeEach(function(){ 9 | cy.visit('http://192.168.0.15:8888'); // Command 10 | 11 | //Get -> Get one or more DOM elements by selector or alias. 12 | cy.get('.new-todo') // command - Get one or more DOM elements by selector or alias. 13 | .type('todo A{enter}') // command 14 | .type('todo B{enter}') // command 15 | }); 16 | 17 | //✅ If the assertion that follows the cy.get() command passes, then the command finishes successfully. 18 | //🚨 If the assertion that follows the cy.get() command fails, then the cy.get() command will requery the application’s DOM again. 19 | //Then Cypress will try the assertion against the elements yielded from cy.get(). If the assertion still fails, cy.get() will try requerying the DOM again, 20 | //and so on until the cy.get() command timeout is reached. 21 | 22 | //In the example, Within a few milliseconds after the DOM updates, cy.get() finds two elements and the should('have.length', 2) assertion passes. 23 | 24 | it.only('should create two items', () => { 25 | cy.get('.todo-list li') // command 26 | .should('have.length', 2) // assertion 27 | }); 28 | 29 | 30 | //A single command followed by multiple assertions retries each one of them – in order. Thus when the first assertion passes, the command will be retried with 31 | //first and second assertion. When the first and second assertion pass, then the command will be retried with the first, second, and third assertion, and so on. 32 | it('should match the content of the
  • elements', ()=>{ 33 | cy.get('.todo-list li') // command 34 | .should('have.length', 2) // assertion 35 | .and(($li) => { //And is an alias of should 36 | // 2 more assertions 37 | expect($li.get(0).textContent, 'first item').to.equal('todo A') 38 | expect($li.get(1).textContent, 'second item').to.equal('todo B') 39 | }) 40 | }); 41 | 42 | // Not every command is retried 43 | /* 44 | --> Cypress only retries commands that query the DOM: cy.get(), .find(), .contains(), etc. 45 | --> Why are some commands NOT retried? : Commands are not retried when they could potentially change the state of the application under test. For example, 46 | Cypress will not retry the .click() command, because it could change something in the application. 47 | --> ome commands that cannot be retried still have built-in waiting. For example, as described in the “Assertions” section of .click(), the click() command waits 48 | to click until the element becomes actionable. 49 | Cypress tries to act like a human user would using the browser. 50 | * Can a user click on the element? 51 | * Is the element invisible? 52 | * Is the element behind another element? 53 | * Does the element have the disabled attribute? 54 | The .click() command will automatically wait until multiple built-in assertion checks like these pass, and then it will attempt to click just once. 55 | */ 56 | 57 | // Built-in assertions 58 | /* 59 | Often a Cypress command has built-in assertions that will cause the command to be retried. For example, the .eq() command will be retried even without any 60 | attached assertions until it finds an element with the given index in the previously yielded list of elements. 61 | */ 62 | 63 | it('should be a demonstration of a built in assertion', () =>{ 64 | cy.get('.todo-list li') // command 65 | .should('have.length', 2) // assertion 66 | .eq(1) // command -> Get A DOM element at a specific index in an array of elements. 67 | }) 68 | 69 | //TimeOuts 70 | // By default each command that retries, does so for up to 4 seconds - the defaultCommandTimeout setting. 71 | 72 | // cypress run --config defaultCommandTimeout=10000 73 | // cy.get('.mobile-nav', { timeout: 10000 }) 74 | // Disable retry -> cy.get('#ssr-error', { timeout: 0 }).should('not.exist') 75 | 76 | //Recommendations to avoid flaky tests: 77 | //1-- to avoid unnecessarily splitting commands that query elements. In our case we first query elements using cy.get() and then query from that list of elements using .find(). 78 | // We can combine two separate queries into one - forcing the combined query to be retried. 79 | //2-- Whenever you write a longer test, we recommend alternating commands with assertions. 80 | 81 | 82 | 83 | 84 | 85 | }); --------------------------------------------------------------------------------