์ฌ์ฉ์๋ฅผ ์ํ Playwright E2E ํ ์คํธ
- #Test
- #E2E
- #์ ๊ทผ์ฑ
- #Playwright
์๋ ํ์ธ์. ํ๋กฌ ์คํ ์ด ํ์์ ํ ์คํธ์ ์ ์์ผ์ ์ ๋ํ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ๊นํํฌ์ ๋๋ค.
์ด๋๋ง ์ ๊ฐ ํ์ ๋ค์ด์จ์ง 4๊ฐ์์ด ์ง๋ฌ์ต๋๋ค. ์ ๊ธฐ๋ฅ์ ๋ง๋ค ๋์ ์๋ํ ํ ์คํธ๋ ๊ฑฐ์ ํ์๊ฐ ๋์์ต๋๋ค. ๋ฐฐํฌ ์ QA์์ ๋์ค๋ ์๋ฌ๋ ์ค์ด๋ค์๊ณ , ๋จ์ ์๋ (๋ชจ๋ฐ์ผ ์ฌํ๋ฆฌ ๊ฐ์) ์ฃ์ง ์ผ์ด์ค๋ค๋ ํจํด์ ์ฐพ์ ์๋ฐฉํ๋ ค ๋ ธ๋ ฅ ์ค ์ ๋๋ค.
์ค๋์ playwright๋ฅผ ์ด์ฉํด ์ค๋ฌด์์ e2e ํ ์คํธ๋ฅผ ๋ง๋ค์ด๊ฐ๋ ๊ณผ์ ์ ์๊ฐํด๋ณด๋ ค ํฉ๋๋ค. ์ ํฌ ํ์ e2e ํ ์คํธ๋ฅผ ๋์ ํ๋ฉด์ ๊ฒช์ ์ํ์ฐฉ์ค๋ฅผ ํ ๋๋ก, ์ฒ์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ถ๋ค์ด ์์ฃผ ํ๋ ์ค์๋ค๋ ๋ค๋ฃน๋๋ค.
์๋ e2e ํ ์คํธ๋ฅผ ์๋ํํ๊ธฐ
๋ฌผ๋ก ์๋ํ๋ถํฐ ์์ํ๋ ๊ฑด ์ข์ ์ผ์ด ์๋๋๋ค. ๋ณดํต ํ ์คํ ๋๊ตฌ๋ ๋๋ฆ์ ์ฌ์ฉ๋ฒ์ด๋ ์์์ ์ธ ๋์๋ค์ด ์์ด์ ์ฒ์์๋ ์ข์ ํ๊ธฐ ์ฝ์ต๋๋ค. ๋ถ๋ช ์์ผ๋ก ํ๋ฉด ๋๋๋ฐ, ์๋ํ ํ ์คํธ๋ ๋ญ๊ฐ ๊ผฌ์ฌ์ ์ ๋๋ ๋ถ์พํ ๊ฒฝํ์ ํ๋ค๋ณด๋ฉด ํ ์คํธ๋ฅผ ํฌ๊ธฐํ๊ธฐ ์ฝ์ต๋๋ค.
๊ทธ๋์ ์๋ ํ ์คํธ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ํ๋ ๊ฒ๋ถํฐ ์์ํ์๊ธธ ๊ถํฉ๋๋ค. ํ ์คํธ์์ (๋ ์, ๋ ์์ฃผ) ๋ฐฐ์ฐ๊ธฐ์์๋ ์ด์ผ๊ธฐํ ๊ฒ์ฒ๋ผ, ๊ธฐํ์๋ฅผ ๊ฒํ ํ ๋๋ถํฐ ์ผ์ด์ค๋ฅผ ๋ค์ํ๊ฒ ์๊ฐํด๋ณด๊ณ ์์ฑํ๋ ๊ฒ ์ค์ํฉ๋๋ค. ํนํ ์ฃผ๋จน๊ตฌ๊ตฌ์ ํ ์คํธ์์ ์ฒด๊ณ์ ์ธ ํ์์ ํ ์คํธ๋ฅผ ํ ์ ์๋๋ก ์๋ฌ์ ํ ์คํธ ์ผ์ด์ค์ ๋ํ ์ดํด๋ฅผ ๋์ฌ์ผ ํฉ๋๋ค. ์์์น ๋ชปํ ์ผ์ด์ค๋ฅผ ๋ง๋ ๋๋ง๋ค ํจํด์ ์ฐพ๊ณ ํ๋ก์ธ์ค๋ฅผ ๊ฐ์ ํด ๋๊ฐ์ผ ํฉ๋๋ค.
playwright๋ฅผ ์ด์ฉํ e2e ํ ์คํธ๋ก ์์ํ๋ ์ด์ ๋, ์๋ ํ ์คํธ์ ์ฌ๋ฌ๋ชจ๋ก ๋ฎ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋น์ฐํ ๊ท์ฐฎ์ ๋จ์ ๊ณผ ์ฅ์ ์ ๊ณต์ ํ๊ธฐ๋ ํฉ๋๋ค. ๊ทธ๋์ ํ ๋จ๊ณ ํ ๋จ๊ณ ์๋ ํ ์คํธ์ ์๋ ํ ์คํธ๋ฅผ ๋น๊ตํด๊ฐ๋ฉด์ ์ด์ผ๊ธฐ๋ฅผ ํด๋ณด๊ฒ ์ต๋๋ค.
๋ง์ดํฌ๋ก์ํํธ์ ์คํ์์ค E2E ์๋ํ ๋๊ตฌ Playwright
ํ ์คํธ์ ๋ํ ์ฑ ๋ค์ ๋ณด๋ฉด ๊ฐ๋น์ผ e2e ํ ์คํธ ์๋ฃจ์ ๋ค์ ๋นํํ๋ ๋ง๋ค์ด ๋ง์ต๋๋ค. ๋คํํ Playwright๋ ๋ง์ดํฌ๋ก์ํํธ์์ ๋ง๋ ์คํ์์ค ํ๋ก๊ทธ๋จ์ผ๋ก, ๋๊ตฌ๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค. JS ๋ฟ๋ง ์๋๋ผ Python ๊ณผ Java ๋ฑ ๋ค์ํ ์ธ์ด๋ฅผ ์ง์ํ์ง์.
๋น์ทํ ์๋ฃจ์ ์ผ๋ก Cypress๋ฅผ ์ฌ์ฉ ์ค์ด์๊ฑฐ๋, ๋ค์ด๋ณด์ จ๋ ๋ถ๋ ๊ณ์ค ๊ฒ๋๋ค. ์ ์ญ์ ๊ณผ๊ฑฐ์ Cypress๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. Cypress๋ ์ฌ๋ฌ๋ชจ๋ก ์ฅ์ ์ด ์์ง๋ง. Playwright๊ฐ test ์๋๋ ๋ ๋น ๋ฅด๊ณ , ๋ค์ํ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํ๋ฉฐ, ์ ๊ทผ์ฑ์ ๊ธฐ๋ฐํ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค์ ์ ๊ณตํฉ๋๋ค. ์ฌ๋ฏธ์๊ฒ๋ Cypress๋ Playwright๋ฅผ ์ด์ฉํด์ Safari๋ฅผ ์ง์ํ๊ณ ์์ ์ ๋์ ๋๋ค.
playwright๋ฅผ ์ค์นํ๋ ๊ฑด ์ด๋ ต์ง ์์ต๋๋ค. vs code extension๋ ์๊ณ ์. e2e์ ํน์ฑ์ ์ด๋ค UI ํ๋ ์์ํฌ๋ฅผ ์ฐ๋์ง๋ ์๊ด ์์ต๋๋ค. ๋ค๋ค playwright๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์๊ณ , ์ค์น๋ ๋ง์น์ จ๋ค๊ณ ์ ์ ํ๊ณ ์ด์ผ๊ธฐ๋ฅผ ๊ณ์ํด๋ณด๊ฒ ์ต๋๋ค. package.json์ ๋ค์์ฒ๋ผ scripts๋ฅผ ์ถ๊ฐํด๋์๋ฉด ์ ์ฉํฉ๋๋ค. ํนํ ui ๋ชจ๋์์๋ ํธ๋ฆฌํ๊ฒ ๊ฐ ๋จ๊ณ์ ์ค๋ ์ท๊ณผ ๋ก๊ทธ๋ฅผ ๋ณด์ค ์ ์์ด์ ์ฌ๋ฌ๋ชจ๋ก ํธ๋ฆฌํฉ๋๋ค. ์ฌ์ ๋ฒํผ๋ง ๋๋ฅด๋ฉด ํ ์คํธ๋ฅผ ์คํํ ์ ์์ฃ .
// package.json
{
// ...
"scripts": {
// ...
"e2e": "playwright test", // ๊ธฐ๋ณธ์ ํค๋๋ฆฌ์ค ๋ชจ๋๋ผ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ ๋ณด์
๋๋ค.
"e2e:headed": "playwright test --headed", // headed ํ๋๊ทธ๋ฅผ ๊ฑธ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ๋ณด์
๋๋ค
"e2e:ui": "playwright test --ui" // ํธ๋ฆฌํ UI๋ฅผ ๋ณผ ์ ์๊ณ , --headed๊น์ง ์ฐ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ๊ฐ์ด ๋ณด์
๋๋ค.
}
// ...
}
์ฌ์ฉ์ ์ ์ฅ์์ ์๋๋ฆฌ์ค ๋ง๋ค๊ธฐ
test๋ฅผ ์งค ๋์๋ ๋ค์ํ ์ผ์ด์ค๋ฅผ ์๊ฐํด๋ณด๊ณ , ์ฌ์ฉ์ ์ ์ฅ์์ ์๋๋ฆฌ์ค๋ฅผ ๋ง๋ญ๋๋ค. e2e๋ ๋ณดํต ์น๋ฐํ๊ฒ ๋ชจ๋ ์ผ์ด์ค๋ฅผ ํ ์คํธํ์ง ์์ต๋๋ค. ๊ฒฝ์ฐ์ ์๊ฐ ๋ง๋ค๋ฉด e2e๊ฐ ์๋๋ผ component๋ unit test๋ก ๋ถ๋ฆฌํ ์ ์์์ง ๊ณ ๋ฏผํด๋ณด๋ ๊ฒ ์ข๊ณ , ํนํ ์ธ๋ถ ์์กด์ฑ๊ณผ ๋ถ์ํจ๊ณผ๊ฐ ์๋ ์์ ํจ์๋ ๊ฐ์ฒด๋ฅผ ๋ถ๋ฆฌํ๋ ๊ฒ ์ข์ต๋๋ค. (์ถํ์ ๋ค๋ฅธ ๊ธ์์ ๋ ์์ธํ ๋ค๋ฃฐ ์์ ์ ๋๋ค)
๊ฑฐ๊พธ๋ก ๋งํ๋ฉด e2e ํ ์คํธ๋ ๋๋๋ก ์ค์ ์ธ๋ถ ์์กด์ฑ๊ณผ ๋ถ์ํจ๊ณผ๊น์ง ํจ๊ป ํ ์คํธํ๋ ๊ฒ ์ข์ต๋๋ค. ์ค์ ๋ธ๋ผ์ฐ์ ์์, ์ค์ (์ ์ต๋ํ ์ ์ฌํ) api๋ก ํ ์คํธ๋ฅผ ํ๋ฉด, ํ๋ฐํธ์๋ ๋ฟ๋ง ์๋๋ผ ์ฌ๋ฌ ์์กด์ฑ์ ๋ฌธ์ ๊น์ง ํจ๊ป ์ฐพ์๋ผ ์ ์์ต๋๋ค.
๋ก๊ทธ์ธ ํ์ง ์์ ์ฑ๋ก ์ํ์ ๊ตฌ๋งคํ๋ ค ํ๋ฉด ๋ก๊ทธ์ธ์ ์๊ตฌํ๊ณ , ๋ก๊ทธ์ธํ๋ฉด, ๋ณด๋ ์ํ์ผ๋ก ๋์๊ฐ๋ค
์ ์๋ ๋ก๊ทธ์ธ ๊ธฐํ ๋ถ์์ ํด๋ดค์๋๋ฐ์. ์ํ์ ๊ตฌ๋งคํ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค. ๊ทธ๋์ ๊ตฌ๋งคํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ก๊ทธ์ธ์ ์์ฒญํ๋๋ฐ์. ๋ก๊ทธ์ธํ ๋ค์ ๋ณด๊ณ ์๋ ์ํ์ด ์๋๋ผ ํํ์ด์ง๋ก ๋์๊ฐ๋ฉด ์ฌ์ฉ์๋ ๋นํฉํ ๊ฒ์ ๋๋คโฆ ๋ก๊ทธ์ธํ ๋ค์ ๋ณด๊ณ ์๋ ์ํ ํ์ด์ง๋ก ๋์์ค๋ ์๋๋ฆฌ์ค๋ฅผ playwright๋ก ์๋ํํด ๋ณด๊ฒ ์ต๋๋ค.
Given ๋ก๊ทธ์ธํ์ง ์์ ์ฑ๋ก
When ์ํ์ ๊ตฌ๋งคํ๋ ค ํ๋ฉด
Then ๋ก๊ทธ์ธํ ์ง AlertDialog๋ก ๋ฌผ์ด๋ณธ๋ค
When ํ์ธ์ ๋๋ฅด๋ฉด
Then ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ๋ค
When ๋ก๊ทธ์ธ์ ์๋ฃํ๋ฉด
Then ์ํ ํ์ด์ง๋ก ๋์์จ๋ค
์ ๊ทผ์ฑ์ผ๋ก ์์ ์ฐพ๊ธฐ
์ ๊ทผ์ฑ์ด ์ด๋๋ ์น ํ๋ก ํธ์๋ ํ ์คํธ ์๋ํ์์ ์ด์ผ๊ธฐํ ๊ฒ์ฒ๋ผ, ์ ๊ทผ์ฑ ํ์ค์ ํ์ฉํ๋ฉด ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ํธ๋ฆฌํ๋ฉด์๋ ํ ์คํธํ๊ธฐ ์ฌ์ด ์ฑ์ ๋ง๋ค ์ ์์ต๋๋ค.
์์ ์ผ์ด์ค๋ฅผ ์ ๊ทผ์ฑ ์์๋ก ๊ตฌ์ฒดํํ๋ฉด ๋ค์์ฒ๋ผ ๋ฒ์ญํ ์ ์์ต๋๋ค.
๋ก๊ทธ์ธํ์ง ์์ ์ฑ๋ก
"์ํ ํ์ด์ง"๋ก ์ด๋ํด์
"๊ตฌ๋งคํ๊ธฐ" [button]์ ํด๋ฆญํ๋ฉด
"๋ก๊ทธ์ธ" [alertdialog]๋ก ๋ฌผ์ด๋ณธ๋ค
"ํ์ธ"[button]์ ํด๋ฆญํ๋ฉด
"๋ก๊ทธ์ธ ํ์ด์ง"๋ก ์ด๋๋๋ค
"fromm ํตํฉ ID"[textbox]์ "test@wonderwall.kr"์ ์
๋ ฅํ๊ณ
"๋น๋ฐ๋ฒํธ"[textbox]์ "1q2w3e4r"์ ์
๋ ฅํ๊ณ
"๋ก๊ทธ์ธ"[button]์ ํด๋ฆญํ๋ฉด
์ํ ํ์ด์ง๋ก ๋์์จ๋ค
playwright ์ฝ๋๋ก๋ ๋ค์๊ณผ ๊ฐ์ด ์ฐ๋ฉด ๋ฉ๋๋ค. ์ด์ ๋ถํฐ ์ด ์ฝ๋๋ฅผ ํ๋์ฉ ๋ฏ์ด๋ณด๊ฒ ์ต๋๋ค.
test('๋ก๊ทธ์ธ ํ์ง ์์ ์ฑ๋ก ์ํ์ ๊ตฌ๋งคํ๋ ค ํ๋ฉด ๋ก๊ทธ์ธ์ ์๊ตฌํ๊ณ , ๋ก๊ทธ์ธํ๋ฉด, ๋ณด๋ ์ํ์ผ๋ก ๋์๊ฐ๋ค', async ({ page }) => {
// Given "์ํ ํ์ด์ง"๋ก ์ด๋ํด์
await page.goto(`${CLIENT_HOST}/goods/275`);
// When "๊ตฌ๋งคํ๊ธฐ" [button]์ ํด๋ฆญํ๋ฉด
await page.getByRole('button', { name: '๊ตฌ๋งคํ๊ธฐ' }).click();
// Then "๋ก๊ทธ์ธ" [alertdialog]๋ก ๋ฌผ์ด๋ณธ๋ค
const alertDialog = page.getByRole('alertdialog', { name: '๋ก๊ทธ์ธ' });
// When "ํ์ธ"[button]์ ํด๋ฆญํ๋ฉด
await alertDialog.getByRole('button', { name: 'ํ์ธ' }).click();
// Then "๋ก๊ทธ์ธ ํ์ด์ง"๋ก ์ด๋ํ๋ค
const form = page.getByRole('form', { name: '๋ก๊ทธ์ธ' });
// ํผ์ด ๋ณด์ผ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค
await form.waitFor();
// When "fromm ํตํฉ ID"[textbox]์ "test@wonderwall.kr"์ ์
๋ ฅํ๊ณ
await form.getByLabel('fromm ํตํฉ ID').fill('taehee.kim@wonderwall.kr');
// When "๋น๋ฐ๋ฒํธ"[textbox]์ "1q2w3e4r"์ ์
๋ ฅํ๊ณ
await form.getByLabel('๋น๋ฐ๋ฒํธ').fill('wonderwall2@');
// When "๋ก๊ทธ์ธ"[button]์ ํด๋ฆญํ๋ฉด
await form.getByRole('button', { name: '๋ก๊ทธ์ธ' }).click();
// Then ์์ํ๋ ์ํ ํ์ด์ง๋ก ๋์์จ๋ค
await page.getByRole('heading', { name: '์์ธํ์ด์ง' }).waitFor();
await expect(page).toHaveURL(/.*\/goods\/275/);
})
locator์ ์ฌ์ฉ์์ action
page.getByRole
๊ณผ ๊ฐ์ playwright์ locator๋ testing-library์ ๋น์ทํ๊ฒ ์๊ฒผ์ต๋๋ค. ํ์ง๋ง element๋ฅผ ๋ฐํํ๋ ํ
์คํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฌ๋ฆฌ, lazyํ๋ค๋ ๊ฒฐ์ ์ ์ธ ์ฐจ์ด๊ฐ ์๋๋ฐ์.
// ์ด๋ค ๋ฒํผ์ ์ฐพ์์ง ๊ณํ์ ์ธ์๋๋ค.
const button: Locator = page.getByRole('button', { name: '๊ตฌ๋งคํ๊ธฐ' });
// ๋ฒํผ์ ์ฐพ๊ณ , ์์ผ๋ฉด ๊ธฐ๋ค๋ฆฌ๊ณ , ์์ผ๋ฉด ํด๋ฆญํฉ๋๋ค
await button.click();
์๋ฅผ ๋ค์ด ์์ฒ๋ผ locator ์ฝ๋๋ง ๋ฉ๊ทธ๋ฌ๋ ์จ๋์ผ๋ฉด, ํ๋ฉด์ ๊ตฌ๋งคํ๊ธฐ ๋ฒํผ์ด ์๋๋ผ๋ ํ ์คํธ๊ฐ ์คํจํ์ง ์์ต๋๋ค. ํ๋ฉด์์ ๊ตฌ๋งคํ๊ธฐ ๋ฒํผ์ ์ฐพ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค. locator ์์ฒด๋ ๊ณํ์ผ ๋ฟ์ ๋๋ค. promise๋ ์๋๊ณ ํ๋ฒํ ๊ฐ์ฒด์ง์.
์ด locator๋ก click์ด๋ fill, waitFor ๊ฐ์ action์ ํ ๋ ๋น๋ก์ ์์๋ฅผ ์ฐพ๊ณ , ํ๊ฐ(evaluate)ํ๊ฒ ๋ฉ๋๋ค.
click์ ๋ง ๊ทธ๋๋ก ํด๋ฆญ์ด๊ณ ์. fill์ ๊ฐ์ ์ ๋ ฅํ๋ ๊ฒ๋๋ค. ์ง์ธ ๋์๋ clear๋ฅผ ์ฐ๊ณ ์. waitFor์ ํ๋ฉด์ ์ด ์์๊ฐ ๋ํ๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆด ๋ ์๋๋ค. ์ด ๊ธฐ๋ค๋ฆฌ๋ ๋์์ด ๋ ํนํ๋ ๋ ์์ธํ ์ค๋ช ํด๋ณด์ง์.
์์ฃผ ํ๋ ์ค์ : role์ด ์๊ฑฐ๋ ์ค์ฒฉํ๊ธฐ
์๋ฅผ ๋ค์ด ๋ชฉ๋ก์ ์์ดํ ์ ๋งํฌ๋ก ๋ง๋๋ ๊ฒฝ์ฐ, listitem ์์ link๋ฅผ ๋ฃ์ด์ค์ผ ํฉ๋๋ค.
<h2 id='arti-list-title'>Arti with fromm store</h2>
<ul aria-labelledby='arti-list-title'>
<li>
<a href='/arti/leechaeyeon'> ์ด์ฑ์ฐ </a>
</li>
<li>
<a href='/arti/kanghyewon'> ๊ฐํ์ </a>
</li>
</ul>
// ๋๋
<div role='list' aria-label='Arti with fromm store'>
<div role='listitem'>
<Link href='/arti/leechaeyeon'> ์ด์ฑ์ฐ </Link>
</div>
<div role='listitem'>
<Link href='/arti/kanghyewon'> ๊ฐํ์ </Link>
</div>
</div>
๊ทธ๋ฌ๋ฉด test์์๋ link๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์์ต๋๋ค.
await page.getByRole('list', { name: 'Arti' }).getByRole('link', { name: '๊ฐํ์' }).click();
๊ทธ๋ฐ๋ฐ element๋ฅผ ๋ ๊ฒน์ผ๋ก ๊ฐ์ธ๋ ๊ฒ ๊ท์ฐฎ์์ ํ๋๋ก ํฉ์น๋ฉด, role์ด ๋ฎ์ด ์ฐ์ฌ์ง๋๋ค!
<div role='list'>
{/* link role์ด listitem role๋ก ๋ฎ์ด ์์์ง๋๋ค! */}
<Link role='listitem' href='/arti/leechaeyeon'> ์ด์ฑ์ฐ </Link>
<Link role='listitem' href='/arti/kanghyewon'> ๊ฐํ์ </Link>
</div>
๋น์ฐํ ํ ์คํธ์์๋ link role์ ์ฐพ์ง ๋ชปํด์ ์๋ฌ๊ฐ ๋๊ณ ์ฝ์ง์ ํ๊ฒ ๋ฉ๋๋ค. ํ๋์ element์ ์ฌ๋ฌ ๊ฐ์ role์ ๋ฌ ์๋ ์์ต๋๋ค. ์์ li ์์ a ํ๊ทธ๋ฅผ ๋ฃ๋ ๊ฒ ์์ฐ์ค๋ฌ์ด ๊ฒ์ฒ๋ผ, ๋ฌด์๋ฏธํ div๊ฐ ์ถ๊ฐ๋๋ ๊ฒ์ด ์๋๋ ๊ผญ, listitem๊ณผ link ํน์ button ๋ฑ์ ๋ณ๊ฐ๋ก ๊ด๋ฆฌํฉ์๋ค.
์์ฃผ ํ๋ ์ค์ 2 : textContent์ accessible name์ ์ฐฉ๊ฐํ๊ธฐ
button์ด๋ link ๋ฑ ์ํธ์์ฉ์ด ๊ฐ๋ฅํ ์์๋ค์ textContent๋ฅผ accessible name์ผ๋ก ๊ฐ์ง๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
ํ์ง๋ง alert๋ list, listitem ๋ฑ์ name์ผ๋ก ์ฐพ์ผ๋ ค ํ๋ฉด ์ฐพ์ง ๋ชปํ๋ค๊ณ ์๋ฌ๊ฐ ๋ฉ๋๋ค. textContent๊ฐ ์์ด๋ name์ผ๋ก ์ฌ๊ฒจ์ง์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
<h2 id='arti-list-title'>Arti with fromm store</h2>
<ul aria-labelledby='arti-list-title'>
<li>
<a href='/arti/leechaeyeon'> ์ด์ฑ์ฐ </a>
</li>
<li>
<a href='/arti/kanghyewon'> ๊ฐํ์ </a>
</li>
</ul>
// ์ฑ๊ณต! link๋ textContent๋ฅผ name์ผ๋ก ๊ฐ์ง๋๋ค.
await page.getByRole('list', { name: 'Arti' }).getByRole('link', { name: '์ด์ฑ์ฐ' }).click();
// ์๋ฌ! ์ด์ฑ์ฐ์ด๋ผ๋ ์ด๋ฆ์ ๊ฐ์ง listitem์ด ์์ต๋๋ค.
await page.getByRole('list', { name: 'Arti' }).getByRole('listitem', { name: '์ด์ฑ์ฐ' }).click();
๊ทธ๋์ ๋๋๋ก link๋ button ๊ฐ์ ์ํธ์์ฉ ๊ฐ๋ฅํ role์ ์ ํ์ฉํ์๊ณ , alert๋ listitem์ ๋ช ์์ ์ผ๋ก aria-label, aria-labelledby๋ฅผ ๋ฌ์์ฃผ์๊ฑฐ๋, filter({ hasText: string })๋ฅผ ์ด์ฉ ํ์ค ์๋ ์์ต๋๋ค.
<div role='alert'>{message}</div>
// ์คํจ! name์ผ๋ก๋ ์ฐพ์ ์ ์์ต๋๋ค.
await expect(page.getByRole('alert', { name: '์ธ์ฆ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.' })).toBeVisible();
// ์ฑ๊ณต!
await expect(page.getByRole('alert').filter({ hasText: '์ธ์ฆ๋ฒํธ๊ฐ ๋ง์ง ์์ต๋๋ค.' })).toBeVisible();
๋ก๋ฉ๊ณผ ๊ธฐ๋ค๋ฆฌ๊ธฐ
playwright์ action์ด๋ expect
์ ๊ฐ์ assertion๋ค์ ๋ณด๋ฉด ๋ค๋ค await์ด ๋ถ์ด ์์ต๋๋ค. ๋น๋๊ธฐ์ธ ๊ฑด ์๊ฒ ๋๋ฐ, ์ธ๋ถ์ ๋ฌด์์ ๊ธฐ๋ค๋ฆฌ๊ธธ๋ ๋น๋๊ธฐ์ผ๊น์?
api์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ฑฐ๋, ์ด๋ฒคํธ์ ์ํด ์ํ์ UI๊ฐ ๋ณ๊ฒฝ๋๋ ๋ฐ์๋ ์๊ฐ์ด ๊ฑธ๋ฆฝ๋๋ค. ๊ทธ ์๊ฐ์ ๋คํธ์ํฌ ์ฌ์ ์ ๋ฐ๋ผ ๋งค์ฐ ์ค๋ ๊ฑธ๋ฆด ์๋ ์๊ณ , ๊ธ๋ฐฉ ๋๋ ์๋ ์์ต๋๋ค.
// Then "๋ก๊ทธ์ธ" [alertdialog]๋ก ๋ฌผ์ด๋ณธ๋ค
const alertDialog = page.getByRole('alertdialog', { name: '๋ก๊ทธ์ธ' });
// When "ํ์ธ"[button]์ ํด๋ฆญํ๋ฉด
await alertDialog.getByRole('button', { name: 'ํ์ธ' }).click();
// Then "๋ก๊ทธ์ธ ํ์ด์ง"๋ก ์ด๋ํ๋ค
const form = page.getByRole('form', { name: '๋ก๊ทธ์ธ' });
// ํผ์ด ๋ณด์ผ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค
await form.waitFor();
์๋ฅผ ๋ค์ด ์์์ ์ ํฌ๋ ๊ฒฝ๊ณ ์ฐฝ์์ ํ์ธ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ๋ค๊ณ ํ์ต๋๋ค.
์ํ๊น๊ฒ๋ ๊ตฌ๋งคํ๊ธฐ ๋ฒํผ์ ํด๋ฆญํ์๋ง์ ๋ฐ๋ก ๊ฒฝ๊ณ ์ฐฝ์ด ๋จ๋ ๊ฑด ์๋๋๋ค. ๊ฒฝ๊ณ ์ฐฝ์ด ๋ฐ ๋์๋ ์๋๋ฉ์ด์ ์ผ๋ก ์์ํ ๋ํ๋๊ณ ์. ํ์ธ ๋ฒํผ์ ๋๋ฌ๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ๋ ๋ฐ์๋ ์๊ฐ์ด ๊ฑธ๋ฆด ๊ฒ๋๋ค. ๋ง์ฝ์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์์ง ์ด๋ํ๊ธฐ ์ ์ playwright๊ฐ โ๋ญ์ผ ๋ฒํผ์ ๋๋ ๋๋ฐ ์๋ฌด ์ผ๋ ์ ์ผ์ด๋๋๋ฐ? ์คํจ์ผ ์คํจ!โ ํ๊ณ ๊นจ์ ธ๋ฒ๋ฆฌ๋ฉด ์ ๋ ๊ฒ์ ๋๋ค.
๋คํํ๋ playwright๋ ๋น์ฅ ํด๋ฆญํ ๋์์ด ๋ณด์ด์ง ์์๋ ์๋์ผ๋ก ๊ธฐ๋ค๋ฆฝ๋๋ค. ํ๋ฉด์ ๋ณด์ด๋๋ผ๋ ์ ๋๋ฉ์ด์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๊ณ ์. ํ๋ฉด์ ๋ณด์ด๋๋ผ๋ disable ๋์ง ์๊ณ ์ํธ์์ฉ์ด ๊ฐ๋ฅํด์ง ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ฉด์ ์ฌ์๋๋ฅผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ await์ ์ ๋ ๋นผ๋จน์ด์๋ ์ ๋๊ฒ ์ต๋๋ค.
๊ทธ๋์ timeout๋ ์์ต๋๋ค. ๊ธฐ๋ณธ์ 5000ms ์ฆ 5์ด์ธ๋ฐ์. ์ฐ๋ฆฌ ์ฌ์ดํธ๊ฐ ๋๋ฌด ๋๋ ค์ ์ฌ์ฉ์๋ 5์ด ์ด์ ๊ธฐ๋ค๋ฆด๊ฒ ํ์คํ๋ค๋ฉดโฆ timeout์ ๋ช ์์ ์ผ๋ก ๋๋ ค์ค์ผ ํ ๊ฒ์ ๋๋ค.
์์ฃผํ๋ ์ค์ 3 : waitForTimeout์ผ๋ก ๊ธฐ๋ค๋ฆฌ๊ธฐ
Donโt use manual assertions that are not awaiting the expect.
๋ฒ ์คํธ ํ๋ํฐ์ค ๋ฌธ์์์๋ ๋งํ์ง๋ง, ์๋์ผ๋ก ์ฌ์๋๋ฅผ ํ๊ณ ๊ธฐ๋ค๋ ค์ฃผ๋ ์น ์ฐ์ ์ assertion์ ์ฌ์ฉํ์ ์ผ ํฉ๋๋ค.
์ฌ์๋๋ฅผ ํ์ง ์๋ ์ผ๋ฐ assertion ๋ค์ ์ฌ์ฉํ๋ฉด ๋๊ธฐ์ ์ผ๋ก ํ ๋ฒ ๊ฒ์ฌํ๊ณ ๋ฐ๋ก ์คํจํด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ด์ฃ .
์ด๋ ๊ฒ ํ ์คํธ๊ฐ ์คํจํ๋ฉด ์ธํฐ๋ท์์ ์๋ชป๋ ์กฐ์ธ์ ๋ณด๊ณ waitForTimeout ๊ฐ์ ๊ณ ์ ๋ ์๊ฐ์ผ๋ก test๋ฅผ ๋๋ฆฌ๊ณ ๋ ํ๋๋ฐ์. ์ด๋ฐ ์์ ๋ฐฉํธ์ด ๋น์ฅ์ ํธ๋ฆฌํ๊ฒ ํ ์คํธ๋ฅผ ํต๊ณผ์์ผ์ฃผ๋๋ฏ ๋ณด์ด์ง๋ง, ์ค์ ๋ก๋ ๋ ๋ฒ๊ฑฐ๋กญ๊ณ ๋๋ฆฌ๊ณ ๋ณ๋์ค๋ฌ์ด(flaky) ํ ์คํธ๋ก ์ด์ด์ง๋ ๊ฒฝ์ฐ๊ฐ ๋ ๋ง์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ค์์ ์ค์ ๋ก ์ ํฌ ํ์์ ์์ฑํ๋ ์๋ชป๋ ํ ์คํธ์ ์์ ๋๋ค. ํ๋ฉด์ ์คํฌ๋กคํ๋ฉด ์ถ๊ฐ๋ก ์ํ์ด ๋ถ๋ฌ์์ง๋์ง ํ ์คํธํ ์ฝ๋์ธ๋ฐ์.
test('์คํฌ๋กค์ ๋ด๋ฆฌ๋ฉด ์ํ์ด ์ถ๊ฐ์ ์ผ๋ก ๋
ธ์ถ๋๋ค.', async ({ page }) => {
await setupAutoMockCache(page);
await page.goto(`${CLIENT_HOST}${PathName}`);
// page๊ฐ 3์ด ์์ ๋ก๋ฉ๋์ง ์๋๋ค๋ฉด?
await page.waitForTimeout(3000);
// toHaveLength๋ promise๋ฅผ ๋ฐํํ์ง ์๊ธฐ ๋๋ฌธ์ await์ ๋ถ์ฌ๋ ์ฌ์๋๋ฅผ ํ์ง ์์ต๋๋ค
await expect(await page.getByRole('listitem').all()).toHaveLength(12);
// ๋ง์ง๋ง ์์ดํ
์ผ๋ก ์คํฌ๋กค
await page.getByRole('listitem').last().scrollIntoViewIfNeeded();
// ์ ๋ฐ์ดํฐ๊ฐ ์์๊ฐ์ ๋์ฐฉํ๋๋ผ๋ 3์ด๋ฅผ ๊ธฐ๋ค๋ ค์ ๋๋ ค์ง๋๋ค.
await page.waitForTimeout(3000);
// 3์ด ๋ค์ ์์ดํ
์ด ์์ง 12๊ฐ๋ผ๋ฉด ๋ฐ๋ก ํ
์คํธ๊ฐ ์คํจํฉ๋๋ค.
await expect(await page.getByRole('listitem').all()).toHaveLength(16);
});
์ผ๋ง๋ ๊ธฐ๋ค๋ ค์ผํ๋์ง ์ ํํ๊ฒ ์๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, waitForTimeout์ ๋๋ถ๋ถ ์ ์ข์ ์ ํ์ ๋๋ค. ์ค์ ๋ก ์คํํ๋ฉด์ ์ ํํ ์๊ฐ์ ์ฐพ์์ผ ํ๊ณ ์. ๋๋ฌด ๋ฆ๊ฒ ์ค๋ฉด ํ ์คํธ๊ฐ ์ฐ์ฐํ ์คํจํฉ๋๋ค. ๊ทธ๋ ๋ค๊ณ ์๊ฐ์ ๋๋ํ ์ก์๋์ผ๋ฉด ๋ฐ์ดํฐ๊ฐ ์ผ์ฐ ๋์ฐฉํด๋ ๋๋ฌด ๋ง์ ์๊ฐ์ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋์ด ํ ์คํธ๊ฐ ๋๋ ค์ง๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์๋ toHaveLength
๋์ web first assertion์ธ toHaveCount
๋ฅผ ์ฐ๋ฉด ๋ฉ๋๋ค. ํด๋นํ๋ locator๋ฅผ ์ฌ์๋ํ๋ฉด์, ์ํ๋ ๊ฐ์๊ฐ ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. ๋ณ๋ค๋ฅธ ๋ก๋ฉ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์๋ ๋์ ์ฝ๋๋ ๊ฐ๊ฒฐํด์ง๊ณ , ๋คํธ์ํฌ ์๋์ ์๊ด ์์ด ์์ ์ ์ผ๋ก ํ
์คํธ๊ฐ ๊ฐ๋ฅํด์ง๋๋ค.
test('์คํฌ๋กค์ ๋ด๋ฆฌ๋ฉด ์ํ์ด ์ถ๊ฐ์ ์ผ๋ก ๋
ธ์ถ๋๋ค.', async ({ page }) => {
await setupAutoMockCache(page);
await page.goto(`${CLIENT_HOST}${PathName}`);
await expect(page.getByRole('listitem')).toHaveCount(12);
// ๋ง์ง๋ง ์์ดํ
์ผ๋ก ์คํฌ๋กค
await page.getByRole('listitem').last().scrollIntoViewIfNeeded();
await expect(page.getByRole('listitem')).toHaveCount(16);
});
์์ผ๋ก waitForTimeout
์ ์จ์ผํ๋ ์ถ์ ๋ ์ด๋ ๊ฒ ์๋์ผ๋ก ์ฌ์๋ํ๋ assertion์ ์ฐพ์๋ณด์๋ฉด, ๋๋ถ๋ถ ๋ ๋์ ๋ต์ ์ฐพ์ผ์ค ์ ์์ ๊ฒ๋๋ค.
์ธ๋ถ ์ํ ์์กด์ฑ ์ฌํํ๊ธฐ
์์์ ๋ณธ ๊ฒ๊ณผ ๊ฐ์ ๋จ์ํ ์ผ์ด์ค๋ค๋ง ์๋ค๋ฉด e2e ํ ์คํธ๋ ๊ทธ๋ฆฌ ์ด๋ ต์ง ์์ ๊ฒ๋๋ค. ์ด์ํ ๋ชจํน์ด๋ ์ธํ ๋ ํ์ ์๊ฒ ์ฃ .
ํ์ง๋ง ์ํ๊น๊ฒ๋ ์ธ์์ ๋ณํฉ๋๋ค. ๋ณํ๋ ์ธ๋ถ์ํ์ ์์กดํ๊ฒ ๋๋ฉด ํ
์คํธ๋ ์ฝ๊ฒ ๊นจ์ง๋๋ค. ์๋ฅผ ๋ค์ด ์์์๋ 275๋ฒ ์ํ์ ๊ตฌ๋งคํ๋ ค ํ๋๋ฐ์. ๋ง์ฝ์ ์๊ฐ์ด ์ง๋์ 275๋ฒ ์ํ์ด ํ๋งค๋ฅผ ์ข
๋ฃํ๊ฑฐ๋ ํ์ ๋๋ฉด ์ด ํ
์คํธ๋ ์คํจํ๊ฒ ๋ ๊ฒ์
๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก test@wonderawll.kr
์ด๋ผ๋ ํ
์คํธ ๊ณ์ ์ด ์๊ฑฐ๋, ํํดํ๋ค๋ฉด ๋ง์ฐฌ๊ฐ์ง๋ก ํ
์คํธ๊ฐ ๋ถ๊ฐ๋ฅํ๊ฒ ์ฃ .
์ด๋ ๊ทธ๋ด์ง๋ ๋ชจ๋ฅธ๋ค๋ ์์ธ๊ฐ ์๋๋ผ, ์ค์ ๋ก e2e๋ฅผ ์ ์ง๋ณด์ํ๋ ํ์ด๋ผ๋ฉด ๋๊ตฌ๋ ๊ฒช๋ ์ผ์ ๋๋ค.
ํ๋กฌ์๋ production๊ณผ ๊ฑฐ์ ๋์ผํ ํ ์คํธ์ฉ dev ํ๊ฒฝ์ด ์์ต๋๋ค. ํ ์คํธ๋ฅผ ์ํ ์ํ ๋ฐ์ดํฐ ๋ฑ์ ๋ฏธ๋ฆฌ ์ ์ ํด๋๋๋ฐ, ๊ด๊ณ์๋ค ์ฌ์ด์ ํ์๋ง ์ ๋๋ฉด ํ ์คํธ์ฉ์ผ๋ก ๋ค์ํ ์กฐ๊ฑด์ ๊ฐ์ง ๋ฐ์ดํฐ๋ค์ ์์๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์๋ฉด 2099๋ ์ ํ๋งค๋ฅผ ์์ํ๋ ์ํ์ด๋, ์ ๋ ํ์ ๋์ง ์๋ ์ํ ๋ฑ์ด ์๊ฒ ์ต๋๋ค.
์ด๋ ๋ฏ ์ธ๋ถ ์ํ๋ฅผ ์ง์ ์ปจํธ๋กคํ ์ ์๋ค๋ฉด, ๋์ผํ๊ฒ ๋ง์ถฐ๋๋ฉด ์ข์ต๋๋ค. ์ค์ api์ ์์กด์ฑ์ ์์งํ๋๋งํผ, ํ ์คํธ์์ ์ฑ๊ณตํ๋๋ฐ ํ๋ก๋์ ์์ ๊นจ์ง๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ด ์ค์ด๋ญ๋๋ค. ๊ฑฐ๊พธ๋ก dev์ production์ ๋ฏธ๋ฌํ ์ฐจ์ด๋ง ํ๋ก๋์ ์์ ์๋ ํ ์คํธ๋ฅผ ์ง์คํ ์ ์์ฃ .
์ด ๋ฌธ์ ์๋ ๋ค์ํ ๊ฒฝ์ฐ์ ์๊ฐ ์์ผ๋ ๋ค๋ฅธ ํด๊ฒฐ์ฑ ์ ๋ ์ฐพ์๋ณด๊ฒ ์ต๋๋ค.
ํต์ ํ ์ ์๋ ๋ถ์ํจ๊ณผ๋ฅผ ์ง์ ์ ๋ฆฌํ๊ธฐ
์ฌ์ฉ์๊ฐ ์ง์ ์ญ์ ํ๊ฑฐ๋ ๋๋๋ฆด ์ ์๋ ๋ถ์ํจ๊ณผ๋ผ๋ฉด, ์์ฑํ๊ณ ์ญ์ ํ๋ ๊ฑธ ํ๋์ ํ ์คํธ๋ก ๋ง๋ค๋ฉด ์ข์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ ๋ด๊ณ , ์ญ์ ํ๋ ๋์๊ณผ, ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ด ๋ด๊ธด ์ํ์, ๋น์ด ์๋ ์ํ๋ฅผ ํ ์คํธํ๊ณ ์ถ๋ค๊ณ ํฉ์๋ค. ๋์ถฉ ์๊ฐํ๋ฉด ์ผ์ด์ค๊ฐ 4๊ฐ๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค. ํ์ง๋ง ๊ท์ฐฎ์ ๊ฒ์ด ์ฅ๋ฐ๊ตฌ๋์ ์ํ๊ฐ ์ด๋ค์ง ๋ชจ๋ฅธ๋ค๋ ๊ฒ์ ๋๋ค. ์ฅ๋ฐ๊ตฌ๋์ ์ด๋ฏธ ์ํ์ด ๋ด๊ฒจ ์์ ์๋ ์๊ณ , ์ ๋ด๊ฒจ ์์ ์๋ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ฅ๋ฐ๊ตฌ๋๋ฅผ ๋ด๋ ํ ์คํธ ์ ์๋ ์ฅ๋ฐ๊ตฌ๋๊ฐ ๋น์ด ์๊ฒ ์ ์ ํด์ค์ผ ํ๊ณ , ์ญ์ ํ๊ธฐ ์ ์๋ ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ด ์ด๋ฏธ ๋ด๊ฒจ ์๊ฒ ์ ์ ํด์ค์ผ ํ๊ฒ ์ง์. mocking์ ํ๋๋ผ๋ ์ฌ๋ฌ๋ชจ๋ก ๋ฒ๊ฑฐ๋ก์ธ ๊ฒ๋๋ค.
๋ ์ฌ์ด ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์ด ์ ์ฒด๋ฅผ ํ๋์ ์๋๋ฆฌ์ค๋ก ํ ์คํธํ๋ ๊ฒ๋๋ค.
Given ๋ก๊ทธ์ธํ ์ฑ๋ก
Given ์ํ ํ์ด์ง์์
When ์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ณ
Then ์ฅ๋ฐ๊ตฌ๋๋ก ์ด๋ํ ์ง ๋ฌผ์ผ๋ฉด
When ์ฅ๋ฐ๊ตฌ๋๋ก ์ด๋ํ๊ณ
When ์ ์ฒด ์ํ์ ์ญ์ ํ๋ฉด
Then ์ญ์ ๋์์ต๋๋ค๋ผ๊ณ ๋จ๊ณ
Then '์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ธด ์ํ์ด ์์ต๋๋ค'๊ฐ ๋ณด์ธ๋ค
When ์ํ ๋ณด๋ฌ ๊ฐ๊ธฐ ๋ฅผ ํด๋ฆญํ๋ฉด
Then ์ํ ๋ชฉ๋ก์ผ๋ก ์ด๋ํ๋ค
test('์ฅ๋ฐ๊ตฌ๋ ์๋๋ฆฌ์ค', async ({ page }) => {
// ์ค์ api
await setupLogin(page);
// ์ํ ํ์ด์ง์์ ์ฅ๋ฐ๊ตฌ๋์ ์ํ ๋ด๊ธฐ
await page.goto(`${CLIENT_HOST}/goods/220`);
await page.getByRole('button', { name: '์ฅ๋ฐ๊ตฌ๋' }).click();
await page.getByRole('alertdialog', { name: '์ํ์ ๋ด์์ต๋๋ค.' }).getByRole('button', { name: '์ด๋ํ๊ธฐ' }).click();
// ์ํ์ด ๋ด๊ธด ์ํ๋ก ์ฅ๋ฐ๊ตฌ๋ ์ง์
์ ์ ์ฒด ์ ํ์ด ํ์ฑํ ๋์ด์์
await expect(page.getByRole('checkbox', { name: '์ ์ฒด' })).toBeChecked();
// ์ ํ๋ ์ ์ฒด ์ํ ์ญ์
await page.getByRole('button', { name: '์ํ์ญ์ ' }).click();
const alertDialog = page.getByRole('alertdialog', { name: '์ํ์ ์ญ์ ' });
await alertDialog.getByRole('button', { name: 'ํ์ธ' }).click();
await page.getByRole('alert').filter({ hasText: '์ญ์ ๋์์ต๋๋ค' }).waitFor();
// ์ฅ๋ฐ๊ตฌ๋๊ฐ ๋น์ด์์
await expect(page.getByText('์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ธด ์ํ์ด ์์ต๋๋ค')).toBeVisible();
await page.getByRole('link', { name: '์ํ ๋ณด๋ฌ ๊ฐ๊ธฐ' }).click();
});
์ธ๋ถ ์ํ์ ๋ํด ๊ฐ์ ์ ์ ๊ฒ ํ๋ฉด ๋ ์ ์ฐํ ํ ์คํธ๋ฅผ ์งค ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ด ํ ์คํธ๋ ์ด๋ฏธ ์ฅ๋ฐ๊ตฌ๋์ ๋ฌผ๊ฑด์ด ๋ด๊ฒจ ์๋ , ๋น์ด ์๋ ํต๊ณผํฉ๋๋ค. ์๋ํ๋ฉด ์ฅ๋ฐ๊ตฌ๋๊ฐ ๋น์ด ์์ ๋์๋ ์ํ์ ํ๋ ๋ด๊ณ , ์ด๋ฏธ ์ฅ๋ฐ๊ตฌ๋์ ํ๋๊ฐ ๋ด๊ฒจ ์์ด๋ ์ถ๊ฐ๋ก ํ๋ ๋ ๋ด์ ๋ค์ ์ ์ฒด ์ญ์ ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฅ๋ฐ๊ตฌ๋์ ๋ช ๊ฐ๊ฐ ๋ด๊ฒจ ์์๋๋ผ๋ ์ ์ฒด ์ญ์ ๋ฅผ ํ๋ฉด ์ฅ๋ฐ๊ตฌ๋๋ ๋น์ด ์๊ฒ ๋ฉ๋๋ค. ์ฌ์ง์ด ์ฅ๋ฐ๊ตฌ๋๋ฅผ ์ ์ฒด ์ญ์ ํ๋๋ฐ ์คํจํ๋๋ผ๋, ๋ค์ ํ ์คํธ๋ฅผ ๋๋ฆฌ๊ธฐ ์ ์ ์๋์ผ๋ก ์ ๋ฆฌํด์ฃผ๊ฑฐ๋ํ ํ์๋ ์์ฃ .
๊ทธ๋ฆฌ๊ณ ์ธ์์ ์ผ๋ก ์ผ์ด์ค๋ณ๋ก ๋ถ๋ฆฌํ ํ ์คํธ์ ๋ฌ๋ฆฌ ์ด๋ ๊ฒ ์๋๋ฆฌ์ค๋ก ๋ง๋ ํ ์คํธ๋, ํ์ด์ง ๊ฐ์ ์ด๋์ ๋น๋กฏํด์ ์ฌ๋ฌ ๋ณต์กํ ์ํ๋ค์ ์งง๊ณ ๊ฐ๊ฒฐํ๊ฒ ํ ๋ฒ์ ํ ์คํธํ๊ฒ ํด์ค๋๋ค. ์์ ๋ง์ ๋๋ฆฐ ๊ฒ์ฒ๋ผ E2E๋ ํตํฉ ํ ์คํธ๋ณด๋ค ๋ ํตํฉ์ ์ผ๋ก ์ ๊ตฌ๊ฐ์ ์ํธ์์ฉ์ ํ ์คํธํฉ๋๋ค. ๊ดํ ํ ์คํธ๋ฅผ ์ชผ๊ฐ์ ๋๋ฆฌ๊ณ ๋ณต์กํ ์ผ์ด์ค๋ฅผ ๋ง๋ค๊ธฐ๋ณด๋ค, ์ข ๊ธธ์ด์ง๋๋ผ๋ ํ๋์ ์ฌ์ฉ์ ์ฌ์ , ์๋๋ฆฌ์ค๋ฅผ ํ ์คํธํ์๋ฉด ์ธ๋ถ ์ํ์ ๋ ์์กดํ๋ฉด์๋ ์น๋ฐํ ํ ์คํธ๊ฐ ๋ ๊ฒ๋๋ค.
๋ก๊ทธ์ธ, ์ค์ ๋ฑ์ ์ ์ ์ํ๋ฅผ ์ ์ ํ๊ธฐ
๋ ๊ท์ฐฎ์ ๊ฒฝ์ฐ๋ ์ธ์ฆ(Authentication)์ ๋๋ค. ๋ก๊ทธ์ธํ ์ํ์ด๊ฑฐ๋, ๊ถํ์ด ์๋ ์ฌ์ฉ์(์๋ฅผ ๋ค์ด ์์ ์ฝํ ์ธ ๋ฅผ ๊ตฌ๋งคํ ์ฌ์ฉ์)๋ง ์ด์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ด ์๋ค๊ณ ํฉ์๋ค. ์ด๋ฅผ ์๋์ผ๋ก ํ ์คํธํ๋ ค๋ฉด ๋ก๊ทธ์ธํ๋ค๊ฐ ๋ก๊ทธ์์ํ๋ค๊ฐ๋ฅผ ๋ฐ๋ณตํด์ผ๊ฒ ์ฃ . ์์์ ํ๋ ๊ฒ์ฒ๋ผ ์ง์ ์์ด๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ ๋ก๊ทธ์ธํ ์๋ ์์ต๋๋ค๋ง. ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ๋ ์ฌ๋ฟ ์์ต๋๋ค.
๋ง์ฝ์ ์ธ์ฆ ํ ํฐ์ cookie๋ localStorage์ ์ ์ฅํ๋ค๋ฉด, playwright๋ ์คํ ๋ฆฌ์ง ์ํ๋ฅผ ํ์ผ๋ก ์ ์ฅํ๋ค๊ฐ ๋ณต์ํด์ฃผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. REST api ๋ฑ์ผ๋ก ์ธ์ฆ์ ํ ์ ์์ผ๋ฉด, api๋ฅผ ์ด์ฉํด์ ๋ก๊ทธ์ธ์ ํ ํ ํฐ์ ์ด์ฉํ ์๋ ์์ต๋๋ค. ์ด๋ฐ ๊ธฐ๋ฅ์ ๋ณดํต ์๋ ๋ก๊ทธ์ธ์ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ ๊ธฐ์ ์ ์ด์ฉํ๋ฉด ์ฝ์ต๋๋ค. ๋ฌผ๋ก ๊ท์ฐฎ์ ๊ฒฝ์ฐ๊ฐ ๋ ์๊ธด ํฉ๋๋ค. 2FA๋ ์บก์ฑ ์ฒ๋ผ ์ค์ ์ฌ์ฉ์์ ์ธ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๊ทธ๋ฐ๋ฐ์. ์ด๋ฐ ๊ฒฝ์ฐ์๋ ์๋์ผ๋ก ์ธ์ฆ ๊ณผ์ ์ ๋ง์น๋ฉด ๋ณดํต ์ฟ ํค๋ ํ ํฐ์ ๋ฐ๊ธ ๋ฐ์ผ๋, ๊ทธ๋ฌํ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํด๋๊ณ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
๋ค์ํ ๊ถํ์ด๋ ์ํ๋ฅผ ๊ฐ์ง ์ฌ์ฉ์๋ฅผ ์ฌ๋ฟ ๋ง๋ค์ด๋๊ณ , ํ
์คํธํ ๋ ๊ฐ๋จํ key ๋ฑ์ผ๋ก ๋ฐ๊ฟ์ ์ค์ ํ ์ ์๊ฒ ๋ง๋์๋ฉด ์ข์ต๋๋ค. ๊ธฐํ์๋ฅผ ์ฝ์ผ๋ฉด์ ์ด๋ค ์ฌ์ฉ์ ์กฐ๊ฑด๋ค์ด ํ์ํ์ง ๋ฏธ๋ฆฌ ์ ๋ฆฌํด์ ๊ธฐํ, ๋์์ด๋, ํ
์คํฐ ์ง๊ตฐ์ ๋ถ๋ค๊ณผ ํฉ์ํด๋์๋ฉด ํธ๋ฆฌํ ๊ฒ๋๋ค. test์์๋ ๊ธฐํ์ ๋ฑ์ ๋์ค๋ ๋๋ฉ์ธ์ ๊ฐ๋
์ ์ด์ฉํ๋ฉด ์ข๊ณ , ํ
์คํธ ๋ฐ์ดํฐ์ ํํด์๋ ํ๊ธ ๋ณ์๋ช
์ ์ฌ์ฉํด๋ณด๋ ๊ฒ๋ ๊ณ ๋ คํด๋ณผ๋งํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ ํฌ ํ์์๋ setupLogin
์ด๋ผ๋ ์
์
ํจ์๋ฅผ ๋ง๋ค์ด์ ์ฐ๊ณ ์๋๋ฐ. ๋ค์๊ณผ ๊ฐ์ด ํ
์คํธ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
const ๋ฒ ํ์น_๊ตฌ๋
_์ค์ด์ง_์์_์น๊ตฌ = 'test-1@wonderwall.kr';
test('๋ฒ ํ์น ๊ตฌ๋
์ค์ด์ง ์์ ์น๊ตฌ๊ฐ, ๋ฒ ํ์น ์ํ์ ๊ตฌ๋งคํ๋ คํ๋ฉด, ์ฃผ๋ฌธ์๋ก ๋์ด๊ฐ๋ค', async ({ page }) => {
await setupLogin(page, ๋ฒ ํ์น_๊ตฌ๋
_์ค์ด์ง_์์_์น๊ตฌ);
// ๊ตฌ๋งค ํ๋ก์ธ์ค...
});
const ๋ฒ ํ์น_๊ตฌ๋
_์ค์ธ_์น๊ตฌ = 'test-2@wonderwall.kr';
test('๋ฒ ํ์น ๊ตฌ๋
์ค์ธ ์น๊ตฌ๊ฐ, ๋ฒ ํ์น ์ํ์ ๊ตฌ๋งคํ๋ คํ๋ฉด, ์ด์ฉ๊ถ ์ฌ๋ฐ๊ธ ๊ฒฝ๊ณ ์ฐฝ์ ๋ณด์ฌ์ค๋ค', async ({ page }) => {
await setupLogin(page, ๋ฒ ํ์น_๊ตฌ๋
_์ค์ธ_์น๊ตฌ);
// ๊ตฌ๋งค ํ๋ก์ธ์ค...
});
ํต์ ํ ์ ์๋ ๋ถ์ํจ๊ณผ mockingํ๊ธฐ
๋ฐ๋ฉด์ ์ฐ๋ฆฌ๊ฐ ํต์ ํ ์ ์๋ ๋ถ์ํจ๊ณผ๋ ์์ต๋๋ค. ๋ณดํต ๊ฒฐ์ ๋ ์ด๋ฉ์ผ ๋ฑ์ด ๊ทธ๋ฐ๋ฐ์. ์ด๋ฐ ๋ถ์ํจ๊ณผ๋ ์ด์ฉ ์ ์์ด mocking์ ํ๊ณ , ๋์ค์ ์๋ ํ ์คํธํด๋ณด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ค์ ๋ก kakaopay ๊ฒฐ์ ํ๋ก์ธ์ค๋ฅผ ๋ชจ๋ ๊ฑฐ์น๊ฒ ์๋ํํ๋ค๊ณ ํด๋, kakao pay ๊ฒฐ์ ๊ณผ์ ์ด ๋ฐ๋๋ฉด ํ ์คํธ๋ฅผ ๋ค์ ๋ง๋ค์ด์ผ ํ๊ณ ์. ์ด๋ฉ์ผ ์ญ์ ๋ค์ด๋ฒ ๋ฉ์ผ, gmail๋ก ์์ํด์ ์๋ฆผ์ด ์ค๋ ๋ฑ ๋ค์ํ๊ณ ๋ณต์กํ ์๋๋ฆฌ์ค๋ฅผ ์์ง ์๋ํํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํ์ง๋ง e2e ํ ์คํธ์์ api mocking์ ์ตํ์ ์๋จ์ด ๋ ๋๊ฐ ๋ง์ต๋๋ค. ์๋ํ๋ฉด ๋ฑ ํ๋๋ผ๋ mocking์ ํ๋ ์๊ฐ, ์ด๋ฅผ ์ ์ ๋ก ํ๋ ์ดํ์ ์ํธ์์ฉ์ด ๋ชจ๋ ๋ง๊ฐ์ง๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋ฅผ ๋ค์ด ์ ํฌ์ ํ๋กฌ ์๋น์ค์์๋ ํ์๊ฐ์ ์ ํ ๋ ํ๋กฌ ํตํฉ ID์ธ email์ ์ ์ ํ์ธ์ ํ๊ณ ๊ฐ์ ์ ํ๋๋ฐ์.
- email๋ก ์ธ์ฆ ์ฝ๋๋ฅผ ๋ณด๋ด๊ณ
- ์ฌ์ฉ์๊ฐ ์ธ์ฆ ์ฝ๋๋ฅผ ์ ๋ ฅํ๋ฉด ์๋ฒ๋ก ๋ณด๋ด ๊ฒ์ฆํ๊ณ
- ๋๋จธ์ง ์ ๋ณด๋ฅผ ์ ๋ ฅํ ๋ค์ ํ์๊ฐ์ ์์ฒญ์ ๋ณด๋ด๊ณ
- ๊ฐ์ ๋ ๊ณ์ ์ผ๋ก ์๋ ๋ก๊ทธ์ธ์ ์์ผ์ค๋ค
๋ ํ๋ฆ์
๋๋ค. ๋ง์ฝ์ email์ ๋ณด๋ด๋ ๊ฑธ mockingํ๋ค๋ฉด ์ด ์ ๊ณผ์ ์ mocking ํด์ผํ ๊ฒ์
๋๋ค. ์ค์ ๋ก mockingํ๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์๊ฒผ์ต๋๋ค. simpleMockApi๋ playwright์ page.route
๋ฅผ ์ ํฌ ํ์ api ์๋ต ํ์์ ๋ง์ถ๋ฉด์, ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ๊ฐ์ผ ์ ํธ ํจ์์
๋๋ค.
async function mockSignupEmailCert(page: Page) {
await simpleMockApi(page, '/auth/email', MockEmail);
await page.route(`${API_HOST}/auth/email/verify`, async (route, request) => {
const data = await request.postDataJSON();
if (data.code === '181029') {
return route.fulfill({ status: 200, json: { code: 200 } });
} else {
return route.fulfill({ status: 200, json: { code: 400 } });
}
});
const me = { id: '497f6eca-6276-4993-bfeb-53cbbbba6f08', created: 1681648843454, email: 'user@example.com' };
await simpleMockApi(page, '/auth/signUp', { token: MockToken, me });
}
์๋ชป๋ ์ธ์ฆ๋ฒํธ๋ฅผ ์
๋ ฅํ๋ฉด ์คํจํ๊ฒ ํ๋ ๋ก์ง๋ ๋ง๋ค์ด์ผ ํ๋ ๊ณต์๊ฐ ๊ฝค ํฝ๋๋ค. MSW
๋ nock
๊ฐ์ ๋ค๋ฅธ ์๋ฃจ์
์ ์ฐ๋๋ผ๋ ๋ฐฑ์๋๋ฅผ ๊ฐ๋จํ๊ฒ๋๋ง ๋ค์ ๋ง๋๋ ๊ฒ์ฒ๋ผ ๋๊ณ , ์ค์ ๋ฐฑ์๋ api์ ๋์์ด 100% ์ผ์นํ๋ค๋ ๋ณด์ฅ๋ ์์ต๋๋ค. ์ค์ ๋ก ๋ฐฑ์๋์์ ์๋ชป๋ ํฌ๋งท์ผ๋ก ๊ฐ์ ๋ณด๋๊ฑฐ๋, ๊ฑฐ๊พธ๋ก ํ๋ฐํธ์์ ์๋ชป ๋ฐฑ์๋์ ์์ฒญ์ ๋ณด๋ธ ๊ฒฝ์ฐ ํ
์คํธ๋ ํต๊ณผํ์ง๋ง ํ๋ก๋์
์์ ์คํจํ๊ฒ ๋ฉ๋๋ค. mocking์ ํ๋ฉด ์คํจํด์ผํ๋๋ฐ ์ฑ๊ณตํ๋ ๊ฑธ๋ก ์ฐฉ๊ฐํ๊ฑฐ๋, ์ฑ๊ณตํด์ผ ํ๋๋ฐ ์คํจํ๋ ๊ฑธ๋ก ์ฐฉ๊ฐํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ ์ฌ๋ฌ๋ชจ๋ก ๊ท์ฐฎ์์ง๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
๊ทธ๋ผ์๋ ์ฌ์ ํ ์ํ๊น์ด ์ ์ ์ด๋ฉ์ผ์ด๋ ๊ฒฐ์ ๋ฑ์ ๋ถ์ํจ๊ณผ๋ ์ค์ ํ๊ฒฝ์์ ์๋์ผ๋ก ํ ์คํธํ๊ธฐ๋ ๊ท์ฐฎ๋ค๋ ๊ฒ์ ๋๋ค. ๊ฒฐ์ ๋ฅผ ํ ์คํธํ๋ ค๋ฉด ์ฌ์ฉ์์๊ฒ ์ ๋ ฅ์ ์๊ตฌํ๋ ๊ฐ์ ๋ค ์ ๋ ฅํด๋ด์ผ ํ๊ณ ์. ์ด๋ฉ์ผ๋ ์ด๋ฉ์ผ์ ์ด์ด์ ์ง์ ํ์ธํด๋ด์ผ ํ๊ณ , ์๋ฆผ ๋ฑ์ ํ ์คํธํ๋ ค๋ฉด ๋ชจ๋ฐ์ผ์์ ๊ฐ๋ฐ ์๋ฒ์ ์ ์ํด์ผ ํ๋๋ฐ, ๊ทธ๋ฌ๋ฉด ๋ฐฐํฌ๋ฅผ ํ๊ฑฐ๋, ngrok ๋ฑ์ ์ด์ฉํด์ localhost๋ฅผ ๋ฐ์์๋ ์ ์ํ ์ ์๊ฒ ํ๋ก์๋ก ์ด์ด์ค์ผ ํ๋๋ฐ์. CORS๋ ๋ณด์ ๋ฑ์ ์ด์๊น์ง ์๊ฐํ๋ฉด ๋ ๋ฒ๊ฑฐ๋ก์์ง๊ณ ๋ ํฉ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ๋ ์ค์ ์์กด์ฑ๊ณผ ์ ์ฌํ ํ๊ฒฝ์ ๊ตฌ์ถํ ์๋ ์์ต๋๋ค. ์ด๋ฉ์ผ์ mailhog ๋ฑ์ ๋ฉ์ผ ์๋ฒ๋ฅผ ์ฐ๋ ๊ฒฝ์ฐ๋ ์๊ณ , https://www.1secmail.com์ฒ๋ผ ๋ฌด๋ฃ๋ก ์ ๊ณต๋๋ ์์ ์ด๋ฉ์ผ ์๋น์ค๋ ์์ต๋๋ค. ๊ฒฐ์ ์ญ์ ํ ์คํธ ํ๊ฒฝ์์ ๊ฒฐ์ ๋ 2FA ์ธ์ฆ ๋ฑ์ ์๊ตฌํ์ง ์๋๋ค๋ฉด ์๋ํํ ์ ์๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
ํ์ง๋ง ์ด ์ญ์ ์ธ๋ถ์์ ์ ์ ํ๊ณ ๊ด๋ฆฌํ๊ธฐ๋ ๋ ๋ณต์กํด์ง๋ฉฐ, ๊ฐ์ง ์ด๋ฉ์ผ ์๋น์ค์ ์ค์ gmail์ด๋ naver mail์ ๋์์ด ๊ฐ๋ค๋ ๋ณด์ฅ๋ ์์ต๋๋ค. mocking์ด๋ ์๋ํ๋ฅผ ํ๊ธฐ์ ์์ ํ ๋ฒ ๋ ์๊ฐํ๊ณ , ์ด๋๊น์ง ํ ์ง, ์๋ ํ ์คํธ๋ ์ด๋จ์ง ๋ ์ ์คํ๊ฒ ์๊ฐํด๋ณด์๊ธธ ๊ถํด๋๋ฆฝ๋๋ค.
e2e๋ ์ผ๋ง๋?
๊ณผํ๊ธฐ์ ํ์ ํ์ฑ์ฑ ๊ต์ ๋์ด ์ฐ์ โ๊ณผํ์ ์ผ๋ง๋?โ ๋ผ๋ ์ฑ ์ด ์์ต๋๋ค. ๊ณผํ์ ํฉ๋ฆฌ์ ์ธ๊ฐ? ์๋๋ฉด ์ฌํ์ ์ผ๋ก ์ง๊ณ ์น๋ ๊ตฌ์ฑ๋ ๊ฒฐ๊ณผ๋ฌผ์ธ๊ฐ? ๊ฐ์ ์ด๋ถ๋ฒ์ ์ธ ์ง๋ฌธ์ด ์๋๋ผ. ๊ณผํ์ ์ผ๋ง๋ ํฉ๋ฆฌ์ ์ธ์ง? ์ผ๋ง๋ ๋ฏฟ์ ์ ์๋์ง ๊ณ ๋ฏผํด๋ณด์๋ ์ ๋ชฉ์ด์์ฃ .
ํ ์คํธ๋ ๋น์ทํฉ๋๋ค. ์ธ์์ ํ ์คํธ๋ฅผ ํ์คํ๋ ๋ถ๋ค๋ ๋ง์ต๋๋ค๋ง, ํ ์คํธ๋ฅผ ์ ํ๊ณ ์ถ์ดํ๋ ๋ถ๋ค๋ ๋ง์ต๋๋ค. ์๋ํ ํ ์คํธ๋ ์๋ ํ ์คํธ๋ ์ ํ ์๋ ์์ต๋๋ค. ํ๊ธด ํด์ผ ํ์ฃ . ๋ฌธ์ ๋ ๋ฌด์์, ์ด๋ป๊ฒ, ์ผ๋ง๋ ํ ์คํธ ํ ์ง์ ๋๋ค. ์ ๋ ์ ๊ฐ ๊ณ ๋ฏผํ๊ณ ์๊ฐํ๋ ๊ณผ์ ์ ์ผ๋ถ ๋ณด์ฌ๋๋ ธ์ต๋๋ค๋ง. ์์ผ๋ก ์๊ฐํด๋ณด์ค ๋ฌธ์ ๋ค์ด ๋ง์ต๋๋ค.
์ ๋ ์ค์ฉ์ฃผ์์์ ๋๋ค. ์๋ํ ํ ์คํธ 100% ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ง๋ค์ด๋ณธ ์ฌ๋์ ๊ทธ๋๋ ์๋ฌ๊ฐ ๋๋ค๋ ๊ฑธ ์๋๋ค. ์ธ์์ ๋ฐฉ๋ฒ์ ๋ง๊ณ ์๋ก ์ํธ ๋ณด์์ ์ ๋๋ค. ํ ์คํธํ๊ธฐ ์ข์ ๊ตฌ์กฐ๊ฐ ์ข์ ๊ตฌ์กฐ๋ผ๊ณ ๋ ํฉ๋๋ค๋ง, ๋ชจ๋ ๋ ๊ฑฐ์๋ฅผ ํ ์คํธํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ ๊ฒ๋ ์๋๋๋ค. ์ ํฌ๋ ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฐ๊ณ ์์ง๋ง ํ์ ์๋ ๋คํต์๋ฅผ ๋ง๊ธฐ๋ ํฉ๋๋ค. ์ธ์์๋ ํ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฉด ๋ ์ฌ์์ง๋ ์ฝ๋๋ ์์ต๋๋ค๋ง. ํ์ฌ์์ โ์ด์ ๋ค ๊ฐ์ด ts-effect๋ rescript๋ก ๊ฐํ์ ํจ์ํ์ ํฉ์๋ค~โํ๋ฉด ๋์ ํ๊ฒ ๋๋ ๊ฒ๋ ์๋๋๋ค.
e2e ํ ์คํธ๋ ๊ฐ๋ ฅํฉ๋๋ค. ์ฌ์ฉ์์ ์ ์ฌํ๊ฒ, ์ง๊ด์ ์ผ๋ก ํ ์คํธ๋ฅผ ์์ฑํ ์ ์๊ณ ์. ์ฝ๋๋ฆฌ๋ทฐ๋ฅผ ํต๊ณผํ ์๋ฌ๋ค์ ๊ฐ์ฅ ๋ง์ด ์ก์๋ด๊ธฐ๋ ํฉ๋๋ค. ๊ทธ ๊ธธ์ด ํญ์ ์ฝ์ง ์๊ณ ์. playwright ๋ฌธ์๋ง ์กฐ๊ธ์ฉ ๊พธ์คํ ์ฝ์ด๋ณด์ ๋ ๋ค์ํ ๋ฌธ์ ๊ฐ ์ด๋ฏธ ํด๊ฒฐ๋์๋ค๋ ๊ฑธ ๊นจ๋ซ๊ฒ ๋๊ณ , e2e๊ฐ ์น๋ช ์ ์ธ ์ค์๋ฅผ ๋ง์์ฃผ๋ ๊ฒฝํ๋ ์ฌ๋ฌ ๋ฒ ํ์๊ฒ๋ ๊ฒ๋๋ค.
ํ์ง๋ง e2e๋ ๋๋ฆฌ๋ฉฐ, mocking์ ํ๋ คํ ์๋ก ์๋ ์ ๋น ์ง๊ธฐ ์ฝ์ต๋๋ค. ์ผ์ด์ค๊ฐ ๋ณต์กํ๋ค๋ฉด component๋ unit test๋ฅผ ํตํด์ ๋ฏฟ์ ์ ์๊ฒ ๋ง๋ค๊ณ , E2E์์๋ ํฐ ํ๋ฆ์ด๋ ์ฐ๊ฒฐ๋ง ๊ฒ์ฌํ๋๊ฒ ์ข์ต๋๋ค.
ํ ์คํธ๋ ๋ฌด์กฐ๊ฑด ๋ง์ด ์ง ๋ค๊ณ ์ข์ ๊ฒ ์๋๋๋ค. ํ ์คํธ๋ ์ฝ๋์ด๊ธฐ ๋๋ฌธ์ ๋ณ๊ฒฝ์ฌํญ์ด ์๊ธฐ๋ฉด ๊ด๋ฆฌ๋ฅผ ํด์ผ ํฉ๋๋ค. ๋ถํ์ํ๊ฒ ๋ง์ ํ ์คํธ๋ ๊ฒฐ๊ตญ ์ ์ง๋ณด์ ๋ถ๋ด์ผ๋ก ๋์์ค๊ฒ ๋ฉ๋๋ค. ๋ง์ ์ฌ๋๋ค์ด ํ ์คํธ๊ฐ ๊ท์ฐฎ๊ธฐ๋ง ํ๊ณ , ๋์์ ์ ๋๋ค๊ณ ์ฐฉ๊ฐํ๊ฒ ๋๋ ์ด์ ์ด๊ธฐ๋ ํฉ๋๋ค.
์์ผ๋ก๋ ๋ค์ํ ๋์๋ค๊ณผ ๋ฌธ์ ๋ค์ ์๊ฐํด๋ณด๋ ค ํฉ๋๋ค. frontend์์ unit์ด๋ component test๋ ๋ฌด์์ ์ผ๋ง๋ ํด์ผํ๋์ง? type์ด๋ schema ๊ฒ์ฆ, ๋ก๊น ์ด๋ ์ค๋ ์ท ํ ์คํธ ๊ฐ์ ์๊ฐ์ ํ ์คํธ๋ ์ด๋ป๊ฒ ํ๋ฉด ์ข์์ง? ์ ์ญ์ ์์ง ์ ๋ต์ ๋ชจ๋ฅด์ง๋ง, ๊ณ์ ๊ณ ๋ฏผํ๊ณ ์์ต๋๋ค. ๋ค์ ๋ ๋ต์ฃ .