์ ‘๊ทผ์„ฑ์ด ์ด๋„๋Š” ์›น ํ”„๋ก ํŠธ์—”๋“œ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”

taehee
  • #Test
  • #Accessibility
  • #React
  • #NextJS

โ€œThe more your tests resemble the way your software is used, the more confidence they can give you.โ€

โ€œํ…Œ์ŠคํŠธ๊ฐ€ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ๋น„์Šทํ• ์ˆ˜๋ก ๋” ๋งŽ์€ ํ™•์‹ ์„ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹คโ€

  • Kent C. Dodds ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฐœ๋ฐœ์ž

์•ˆ๋…•ํ•˜์„ธ์š”. ์›๋”์›”์—์„œ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ์™€ ์• ์ž์ผ์„ ์ „๋„ํ•˜๋Š” ๊น€ํƒœํฌ์ž…๋‹ˆ๋‹ค.

์ „์— ํ…Œ์ŠคํŠธ์—์„œ (๋” ์ž˜, ๋” ์ž์ฃผ) ๋ฐฐ์šฐ๊ธฐ๋ฅผ ์“ฐ๋ฉด์„œ๋„ ์–ธ๊ธ‰ํ–ˆ์ง€๋งŒ. ์ˆ˜๋™, ์ž๋™ ํ…Œ์ŠคํŠธ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋†“์น˜๊ณ  ์žˆ๋˜ ๋ถ€๋ถ„์„ ๊นจ๋‹ซ๊ฒŒ ๋„์™€์ฃผ๋Š” ํ•™์Šต ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธํ•  ์‹œ๊ฐ„์ด ์—†๋‹ค๊ณ  ๋งํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ง€๋งŒ, ๋’ค๋Šฆ๊ฒŒ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜๋ก ๋น„์šฉ์€ ์ปค์ง€๊ณ  ์ถœ์‹œ๋Š” ์˜คํžˆ๋ ค ๋Šฆ์–ด์ง‘๋‹ˆ๋‹ค. ์ผ์ •์„ ์ •ํ™•ํ•˜๊ฒŒ ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง€๊ณ , ์Šคํ”„๋ฆฐํŠธ ๋งˆ์ง€๋ง‰์— ์•ผ๊ทผํ•˜๊ฒŒ ๋  ๊ฐ€๋Šฅ์„ฑ์€ ๋†’์•„์ง€์ฃ .

์ œ๊ฐ€ ํšŒ์‚ฌ์— ๋“ค์–ด์™€์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ „๋„?ํ•˜๊ธฐ ์‹œ์ž‘ํ•œ์ง€ ์„ธ ๋‹ฌ์ด ์ง€๋‚ฌ์Šต๋‹ˆ๋‹ค. ์›๋ž˜ 1๊ฐœ ๋ฐ–์— ์—†๋˜ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋Š” ์ด์ œ unit + component test 98๊ฐœ, e2e ํ…Œ์ŠคํŠธ๋„ 35๊ฐœ๋กœ ๋Š˜์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค. ํƒ์ƒ‰์  ํ…Œ์ŠคํŠธ๋„ ์ˆ˜์‹œ๋กœ ์‹ค์‹œํ•ด์„œ ์ƒˆ ๊ธฐ๋Šฅ์€ ๋ฌผ๋ก ์ด๊ณ , ๊ธฐ์กด ๊ธฐ๋Šฅ์—์„œ๋„ ๋‹ค์–‘ํ•œ ์—๋Ÿฌ๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ  ์žˆ์ฃ .

์ด๋ฒˆ์—๋Š” ์ ‘๊ทผ์„ฑ์„ ํ† ๋Œ€๋กœ ์›น UI ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ์–ด๋–ป๊ฒŒ ๋ฐœ์ „ํ–ˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  WAI-ARIA ํ‘œ์ค€์˜ role๊ณผ aria ์†์„ฑ๋“ค์„ ์ด์šฉํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

UI ํ…Œ์ŠคํŠธ๋Š” ์ž๋™ํ™” ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

  • โ€œ์ตœ์•…์˜ ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธโ€ ์บก์ฒ˜ & ๋ฆฌํ”Œ๋ ˆ์ด ์ž๋™ํ™”๋ž€ ์‚ฌ์šฉ์ž์˜ ์กฐ์ž‘์„ ๊ธฐ๋กํ•˜๊ณ , ๊ทธ๊ฒƒ์„ ์žฌ์ƒํ•˜๋Š” GUI ์ƒ์—์„œ์˜ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค. โ€ฆ ํ•˜์ง€๋งŒ ์บก์ฒ˜ & ๋ฆฌํ”Œ๋ ˆ์ด ์ž๋™ํ™”๋Š” ์ƒ๋‹นํžˆ ์—ด์•…ํ•œ ๋ฐฉ์‹์— ์†ํ•ฉ๋‹ˆ๋‹ค.

[๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์‹œํ”„ํŠธ ๋ ˆํ”„ํŠธ ํ…Œ์ŠคํŠธ] ๋‹ค์นดํ•˜์‹œ ์ฃผ์ด์น˜

ํ…Œ์ŠคํŠธ๋Š” ํƒ์ƒ‰์  ํ…Œ์ŠคํŠธ์™€ ๊ฐ™์€ ์ˆ˜๋™ ํ…Œ์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜์ง€๋งŒ, ๋ณดํ†ต์€ ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋ฅผ ์ด์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ์™€ ๋‹ฌ๋ฆฌ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์งœ์ง€ ์•Š๋Š” ๋ถ„๋“ค๋„ ๋งŽ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•œ ์˜›๋‚  ์ฑ…๋“ค์„ ์ฝ์–ด๋ด๋„ UI ํ…Œ์ŠคํŠธ ์ž๋™ํ™”๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๋น„ํšจ์œจ์ ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ๊ณผ๊ฑฐ์™€ ํ˜„์žฌ๋Š” ๋ฌด์—‡์ด ๋‹ฌ๋ผ์กŒ๋Š”์ง€, ์˜› ํ˜„์ธ๋“ค์€ ์™œ UI ํ…Œ์ŠคํŠธ๋Š” ๋น„ํšจ์œจ์ ์ด๋ผ ์ƒ๊ฐํ–ˆ๋Š”์ง€ ๊ณ ๋ฏผํ•ด๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ ์–ธํ˜• UI, ํ”„๋ก ํŠธ์—”๋“œ์˜ ๋ถ„๋ฆฌ

๊ณผ๊ฑฐ์— UI๋Š” ๋ช…๋ นํ˜•์œผ๋กœ ๊ฐœ๋ฐœํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋„ ๋ชจ๋ฐ”์ผ ์•ฑ ์ชฝ์€ ๋ช…๋ น์ ์ธ UI๊ฐ€ ๋งŽ์ง€์š”. UI ์ƒํƒœ๋Š” ๋„ˆ๋ฌด๋‚˜ ๋‹ค์–‘ํ•œ๋ฐ, ์ด๋ฅผ ๋™์‹œ๋‹ค๋ฐœ์ ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋‹ค๋ณด๋‹ˆ ์—๋Ÿฌ๊ฐ€ ๋ฐฅ ๋จน๋“ฏ์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์˜› ์‚ฌ๋žŒ๋“ค์€ Model์„ View์—์„œ ๋ถ„๋ฆฌํ•ด๋‚ด๋ ค๊ณ  ํ–ˆ์ง€์š”. Model์€ ๋” ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์› ๊ณ , View ๋ ˆ์ด์–ด๋Š” ์ตœ๋Œ€ํ•œ ์–‡๊ฒŒ ๊ฐ€์ ธ๊ฐ€๋ ค ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ง€ ์•Š์•„๋„ ๋  ์ •๋„๋กœ ๋ป”ํ•˜๊ณ  ๋‹จ์ˆœํ•˜๊ณ  ๋ฉ์ฒญํ•œ UI๋งŒ ๋‚จ๊ฒจ๋‘๋ ค ํ–ˆ์ฃ .

ํ•˜์ง€๋งŒ ์„ธ์ƒ์€ ๋ณ€ํ–ˆ์Šต๋‹ˆ๋‹ค. Web์—์„œ๋Š” React, Vue, Svelte์™€ ๊ฐ™์ด ์„ ์–ธ์ ์ธ UI๊ฐ€ ๋“์„ธํ–ˆ์Šต๋‹ˆ๋‹ค. MVC ํŒจํ„ด์€ ์—ญ์‚ฌ ์†์œผ๋กœ ์‚ฌ๋ผ์ง€๊ณ , ๋ฆฌ์•กํŠธ๋Š” ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ์„œ UI๋ฅผ ์žฌ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. MVC ํŒจํ„ด์„ ๋ฆฌ์•กํŠธ์— ์–ต์ง€๋กœ ๋ผ์›Œ๋งž์ถฐ์„œ ์ดํ•ดํ•ด๋ณด๋ ค ํ•˜๋ฉด ๋ญ”๊ฐ€ ์ž˜ ์•ˆ ํ’€๋ฆฌ๋Š” ์ด์œ ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์•กํŠธ์˜ ์„ ์–ธ์ ์ธ UI๋Š” ์„ฑ๋Šฅ์—์„œ๋Š” ์†ํ•ด๋ฅผ ๋ณด์•˜์ง€๋งŒ (๊ฐ€์ƒ ๋”์€ ๋Š๋ฆฝ๋‹ˆ๋‹ค!), ๋” ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ณ  ๋” ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์› ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ ๋ชจ๋ธ์„ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ ์„ค๊ณ„ํ•ด์„œ ์•ฑ์—์„œ ๋ถ„๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์—ˆ์ฃ .

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ๋ถ„๋ฆฌ๋˜๋ฉด์„œ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ๊ฐ€ ๋Œ€๋ถ€๋ถ„์˜ ๋ชจ๋ธ๊ณผ ๋กœ์ง์„ ๊ฐ€์ ธ๊ฐ€๋ฉด์„œ, ํ”„๋ก ํŠธ์—”๋“œ์—๋Š” ๋ชจ๋ธ์ด๋ผ ๋ถ€๋ฅผ๋งŒํ•œ ๋กœ์ง์ด ๊ฑฐ์˜ ๋‚จ์ง€ ์•Š๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋ก ํŠธ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋ณด์—ฌ์ฃผ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์— ๋ณด๋‚ด๋Š” ์ˆ˜๋™์ ์ธ ์—ญํ• ๋งŒ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฑ…์—์„œ ์„ ๋ฐฐ๋‹˜๋“ค์ด ํ•˜๋Š” ๋ง์— ์˜๋ฌธ์ด ์ƒ๊น๋‹ˆ๋‹ค. ์œ ๋‹› ํ…Œ์ŠคํŠธ๋Š” ๋งŽ์ด ํ•˜๊ณ , UI ํ…Œ์ŠคํŠธ๋Š” ์ž๋™ํ™”ํ•˜์ง€ ๋ง๋ผ๋Š”๋ฐโ€ฆ ๊ทธ๋Ÿฌ๋ฉด ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋Š” ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜์ง€ ๋ง๋ผ๋Š” ๊ฒŒ ์•„๋‹Œ๊ฐ€์š”?

๋ฌผ๋ก  ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜คํžˆ๋ ค ๋ณต์žกํ•ด์ง„ ๊ฒƒ์€ UI ๋กœ์ง๊ณผ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. ์ ‘๊ณ  ํŽผ์ณ์ง€๋Š” UI, disable, hoverํ–ˆ์„ ๋•Œ์—๋งŒ ๋ณด์ด๋Š” UI, ๋งํฌ์™€ ๋ฒ„ํŠผ, ์ฒดํฌ๋ฐ•์Šค, ์™ธ๋ถ€ ์˜์กด์„ฑ์ธ ์„œ๋ฒ„์™€์˜ ์ƒํ˜ธ์ž‘์šฉ, ๋นจ๊ฐ„์ƒ‰๊ณผ ํšŒ์ƒ‰โ€ฆ ์ด๋Ÿฐ ๊ฑด unit test ๋งŒ์œผ๋กœ ๊ฒ€์ฆํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋Š” UI๋ฅผ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. UI ํ…Œ์ŠคํŠธ๋Š” ๊ทธ๋Ÿฌ๋ฉด ์™œ ์–ด๋ ค์šธ๊นŒ์š”?

css, xpath, capture & replay๋Š” ์™œ ์‹คํŒจํ–ˆ๋‚˜

๋งˆ์šฐ์Šค๋กœ ๋ฒ„ํŠผ ํด๋ฆญ : Button_Push, x=200, y=300

ํ•˜์ง€๋งŒ ๋งŒ์•ฝ ๊ทธ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด ์ž๋™ํ™”ํ•œ ๋’ค์— ๋Œ€์ƒ ์†Œํ”„ํŠธ์›จ์–ด์˜ UI๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์•ž์˜ ์˜ˆ์—์„œ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ฒ„ํŠผ์˜ ์œ„์น˜๋ฅผ x=200, y=300์—์„œ ์•ฝ๊ฐ„ ์›€์ง์—ฌ ๋ณธ๋‹ค๋ฉด ์–ด๋–จ๊นŒ์š”? ๊ทธ๋Ÿฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ 100๊ฐœ๋ผ๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?

[๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์‹œํ”„ํŠธ ๋ ˆํ”„ํŠธ ํ…Œ์ŠคํŠธ] ๋‹ค์นดํ•˜์‹œ ์ฃผ์ด์น˜

์ œ๊ฐ€ ์ „์— ์ผํ–ˆ๋˜ ํ•œ ํšŒ์‚ฌ์—์„œ๋Š” xpath๋‚˜ css ์„ ํƒ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. playwright์—์„œ xpath๋กœ ์ฃผ๋ฌธ์„œ์— ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ์–‘์ด ๋ฉ๋‹ˆ๋‹ค.

// ์ฃผ๋ฌธ์„œ์— ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ
await page.locator('//*[@id="main"]/div/section[1]/div[3]/label/div/div[1]/input').fill('01011112222')

xpath๋Š” dom tree์—์„œ ํ•ด๋‹น element๋กœ ๊ฐ€๋Š” ๊ฒฝ๋กœ์ธ๋ฐ์š”. ์ด๋Ÿฌ๋ฉด ์ค‘๊ฐ„์— tree๊ฐ€ ์กฐ๊ธˆ๋งŒ ๋ณ€ํ•ด๋„ test๊ฐ€ ๊นจ์ง‘๋‹ˆ๋‹ค. ์Šคํƒ€ํŠธ์—…์—์„œ ์ฃผ๋ฌธ์„œ์—๋Š” ๋งค์ผ ๊ฐ™์ด UI์— ์ƒˆ๋กœ์šด ์š”์†Œ๊ฐ€ ์ถ”๊ฐ€๋˜๊ณ  ์‚ฌ๋ผ์ง€์ง€์š”. ์ฝ”๋“œ๊ฐ€ ์ง๊ด€์ ์ด์ง€๋„ ์•Š์•„์„œ ์ฃผ์„๋„ ๋‹ฌ์•„์ค˜์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ๊ฒช์œผ๋‹ˆ ๋ณดํ†ต์€ xpath๊ฐ€ ์•„๋‹ˆ๋ผ css๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ง์ด์ฃ .

// ์ฃผ๋ฌธ์„œ์— ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ
await page.locator('input.phone-input').fill('01011112222')

ํ•˜์ง€๋งŒ ์ €ํฌ ์ฃผ๋ฌธ์„œ์—๋Š” ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ํ•„๋“œ๊ฐ€ 3๊ฐœ๋‚˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ๋ฌธ์ž ์ •๋ณด, ๋ฐฐ์†ก์ง€ ์ •๋ณด, ์ด๋ฒคํŠธ ์ •๋ณด๊ฐ€ ๊ทธ๊ฒƒ์ธ๋ฐ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด ํ•™์ƒ์ด์‹  ๊ณ ๊ฐ ๋ถ„์ด ์นœ๊ตฌ์—๊ฒŒ ์„ ๋ฌผ์„ ํ•œ๋‹ค๋ฉด ์ฃผ๋ฌธ์ž๋Š” ๋ถ€๋ชจ์ด์ง€๋งŒ, ๋ฐฐ์†ก ์ •๋ณด๋Š” ์นœ๊ตฌ์˜ ์ „ํ™”๋ฒˆํ˜ธ๋กœ, ์ด๋ฒคํŠธ๋Š” ๋ณธ์ธ์˜ ์ „ํ™”๋ฒˆํ˜ธ๋กœ ์‘๋ชจํ•  ์ˆ˜๋„ ์žˆ๊ฒ ์ฃ . ์ด๋Ÿฌ๋ฉด ๊ฐ™์€ input์ด 3๊ฐœ ์žˆ์œผ๋‹ˆ css ์„ ํƒ์ž๊ฐ€ ๊ฒน์น  ๊ฒ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ๊ณ ์ณ์ค˜์•ผ๊ฒ ์ฃ ?

// ์ฃผ๋ฌธ์ž ์ •๋ณด์— ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ
await page.locator('section.customer-info-section input.phone-input').fill('01011112222')

์ด๋ ‡๊ฒŒ classname ์ถฉ๋Œ์˜ ๊ณ ํ†ต์„ ๊ฒช๋˜ ์‚ฌ๋žŒ๋“ค์€, classname์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” css module์ด๋‚˜ styled component, tailwind ์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ๋Œ€์•ˆ์„ ์ฐพ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋Š” ์ด๋Ÿฐ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๋˜ ์–ด๋ ค์›Œ์ง„๋‹ค๋Š” ์ ์ด์—ˆ์–ด์š”.

์˜ˆ๋ฅผ ๋“ค์–ด ์ €ํฌ๋Š” styled component๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ๋ฌด์ž‘์œ„๋กœ ์ƒ์„ฑ๋œ classname ์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

<div data-size='h48' data-state='focused' class='sc-781e12f9-3 bjGNWZ'>
    <div class='sc-781e12f9-4 bHoUeX'>
        <p id=':r2:' class='sc-781e12f9-2 ebNRsq'>
            *ํœด๋Œ€์ „ํ™” (-์ œ์™ธ)
        </p>
        <input aria-labelledby=':r2:' type='tel' placeholder='ํœด๋Œ€์ „ํ™”๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”' value='' />
    </div>
    <div class='sc-781e12f9-5 fYyzVC'></div>
</div>

์ด๋ฅผ playwright locator๋กœ ๊ฐ€์ ธ์˜ค๋ ค ํ•˜๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ๋˜๊ฒ ์ฃ . ์ฝ๊ธฐ๋„ ์–ด๋ ต์ง€๋งŒ, css๋ฅผ ์กฐ๊ธˆ์ด๋ผ๋„ ๋ฐ”๊พธ๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์งˆ ๊ฒ๋‹ˆ๋‹ค.

// ์ฃผ๋ฌธ์ž ์ •๋ณด์— ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ
await page.locator('sc-781e12f9-2 input[type=tel]').fill('01011112222')

๊ณผ๊ฑฐ์— ์“ฐ๋˜ GUI ํ”„๋ ˆ์ž„์›Œํฌ๋“ค์˜ ์‚ฌ์ •๋„ ๋” ๋ชปํ•˜๋ฉด ๋ชปํ–ˆ์ง€, ๋” ๊ดœ์ฐฎ์ง€๋Š” ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋งค๋ฒˆ ์ธ์œ„์ ์œผ๋กœ test id๋ฅผ ๋‹ฌ์•„์ฃผ๊ฑฐ๋‚˜, ํ™”๋ฉด์—์„œ ์–ด๋–ค ์œ„์น˜์— ์žˆ๋Š”์ง€ ์ขŒํ‘œ๋ฅผ ์ง€์ •ํ•ด์„œ ํด๋ฆญํ•˜๋Š” ์‹์ด์—ˆ๋Š”๋ฐ์š”. ๊ทธ๋ž˜์„œ ์˜› ํ…Œ์Šคํ„ฐ๋“ค์€ ๋‹น์‹œ ์—…์ฒด๋“ค์ด ํ™๋ณดํ•˜๋Š” UI ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋„๊ตฌ๋ฅผ ๋ฏฟ์ง€ ๋ชปํ•˜๊ฒŒ ๋œ ๊ฒƒ์ด์—ˆ์ฃ .

์ด๋Ÿฐ ํ…Œ์ŠคํŠธ ๋ฐฉ์‹๋“ค์€ ์‚ฌ๋žŒ์—๊ฒŒ ์ง๊ด€์ ์ด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์ž…์žฅ์ด ์•„๋‹ˆ๋ผ, ๊ธฐ์ˆ ์ ์ธ ๋ฉด์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์—โ€ฆ ๊ธฐ์ˆ ์ด ๋ฐ”๋€Œ๋ฉด ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ์‹๋„ ๋ฐ”๋€Œ์–ด์•ผ ํ•˜์ฃ  User Interface๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ธ๋ฐ๋„ ๋ง์ด์ฃ !

ํ•˜์ง€๋งŒ ํ…Œ์ŠคํŠธ์—์„œ ์‹œ๋งจํ‹ฑํ•œ ์ ‘๊ทผ์„ฑ ํ‘œ์ค€์„ ์ด์šฉํ•˜๋ฉด์„œ ์ด์•ผ๊ธฐ๊ฐ€ ๋‹ฌ๋ผ์กŒ์Šต๋‹ˆ๋‹ค.

role๊ณผ accessible name์„ ์ด์šฉํ•ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ

์ตœ๊ทผ web์€ ๋ฌผ๋ก ์ด๊ณ  ์•ˆ๋“œ๋กœ์ด๋“œ๋‚˜ IOS์—์„œ๋„ UI ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์€ ๋ฌด์—‡์ด๊ธธ๋ž˜, UI ํ…Œ์ŠคํŠธ์— ๋„์›€์ด ๋˜์—ˆ์„๊นŒ์š”? ์‹œ๊ฐ ์žฅ์• ์ธ ๋ถ„๋“ค์ด ์ด์šฉํ•˜๋Š” ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ๋Š”, ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋„๊ตฌ๋กœ๋„ ๊ฒ€์‚ฌํ•˜๊ธฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์€ ๋‹ค์–‘ํ•œ ์ธก๋ฉด์ด ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ํŠนํžˆ ์ €์‹œ๋ ฅ์ž๋ฅผ ํฌํ•จํ•œ ์‹œ๊ฐ ์žฅ์• ์ธ ๋ถ„๋“ค์ด ์ด์šฉํ•˜๋Š” ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ฅผ ์ด์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค.

์‹œ๊ฐ ์žฅ์• ์ธ ๋ถ„๋“ค์€ UI๋ฅผ ๋ˆˆ์œผ๋กœ ์ฝ๊ธฐ ์–ด๋ ค์›Œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ผ๋Š” ๊ธฐ๊ณ„๊ฐ€ ํ™”๋ฉด์„ ์ฝ์–ด์ฃผ๋Š”๋ฐ์š”. ๊ธฐ๊ณ„๊ฐ€ UI๋ฅผ ์ฝ๊ธฐ ์œ„ํ•ด์„œ๋Š” UI์˜ ์‹œ๋งจํ‹ฑํ•œ ์˜๋ฏธ๋“ค์„ ์•Œ ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ณ , ์—ฌ๊ธฐ์—๋Š” ํ‘œ์ค€์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์–ด๋–ค ๊ฐœ๋ฐœ์ž๊ฐ€ div์— onClick์„ ๋‹ฌ์•„๋†“๊ณ  ๋ฒ„ํŠผ์ด๋ผ๊ณ  ํ•œ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด๊ฒŒ ๋ฒ„ํŠผ์ธ์ง€ ๊ทธ๋ƒฅ div์ธ์ง€, ์•„๋‹ˆ๋ฉด ๋งํฌ์ธ์ง€, ์ฒดํฌ๋ฐ•์Šค์ธ์ง€โ€ฆ ๊ธฐ๊ณ„๋Š” ์•Œ ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ Accessible Rich Internet Applications ์„ ์ค„์—ฌ์„œ ARIA ํ‘œ์ค€์ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. ARIA๋Š” ์›น ์ฝ˜ํ…์ธ ๊ฐ€ ๋ชจ๋‘์—๊ฒŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก, element๊ฐ€ ์–ด๋–ค ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ! ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋„๊ตฌ๋„ ๊ฐ™์€ ์ •๋ณด๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด <button> ๊ตฌ๋งคํ•˜๊ธฐ </button> ๊ฐ™์€ ์š”์†Œ๊ฐ€ ์žˆ๋‹ค๋ฉด, ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋Š” โ€œ๊ตฌ๋งคํ•˜๊ธฐ, ๋ฒ„ํŠผโ€์ด๋ผ๊ณ  ์ฝ์–ด์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ž๋™ํ™” ๋„๊ตฌ์—๊ฒŒ๋„ โ€œ๊ตฌ๋งคํ•˜๊ธฐ ๋ฒ„ํŠผ์„ ์ฐพ์•„์„œ ํด๋ฆญํ•ด!โ€ ํ•˜๊ณ  ๋ช…๋ นํ•  ์ˆ˜ ์žˆ์ฃ .

์ด ๋ฐฉ์‹์€ Kent C dodds ์”จ๊ฐ€ testing-library๋ฅผ ํ†ตํ•ด ์ฒ˜์Œ ๊ฐœ๋ฐœํ–ˆ๊ณ , playwright ๋“ฑ๋„ ๋ฐ›์•„ ๋“ค์˜€์Šต๋‹ˆ๋‹ค. playwright๋กœ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

await page.getByRole('button', { name: '๊ตฌ๋งคํ•˜๊ธฐ' }).click();

์ง๊ด€์ ์ด์ง€ ์•Š๋‚˜์š”? ์•ž์„œ ์ด์•ผ๊ธฐํ–ˆ๋˜ ์ฃผ๋ฌธ์ž ์ •๋ณด๋„ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ง๊ด€์ ์œผ๋กœ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

await page.getByRole('region', { name: '์ฃผ๋ฌธ์ž ์ •๋ณด' })
					.getByPlaceholder('*ํœด๋Œ€์ „ํ™”').fill('01011112222');

์ด๋Ÿฐ ์ ‘๊ทผ์„ฑ ์š”์†Œ๋Š” ํŠน์ • ํ”„๋ ˆ์ž„์›Œํฌ๋‚˜ css, dom tree ๊ตฌ์กฐ ๋“ฑ์— ์˜์กดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ฃผ๋ฌธ์„œ์— ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ์ด ํ…Œ์ŠคํŠธ๋Š” ๊นจ์ง€์ง€ ์•Š์„ ๊ฒ๋‹ˆ๋‹ค. ์ฃผ๋ฌธ์ž ์ •๋ณด section์— *ํœด๋Œ€์ „ํ™”๋ผ๋Š” placeholder๋ฅผ ๊ฐ€์ง„ input๋งŒ ์žˆ๋‹ค๋ฉด ๋ง์ด์ฃ .

div์— role์„ ์ค˜์„œ semantic html์„ ๋งŒ๋“ค๊ธฐ

์ด๋Ÿฌํ•œ role์€ ๋Œ€๋ถ€๋ถ„ semantic html์— ๋Œ€์‘๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด button์€ button role์„ ๊ฐ€์ง€๊ณ ์š”. input[type=checkbox]๋Š” checkbox role์„ ๊ฐ€์ง€๊ณ ์š”. section ์€ region role์„ ๊ฐ€์ง€๋Š” ์‹์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ํšŒ์‚ฌ๋‚˜ ๋””์ž์ด๋„ˆ๊ฐ€ ์›ํ•˜๋Š” ์Šคํƒ€์ผ๋กœ ์ปค์Šคํ…€ํ•˜๊ณ  ์‹ถ๋‹ค๊ฑฐ๋‚˜, browser์— ๋”ฐ๋ผ์„œ๋Š” css reset์ด ์ž˜ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์‹์˜ ์ด์œ ๋กœ semantic role ๋Œ€์‹  div ๋“ฑ์„ ์ด์šฉํ•ด ์ง์ ‘ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊นŽ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ ํšŒ์‚ฌ๋„ ๊ทธ๋Ÿฐ๋ฐ์š”. div์— onClick์„ ๋‹ฌ์•„๋‘” ์ปดํฌ๋„ŒํŠธ๋Š” button role์ด ์—†์–ด์„œ ์‹œ๋งจํ‹ฑํ•˜์ง€๋„ ์•Š๊ณ , ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋„ ์ฐพ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด button tag๋ฅผ ๊ผญ ์จ์•ผ ํ•˜๋Š” ๊ฑธ๊นŒ์š”?

๋‹คํ–‰ํžˆ๋„ ํ‰๋ฒ”ํ•œ div๋„ role์„ ์ ํ•ฉํ•˜๊ฒŒ ๋‹ฌ์•„์ฃผ๊ณ , role์— ๋งž๋Š” ๋™์ž‘์„ ๊ตฌํ˜„ํ•ด์ฃผ๋ฉด semanticํ•œ html์ฒ˜๋Ÿผ ๋Œ€์šฐ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐ role์„ ํ•ด์„คํ•˜๋Š” mdn ๋ฌธ์„œ์— ์นœ์ ˆํ•˜๊ฒŒ ๋‚˜์™€ ์žˆ์–ด์„œ, ๋ณด๊ณ  ๋”ฐ๋ผํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜์ง€์š”.

๋‹ค์Œ์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ด๋ณธ ์ปค์Šคํ…€ Button ์ปดํฌ๋„ŒํŠธ์˜ ์˜ˆ์ž…๋‹ˆ๋‹ค. ์‹ค๋ฌด์—์„œ ์“ฐ๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ํ•ต์‹ฌ๋งŒ ๋‚จ๊ฒผ๊ณ  ํƒ€์ž…๋„ ์ƒ๋žตํ–ˆ์Šต๋‹ˆ๋‹ค.

function Button({ children, onClick, disabled=false }){
    function handleCommand(event){
        if (disabled){
            return;
        }

        if (
            event instanceof KeyboardEvent &&
            event.key !== "Enter" &&
            event.key !== " "
        ) {
            return;
        }

        onClick()
    }

    return (
        <div tabindex={0} role="button" aria-disabled={disabled} onClick={handleCommand} onKeyDown={handleCommand}>
            {children}
        </div>
    )
}

aria-label ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„ ๋ถ™์—ฌ์ฃผ๊ธฐ

ํ•œํŽธ ์ž‘์—…์„ ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์งœ๋‹ค๋ณด๋ฉด ์ ‘๊ทผ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„(accessible name)์ด ๋ฌด์—‡์ธ์ง€ ๋ชจํ˜ธํ•œ ๊ฒฝ์šฐ๋„ ๋งŒ๋‚ฉ๋‹ˆ๋‹ค. ํŠนํžˆ icon ๋ฒ„ํŠผ์˜ ์‚ฌ๋ก€๋ถ€ํ„ฐ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ”๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. fromm store ๋กœ๊ณ  ์šฐ์ธก์— ์นดํŠธ ๋ชจ์–‘์˜ ์•„์ด์ฝ˜ ๋งํฌ์™€ ๋ง‰๋Œ€๊ธฐ 3๊ฐœ๊ฐ€ ์žˆ๋Š” ํ–„๋ฒ„๊ฑฐ ๋ฉ”๋‰ด ์•„์ด์ฝ˜์ด ์žˆ๋‹ค

์œ„ ์‚ฌ์ง„์„ ๋ณด์‹œ๋ฉด ์ด๋ฏธ์ง€์™€ ์•„์ด์ฝ˜๋“ค์ด ๋ณด์ž…๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฐ๋ฆฌ๋”๋‚˜ ํ…Œ์ŠคํŠธ ๋„๊ตฌ, ๊ฒ€์ƒ‰์—”์ง„๊ณผ ๊ฐ™์€ ๊ธฐ๊ณ„๋Š” ์ด๋ฏธ์ง€ ์ธ์‹ AI๋ผ๊ณ  ํ•ด๋„ ์ด๋Ÿฐ ์•„์ด์ฝ˜์˜ ์ด๋ฏธ์ง€๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฐ๋ฆฌ๋”๋กœ ์ฝ์–ด๋ณด๋ฉด ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…์„ ์ฝ์–ด์ฃผ๊ฑฐ๋‚˜, โ€œ๋ผ๋ฒจ์ด ์ง€์ •๋˜์ง€ ์•Š์Œโ€ ๊ฐ™์€ ๋ง๋งŒ ๋ฐ˜๋ณตํ•ด์„œ ๋‚˜์˜ค์ง€์š”.

'์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. Chrome d21e0c09๋กœ ์‹œ์ž‘๋˜๋Š” ๋ฌด์˜๋ฏธํ•œ ํŒŒ์ผ๋ช…์„ ์ฝ์–ด์ฃผ๊ณ  ์žˆ๋‹ค'

<img src='https://store-dev-contents.frommyarti.com/store_goods/image/393/d21e0c09-f03d-45ac-a41e-6883a2f69d31_1687221845859'/>

๊ทธ๋Ÿฌ๋ฉด โ€˜๋ผ๋ฒจโ€™์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ์š”? ๋ผ๋ฒจ์€ ์ด๋ ‡๊ฒŒ ํ…์ŠคํŠธ๊ฐ€ ์—†๋Š” ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅํ•œ ๋งํฌ๋‚˜ ๋ฒ„ํŠผ ๋“ฑ์— ๋‹ฌ์•„์ฃผ๋Š” ์ด๋ฆ„์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด aria-label attribute๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ฌ์•„์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<Button aria-label='์ „์ฒด ๋ฉ”๋‰ด' onClick={() => {
    showMenu();
}}>
    <Icon src={menuIcon} />
</Button>

๊ทธ๋Ÿฌ๋ฉด ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋Š” ๋ฌผ๋ก ์ด๊ณ , ํ…Œ์ŠคํŠธ ๋„๊ตฌ์™€ ๊ฒ€์ƒ‰ ์—”์ง„ ๋กœ๋ด‡๋„ ์ด๊ฒŒ ๋ฉ”๋‰ด ๋ฒ„ํŠผ์ด๋ผ๋Š” ๊ฑธ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

'์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. ๋ฉ”๋‰ด ๋ฒ„ํŠผ์ด ํฌ์ปค์Šค ๋˜์–ด ์žˆ๊ณ , ์ „์ฒด๋ฉ”๋‰ด, ํ˜„์žฌ ๋ฒ„ํŠผ ์ด๋ผ๊ณ  ๋‚˜์˜จ๋‹ค.'

๋ฒจ์€ ์ด๋ฏธ์ง€ ๋Œ€์‹ ์— ํ…์ŠคํŠธ๋ฅผ ๋†“์•„๋„ ์ƒ๊ด€ ์—†๊ฒŒ ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ํด๋ž˜์Šค ๋ช…์ฒ˜๋Ÿผ ๋„ฃ์–ด๋‘๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋Š”๋ฐ ๊ทธ๋Ÿฌ๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. i18n ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ฑ์„ ์ด์šฉํ•œ ๋ฒˆ์—ญ์ด ์žˆ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์ดํŠธ์˜ ์–ธ์–ด์— ๋งž๊ฒŒ ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

// ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค. X
<Button aria-label='test menu button alt' onClick={() => {
    showMenu();
}}>
    <Icon src={menuIcon} />
</Button>
// ์‚ฌ์šฉ์ž ์–ธ์–ด์— ๋งž๊ฒŒ ๋ฒˆ์—ญ ํ•ฉ๋‹ˆ๋‹ค. O
const { t } = useTranslation('common');

<Button aria-label={t('header-menu-alt'))} onClick={() => {
    showMenu();
}}>
    <Icon src={menuIcon} />
</Button>

section์ด๋‚˜ list์— aria-labelledby์™€ aria-current ์ด์šฉํ•˜๊ธฐ

๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ฌ๋Ÿฟ ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์—ฌ๋Ÿฌ section์ด๋‚˜ list๋กœ ๋‚˜๋ˆ ์ค˜์•ผ ํ•˜๋Š”๋ฐ์š”.

์˜ˆ๋ฅผ ๋“ค์–ด ์ €ํฌ ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€๋Š” ์—ฌ๋Ÿฌ ๋‹จ๊ณ„(multi step)๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ section ๋งˆ๋‹ค โ€˜ํ™•์ธโ€™์ด๋ผ๋Š” ๋˜‘๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง„ ๋ฒ„ํŠผ์ด ๋ฐ˜๋ณต๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ํ˜„์žฌ ์Šคํ…๋งŒ ์‹œ๊ฐ์ ์œผ๋กœ ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ๋ชจ๋“  step์ด ๋ณด์ด์ง€ ์•Š๊ฒŒ ๋ Œ๋” ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

ํ™•์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ผ๊ณ ๋งŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋Š” ์ด ์ค‘์— ์–ด๋–ค ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์•ผ ํ•˜๋Š”์ง€ ๋ชจ๋ฆ…๋‹ˆ๋‹ค.

// Error: page.getByRole('button', { name: 'ํ™•์ธ' }) resolved to 4 elements:
await page.getByRole('button', { name: 'ํ™•์ธ' }).click();

'ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€์˜ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. ๊ฐ€์ž…์•ฝ๊ด€, ์ด๋ฉ”์ผ ์ฃผ์†Œ, ์ธ์ฆ๋ฒˆํ˜ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์˜

์ด๋Ÿฐ ๋•Œ์—๋Š” ๊ฐ section์˜ ์ œ๋ชฉ์— id๋ฅผ ๋‹ฌ์•„์ฃผ๊ณ , aria-labelledby๋ฅผ ์จ์„œ ์—ฐ๊ฒฐํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด region role์„ ๊ฐ€์ง€๊ฒŒ ๋˜๊ณ , section ์•ˆ์—์„œ button์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<section aria-labelledby='policy-section-title'>
    <h2 id='policy-section-title'>๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ๊ฐ€์ž…์•ฝ๊ด€์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.</h2>
    ...
    <Button>ํ™•์ธ</Button>
</section>
<section aria-labelledby='email-section-title'>
    <h2 id='email-section-title'>๊ฐ€์ž…ํ•˜์‹ค ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”</h2>
    ...
    <Button>ํ™•์ธ</Button>
</section>
...
await page.getByRole('region', { name: '๊ฐ€์ž…ํ•˜์‹ค ์ด๋ฉ”์ผ ์ฃผ์†Œ' })
          .getByRole('button', { name: 'ํ™•์ธ' })
          .click();

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด section์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ, ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” section์ด ๋ฌด์—‡์ธ์ง€๋Š” ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ์œ„์น˜๋Š” aria-current๋กœ ์„ค์ •ํ•˜๊ณ , ๊ทธ ์™ธ์˜ ํŽ˜์ด์ง€์—๋Š” focusํ•  ์ˆ˜ ์—†๊ฒŒ focus lock์„ ๊ฑธ์–ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” aria-current='step' attribute๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ํ˜น์€ ๋’ค๋กœ ๊ฐ€๋Š” step ๊ฐ„์˜ ์ด๋™์„ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

<section aria-labelledby='policy-section-title' aria-current={isCurrent ? 'step' : undefined}>
    <h2 id='policy-section-title'>๋งŒ๋‚˜์„œ ๋ฐ˜๊ฐ€์›Œ์š”. ๊ฐ€์ž…์•ฝ๊ด€์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.</h2>
    ...
    <Button>ํ™•์ธ</Button>
</section>
await expect(page.getByRole('region', { name: '๊ฐ€์ž…์•ฝ๊ด€' })).toHaveAttribute('aria-current', 'step');

aria-expanded๋กœ ์ ‘๊ณ  ํŽผ์นœ ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ

'๋ฐฐ์†ก์ •๋ณด์™€ ๊ตํ™˜, ํ™˜๋ถˆ, A/S ์•ˆ๋‚ด ๋ผ๊ณ  ์ ํžŒ ๊ตฌ์—ญ์„ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. ๋ฐฐ์†ก์ •๋ณด๋Š” ์ ‘ํ˜€์ ธ ์žˆ๊ณ , ๊ตํ™˜ ํ™˜๋ถˆ A/S ์•ˆ๋‚ด๋Š” ํŽผ์ณ์ ธ์„œ ๋‚ด์šฉ์ด๋ณด์ธ๋‹ค.

๋˜ ์žฌ๋ฏธ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ ‘๊ณ  ํŽผ์ณ์ง€๋Š” ์š”์†Œ๋“ค์ž…๋‹ˆ๋‹ค. ํ™”๋ฉด์ด ์ข์„ ๋•Œ ๋งค์šฐ ๊ธธ๊ณ  ๋งŽ์€ ์ •๋ณด๋ฅผ ๊ฐ์ถฐ๋‘๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ์š”. ๋ฌธ์ œ๋Š” ์ด๋Ÿฐ ์š”์†Œ๋“ค์ด ์ ‘ํ˜€์ ธ ์žˆ๋Š”์ง€, ํŽผ์น  ์ˆ˜ ์žˆ๋Š”์ง€ ์‹œ๊ฐ ์žฅ์• ์ธ์€ ๋ฌผ๋ก ์ด๊ณ  ๊ธฐ๊ณ„๋“ค๋„ ์•Œ ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒ๋‹ˆ๋‹ค. ํ…์ŠคํŠธ๊ฐ€ ์—†๋Š” ๊ฒƒ์ด๋ฉด ๋ชจ๋ฅด์ง€๋งŒ, ๋ˆˆ์—๋งŒ ๋ณด์ด์ง€ ์•Š์„ ๋ฟ์ด์ง€ ์‹ค์ œ๋กœ๋Š” ์ˆจ๊ฒจ์ง„ ์ฑ„๋กœ ๋ Œ๋”๋˜์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. transition ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•ด์„œ๋ผ๊ฑฐ๋‚˜, tab์„ ํ†ตํ•ด ์ ‘ํ˜€์ง„ ์ปดํฌ๋„ŒํŠธ ์•ˆ์˜ ์š”์†Œ์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋‚˜, ๋ณด์ด์ง€ ์•Š๋Š” ๋‚ด์šฉ์ด ๊ฒ€์ƒ‰์€ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋“ฑ์ด ๊ทธ๋ ‡์ง€์š”.

์ด๋Ÿฐ ๊ฒฝ์šฐ aria-expanded๋ฅผ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŽผ์ณ์ ธ์žˆ๋Š”์ง€ ์•Œ๋ ค์ฃผ๊ณ , ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<InfoButton
  aria-expanded={isOpen}
  aria-controls="delivery-info"
  onClick={() => {
    setIsOpen((old) => !old)
  }}
  type="button">
  ๋ฐฐ์†ก์ •๋ณด
  <Arrow />
</InfoButton>
<div id="delivery-info" aria-hidden={!isOpen}>
  ...
</div>

aria ์†์„ฑ์€ ์ž์˜์ ์ธ props์™€ ๋‹ฌ๋ฆฌ dom์— ๋ Œ๋”๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๋‹ค์Œ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

const showMoreButton = page.getByRole('button', { name: '๋ฐฐ์†ก์ •๋ณด' });

// ๋‹ซํ˜€ ์žˆ์Œ
await expect(showMoreButton).toHaveAttribute('aria-expanded', 'false');

// ํด๋ฆญํ•˜๋ฉด
await showMoreButton.click();

// ์—ด๋ฆผ
await expect(showMoreButton).toHaveAttribute('aria-expanded', 'true');

// ๋‹ค์‹œ ํด๋ฆญํ•˜๋ฉด
await showMoreButton.click();

// ๋‹ซํž˜
await expect(showMoreButton).toHaveAttribute('aria-expanded', 'false');

๋ฌผ๋ก  ํ…Œ์ŠคํŠธ๊ฐ€ ์Šคํƒ€์ผ๊นŒ์ง€ ํ…Œ์ŠคํŠธํ•ด์ฃผ์ง„ ์•Š์Šต๋‹ˆ๋‹ค๋งŒ. ์ด๋ ‡๊ฒŒ ๋งŒ๋“  aria ์†์„ฑ์„ ์ด์šฉํ•ด์„œ ์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ์„ ๊ฑธ์–ด์ฃผ๋ฉด, css์™€ ์—ฐ๊ฒฐ์„ ์ข€ ๋” ์‰ฝ๊ฒŒ ํ™•์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์œ„์˜ ์ •๋ณด ์ปดํฌ๋„ŒํŠธ๋Š” ํŽผ์ณ์ง€๋ฉด ์šฐ์ธก์˜ ํ™”์‚ดํ‘œ๊ฐ€ 180๋„ ํšŒ์ „ํ•˜๋Š”๋ฐ์š”. ์ด๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ๋Š” styled ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ, ๋‹ค๋ฅธ css framework๋“ค๋„ aria ๋ฅผ ์ด์šฉํ•œ ์Šคํƒ€์ผ๋ง์„ ์ง€์›ํ•˜๋‹ˆ ์ฐพ์•„๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. (์ €๋Š” ํšŒ์‚ฌ์— ๋“ค์–ด์˜ค๊ธฐ ์ „๊นŒ์ง€ ์ฃผ๋กœ tailwind๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.)

export const ArccordionButton = styled(ClickableComponent)`
    // ... ์ƒ๋žต

    &[aria-expanded='true'] ${ArrowIcon} {
        transform: rotateZ(180deg);
    }
`;

ํ—ค๋“œ๋ฆฌ์Šค ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๊ธฐ

ํ•œํŽธ์œผ๋กœ๋Š” Tab์ด๋‚˜ Select, Dropdown, Datepicker ์ฒ˜๋Ÿผ ๋งค์šฐ ๋ณต์žกํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ธฐ๋Š” ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค๋ณด๋‹ˆ ์ ‘๊ทผ์„ฑ๋„ ํ…Œ์ŠคํŠธ๋„ ํฌ๊ธฐํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ์š”. ๊ฒฐ๊ตญ ์ˆ˜ ๋งŽ์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ๊ณ ์น˜๋ ค ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ์กด์— ์ž˜ ์ž‘๋™ํ•˜๋˜ ๊ธฐ๋Šฅ์„ ๋ง๊ฐ€ํŠธ๋ฆฌ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๋•Œ์—๋Š” Radix UI, Ariakit, ๊ทธ๋ฆฌ๊ณ  ์ตœ๊ทผ์— chakra ui ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“  Ark UI ๊ณผ ๊ฐ™์ด ์ž˜ ๋งŒ๋“  ํ—ค๋“œ๋ฆฌ์Šค ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ—ค๋“œ๋ฆฌ์Šค ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋กœ์ง๋งŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๊ณ , ์Šคํƒ€์ผ์€ ํšŒ์‚ฌ์™€ ์ œํ’ˆ์— ๋งž๊ฒŒ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ž…๋‹ˆ๋‹ค. ๋‹ค๋“ค ์ ‘๊ทผ์„ฑ์„ ์—ผ๋‘์— ๋‘๊ณ  Aria๋ฅผ ์ •๊ตํ•˜๊ฒŒ ๋‹ฌ์•„๋‘์—ˆ์„ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ํ‚ค๋ณด๋“œ ์ ‘๊ทผ์ด๋‚˜, focus ๊ด€๋ฆฌ, ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ ๋“ฑ์„ ์ •๊ตํ•˜๊ณ  ์„ธ๋ฐ€ํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ €ํฌ ํŒ€์—์„œ๋Š” ์ด ์ค‘์— ๊ฐ€์žฅ ์„ฑ์ˆ™ํ•œ Radix๋ฅผ ๋„์ž…ํ•ด์„œ ์‚ฌ์šฉ ์ค‘์ธ๋ฐ์š”. ์ƒํ’ˆ์˜ ์˜ต์…˜์„ ์„ ํƒํ•˜๋Š” Select ์ปดํฌ๋„ŒํŠธ๋Š” combobox์™€ option role์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ, ๋‹ค์Œ์ฒ˜๋Ÿผ ์ง๊ด€์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ธฐ๋ณธ๊ฐ’ ์ค‘์— ์ €ํฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ๊ณผ ๋‹ค๋ฅธ ๋™์ž‘์€ ์ปค์Šคํ…€ํ•˜๊ธฐ๋„ ์‰ฌ์› ๊ณ ์š”.

'์ƒํ’ˆ ์ •๋ณด๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. ์˜ต์…˜์„ ์„ ํƒํ•˜์„ธ์š” ๋ผ๊ณ  ์ ํžŒ ์š”์†Œ๋ฅผ ํด๋ฆญํ–ˆ๋”๋‹ˆ, Night .version, Day .version ๊ณผ ๊ฐ™์€ ์˜ต์…˜๋“ค์ด ๋œฌ๋‹ค.'

// e2e/goods.ts
test('์ผ์ฒดํ˜• ์˜ต์…˜์„ ์„ ํƒํ•˜๊ณ  ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค', async ({ page }) => {
    await page.goto(์ผ์ฒดํ˜•_์˜ต์…˜_์ƒํ’ˆ_์ƒ์„ธ_๋งํฌ);

    await page.getByRole('combobox', { name: '์˜ต์…˜์„ ์„ ํƒํ•˜์„ธ์š”' }).click();

    await page.getByRole('option', { name: 'Night .version' }).click();

    // x ๋ฒ„ํŠผ์„ ํด๋ฆญ
    await page.getByRole('button', { name: '์„ ํƒํ•œ ๋ฏธ๋‹ˆ์•จ๋ฒ” 2์ง‘ : Over The Moon ํฌ์นด ์•จ๋ฒ” Day .version ๋นผ๊ธฐ' }).click();

    await expect(page.getByRole('button', { name: '์„ ํƒํ•œ ๋ฏธ๋‹ˆ์•จ๋ฒ” 2์ง‘ : Over The Moon ํฌ์นด ์•จ๋ฒ” Day .version ๋นผ๊ธฐ' })).not.toBeVisible();
});

์ž‘๋™ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ๋ฌธ์„œ๋กœ์„œ ํ…Œ์ŠคํŠธ

์ด๋Ÿฌํ•œ ์ ‘๊ทผ์„ฑ์˜ ์–ธ์–ด๋Š” ์ธ๊ฐ„๊ณผ ๊ธฐ๊ณ„ ์‚ฌ์ด์— ์•ฝ์†ํ•œ ๊ณต์šฉ์–ด์™€๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ธฐํš์„œ๋‚˜ ์š”๊ตฌ์‚ฌํ•ญ ์ •์˜์„œ์— ๋‚˜์˜จ ๋‚ด์šฉ์„ ๊ฑฐ์˜ ๊ทธ๋Œ€๋กœ ํ…Œ์ŠคํŠธ๋กœ ์˜ฎ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฑฐ๊พธ๋กœ ๊ธฐํš์„œ ๋“ฑ์— ๋ชจํ˜ธํ•˜๊ฒŒ ์ •์˜๋œ ๋ถ€๋ถ„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ณ ์š”.

์˜ˆ๋ฅผ ๋“ค์–ด โ€œ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์œผ๋ฉด ์žฅ๋ฐ”๊ตฌ๋‹ˆ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์—†๋‹ค.โ€๋Š” ์ •์ฑ…์ด ์žˆ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค. ๊ทธ๋ž˜์„œ โ€œ๋กœ๊ทธ์ธ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ์„ ์š”๊ตฌํ•˜๋Š” ์ฐฝ์„ ๋„์šด๋‹คโ€, โ€œํ™•์ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹คโ€, โ€œ์ทจ์†Œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋’ค๋กœ๊ฐ€๊ธฐ ๋œ๋‹คโ€ ๊ฐ™์€ ์‹์œผ๋กœ ๊ตฌ์ฒด์ ์ธ ๊ธฐํš์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์ ‘๊ทผ์„ฑ์˜ ์–ธ์–ด๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ๋กœ ์˜ฎ๊ธฐ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ฝค ์ง๊ด€์ ์ด์ฃ ? ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ œํ’ˆ์„ ์ธ์ˆ˜ํ•˜๋Š” ๊ณ„์•ฝ ์กฐ๊ฑด์ด๋ผ๊ณ  ํ•ด์„œ, โ€œ์ธ์ˆ˜ ํ…Œ์ŠคํŠธโ€๋ผ๊ณ ๋„ ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

// ์ƒํ’ˆ ๋ชฉ๋ก ํŽ˜์ด์ง€์—์„œ
await page.goto(`${CLIENT_HOST}/goods/product`);

// ์žฅ๋ฐ”๊ตฌ๋‹ˆ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ฉด
await page.getByRole('link', { name: '์žฅ๋ฐ”๊ตฌ๋‹ˆ' }).click();

// ๋กœ๊ทธ์ธ์„ ์š”๊ตฌํ•˜๋Š” ๊ฒฝ๊ณ ์ฐฝ์ด ๋œฌ๋‹ค
const alertDialog = page.getByRole('alertdialog', { name: '๋กœ๊ทธ์ธ' });

// ๊ฒฝ๊ณ  ์ฐฝ ์•ˆ์— ์žˆ๋Š” ํ™•์ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด
await alertDialog.getByRole('button', { name: 'ํ™•์ธ' }).click();

// ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ณ , ๋กœ๊ทธ์ธ ํ—ค๋”๊ฐ€ ๋ณด์ธ๋‹ค.
await expect(page.getByRole('heading', { name: '๋กœ๊ทธ์ธ' })).toBeVisible()

์ด๋Ÿฐ ํ…Œ์ŠคํŠธ๋Š” ์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์ „๋ถ€ํ„ฐ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฏธ๋ฆฌ ์งœ๋‘˜ ์ˆ˜๋„ ์žˆ๊ณ ์š”. ํ…Œ์ŠคํŠธ๋ฅผ ์งœ๋Š” ์‚ฌ๋žŒ๊ณผ, ๊ฐœ๋ฐœ ํ•˜๋Š” ์‚ฌ๋žŒ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋กœ๋Š” ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๊ธฐํš์ž ๋‹˜๊ณผ ๊ณต์œ ํ•˜๋ฉด์„œ, ๊ธฐํš์„œ์— ๋น ์ง„ ์ผ€์ด์Šค๋ฅผ ๋…ผ์˜ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌด์—‡๋ณด๋‹ค๋„ ๋Œ๋ ค๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์„œ์ด๊ธฐ๋„ ํ•˜๊ณ ์š”!

test์™€ ํ•จ๊ป˜ ์ž์‹ ๊ฐ ์žˆ๊ฒŒ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ

test๋Š” ๊ฐœ๋ฐœ ์ „์— ๋ฏธ๋ฆฌ ์ž‘์„ฑํ•ด๋‘๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์ˆ˜์‹œ๋กœ ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ๊ณ ์š”. ๊ธฐํš์„œ์—์„œ ๋น ํŠธ๋ฆฐ ๊ณณ์ด ์—†๋Š”์ง€ ๊นŒ๋จน์ง€ ์•Š๊ณ  ํ™•์ธํ•  ์ˆ˜๋„ ์žˆ์ฃ .

ํ•˜์ง€๋งŒ ๊ธฐ๋Šฅ์„ ์™„์„ฑํ•œ ๋’ค์—๋„ test์˜ ์—ญํ• ์€ ๋๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋ฉด์„œ, ๊ธฐ์กด์— ๋งŒ๋“ค์–ด์ง„ ๊ธฐ๋Šฅ์ด ๋ง๊ฐ€์ง€์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ์š”. ์ด๋ฅผ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋ผ ํ•ฉ๋‹ˆ๋‹ค.

ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๋Š” ํŠนํžˆ ์•ˆ์ •์ ์ธ ๊ฒŒ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜๋ก ์œ ์ง€๋ณด์ˆ˜ ๋ถ€๋‹ด๋„ ์ปค์ง€๊ธฐ ๋•Œ๋ฌธ์ด์ฃ . ์ฝ”๋“œ๋ฅผ ํ•œ ์ค„ ๊ณ ์น  ๋•Œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ์ˆ˜ ๋ฐฑ ์ค„ ๊ณ ์ณ์•ผ ํ•œ๋‹ค๋ฉดโ€ฆ ๊ตฌํ˜„ ์ฝ”๋“œ๋ณด๋‹ค ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ์ฝ”๋“œ๋ฅผ ๊ณ ์น˜๋Š”๋ฐ ๋” ๋งŽ์€ ์‹œ๊ฐ„์„ ์“ฐ๊ฒŒ ๋  ๊ฒ๋‹ˆ๋‹ค.

์ตœ๊ทผ์— Header ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜๋ฉด์„œ, ์ƒํ’ˆ ์ •๋ ฌ์ด ๋˜์ง€ ์•Š๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ์ƒ๊ธด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์ด ๋ฒ„๊ทธ๋ฅผ ์žก์•„์คฌ๋Š”๋ฐ์š”. ํ…Œ์ŠคํŠธ DB์—์„œ ๊ฐ€์žฅ ๋น„์‹ผ ์ƒํ’ˆ์ด๊ณ , ๊ธฐ๋ณธ ์ •๋ ฌ์—์„œ๋Š” ๋งจ ์œ„์—์„œ ๋ณด์—ฌ์ง€๋Š” 42,000์› ์งœ๋ฆฌ ์ƒํ’ˆ์ด ๋ณด์ด์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” ๊ฐ„๋‹จํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ •๋ ฌ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ , ๋‚ฎ์€ ๊ฐ€๊ฒฉ ์ˆœ ์˜ต์…˜์„ ์„ ํƒํ•˜๋Š” ์‹์œผ๋กœ ๋˜์–ด ์žˆ์ฃ .

test('์ •๋ ฌํ•  ์ˆ˜ ์žˆ๋‹ค', async ({ page }) => {
    // given
    await page.goto(`${CLIENT_HOST}/product`, { waitUntil: 'networkidle' });
    await expect(page.getByRole('link', { name: '42,000์›' })).toBeVisible();

    // when
    await page.getByRole('button', { name: '์ •๋ ฌ' }).click();
    await page.getByRole('option', { name: '๋‚ฎ์€ ๊ฐ€๊ฒฉ์ˆœ' }).click();

    // then
    await expect(page.getByRole('link', { name: '42,000์›' })).not.toBeVisible();
});

์ด ํ…Œ์ŠคํŠธ๋Š” ๊ฐ•๊ฑดํ•ฉ๋‹ˆ๋‹ค. ์ƒํ’ˆ ํŽ˜์ด์ง€๋Š” inline-block์œผ๋กœ ๋˜์–ด ์žˆ๋˜ ๊ฒƒ์„, grid๋กœ ๋ฐ”๊พธ๊ธฐ๋„ ํ–ˆ๊ณ ์š”. CSR์„ ํ•˜๋˜ ๊ฒƒ์„ SSG๋กœ ๋ฐ”๊พธ๋ ค๊ณ  ์‹œ๋„ ์ค‘์ด๊ธฐ๋„ ํ•˜๊ณ ์š”. ์ด๋ฏธ์ง€๋„ img ํƒœ๊ทธ์—์„œ Next image๋กœ ๋ชจ๋‘ ๋ฐ”๊พธ๋Š” ๊ฐ•๋„ ๋†’์€ ๋ฆฌํŒฉํ† ๋ง์„ ๊ฑฐ์ณ์™”์ง€๋งŒ. ์‹ค์ œ ์‚ฌ์šฉ์ž ๋™์ž‘์— ์•„๋ฌด ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ๋Š” ์ž˜ ํ†ต๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์–ด๋Š ๋‚ โ€ฆ Header์˜ ๊ตฌํ˜„ ๋ฐฉ์‹์„ ๋ฐ”๊ฟจ๋”๋‹ˆ, ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์กŒ์Šต๋‹ˆ๋‹ค. ํ—ค๋”๋ž‘์€ ์•„๋ฌด ์ƒ๊ด€ ์—†์–ด๋ณด์ด๋Š”๋ฐ ์–ด์งธ์„œ? ๊ฐ„๋‹จํ•˜๊ฒŒ ์›์ธ์„ ๋งํ•˜์ž๋ฉด ์—‰๋šฑํ•˜๊ฒŒ๋„ ์ž‘์—…์ž๊ฐ€ Header๋ž‘ ๋ฌด๊ด€ํ•œ useEffect์˜ ์˜์กด์„ฑ ๋ฐฐ์—ด์„ ๊ฑด๋“œ๋ ธ๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค. page๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ์„ cancelํ•˜๋Š” ์ฝ”๋“œ์˜€๋Š”๋ฐ์š”. eslint disable ๋˜์–ด ์žˆ๋Š” ๊ฒŒ ๋งˆ์Œ์— ์•ˆ ๋“ค์–ด์„œ, lint ๊ฐ€ ์‹œํ‚ค๋Š”๋Œ€๋กœ ์˜์กด์„ฑ ๋ฐฐ์—ด์— cancel๋ฅผ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค. ์•ˆํƒ€๊น๊ฒŒ๋„ cancel๋Š” ์ฐธ์กฐ๊ฐ€ ์•ˆ์ •์ ์ธ ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ์—ˆ๊ณ ์š”, ์ •๋ ฌ ๋ฐฉ์‹์„ ๋ฐ”๊ฟ€ ๋•Œ๋งˆ๋‹ค ์š”์ฒญ์„ ์ทจ์†Œ ์‹œ์ผœ๋ฒ„๋ ธ๋˜ ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๊ตฌ์ฒด์ ์ธ ์ด์œ ๊ฐ€ ๋ฌด์—‡์ด์—ˆ๋“ , ์ฝ”๋“œ ๋ฆฌ๋ทฐ์–ด๋“ค์€ ์ด๋Ÿฐ ๋ถ€์ž‘์šฉ์„ ์ƒ๊ฐํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. Header๋ฅผ ๋ณ€๊ฒฝํ•œ PR์€ ์ฝ”๋“œ๋ฆฌ๋ทฐ๋ฅผ ํ†ต๊ณผํ–ˆ์ง€๋งŒโ€ฆ ๋จธ์ง€ ๋˜๊ธฐ ์ง์ „์— e2e๊ฐ€ ์ด ์—๋Ÿฌ๋ฅผ ๊ฒ€๊ฑฐํ•ด์ฃผ์—ˆ์ฃ .

ํ…Œ์ŠคํŠธ๋Š” ๊ทธ ์™ธ์—๋„ ๋งŽ์€ ๋ฒ„๊ทธ๋ฅผ ๋ฏธ๋ฆฌ ์žก์•„์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ๋ฌธ ์™„๋ฃŒ ํŽ˜์ด์ง€์— ๋“ค์–ด์˜ค๋ฉด ์ƒํ’ˆ ํŽ˜์ด์ง€๋กœ ๋‚ ์•„๊ฐ€๋ฒ„๋ฆฌ๋Š” ๋ฒ„๊ทธ. ์ฃผ๋ฌธ์„œ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€์ž๋งˆ์ž ์‚ฌ์ดํŠธ๊ฐ€ ์ฃฝ์–ด๋ฒ„๋ฆฌ๋Š” ๋ฒ„๊ทธ ๋“ฑ๋“ฑโ€ฆ e2e๊ฐ€ ๊นจ์ง€๋Š” ๊ฒฝ์šฐ๋Š” ๋ณดํ†ต ์ ‘๊ทผ์„ฑ์ด ์ž˜๋ชป๋˜์–ด ์žˆ๋˜ ๊ฒƒ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ฐ”๋กœ์žก์€ ๊ฒฝ์šฐ ๋ฟ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐํš์„œ ์ž์ฒด๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ์ „๊นŒ์ง€๋Š”, ํšŒ๊ท€ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ธฐํš์„œ์— ์ •์˜๋œ ๋™์ž‘๋“ค์„ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋งค PR๊ณผ ๋ฐฐํฌ๋งˆ๋‹ค ์ฝ”๋“œ๋ฅผ ์ง€์ผœ์ฃผ๊ฒ ์ง€์š”.

ํ…Œ์ŠคํŠธ ์ปดํŒŒ์ผ๋Ÿฌ : UI ํ…Œ์ŠคํŠธ๋ฅผ ๋” ์‰ฝ๊ณ  ์ง๊ด€์ ์ธ ์–ธ์–ด๋กœ ์งค ์ˆ˜๋Š” ์—†์„๊นŒ?

์—ฌ์ „ํžˆ ์•„์‰ฌ์šด ์ ์€ ์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋Š” ๊ฒฐ๊ตญ QA ์—”์ง€๋‹ˆ์–ด๋‚˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฌผ๋ก  ๊ธฐํš์ž ๋ถ„๋“ค ์ค‘์— ์ฝ”๋”ฉ์„ ๋ฐฐ์›Œ์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์‹œ๋Š” ๋ถ„๋“ค๋„ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ์—ฌ๋Ÿฌ๋ชจ๋กœ ์–ด๋ ค์šด ์ผ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋Š” ์ตœ๊ทผ์— ํ…Œ์ŠคํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๋ผ๋Š” ๊ฑธ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. yaml ํŒŒ์ผ์— ํ•œ๊ตญ์–ด๋กœ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋ช…์„ธ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ๋„๊ตฌ์ธ๋ฐ์š”. ์ด ์—ญ์‹œ ํ…Œ์ŠคํŠธ๊ฐ€ ์ ‘๊ทผ์„ฑ์— ๊ธฐ๋ฐ˜ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋งŒ๋“ค๊ธฐ ์–ด๋ ค์› ์„ ๊ฒ๋‹ˆ๋‹ค.

'์—๋””ํ„ฐ๋ฅผ ์ฐ์€ ์Šคํฌ๋ฆฐ์ƒท. ์ขŒ์ธก์—๋Š” ํ•œ๊ตญ์–ด๋กœ ์ ํžŒ ๊ฒฝ๊ณ ์ฐฝ ์˜ต์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š” ๊ฐ™์€ ๋ฌธ๊ตฌ๋“ค์ด ์žˆ๊ณ , ์˜ต์…˜์„ ์„ ํƒํ•ด์ฃผ์„ธ์š” }); ๊ฐ™์€ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜๋˜๊ณ  ์žˆ๋‹ค

์ ‘๊ทผ์„ฑ์€ ํ…Œ์ŠคํŠธ๋‚˜ ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™” ๊ฐ™์€ ์ด๋“์ด ์—†๋”๋ผ๋„, ์ ‘๊ทผ์„ฑ์€ ์‡ผํ•‘๋ชฐ ๋“ฑ์˜ ์‚ฌ์—…์ž๋“ค์ด ๋ฒ•์œผ๋กœ ์ง€์ผœ์•ผํ•˜๋Š” ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค๋งŒ. ์ €ํฌ ํšŒ์‚ฌ๋ฅผ ๋น„๋กฏํ•ด ๋งŽ์€ ์—…์ฒด๋“ค์ด ์ด๋Ÿฌํ•œ ์‚ฌ์‹ค์„ ์ž˜ ๋ชจ๋ฅด๊ณ  ์žˆ๊ณ , ํ•˜๊ณ  ์‹ถ์–ด๋„ ์–ด๋ ค์›Œํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ ‘๊ทผ์„ฑ์€ ์‹œ๊ฐ ์žฅ์• ์ธ ๋ถ„๋“ค๋งŒ์„ ์œ„ํ•œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ ‘๊ทผ์„ฑ์€ ๋ชจ๋‘๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด๊ณ  ์ž˜ ๋งŒ๋“  ์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ์ผ์„ ๋” ํŽธํ•˜๊ฒŒ ๋„์™€์ค„ ๊ฒ๋‹ˆ๋‹ค.

์ €ํฌ ํŒ€๋„ ๋ถ€์กฑํ•œ ๊ฒŒ ๋งŽ์ง€๋งŒ, ๋…ธ๋ ฅํ•˜๊ณ  ๊ฐœ์„ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์•ž์œผ๋กœ๋„ ํ…Œ์ŠคํŠธ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค์–‘ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ํ’€์–ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค. ๋˜ ๋ต™์ฃ .

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

Art Changes Life

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

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