์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ Playwright E2E ํ…Œ์ŠคํŠธ

taehee
  • #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๊นŒ์ง€ ์“ฐ๋ฉด ๋ธŒ๋ผ์šฐ์ €๋„ ๊ฐ™์ด ๋ณด์ž…๋‹ˆ๋‹ค.
    }
    // ...
}

playwright ui mode ์ฐฝ์˜ ์Šคํฌ๋ฆฐ์ƒท. ์™ผ์ชฝ์—๋Š” ํ…Œ์ŠคํŠธ ๋ชฉ๋ก์ด, ์ค‘์•™์—๋Š” ํ…Œ์ŠคํŠธ ๋กœ๊ทธ๊ฐ€, ํ•˜๋‹จ์—๋Š” ํ…Œ์ŠคํŠธ ์†Œ์Šค ํƒญ์ด ๋ณด์ด๊ณ , ์šฐ์ธก์—๋Š” ์Šค๋ƒ…์ƒท ํ™”๋ฉด์ด ์žˆ๋‹ค

์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์‹œ๋‚˜๋ฆฌ์˜ค ๋งŒ๋“ค๊ธฐ

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์— ์ ์œ  ํ™•์ธ์„ ํ•˜๊ณ  ๊ฐ€์ž…์„ ํ•˜๋Š”๋ฐ์š”.

  1. email๋กœ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๋ณด๋‚ด๊ณ 
  2. ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์„œ๋ฒ„๋กœ ๋ณด๋‚ด ๊ฒ€์ฆํ•˜๊ณ 
  3. ๋‚˜๋จธ์ง€ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•œ ๋’ค์— ํšŒ์›๊ฐ€์ž… ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ 
  4. ๊ฐ€์ž…๋œ ๊ณ„์ •์œผ๋กœ ์ž๋™ ๋กœ๊ทธ์ธ์„ ์‹œ์ผœ์ค€๋‹ค

๋Š” ํ๋ฆ„์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ์— 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 ๊ฒ€์ฆ, ๋กœ๊น…์ด๋‚˜ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ ๊ฐ™์€ ์‹œ๊ฐ์  ํ…Œ์ŠคํŠธ๋Š” ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ข‹์„์ง€? ์ € ์—ญ์‹œ ์•„์ง ์ •๋‹ต์„ ๋ชจ๋ฅด์ง€๋งŒ, ๊ณ„์† ๊ณ ๋ฏผํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋˜ ๋ต™์ฃ .

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ