์น ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ 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();
});
์ด๋ ๊ฒ ์๋๋ฆฌ์ค ํ
์คํธ๋ฅผ ํ ์ ์๋ค.
์คํ
headless ์คํ
๋ธ๋ผ์ฐ์ ์๋จ๊ณ ํจ์ฌ ๋น ๋ฅด๊ฒ ๊ฒ์ฌ๋จ.
playwright.config.ts์ headless: !!process.env.CI,
ํ๊ฒฝ ๋ณ์ process.env.CI ์ด๊ฑฐ๋
์๋์ฒ๋ผ ์คํํ ๋ ์ง์ ์จ์ฃผ๋ฉด ๋๋ค.
CI=true npx playwright test
.gitignore ํ์ผ์ ์๋ฌ ์ํฉ ์คํฌ๋ฆฐ์ท ๋ด๊ธฐ๋ test-results ๋๋ ํฐ๋ฆฌ ์ถ๊ฐ
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๋ฅผ ํ๋๊ฒ ์ข๋ค.