background

  • Story Review What to do when someone says you have a performance problem
  • Last time, the product complained that my page loading was too slow, so I optimized a wave, but I also encountered a lot of bad things in the optimization
  • test: how do you fool around in the test environment every day, you see the page several times reported error loading can not come out, you can self-test ah, no wonder the product said you areGood closed employees
  • I: Humble front end, sprayed every day!

So I’m not satisfied, no, MY retort, let’s take a look at the test feedback question:

  1. When the page value is loaded asynchronously, the language package is not loaded. As a result, multi-language keys are displayed on the page

foreplay

If you want to solve a problem, you have to go deep into it. Let’s call to analyze the causes of the problem:

  1. Our original multi-language loading and the initialization of VUE were serial, and vUE was initialized only after the multi-language loading was completed, so the normal display of multi-language page could be guaranteed. However, in order to improve the rendering of the first screen, multilingual and VUE initialization are parallel. We know that asynchronous loading must be slower than vUE initialization, so we should not optimize for the sake of optimization, considering the actual scene.
  2. After optimization, comprehensive coverage test is not carried out, resulting in protocol request without adding special header request header request error.
  3. Is also not a full range of coverage test, some functions are not special processing, error reporting.
  4. The verification code fails to be sent, and no error message is displayed. The test was so confusing that I didn’t know what was wrong.

conclusion

In addition to the first problem is to load, other question boils down to is, we don’t have a good automated test system, to cover our all function points on a page, when we changed the main underlying logic, run over the automated tests, an error page is recorded, and then one by one than trying to repair, so it can avoid a lot of unnecessary trouble.

How to implement

When it comes to automated testing on the Web, many people are familiar with Selenium 2.0 (Selenium WebDriver), which supports multiple platforms, languages, and browsers (driven by various browser drivers) and provides a rich API interface. With the development of front-end technology, Selenium 2.0 gradually presents the disadvantages of complex environment installation, unfriendly API calls, and low performance. Puppeteer, a new generation of automated testing tools, is simpler to install, performs better, and is more efficient than the Selenium WebDriver environment. It also provides a simpler API for executing Javascript in the browser, as well as web interception.

Most of the things you can do manually in a browser can be done with Puppeteer! Here are a few examples to start with:

  • Generate screenshots and PDF of the page.
  • Grab the SPA and generate the pre-rendered content (that is, “SSR”).
  • Grab the content you need from the website.
  • Automatic form submission, UI testing, keyboard input, etc
  • Create an up-to-date automated test environment. Run tests directly in the latest version of Chrome, using the latest JavaScript and browser features.
  • Capture a timeline trace of your site to help diagnose performance issues.

Puppeteer provides a way to launch Chromium instances. When Puppeteer is connected to a Chromium instance, a Browser object is created using puppeteer.launch or puppeteer.connect, and a Page instance is created using Browser. Navigate to a Url, then save the screenshot. A Browser instance can have multiple Page instances. The following is a typical example of Puppeteer automation:

const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'screenshot.png'});
  await browser.close();
});
Copy the code

To sum up, we choose the automated test tool based on Puppeteer to develop the project. Through a series of apis provided by Puppeteer, the process of accessing the target page, simulating abnormal scenarios and generating screenshots is automated. Finally, through manual comparison of screenshots, judge whether the page degradation process is in line with expectations, user experience is friendly.

Implementation scheme

Automated process

  1. Simulate user access page operation;
  2. Intercept network request, request error, generate error information screenshots;
  3. Key position screenshots;
  4. Simulate abnormal scenarios and take screenshots to see if the page meets expectations;
  5. Modify the UA of the page and take screenshots to see whether the page meets expectations at different ends.
  6. Mock requests to modify returned data and take screenshots to see if the page is as expected;

Stop it. Start fighting

After the project is initialized with NPM init, Puppeteer dependencies can be installed: NPM I Puppeteer: The latest version of Chromium is automatically downloaded during installation.

const puppeteer = require('puppeteer');
(async() = > {// Create a browser instance
      const browser = await puppeteer.launch({
        headless: false
      });
      // Create a table page
      const page = await browser.newPage();
      // Access a page
      await page.goto('https://example.com');
      // Generate a screenshot of the page
      await page.screenshot({path: 'screenshot.png'});
      // Access the browserbrowser.close(); }) ();Copy the code

Thus we completed a simple test process.

Common Data configuration

In order to improve the maintainability and extensibility of the test script, we configured the test case information into JSON files, so that when writing the test script, we only need to focus on the implementation of the test flow. Test case JSON data configuration includes public data (global) and private data:

  • Public Data (Global): Data required by each test case, such as the address, name, description, and device type of the target page to simulate access.
  • Private data: Data specific to each test case, such as test module information, API address, test scenario, expected result, etc.
// Return to the UA of QQ
import { QQ } from 'util'

{
  "global": {
    "url": "https://xxx.com"."pageName": "index"."pageDesc": "Home page"."device": "iPhone 7"."name": "Zhang"."idNum": "4115021968XXXXXXXXXX"."phone": "13412344321"."email": "[email protected]"."UA": QQ,
    ....
    // Enterprise information, etc
  },
  "homePageApi": {
    "moduleDesc": "Home page main interface"."apiBase": "https://xxx/v2/home/".// Business logic
    "type": "Authentication Mode".// Page configuration
    "config": "Authentication Mode",},... }Copy the code

Intercepts interface requests and modifies data screenshots

async test () => {
  ... // Create a Page instance and visit the home Page
  const browser = await puppeteer.launch({
    headless: false
  });
  // Create a table page
  const page = await browser.newPage();
  // Access a page
  await page.goto(global.url);
  // Set request blocking
  await page.setRequestInterception(true) 
  // Listen for the request event, which is triggered when the request is made
  page.on("request", interceptionEvent)
 
  ...
}
Copy the code

If the test case needs to intercept different requests or simulate multiple scenarios, you need to set up multiple request listening events. After an event is executed, you must remove the event listener before continuing the next event listener.

Add event listener: page. On (“request”, eventFunction)

Remove event listening: page.off(“request”, eventFunction)

    // Set request blocking
    await page.setRequestInterception(true)
    const interceptionPhone = requestInterception("phone")
    // Add event 1 listener
    page.on("request", interceptionPhone)
    await page.goto(url)
    await page.screenshot({
      path: './result/phone.png'
    })
    // Remove event 1 listener
    page.off("request", interceptionPhone)
    const interceptionEmail = requestInterception("email")
    // Add event 2 listener
    page.on("request", interceptionEmail)
    await page.goto(url)
    await page.screenshot({
      path: './result/email.png'
    })
    // Remove event 2 listener
    page.off("request", interceptionEmail)
Copy the code

Simulate abnormal data scenarios to generate mock data.

// We can mock any data we want according to different types
function requestInterception (type) {
  let mockData
  switch (type) {
      // Modify the return status code
    case "phone":
      if(!global.isreally){mockData = {wrong user data}}break
    case "email": // Modify the returned content type
      mockData = {
        contentType: setValue
      }
      break.default:
      break
  }
  return async req => {
   // If it is an API that needs to be intercepted, modify the return data via req.respond(mockData), otherwise continue to request something else
      return req.respond(mockData) // Modify the returned data}}Copy the code

Analog interface returns 500:

Const interception500 = requestInterception("500") page.on("request", interception500) // This is an interception that will be triggered when the request is receivedCopy the code

Simulated abnormal data:

 const iconInterception = requestInterception("error")
 page.on("request", iconInterception)
Copy the code

There are two implementations of mock data generation, depending on the situation:

  • Mock data is generated by modifying the actual data returned by the interface directly, requiring the real-time data returned by the interface first
  • Store a complete copy of the interface data locally and generate mock data by modifying the way the data is stored locally (the examples in this article are implemented based on this scheme)

If you choose the first solution, you need to intercept the interface request, obtain the real-time return data of the interface through req.response(), and modify the real-time return data according to the test scenario as mock data.

Before simulating the refresh button click, wait for the button rendering to complete and then trigger the button click. (To prevent errors due to DOM not being found when DOM rendering is not completed after page refresh)

Cancel the interception and restore the network

await page.setRequestInterception(false)
Copy the code

Set different uAs

async test () => {
  ... // Create a Page instance and visit the home Page
  / / set the UA
  await page.setUserAgent(global.UA)
  // Listen for the request event, which is triggered when the request is made
  await page.screenshot({
    path: `./result/The ${global.UA}.png`})... }Copy the code

Simulating user operations

The user clicks on a button

await page.click('.main-content .phoneBtn')
Copy the code

Focus on the user input box to simulate user input

const input = await page.$('#code-content .el-form-item__content .el-input__inner')
await input.tap()
await input.type('123456', {
    delay: 200,})Copy the code

User scrolling

await page.evaluate((top) = > { window.scrollTo(0, top) }, top)
Copy the code

Page. The evaluate (pageFunction,… Args) : Executes JavaScript code in the context of the current page instance, which is the JS code that calls the current page

An error occurs on the listening page

page.on('error'.(e) = > { 
  await page.screenshot({
    path: `./result/${e.error}.png`})})Copy the code

Of course Puppteer provides much more than that, we can find the answer here zhaoqize. Making. IO/puppeteer – a…

Plug-in form

Each page function of the whole project is independent. We can separate the page function and maintain it separately to form a plug-in system. When a page is modified, only a corresponding plug-in can be modified.

// Queue function is mainly some logical judgment and business processing core code in a word
function runPromiseByQueue(myPromises) {
  myPromises.reduce(
    (previousPromise, nextPromise) = > previousPromise.then(() = > nextPromise()),
    Promise.resolve()
  );
}
// Each plug-in returns a promise,
function run(argv) {
  return new Promise((resolve, reject) = > {
    puppeteer.launch().then(async browser => {
      ......
    });
  })
 }

queue([
  phone,
  face,
  bank,
])
Copy the code

The results

Run results of some pagesAccordingly, we have generated a total of 3 error result pages:

Feedback from the test

Hahahahahahaha ~~~~~!! Still a good front end but a manufacturer.