มาลองทำ UI/Regression Test ด้วย Jest และ Puppeteer กันเถอะ

สมัยนี้เวลาเราทำเว็บหรือเว็บแอพพลิเคชันแต่ละทีนั้น มักจะหลีกหนีไม่พ้นในการที่จะต้องทำการเปลี่ยนแปลงตาม Change Requirement หรือ เจอปัญหาต่างๆ หรือแม้แต่อยากตรวจสอบ functional ของเว็บว่ายังทำงานได้ตามปกติหรือไม่ ในสมัยโบราณเรามักจะเทส manual exploratory กัน แต่สมัยนี้ใครทำแบบนั้นอยู่คงต้องปรับตัวให้เข้ากับยุคสมัยแห่ง automate มาๆ มา automate test กัน

ปกติแล้วใครที่เคยทำ selenium มาก่อนก็คงจะคุ้นเคยกับ selenium grid/web driver เป็นอย่างดีวันนี้เราจะมาเล่นของใหม่กันหน่อย เราจะเขียน test ด้วย jest และ puppeteer กัน

Puppeteer เป็นเครื่องมือจาก google สำหรับ drive chrome/chromium ให้ทำงาน automate ด้วย Node JS โดยคุณสมบัติหลักๆของ Puppeteer ก็จะประมาณนี้

  • เรียกดูหน้า Website ได้ทั้งแบบปกติ และ Single Page Application (รวมถึง Server Side Rendering)
  • บันทึก screenshots ของเว็บที่กำลังเทส
  • สั่งให้ทำ interaction กับ UI ได้
  • บันทึกผล timeline trace เพื่อใช้ในการวิเคราะห์ปัญหา

Jest เป็น Test Framework จาก Facebook สำหรับทดสอบ JavaScript code ซึ่งหน้าตาของเทสก็ใกล้เคียงกันกับ Mocha มากๆเลย ทำไมถึงใช้ Jest เหรอครับ เพราะปกติใช้ Mocha แล้วอยากเปลี่ยนมาใช้ Jest ไงล่ะ :P

ก่อนอื่นเราก็มากำหนดโครงสร้างของ Test กันก่อน เพื่อให้เราสามารถเขียนเทสต์ได้ง่าย และเป็น Modular ตามประสา SE ที่น่ารัก สำหรับวันนี้เราจะเทสฟังก์ชัน Register ของเว็บไซต์กัน เราจะกำหนด project structure หน้าตาประมาณนี้

- package.json
- prepare.js
- setup.js
- jest.config.js
- test
  |- config.register.js
  |- register.test.js
  |- register.fn.js

มาเริ่มทีละ step นะครับ อันดับแรกสุด เราจะเตรียม folder สำหรับเก็บ screenshot ถ้าหากว่าไม่มีเราจะได้ไม่ error

#prepare.js
const fs = require('fs');
const path = require('path');
const screenshotDir = path.resolve(__dirname, 'screenshots');

if (!fs.existsSync(screenshotDir)) {
    fs.mkdirSync(screenshotDir);
}

หลังจากนั้นเราก็กำหนดให้รัน prepare.js ใน pretest hook และกำหนดให้รัน jest ใน test ตามนี้ใน package.json

{
  "name": "JestPuppeteerUITest",
  "version": "1.0.0",
  "description": "UI Testcase on register account",
  "main": "index.js",
  "scripts": {
    "pretest": "node prepare.js",
    "test": "node node_modules/jest/bin/jest.js --maxWorkers=1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "jest": "^22.1.1",
    "path": "^0.12.7",
    "puppeteer": "^1.0.0"
  }
}

จากนั้นเราก็มาดูตัวเทสกัน เราแบ่งออกเป็น 3 ส่วนด้วยกันคือ

- test
  |- config.register.js // configuration สำหรับ test
  |- register.test.js   // jest test case runner
  |- register.fn.js     // ตัวทดสอบที่เขียนด้วย puppeteer

มาดูที่ตัวเทสกันก่อนนะครับ เราเขียน puppeteer เพื่อทำการทดสอบว่ามันทำการลงทะเบียนผู้ใช้งานได้จนครบลูป แต่ผมไม่ได้เขียนจนครบสมบูรณ์มากนะครับ เอาแค่ใช้งานได้ก่อน

const puppeteer = require('puppeteer');
const path = require('path');

module.exports = async(config) => {

    const browser = await puppeteer.launch({
        headless: false
    });

    const page = await browser.newPage();

    await page.setViewport({
        width: 1280,
        height: 800
    });

    // open test target
    await page.goto(config.url);

    // fill out the form
    await page.click(config.ids.male);
    await page.type(config.ids.email, config.data.email(config.data.id))
    await page.type(config.ids.name, config.data.name(config.data.id));
    await page.type(config.ids.password, config.data.password);
    await page.type(config.ids.confirmPassword, config.data.password);
    
    // save screenshot
    await page.screenshot({
        path: path.resolve("screenshots") + '/register_result-' + config.data.screenshot(config.data.id) + '.png'
    });

    // submit the form
    await page.$eval(config.ids.form, form => form.submit());

    // You can add evaluation/test logic here

    await browser.close();

};

สำหรับตัว configuration เราก็กำหนดเป็น parameter ตามที่ตัว business test case ใน puppeteer ต้องการ

module.exports = {
    timeout: 20000,
    url: "https://www.xxxxxx.co.th/customer/account/create/",
    ids: {
        form: "#form-account-create",
        male: "#RegistrationForm_gender_0",
        email: "#RegistrationForm_email",
        name: "#RegistrationForm_first_name",
        password: "#RegistrationForm_password",
        confirmPassword: "#RegistrationForm_password2",
        submit: "#send"
    },
    data: {
        id: 0,
        email: (id) => "youshouldverifyemail" + id + "@gmail.com",
        screenshot: (id) => "youshouldverifyemail" + id,
        name: (id) => "Jack Ma" + id,
        password: "JackMa123"
    }
};

และสุดท้ายก็เป็นตัว test runner ของเราที่จะ execute test นี้ขึ้นมา


const register = require('./register.fn.js')
let config = require('./config.register.js');

describe('Register', () => {
    jest.setTimeout(config.timeout);

    it('should register user correctly', () => {
        config.data.id = 23;
        return register(config);
    });

});

และเราสามารถ run script ได้ ด้วยคำสั่ง yarn test หรือ npm test ได้ตามปกติสำหรับ code สามารถเข้าไปดูตัวเต็มๆได้ที่ GitHub Repositoryนี้ได้เลยนะครับ https://github.com/mahasak/JestPuppeteerUITest

Happy Coding, Cheers !!!!