preface

Page interaction has always been the most important part of the front end, but it has always been a difficult problem to implement automated testing at the UI level. In the past, we could only manually click the page to test page interaction and data transfer. Therefore, E2E testing is born out of necessity. E2E testing is extremely convenient to solve the user level interaction testing.

Puppeteer

Puppeteer. As the name suggests, Puppeteer is a Node library that provides a number of advanced apis for controlling Chromium or Chrome via the DevTools protocol.

The Puppeteer API is hierarchical and reflects the browser structure.

  • Browser: a Browser instance that can have a Browser context and can create a Browser object through puppeteer.launch or puppeteer.connect.
  • BrowserContext: This instance defines a browser context that can have multiple pages. A browser context is created by default when a browser instance is created (it cannot be closed). In addition to using the createIncognitoBrowserContext () to create an anonymous browsers context (not Shared with other context browser cookies/cache).
  • Page: Contains at least one main frame, in addition to the main frame may exist other frames, such as iframe.
  • Frame: The Frame in the page. At each point in time, the page exposes the details of the current Frame through the page.mainframe () and frame.childframes () methods. There is at least one execution context for the framework
  • ExecutionCOntext: represents a JavaScript ExecutionCOntext.
  • Worker: Has a single execution context for easy interaction with WebWorkers.

What can puppeteer do?

  • Generate screen captures and PDF of the page.
  • Grab the SPA and generate pre-rendered content (that is, “SSR”).
  • Automatic form submission, UI testing, keyboard input, etc.
  • Create the latest automated test environment. Run tests directly in the latest version of Chrome using the latest JavaScript and browser features.
  • Capture [timeline tracking] of your site
  • Implement a simple crawler

Why choose puppeteer ?

Framework or library Description cross-browser Auto-generated code The community is active Core principles
PhantomJS [star] 28.5 k A webKit-based headless browser with no UI. No No Yes Headless browser based on WebKit kernel
Nightwatch [STR 10.8 k] Node.js invokes the end-to-end (E2E) testing framework implemented by the Webdriver API. Yes No No webdriver
The Puppeteer [star] 72.2 k Puppeteer is a software provider built on the DevTools protocol (devtools-protocol), controls the high-level API node libraries for Chrome or Chromium. No Yes Yes ChromeDevTools agreement
The Selenium [star] 21.2 k Selenium is a browser automation testing library. Written in multiple languages, Selenium can be used to do any task that requires interaction with a browser. Selenium IDE was officially released from maintenance in 2018. Selenium IDE is Dead. Yes No No webdriver

Nightwatch and Selenium are both webDriver types. Webdriver operates directly on elements of the browser page, and even the browser itself (taking screenshots, window sizes, starting, closing, installing plug-ins, configuring certificates, and so on). Thanks to the use of the browser’s native API, the speed is greatly improved, as long as the browser supports WebDriver can be implemented. PhantomJS, which is a home browser, is all about internal clicking, page-turning, and other human-related operations that require programming.

Puppeteer is built on the ChromeDevTools protocol, which allows tools to examine, debug, and configure Chrome and other Blink-based browsers. Chrome Development tools uses this protocol and the team maintains its API. Tools can be divided into multiple domains including DOM, debugger, network, and so on. Each field defines the many commands it supports and the events it generates. Commands and events are serialized JSON objects with fixed structures.

The key is to automate the generation of test code and online testing by extending Handless Recorder to Google at the same time.

To sum up, Puppeteer has Google dad behind it, star is growing year by year, and the community is active. If you need to develop across browsers, use Nightwatch

How to use puppeteer ?

Install dependencies

Execute command under new Node project:

npm install puppeteer –save

According to the need to introduce

const puppeteer = require(‘puppeteer’)

Basic usage and common functions

The overall use of the artifact is relatively simple, the following begins our use of the road.

Start the Browser

The core function is to call puppeteer.launch() asynchronously and create an instance of Browser according to the corresponding configuration parameters.

; (async() = > {const path = require('path');
    const puppeteer = require('puppeteer');

    const chromiumPath = path.join(__dirname, '.. / '.'chromium/chromium/chrome.exe');
    // Start chrome
    const browser = await puppeteer.launch({
        // Specify the path to the browser
        executablePath: chromiumPath,
        // Whether the browser mode is headless. The default mode is headless
        headless: false}); }) ()Copy the code

To access the page

To access a page, you first create a browser context, then create a new page based on that context, and finally specify the URL to visit.

; (async() = > {const puppeteer = require('puppeteer')
  const browser = await puppeteer.launch({
    // Set header mode (default is true, headless mode)
    headless: false,})// A new page is created in a default browser context
  const page1 = await browser.newPage()

  // Blank page access asks the specified url
  await page1.goto('https://www.baidu.com')

  // Wait for the jump to end
  const navigationPromise = page1.waitForNavigation()
  await navigationPromise

  // Create an anonymous browser context
  const browserContext = await browser.createIncognitoBrowserContext()

  // Create a new page in this context
  const page2 = await browserContext.newPage()
  page2.goto('https://www.baidu.com')
})()
Copy the code

Common operations on the page

  1. page.goto(url, options)
  2. page.goBack(options)
  3. page.goForward(options)
  4. page.reload(options)
  5. browser.createIncognitoBrowserContext()

The device emulation

Often, browsing results for different types of devices are needed, so device simulation can be used. Here is a simulation of the browser results for an iPhone X device

; (async() = > {try {
    const puppeteer = require('puppeteer')
    // Start chrome
    const browser = await puppeteer.launch({
      // Whether the browser mode is headless. The default mode is headless
      headless: false,})const page = await browser.newPage()

    // Device emulation: Emulates an iPhone X
    // user agent
    await page.setUserAgent(
      'the Mozilla / 5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
    )

    // makeLinkJump
    await page.goto('https://www.baidu.com')

    // Viewport simulation
    // await page.setViewport({ width: 1680, height: 841 })

    / / set the cookie
    // await page.setCookie([{ name: 'yhx', birthday: '1998' }])
  } catch (err) {
    console.log(err)
  }
})()

Copy the code

Get DOM node

There are two ways to get a DOM node. One way is to call the native function that comes with the page directly, and the other way is to get it by executing JS code.

The following methods are commonly used:

  1. page.$(selector)
  2. page.$$(selector)

Similar to the document. QuerySelector and document. QuerySelectorAll.

The object they return is <Promise<? ElementHandle>>, which is used to determine whether an element exists

  1. page.$eval(selector, pageFunction[, …args])
  2. page.$$eval(selector, pageFunction[, …args])
  3. page.evaluate(pageFunction[, …args])

On the basis of 1 and 2, 3 and 4 can execute some methods and are more commonly used in practice

; (async() = > {const puppeteer = require('puppeteer')
  // Start chrome
  const browser = await puppeteer.launch({
    // Whether the browser mode is headless. The default mode is headless
    headless: false,})// A new page is created in a default browser context
  const page1 = await browser.newPage()

  await page1.setViewport({ width: 1200.height: 1000 })
  // Blank page to access the specified url
  await page1.goto('https://www.baidu.com')

  // Wait for the title node to appear
  await page1.waitForSelector('.s_btn')

  // Get the node using the page method
  const baiduBtnDomText1 = await page1.$eval('.s_btn'.(el) = > el.value)
  console.log(baiduBtnDomText1) // Do a search
  await page1.waitForTimeout(1000)

  // Use evaluate to insert the js script
  const baiduBtnDomText2 = await page1.evaluate(() = > {
    const baiduBtnDom = document.querySelector('.s_btn')
    baiduBtnDom.value = Zaihui Technology
    return baiduBtnDom.value
  })
  console.log(baiduBtnDomText2) // Do a search
  await page1.waitForTimeout(1000)
})()

Copy the code

Simulating user operations

Page itself provides the original Mouse and Keyboard analog input classes.

  1. page.mouse
  2. page.keyboard

However, it also provides more convenient and fast functions to simulate user actions

  1. Page. Click (selector[, options]) simulates clicking on the selected element
  2. Page. Type (selector, text[, options]) is entered in the selected input box
  3. Page. Hover (selector) simulates mouse movement over the selected element
  4. page.select(selector, … Values) simulates the select option on the selected element
  5. Page. Tap (selector) simulates touch on the selected element

Simulating user actions is often accompanied by delayed waiting

Wait for the operating

The following wait functions are provided

  1. page.waitForTimeout(number)
  2. page.waitForNavigation(options)
  3. page.waitForSelector(selector[, options])
  4. page.waitForXPath(xpath[, options])
  5. page.waitForFunction(pageFunction[, options[, …args]])
  6. page.waitFor(selectorOrFunctionOrTimeout[, options[, …args]])

Listening to the

close frameattached pageerror
console framedetached request
dialog framenavigated requestfailed
domcontentloaded load requestfinished
error metrics response

Take the example of listening for requests and responses

The following is to monitor the request and response of a JS script in Baidu. The request event is to monitor the request, and the response event is to monitor the response.

; (async() = > {// Start chrome
  const puppeteer = require('puppeteer')
  const browser = await puppeteer.launch({
    // Set header mode (default is true, headless mode)
    headless: false,})// A new page is created in a default browser context
  const page1 = await browser.newPage()

  page1.on('request'.(request) = > {
    if (
      request.url() ===
      'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/esl-ef22c5ed31.js'
    ) {
      console.log(request.resourceType())
      console.log(request.method())
      console.log(request.headers())
    }
  })

  page1.on('response'.(response) = > {
    if (
      response.url() ===
      'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/esl-ef22c5ed31.js'
    ) {
      console.log(response.headers())
    }
  })

  // Blank page to access the specified url
  await page1.goto('https://www.baidu.com')
})()

Copy the code

Intercept a request

By default only read-only property request events, are not able to intercept the request, if you want to intercept the request requires through page. SetRequestInterception < Boolean > request interceptor (value) to start, It then uses the Request. abort, request.continue, and request.respond methods to decide what to do next with the request.

; (async() = > {// Start chrome
  const puppeteer = require('puppeteer')
  const browser = await puppeteer.launch({
    // Whether the browser mode is headless. The default mode is headless
    headless: false,})// A new page is created in a default browser context
  const page1 = await browser.newPage()

  // Request interception is enabled
  await page1.setRequestInterception(true) // true to enable, false to disable
  page1.on('request'.(request) = > {
    if (
      request.url() ===
      'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/esl-ef22c5ed31.js'
    ) {
      // Terminate the request
      request.abort()
      console.log('This request has been terminated!! ')}else {
      // Continue the request
      request.continue()
    }
  })

  // The blank page just asks the specified url
  await page1.goto('https://www.baidu.com')
})()
Copy the code

screenshots

A screenshot is a very useful function. You can save a snapshot by capturing it, which is convenient for troubleshooting later. (Note: Screenshot in headless mode)

; (async() = > {// Start chrome
  const puppeteer = require('puppeteer')
  const browser = await puppeteer.launch({
    // Whether the browser mode is headless. The default mode is headless
    headless: false,})const page1 = await browser.newPage()
  await page1.goto('https://www.baidu.com')

  // A new page is created in a default browser context
  // Screenshot operation, use Page. Screenshot function
  // Screenshot of the entire Page :Page. Screenshot function default screenshot of the entire Page, plus the fullPage parameter is a full screen screenshot
  await page1.screenshot({
    path: '.. /imgs/fullScreen.png'.fullPage: true,})// Capture the contents of an area of the screen
  await page1.screenshot({
    path: '.. /imgs/partScreen.jpg'.type: 'jpeg'.quality: 80.clip: {
      x: 0.y: 0.width: 375.height: 300,
    },
  })

  browser.close()
})()
Copy the code

Generating PDF

In addition to preserving snapshots using screenshots, you can also preserve snapshots using PDF.

; (async() = > {// Start chrome
  const puppeteer = require('puppeteer')
  const browser = await puppeteer.launch({
    // Whether the browser mode is headless. The default mode is headless
    headless: true,})const page1 = await browser.newPage()
  await page1.goto('https://www.baidu.com')

  // Generate a PDF file from the Page content, using page.pdf -- note: this can only be called in headless mode
  await page1.pdf({
    path: '.. /pdf/baidu.pdf',
  })

  browser.close()
})()
Copy the code

Google Extension

Handleless Recorder

Handleless Recorder is a Chrome extension that logs your browser interactions and generates Puppeteer scripts. Manual writing E2E tests has always been a difficult problem, and with the help of the Handleless Recorder extension program can greatly help us to write E2E tests. To put it simply, Handleless Recorder helps you record actions performed in Chrome and generate Punppeter code. The implementation of “one operation, test generation” greatly shortens the time for us to write E2E tests, reduces the amount of handwritten test code, and frees up our hands. Take the hassle out of writing E2E tests.

E2E test

End To End (E2E) is an end-to-end sandbox test. Test cases are written To simulate user operations To ensure that components communicate normally and data is transferred as expected during page interaction.

So for a Web application, the user environment is the browser, and the user actions are moving, clicking, and requesting, and those are the parts that we need to simulate, so let’s jump right into the environment and practice.

How to start?

One of the most original test requirements

Expect -> assert results -> cause of failure

; (async() = > {const puppeteer = require('puppeteer')

  // Start chrome
  const browser = await puppeteer.launch({
    // Whether the browser mode is headless. The default mode is headless
    headless: false,})try {
    const page = await browser.newPage()
    await page.goto('https://www.baidu.com')
    await page.waitForTimeout(1500)
    page.goton()
    console.table([`define: test goto baidu.com`.'result: success'])}catch (err) {
    console.table([
      `define: test goto baidu.com`.'result: fail'.`errmsg: The ${String(err)}`,])}await browser.close()
})()

Copy the code

Puppeteer can be used in conjunction with the Jest testing framework

  • Jest: Is a full-featured testing framework developed by Facebook. It requires very little configuration and can be used directly
  • Puppeteer: a Google-created Node.js library that provides a convenient API for controlling Headless Chrome

npm install jest –save

npm install jest-puppeteer –save

Create the jest.config.js configuration file

module.exports = {
  preset: 'jest-puppeteer'.// Call preset so that Jest can be used with Puppeteer
  testPathIgnorePatterns: ['<rootDir>/node_modules/'.'<rootDir>/src/'].testMatch: ['* * /? (*) +(e2e|spec|test).[tj]s? (x)'].// Specify the file to test
}
Copy the code

Create the jest-puppeteer.config.js configuration file

module.exports = { launch: { headless: process.env.HEADLESS ! == 'false', devtools: true, }, }Copy the code

Write a single test using Jest and Puppeteer

const puppeteer = require('puppeteer')
const timeout = 30000
let browser
let page
describe('crm login'.() = > {
  beforeAll(async () => {
    browser = await puppeteer.launch({
      // Set header mode (default is true, headless mode)
      headless: false,
    })
    page = await browser.newPage()
  })
	...

  it('descripe your test'.async() = > {// await page something
  },timeout)
	...

  afterAll(async() = > {await browser.close()
  })
})

Copy the code

The test report

First we install jest-html-report

cnpm install jest-html-report –save-dev

In jest. Config. js, configure the attributes of jest- html-Reporter

Attributes used:

Property Description Type Default
pageTitle The title of the document string “Test Suite”
outputPath The path to where the plugin will output the HTML report string “./test-report.html”
includeFailureMsg If this setting is set to true, this will output the detailed failure message for each failed test. boolean false

For other attributes, see the official documentation: github.com/Hargne/jest…

! [image-20210721145220237](/Users/apple/Library/Application Support/typora-user-images/image-20210721145220237.png)

Juejin. Cn/post / 696937…

Juejin. Cn/post / 684490…

Juejin. Cn/post / 685041…

Copyfuture.com/blogs-detai…