4. Playwright

์‹ค์Šต ๋ ˆํฌ

์›น ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ E2E ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋„๊ตฌ.

Headless Chrome์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ Puppeteer๋ฅผ ๊ณ„์Šนํ•˜๋ฉด์„œ, ๋” ๋งŽ์€ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง€์›ํ•œ๋‹ค.

E2E test๋ž€

end to end ์ข…๋‹จ ๊ฐ„ ํ…Œ์ŠคํŠธ๋กœ, ํ”„๋กœ๋•ํŠธ๋ฅผ ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ GUI๋ฅผ ํ†ตํ•ด ์‹œ๋‚˜๋ฆฌ์˜ค, ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ๋“ฑ์„ ์ˆ˜ํ–‰.

Endpoint ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ๊ธฐ๋Šฅ์ด ์ž˜ ์ž‘๋™ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด E2E Test๋งŒ์ด๋ผ๋„ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

Headless Chrome

"์ฐฝ์ด ์—†๋‹ค."

๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ด์šฉํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฐฝ์ด ๋œจ๊ณ  html, css๋ฅผ ๋ถˆ๋Ÿฌ์™€ ํ™”๋ฉด์— ๊ทธ๋ฆฐ๋‹ค.

ํ•˜์ง€๋งŒ ์ด์™€๊ฐ™์€ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์šด์˜์ฒด์ œ์— ๋”ฐ๋ผ ํฌ๋กฌ์ด ์‹คํ–‰ ๋ ์ˆ˜๋„, ๋˜์ง€ ์•Š์„์ˆ˜๋„ ์žˆ๋‹ค.

์˜ˆ๋ฅผ๋“ค์–ด ์šฐ๋ถ„ํˆฌ ์„œ๋ฒ„๋Š” 'ํ™”๋ฉด'์ž์ฒด๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ํฌ๋กฌ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด ๋ฐ”๋กœ Headless ๋ชจ๋“œ์ด๋‹ค.

๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ์‹ค์ œ๋กœ ์šด์˜์ฒด์ œ์˜ ์ฐฝ์œผ๋กœ ๋„์šฐ์ง€ ์•Š๊ณ  ํ™”๋ฉด์„ ๊ทธ๋ ค์ฃผ๋Š” ์ž‘์—…์„ ๊ฐ€์ƒ์œผ๋กœ ์ง„ํ–‰,

์‹ค์ œ ๋ธŒ๋ผ์šฐ์ €์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ ์ฐฝ์€ ๋œจ์ง€ ์•Š๋Š” ๋ฐฉ์‹์ด๋‹ค.

์„ค์น˜

npm i -D @playwright/test eslint-plugin-playwright

playwright.config.ts ํŒŒ์ผ

import { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
 testDir: './tests',
 retries: 0,
 use: {
  baseURL: 'http://localhost:8080',
  headless: !!process.env.CI,
  screenshot: 'only-on-failure',
 },
};

export default config;

tests/.eslintrc.js ํŒŒ์ผ

module.exports = {
 env: {
  jest: false,
 },
 extends: ['plugin:playwright/playwright-test'],
 rules: {
  'import/no-extraneous-dependencies': 'off',
 },
};

test/home.spec.ts ์ƒ์„ฑ

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

test('show all products', async ({ page }) => {
  await page.goto('/');

  await expect(page.getByText('ํ‘ธ๋“œ์ฝ”ํŠธ')).toBeVisible();
  await expect(page.getByText('์ž์žฅ๋ฉด')).toBeHidden();
});

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด chromium์ด ์—†๋‹ค๊ณ ํ•˜๋ฉด์„œ ํ†ต๊ณผ ์•ˆ๋จ.

ํฌ๋กฌ์„ ์“ฐ๊ธฐ ์œ„ํ•ด ์„ค์ •ํ•ด์ค€๋‹ค.

//playwright.config.ts
const config: PlaywrightTestConfig = {
  testDir: './tests',
  retries: 0,
  use: {
    baseURL: 'http://localhost:8080',
    headless: !!process.env.CI,
    screenshot: 'only-on-failure',
    channel: 'chrome', // ์ถ”๊ฐ€
  },
};

export default config;

์„œ๋ฒ„๋„ ์ผœ๊ณ  ํ”„๋ก ํŠธ๋„ ์ผœ๊ณ  ํ•ด์•ผ๋จ.

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

test('show all products', async ({ page }) => {
  await page.goto('/');

  await expect(page.getByText('ํ‘ธ๋“œ์ฝ”ํŠธ')).toBeVisible();
});

test('Filter products', async ({ page }) => {
  await page.goto('/');

  await expect(page.getByText('ํ‘ธ๋“œ์ฝ”ํŠธ')).toBeVisible();

  const searchInput = page.getByLabel('๊ฒ€์ƒ‰');

  await searchInput.fill('๋ฉ”');

  await expect(page.getByText('๋ฉ”๊ฐ€๋ฐ˜์ ')).toBeVisible();

  await searchInput.fill('๋ฉ”๋กฑ');

  await expect(page.getByText('๋ฉ”๊ฐ€๋ฐ˜์ ')).toBeHidden();

  await searchInput.fill('๋ฉ”');

  await expect(page.getByText('๋ฉ”๊ฐ€๋ฐ˜์ ')).toBeVisible();
});

์ด๋ ‡๊ฒŒ ์‹œ๋‚˜๋ฆฌ์˜ค ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹คํ–‰

npx playwright test

headless ์‹คํ–‰

๋ธŒ๋ผ์šฐ์ € ์•ˆ๋œจ๊ณ  ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ๊ฒ€์‚ฌ๋จ.

playwright.config.ts์— headless: !!process.env.CI,

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ process.env.CI ์ด๊ฑฐ๋Š”

์•„๋ž˜์ฒ˜๋Ÿผ ์‹คํ–‰ํ•  ๋•Œ ์ง์ ‘ ์จ์ฃผ๋ฉด ๋œ๋‹ค.

CI=true npx playwright test

.gitignore ํŒŒ์ผ์— ์—๋Ÿฌ ์ƒํ™ฉ ์Šคํฌ๋ฆฐ์ƒท ๋‹ด๊ธฐ๋Š” test-results ๋””๋ ‰ํ„ฐ๋ฆฌ ์ถ”๊ฐ€

/test-results/

icrease button ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ

test('Click the "increase" button', async({page}) => {
  await page.goto('/')

  const count = 130

  await Promise.all((
    [...Array(count)].map( async ()=> {
      await page.getByText('increase').click()
    })
  ))
})

  await expect(page.getByText(`${count}`)).toBeVisible()

codeceptjs

playwright ๋ณด๋‹ค ์ข€ ๋” ๊ฐ„๋‹จํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์žˆ๋‹ค. -> codeceptjs

์ฝ”๋“œ๊ฐ€ ๋” ์ธ๊ฐ„ ์นœํ™”์ ์ด๋‹ค.

Feature('๊ณผ์ œ ํ…Œ์ŠคํŠธ');

Scenario('๋ฉ”๋‰ดํŒ ํ•„ํ„ฐ๋ง', ({ I }) => {
  I.amOnPage('/');

  I.see('ํ‘ธ๋“œ์ฝ”ํŠธ ํ‚ค์˜ค์Šคํฌ');

  I.see('๋ฉ”๊ฐ€๋ฐ˜์ ');
  I.see('๋ฉ”๋ฆฌ๊น€๋ฐฅ');
  I.see('ํ˜น๋“ฑ๊ณ ๋ž˜์นด๋ ˆ');

  I.click('์ค‘์‹');
  I.see('์งœ์žฅ๋ฉด');
  I.dontSee('๊น€๋ฐฅ');
});

๊ธฐํš์ž์™€์˜ ์†Œํ†ต์—์„œ๋„ ํ™œ์šฉํ•˜๋ฉด ์ข‹๊ฒ ๋‹ค.

์š”์ 

  • ๋กœ์ง์„ ๊ฐ€๋Šฅํ•œ ๋งŽ์ด ๋ถ„๋ฆฌํ•œ๋‹ค.

  • ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ๋ฅผ ํ†ตํ•ด ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋‚˜๋ˆˆ๋‹ค.

  • ๊ณต์šฉ์ธ ๋ถ€๋ถ„์„ ์ข€ ๋” ๋นก๋นกํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

  • ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์™ธ์— ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ •๋ง๋กœ ๋‚˜์˜ค๋Š”์ง€ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ E2E test๋ฅผ ํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.

Last updated