ลอง Playwright เทสแอพ React + Vite แบบง่ายๆ

Playwright Apr 12, 2023

หลังจากวันก่อน ได้ลองใช้งาน Playwright ไป วันนี้ก็เลยลองเอาตัว Playwright มาลองทำ testing ง่ายๆ ด้วยการจำลองเว็บ โดยใช้ default เว็บ ของ React + Vite (เว็บที่เป็น counter)

ลองใช้งาน Playwright ในการทำ e2e testing
สวัสดีครับ บทความนี้มาลองเล่น Playwright เพื่อเอามาทำ automate e2e testing ครับ Playwright เป็น e2e testing framework (end to end) ที่พัฒนาโดย Microsoft ตัว API มีความคล้ายกับ Puppeteer (แน่นอนแหละ main contributor ของ puppeteer ย้ายมา playwright) Fast and reliable end-to-end testing

สร้างโปรเจ็คไว้เทส

เริ่มแรก ผมสร้างเว็บ เพื่อจะเอาไว้เทสก่อน ซึ่งใช้ starter ของ React + Vite เลย

npm create vite@latest my-react-app -- --template react-ts

จากนั้น ก็ install

cd my-react-app
npm install

ผมมีการเพิ่ม Input นิดหน่อย เพื่อให้เวลา Input และกด Set Title ให้มันไปเปลี่ยนค่า React + Vite เป็นค่าที่เราใส่ ไฟล์ src/App.tsx ผมปรับแต่งนิดหน่อย แบบนี้

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
  const [count, setCount] = useState(0)
  const [title, setTitle] = useState('Vite + React')

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const formData = new FormData(e.currentTarget)
    setTitle(formData.get('title') as string)
  }

  return (
    <div className="App">
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://reactjs.org" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>{title}</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>

      <form onSubmit={handleSubmit}>
        <input type="text" name="title" className="input-title" />
        <button type="submit">Set Title</button>
      </form>
    </div>
  )
}

export default App

เมื่อลองรัน dev server จะเป็น url http://localhost:5173 และหน้าตาก็ประมาณนี้

ซึ่งถ้าเราดูตัวเว็บ React สิ่งที่มีคือ

  • มี document title ชื่อ Vite + React + TS
  • มีปุ่ม count ( count is 0 เริ่มต้น) เมื่อเรากด ค่ามันก็จะเพิ่มทีละ 1
  • มี input เพื่อ set ค่า title ( h1)

ทีนี้ ส่วนของเทส ผมก็จะตั้งเทสเคส ไว้ 3 ตัว คร่าวๆ คือ

  1. เทสว่า เว็บมี document title หรือไม่
  2. เทสว่า เมื่อกด count ค่าจะเพิ่มตามจำนวนคลิ๊กมั้ย
  3. เทสว่า เมื่อพิมพ์ input และกด Set Title ค่า heading 1 จะเปลี่ยนมั้ย

สร้างโปรเจ็ค Playwright

npm init playwright@latest

เช็ค document title

import { test, expect } from '@playwright/test'

const BASE_URL = 'http://localhost:5173/'

test('has document title', async ({ page }) => {
  await page.goto(BASE_URL)
  await expect(page).toHaveTitle('Vite + React + TS')

  await page.screenshot({ path: `screenshots/example-has-title.png` })
})

เทสนี้ไม่มีอะไรมาก

  1. สั่งให้เปิดเว็บด้วย page.goto()
  2. เช็คว่า page มี document title มั้ย ด้วย .toHaveTitle()
  3. ลอง screenshot ถ่ายรูปไว้ด้วย ด้วยคำสั่ง page.screenshot

เทส click count button

test('click count button', async ({ page }) => {
  await page.goto(BASE_URL)

  const counter = page.locator('.card button')
  counter.click()

  await expect(counter).toHaveText('count is 1')
})
  1. ก็เปิดเว็บ React ด้วย goto() เหมือนเดิม
  2. จากนั้น ใช้ page.locator เพื่อหา element ด้วย selector .card button แบบเดียวกับ css / js
  3. เมื่อได้ element ก็ทำการกด click 1 ที  element.click()
  4. สุดท้าย expect ว่า element ต้องมี text เป็น count is 1

เรื่องของ locator เราสามารถใช้ ได้หลายวิธีเช่น หา button ในเพจนี้

  • page.locator('.card button')
  • page.getByRole('button')  - มีโอกาสได้ หลาย elements
  • page.getByText('count is 0') - ใช้ได้เฉพาะเริ่มต้นครั้งแรก

ส่วนตัวผมชอบใช้ selector มากกว่า เพราะถ้าเคสนี้ใช้ getByRole() หรือ getByText() เวลา expect ก็จะหา element นั้นไม่เจอ เพราะค่า มันเปลี่ยนแล้ว

const counter = await page.getByRole('button', { name: 'count is 0' })
counter.click()

 // ✅
 await expect(page.locator('.card button')).toHaveText('count is 1')
 
 // ❌
 await expect(counter).toHaveText('count is 1')
Locators | Playwright
[Locator]s are the central piece of Playwright’s auto-waiting and retry-ability. In a nutshell, locators represent a way to find element(s) on the page at any moment.
อ่าน Locators เพิ่มเติม

เทส set heading title

test('set heading title', async ({ page }) => {
  await page.goto(BASE_URL)

  const input = page.locator('input')
  await input.fill('Hello World')

  const button = page.locator('button[type=submit]')
  button.click()

  await expect(page.locator('h1')).toHaveText('Hello World')

  await page.screenshot({ path: `screenshots/example-set-title.png` })
})

เทสสุดท้าย เริ่มจาก

  1. page.goto เพื่อเปิดเว็บ
  2. หา element ด้วย page.locator เนื่องจากเว็บมี input ที่เดียว
  3. ทำการกรอกค่าด้วย  input.fill()
  4. หา element button ที่เป็น type=submit จากนั้นกด click
  5. expect หา h1 ต้องมีค่า Hello World
  6. สุดท้าย screenshots ไว้ซักหน่อย

ลองรันเทส playwright ดูผลลัพธ์

npx playwright test


Running 9 tests using 5 workers
  9 passed (4.7s)

To open last HTML report run:

  npx playwright show-report

สุดท้าย ไฟล์ test ของผม react-vite.spec.ts เผื่ออยากลองเอาไปรัน

import { test, expect } from '@playwright/test'

const BASE_URL = 'http://localhost:5173/'

test.describe('React Vite', () => {
  test('has document title', async ({ page }) => {
    await page.goto(BASE_URL)
    await expect(page).toHaveTitle('Vite + React + TS')

    await page.screenshot({ path: `screenshots/example-has-title.png` })
  })

  test('click count button', async ({ page }) => {
    await page.goto(BASE_URL)

    // const counter = page.locator('.card button')
    const counter = await page.getByText('count is 0')
    counter.click()

    await expect(page.locator('.card button')).toHaveText('count is 1')
  })

  test('set heading title', async ({ page }) => {
    await page.goto(BASE_URL)

    const input = page.locator('input')
    await input.fill('Hello World')

    const button = page.locator('button[type=submit]')
    button.click()

    await expect(page.locator('h1')).toHaveText('Hello World')

    await page.screenshot({ path: `screenshots/example-set-title.png` })
  })
})

สรุป

วันนี้ก็ลองเทส Playwright ด้วยการใช้ Selector ง่ายๆ มีการ fill input และก็ take screenshot ไป หวังว่าบทความนี้ะเป็นไอเดีย และแนวทางให้ใครหลายๆ คนที่ลองใช้ Playwright ลองไปเล่นกันดูนะครับ

Happy Coding ❤️

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com