├── 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 | > 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