Create automation framework using puppeteer and jest

How to create an End to end automation framework using puppeteer and Jest. In this puppeteer tutorial, Let’s automate some real-time testcases that follow a Page Object Model(POM) Architecture and create end to end automation framework.

Automation framework using puppeteer and jest

Create a simple automation project and add some impotent configuration and step by step will add the POM resources.

Create a Project folder “PuppeteerWithJest

Open Command Terminal in VS code and start the project by using npm init

PS G:\Puppeteer tutorial\PuppeteerWithJest> npm init    
This utility will walk you through creating a package.json file.  
It only covers the most common items, and tries to guess sensible 
defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (puppeteerwithjest)
version: (1.0.0)
description: Automation framework with Puppeteer and jest
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to G:\Puppeteer tutorial\PuppeteerWithJest\package.json:

{
  "name": "puppeteerwithjest",
  "version": "1.0.0",
  "description": "Automation framework with Puppeteer and jest",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)
PS G:\Puppeteer tutorial\PuppeteerWithJest>

Install required dependencies

npm install jest puppeteer jest-puppeteer

Install babel dependencies

npm install @babel/core @babel/preset-env babel-jest

Project Structure

Configuration Setup

jest configuration

Enter the below commands and follow the instruction to enable the jest configuration

PS G:\Puppeteer tutorial\PuppeteerWithJest> .\node_modules\.bin\jest --init

The following questions will help Jest to create a suitable configuration for your project

√ Would you like to use Jest when running "test" script in "package.json"? ... yes
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing » node
√ Do you want Jest to add coverage reports? ... no
√ Which provider should be used to instrument code for coverage? » babel
√ Automatically clear mock calls, instances and results before every test? ... no

✏️  Modified G:\Puppeteer tutorial\PuppeteerWithJest\package.json

�  Configuration file created at G:\Puppeteer tutorial\PuppeteerWithJest\jest.config.js
PS G:\Puppeteer tutorial\PuppeteerWithJest>

Create a jest.config.js file and set some important and required things.

/*
 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

const { Puppeteer } = require("puppeteer");

module.exports = {
  // All imported modules in your tests should be mocked automatically
  // automock: false,

  // Stop running tests after `n` failures
  // bail: 0,
  bail:5,

  // The directory where Jest should store its cached dependency information
  // cacheDirectory: "C:\\Users\\Asus\\AppData\\Local\\Temp\\jest",

  // Automatically clear mock calls, instances and results before every test
  // clearMocks: false,

  // Indicates whether the coverage information should be collected while executing the test
  // collectCoverage: false,

  // An array of glob patterns indicating a set of files for which coverage information should be collected
  // collectCoverageFrom: undefined,

  // The directory where Jest should output its coverage files
  // coverageDirectory: undefined,

  // An array of regexp pattern strings used to skip coverage collection
  // coveragePathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // Indicates which provider should be used to instrument code for coverage
  // coverageProvider: "babel",

  // A list of reporter names that Jest uses when writing coverage reports
  // coverageReporters: [
  //   "json",
  //   "text",
  //   "lcov",
  //   "clover"
  // ],

  // An object that configures minimum threshold enforcement for coverage results
  // coverageThreshold: undefined,

  // A path to a custom dependency extractor
  // dependencyExtractor: undefined,

  // Make calling deprecated APIs throw helpful error messages
  // errorOnDeprecated: false,

  // Force coverage collection from ignored files using an array of glob patterns
  // forceCoverageMatch: [],

  // A path to a module which exports an async function that is triggered once before all test suites
  // globalSetup: undefined,

  // A path to a module which exports an async function that is triggered once after all test suites
  // globalTeardown: undefined,

  // A set of global variables that need to be available in all test environments
  // globals: {},

  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
  // maxWorkers: "50%",

  // An array of directory names to be searched recursively up from the requiring module's location
  // moduleDirectories: [
  //   "node_modules"
  // ],

  // An array of file extensions your modules use
  // moduleFileExtensions: [
  //   "js",
  //   "jsx",
  //   "ts",
  //   "tsx",
  //   "json",
  //   "node"
  // ],

  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
  // moduleNameMapper: {},

  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
  // modulePathIgnorePatterns: [],

  // Activates notifications for test results
  // notify: false,

  // An enum that specifies notification mode. Requires { notify: true }
  // notifyMode: "failure-change",

  // A preset that is used as a base for Jest's configuration
  // preset: undefined,
    preset: "jest-Puppeteer"
  // Run tests from one or more projects
  // projects: undefined,

  // Use this configuration option to add custom reporters to Jest
  // reporters: undefined,

  // Automatically reset mock state before every test
  // resetMocks: false,

  // Reset the module registry before running each individual test
  // resetModules: false,

  // A path to a custom resolver
  // resolver: undefined,

  // Automatically restore mock state and implementation before every test
  // restoreMocks: false,

  // The root directory that Jest should scan for tests and modules within
  // rootDir: undefined,

  // A list of paths to directories that Jest should use to search for files in
  // roots: [
  //   "<rootDir>"
  // ],

  // Allows you to use a custom runner instead of Jest's default test runner
  // runner: "jest-runner",

  // The paths to modules that run some code to configure or set up the testing environment before each test
  // setupFiles: [],

  // A list of paths to modules that run some code to configure or set up the testing framework before each test
  // setupFilesAfterEnv: [],

  // The number of seconds after which a test is considered as slow and reported as such in the results.
  // slowTestThreshold: 5,

  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
  // snapshotSerializers: [],

  // The test environment that will be used for testing
  // testEnvironment: "jest-environment-node",

  // Options that will be passed to the testEnvironment
  // testEnvironmentOptions: {},

  // Adds a location field to test results
  // testLocationInResults: false,

  // The glob patterns Jest uses to detect test files
  // testMatch: [
  //   "**/__tests__/**/*.[jt]s?(x)",
  //   "**/?(*.)+(spec|test).[tj]s?(x)"
  // ],

  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
  // testPathIgnorePatterns: [
  //   "\\\\node_modules\\\\"
  // ],

  // The regexp pattern or array of patterns that Jest uses to detect test files
  // testRegex: [],

  // This option allows the use of a custom results processor
  // testResultsProcessor: undefined,

  // This option allows use of a custom test runner
  // testRunner: "jest-circus/runner",

  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
  // testURL: "http://localhost",

  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
  // timers: "real",

  // A map from regular expressions to paths to transformers
  // transform: undefined,

  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
  // transformIgnorePatterns: [
  //   "\\\\node_modules\\\\",
  //   "\\.pnp\\.[^\\\\]+$"
  // ],

  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
  // unmockedModulePathPatterns: undefined,

  // Indicates whether each individual test should be reported during the run
  // verbose: undefined,

  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
  // watchPathIgnorePatterns: [],

  // Whether to use watchman for file crawling
  // watchman: true,
};

Babel configuration in puppeteer

Create a file babel.config.js and copy the below code

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current"
        }
      }
    ]
  ]
};

jest-puppeteer.config.js

module.exports = {
    launch:{
        headless: false
    },
    browserContext: "default"
};

Set TestExecution timeout in Jest Puppeteer

Open jest.config.js and update

testTimeout: 60 * seconds,

Create Pages and Components

Create a page folder and a component folder under the page folder.

Now Create a BasePage.js file under the page folder that will be used by all other pages.

export default class BasePage {
    async wait(time) {
        await page.waitFor(time)
    }

    async getTitle() {
        return await page.title()
    }

    async getUrl() {
        return await page.url()
    }
}

FeedbackPage.js

import BasePage from './BasePage'

export default class FeedbackPage extends BasePage {
    async visit() {
        await page.goto('http://zero.webappsecurity.com/feedback.html')
    }

    async isFeedbackFormDisplayed() {
        await page.waitForSelector('#name')
        await page.waitForSelector('#email')
        await page.waitForSelector('#subject')
        await page.waitForSelector('#comment')
    }

    async submitFeedback(name, email, subject, comment) {
        await page.type('#name', name)
        await page.type('#email', email)
        await page.type('#subject', subject)
        await page.type('#comment', comment)
        await page.click('input[type="submit"]')
    }
}

HomePage.js

import BasePage from './BasePage'

export default class HomePage extends BasePage {
    async visit() {
        await page.goto('http://zero.webappsecurity.com/')
    }

    async isNavbarDisplayed() {
        await page.waitForSelector('#pages-nav')
        await page.waitForSelector('#homeMenu')
        await page.waitForSelector('#onlineBankingMenu')
        await page.waitForSelector('#feedback')
    }

    async clickHomepageLink() {
        await page.click('#homeMenu')
    }

    async clickOnlineBankingLink() {
        await page.click('#onlineBankingMenu')
    }

    async clickFeedbackLink() {
        await page.click('#feedback')
    }
}

LoginPage.js

import BasePage from './BasePage'

export default class LoginPage extends BasePage {
    async visit() {
        await page.goto('http://zero.webappsecurity.com/login.html')
    }

    async isLoginFormDisplayed() {
        await page.waitForSelector('#login_form')
        await page.waitForSelector('#user_login')
        await page.waitForSelector('#user_password')
    }

    async login(user, password) {
        await page.waitForSelector('#login_form')
        await page.type('#user_login', user)
        await page.type('#user_password', password)
        await page.click('.btn-primary')
    }
}

Create a Components under the Component folder

TopBar.js

export default class TopBar {
    async isTopBarDisplayed() {
        await page.waitForSelector('.brand')
        await page.waitForSelector('#signin_button')
    }

    async clickSignInButton() {
        await page.click('#signin_button')
    }
}

Create TestCases

Now create a “Tests” folders  and Testfile under the Tests folder

import HomePage from '../pages/HomePage'
import TopBar from '../pages/components/TopBar'
import FeedbackPage from '../pages/FeedbackPage'
import LoginPage from '../pages/LoginPage'

import { username, password, timeout } from '../config'

describe('End-to-end Test', () => {
    let homePage
    let topBar
    let feedbackPage
    let loginPage

    beforeAll(async () => {
        jest.setTimeout(timeout)
        homePage = new HomePage()
        topBar = new TopBar()
        feedbackPage = new FeedbackPage()
        loginPage = new LoginPage()
    })

    it('should load homepage', async () => {
        await homePage.visit()
        await homePage.isNavbarDisplayed()
    })

    it('should submit feedback', async () => {
        await feedbackPage.visit()
        await feedbackPage.isFeedbackFormDisplayed()
        await feedbackPage.submitFeedback(
            'Codedec',
            'codedec@email.com',
            'subject',
            'here comes your super long comment message'
        )
    })

    it('should login to application', async () => {
        await homePage.visit()
        await topBar.isTopBarDisplayed()
        await topBar.clickSignInButton()
        await loginPage.isLoginFormDisplayed()
        await loginPage.login(username, password)
    })
})

Config.js

export const username = 'username'
export const password = 'password'
export const timeout = 15000

package.json

{
  "name": "jest-pptr-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest --forceExit"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "babel-jest": "^26.3.0",
    "jest": "^26.4.2",
    "jest-puppeteer": "^4.4.0",
    "puppeteer": "^5.2.1"
  },
  "devDependencies": {
    "prettier": "^2.1.1"
  }
}

Output: