๐Ÿ‘จโ€๐Ÿ’ป

์†Œํ”„ํŠธ์›จ์–ด๊ฐ€ ์“ฐ์ด๋Š” ๋ฐฉ์‹๊ณผ ๋‹ฎ์€ ํ…Œ์ŠคํŠธ

Kent C.Dodds ๋‹˜์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜ ๋ฌธ์žฅ์ด ์ƒ๋‹นํžˆ ์ž์ฃผ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฐ€ ๋งŒ๋“  Testing Library์˜ ์›์น™์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

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

์‚ฌ์šฉ์ž(end-user)๋Š” ์ œํ’ˆ์„ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์—ˆ๋Š”์ง€ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ˆˆ ์•ž์— ๋ณด์ด๋Š”(๋˜๋Š” ๊ท€๋กœ ๋“ฃ๋Š”) ํ…์ŠคํŠธ, ๋ฒ„ํŠผ, ์ž…๋ ฅ ์ฐฝ๊ณผ ๊ฐ™์€ UI๋ฅผ ์ธ์ง€ํ•˜๊ณ  ๊ทธ์— ๋งž๊ฒŒ ํ–‰๋™(์ž…๋ ฅ, ํด๋ฆญ, ์Šคํฌ๋กค ๋“ฑ)ํ•˜์ฃ .

ํ…Œ์ŠคํŠธ ๋„๊ตฌ ์—ญ์‹œ ์ œํ’ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๋˜ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๋กœ ์ทจ๊ธ‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Testing Library์—์„œ๋Š” UI ์š”์†Œ๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•(query)์˜ ์šฐ์„  ์ˆœ์œ„๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.

  1. ๋ชจ๋“  ์‚ฌ์šฉ์ž์˜ ๊ฒฝํ—˜์„ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋Š” ์ฟผ๋ฆฌ: role, placeholder ์†์„ฑ, label ์š”์†Œ, DOM text node ๋“ฑ
  2. ์ ‘๊ทผ์„ฑ์„ ๊ณ ๋ คํ•œ ์ฟผ๋ฆฌ: alt, title ์†์„ฑ
  3. Test ID: ์œ„ ํ•ญ๋ชฉ์˜ ์˜ˆ์™ธ ์ผ€์ด์Šค ๋Œ€์‘, data-testid ๋“ฑ์˜ ์†์„ฑ

์ฒซ ๋ฒˆ์งธ์™€ ๋‘ ๋ฒˆ์งธ ์šฐ์„  ์ˆœ์œ„๋Š” ์›น ์ ‘๊ทผ์„ฑ ํ‘œ์ค€์— ๋”ฐ๋ฅด๋Š” ์†์„ฑ ๋ฐ ์š”์†Œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ๊ทธ ๋™์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์ ‘๊ทผ์„ฑ ๊ด€๋ จ ์ •๋ณด๋ฅผ selector๋กœ ํ™œ์šฉํ•œ ๊ฒฝํ—˜์ด ์—†์—ˆ๋Š”๋ฐ ๊ฐ€์žฅ ๋†’์€ ์šฐ์„  ์ˆœ์œ„์— ์žˆ๋‹ค๋Š” ์ ์ด ๋ˆˆ์— ๋„์—ˆ์Šต๋‹ˆ๋‹ค. ์ ‘๊ทผ์„ฑ ๊ด€๋ จ ์†์„ฑ์„ ์ž˜ ํ™œ์šฉํ•˜๋ ค๋ฉด accessibility tree๋ฅผ ๋จผ์ € ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ (Accessibility Tree)

์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ๋Š” DOM ํŠธ๋ฆฌ์˜ ๋ถ€๋ถ„ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. DOM ํŠธ๋ฆฌ๊ฐ€ DOM ์š”์†Œ๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฒƒ์ฒ˜๋Ÿผ ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ๋Š” ์ ‘๊ทผ์„ฑ ๊ฐ์ฒด(accessibility object)๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ DOM ์š”์†Œ๋กœ๋ถ€ํ„ฐ ์ ‘๊ทผ์„ฑ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ ‘๊ทผ์„ฑ ๊ฐ์ฒด๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์š”์†Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. (์ถœ์ฒ˜: MDN)

nameThe name of a user interface element
descriptionAn accessible description provides additional information, related to an interface element, that complements the accessible name
roleMain indicator of type
stateA dynamic property expressing characteristics of an object that may change in response to user action or automated processes

์ด ์š”์†Œ๋ฅผ ์กฐํ•ฉํ•ด accessible name๊ณผ accessible description์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์ฒดํฌ๋ฐ•์Šค ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด accessible name์ด ์–ด๋–ป๊ฒŒ ์ •ํ•ด์ง€๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. (์ž์„ธํ•œ ์‚ฐ์ • ๋ฐฉ์‹์€ Accessible Name and Description Computation 1.1์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”)

<!-- Case 1: id์™€ for ์†์„ฑ์œผ๋กœ ์—ฐ๊ฒฐ์ง“๊ธฐ --> <input id="my-checkbox" type="checkbox" /> <label for="my-checkbox">๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”?</label> <!-- Case 2: id์™€ aria-labelledby ์†์„ฑ์œผ๋กœ ์—ฐ๊ฒฐ์ง“๊ธฐ --> <input aria-labelledby="my-checkbox" type="checkbox" /> <label id="my-checkbox">๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”?</label> <!-- Case 3: label ์š”์†Œ๋กœ ๊ฐ์‹ธ๊ธฐ --> <label> <input type="checkbox" /> ๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”? </label>

ํฌ๋กฌ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์˜ '์š”์†Œ(Element)' ํƒญ์—๋Š” ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ๋ฅผ ํฌํ•จํ•œ ์ •๋ณด๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ์œ„์˜ 3๊ฐ€์ง€ ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ๋ชจ๋‘ ์•„๋ž˜ ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๊ฐ™์ด '๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”?'๊ฐ€ accessible name์œผ๋กœ ์„ค์ •๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํฌ๋กฌ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ accessible name์ด '๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”?'๋กœ ์„ค์ •๋œ ๊ฒƒ์„ ํ™•์ธ

์ ‘๊ทผ์„ฑ ์ •๋ณด ๊ธฐ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

Testing Library์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ ์•ž์„œ ์‚ดํŽด๋ณธ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์š”์†Œ๋ฅผ ์„ ํƒํ•ด ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { screen } from '@testing-library/dom' document.body.innerHTML = ` <label> <input type="checkbox" /> ๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”? </label> ` test('role ์†์„ฑ์œผ๋กœ ์„ ํƒํ•˜๊ธฐ', async () => { const checkbox = screen.getByRole('checkbox') expect(checkbox).not.toBeChecked() }) test('label text ๊ฐ’์œผ๋กœ ์„ ํƒํ•˜๊ธฐ', async () => { const checkbox = screen.getByLabelText('๊ฐ•์•„์ง€๋ฅผ ์ข‹์•„ํ•˜์‹œ๋‚˜์š”?') expect(checkbox).not.toBeChecked() })

๋งŒ์•ฝ ์œ„์˜ ๊ฐ’๋งŒ์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์ธ ๊ฒฝ์šฐ placeholder, alt, title๊ณผ ๊ฐ™์€ ์†์„ฑ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

import { screen } from '@testing-library/dom' document.body.innerHTML = ` <input type="text" placeholder="๊ฐ•์•„์ง€ ์ด๋ฆ„" value="ํ‘ธ๋“ค" /> <img src="/puppy.png" alt="ํ‘ธ๋“ค" title="๊ฐˆ์ƒ‰ ํ‘ธ๋“ค์ž…๋‹ˆ๋‹ค" /> ` test('placeholder ์†์„ฑ์œผ๋กœ ์„ ํƒํ•˜๊ธฐ', async () => { const input = screen.getByPlaceholderText('๊ฐ•์•„์ง€ ์ด๋ฆ„') expect(input).toHaveValue('ํ‘ธ๋“ค') }) test('alt ๊ฐ’์œผ๋กœ ์„ ํƒํ•˜๊ธฐ', async () => { const img = screen.getByAltText('ํ‘ธ๋“ค') expect(img).toHaveAttribute('title', '๊ฐˆ์ƒ‰ ํ‘ธ๋“ค์ž…๋‹ˆ๋‹ค') }) test('title ๊ฐ’์œผ๋กœ ์„ ํƒํ•˜๊ธฐ', async () => { const img = screen.getByTitle('๊ฐˆ์ƒ‰ ํ‘ธ๋“ค์ž…๋‹ˆ๋‹ค') expect(img).toHaveAttribute('alt', 'ํ‘ธ๋“ค') })

label, input๊ณผ ๊ฐ™์€ ํผ ์š”์†Œ, ์ด๋ฏธ์ง€ ์š”์†Œ ๋“ฑ์€ ๊ธฐ๋ณธ์ ์ธ ์ ‘๊ทผ์„ฑ ์ •๋ณด๋ฅผ ์ถฉ๋ถ„ํžˆ ๊ฐ–์ถ”๊ณ  ์žˆ์–ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ์ด ๋น„๊ต์  ๋ช…ํ™•ํ•œ ํŽธ์ž…๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด ์‹ค๋ฌด์—์„œ๋Š” div, span ์š”์†Œ์™€ ๊ฐ™์ด ์ ‘๊ทผ์„ฑ ์ •๋ณด์™€ ์˜๋ฏธ๊ฐ€ ๋ถ€์—ฌ๋˜์ง€ ์•Š์€ ๋งˆํฌ์—…์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ข…์ข… ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์š”์†Œ์— ๋Œ€ํ•ด Testing Library ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด testid๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

import { screen } from '@testing-library/dom' document.body.innerHTML = ` <div data-testid="my-pet"> <p>์šฐ๋ฆฌ์ง‘ ๊ฐ•์•„์ง€๋Š” ๊ฐˆ์ƒ‰ ํ‘ธ๋“ค</p> <div> ` test('testid ์†์„ฑ์œผ๋กœ ์„ ํƒ', () => { const div = screen.getByTestId('my-pet') expect(div).toHaveTextContent('์šฐ๋ฆฌ์ง‘ ๊ฐ•์•„์ง€๋Š” ๊ฐˆ์ƒ‰ ํ‘ธ๋“ค') })

ํ•˜์ง€๋งŒ Testing Library์—์„œ๋Š” testid๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ๋‘˜ ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๋Š” ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด๊ณ  document.querySelector('[data-testid="my-pet"]')๋ฅผ ์‹คํ–‰ํ•ด UI๋ฅผ ์ฐพ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ˆˆ์•ž์— ์žˆ๋Š” (๋˜๋Š” ๊ท€๋กœ ๋“ฃ๋Š”) ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋”ฐ๋ผ๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ testid๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์€ ์ œํ’ˆ์ด ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ณผ์ •๊ณผ ๋‹ฎ์•„ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
(๋ฐ˜๋ฉด์— ์ œํ’ˆ ์ „์ฒด ๋ฒ”์œ„๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋Š” e2e ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค)

๊ทธ๋ ‡๋‹ค๋ฉด ์ ‘๊ทผ์„ฑ ์ •๋ณด๊ฐ€ ๋ถ€์กฑํ•œ ๋งˆํฌ์—…์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•์—๋Š” ์–ด๋–ค๊ฒŒ ์žˆ์„๊นŒ์š”?

ARIA ์†์„ฑ์„ ์ง์ ‘ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ ‘๊ทผ์„ฑ ๊ด€๋ จ ์†์„ฑ์€ ํŽ˜์ด์ง€์˜ ์‹œ๊ฐํ™”๋œ ์ •๋ณด์— ๋น„ํ•ด ๋ณ€๋™ ๊ฐ€๋Šฅ์„ฑ์ด ์ ๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ ๊นจ์ง€์ง€ ์•Š๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„์ด์ฝ˜ ๋ฒ„ํŠผ๊ณผ ๊ฐ™์ด ์ ‘๊ทผ์„ฑ ์†์„ฑ์„ ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€ํ”ผํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ Button ์ปดํฌ๋„ŒํŠธ๋Š” ํ…์ŠคํŠธ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— button ์š”์†Œ๊ฐ€ ๊ฐ€์ง„ ๊ธฐ๋ณธ role ์†์„ฑ์„ ํ™œ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { fireEvent, render, screen } from '@testing-library/react' const Button = ({ onClick }) => ( <button onClick={onClick}> <svg width="17" height="17" xmlns="http://www.w3.org/2000/svg"> <path d="m.967 14.217 5.8-5.906-5.765-5.89L3.094.26l5.783 5.888L14.66.26l2.092 2.162-5.766 5.889 5.801 5.906-2.092 2.162-5.818-5.924-5.818 5.924-2.092-2.162Z" fill="#000" /> </svg> </button> ) test('role ์†์„ฑ์œผ๋กœ ์„ ํƒ ๋ฐ ํด๋ฆญ', () => { const handleClick = jest.fn() render(<Button onClick={handleClick} />) fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) })

์ด ํ…Œ์ŠคํŠธ๋Š” ๋ฒ„ํŠผ์ด ํ•˜๋‚˜์ผ ๋• ์ž˜ ๋™์ž‘ํ•˜์ง€๋งŒ ๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ ๋‚ด์˜ ๋ฒ„ํŠผ์ด ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ์ฟผ๋ฆฌ ๋ฐฉ์‹์ด ๋” ๋ณต์žกํ•ด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ aria-label ์†์„ฑ์„ ํ™œ์šฉํ•ด ์š”์†Œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { fireEvent, render, screen } from '@testing-library/react' const Button = ({ onClick }) => ( <button aria-label="๋ชจ๋‹ฌ ๋‹ซ๊ธฐ"> <svg aria-hidden="true" focusable="false" width="17" height="17" xmlns="http://www.w3.org/2000/svg"> <path d="m.967 14.217 5.8-5.906-5.765-5.89L3.094.26l5.783 5.888L14.66.26l2.092 2.162-5.766 5.889 5.801 5.906-2.092 2.162-5.818-5.924-5.818 5.924-2.092-2.162Z" fill="#000" /> </svg> </button> ) test('aria-label ์†์„ฑ์œผ๋กœ ์„ ํƒ ๋ฐ ํด๋ฆญ', () => { const handleClick = jest.fn() render(<Button onClick={handleClick} />) fireEvent.click(screen.getByLabelText('๋ชจ๋‹ฌ ๋‹ซ๊ธฐ')) expect(handleClick).toHaveBeenCalledTimes(1) })

aria-label ์†์„ฑ์„ ํ• ๋‹นํ•ด ์•„๋ž˜ ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๊ฐ™์ด ์ ‘๊ทผ์„ฑ๋„ ๋†’์ด๊ณ  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋์Šต๋‹ˆ๋‹ค.

aria-label ์†์„ฑ์— ๋”ฐ๋ฅธ ์ ‘๊ทผ์„ฑ ์ •๋ณด

์ถ”๊ฐ€๋กœ ์•„์ด์ฝ˜๊ณผ ๊ฐ™์ด ์ค‘๋ณต์„ ๊ณ ๋ คํ•ด ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ์—์„œ ์ œ์™ธํ•  ์š”์†Œ๋Š” aria-hidden๊ณผ svg focusable ์†์„ฑ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งˆ๋ฌด๋ฆฌ

Testing Library ๋ฌธ์„œ๋ฅผ ์ฝ๊ณ , ์‹ค์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋‹ˆ ์žฅ์ ์ด ์ƒ๋‹นํžˆ ๋งŽ์•˜์Šต๋‹ˆ๋‹ค.

  • ํƒœ๊ทธ๋ช…์ด๋‚˜ ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ(data-*)์— ์˜์กดํ•˜๋˜ ๊ธฐ์กด ํ…Œ์ŠคํŠธ ๋ฐฉ์‹๊ณผ ๋‹ฌ๋ฆฌ ์ฟผ๋ฆฌ์˜ ์šฐ์„  ์ˆœ์œ„์™€ ํ—ˆ์šฉ ๋ฒ”์œ„๊ฐ€ ์‹ค์ œ ์‚ฌ์šฉ์ž์˜ ํ–‰๋™๊ณผ ์œ ์‚ฌํ•จ
  • getByRole, getByLabelText์™€ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ณผ์ •์—์„œ ์›น ํ‘œ์ค€๊ณผ ์ ‘๊ทผ์„ฑ์„ ์ค€์ˆ˜ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ œํ’ˆ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋จ
  • React ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ๊ณผ์ • ์ค‘์˜ props, state ์ƒํƒœ ์ œ์–ด๊ฐ™์€ ์„ธ๋ถ€ ๊ตฌํ˜„์ด ์•„๋‹Œ ๊ฒฐ๊ณผ๋ฌผ์„ ๊ธฐ์ค€์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์ข… ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ์„ ๋‹ค๋ฃจ๋Š” e2e ํ…Œ์ŠคํŠธ์ฒ˜๋Ÿผ ์ž์‹ ๊ฐ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Œ

ํ‰์†Œ Playwright, Cypress์™€ ๊ฐ™์€ e2e ํ…Œ์ŠคํŠธ๋ฅผ ์ง€ํ–ฅํ–ˆ๋˜ ํฐ ๋ชฉ์  ์ค‘ ํ•˜๋‚˜๊ฐ€ ์‹ค์ œ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ œํ’ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ž๋™ํ™”ํ•˜๊ธฐ ์œ„ํ•จ์ด์—ˆ๋Š”๋ฐ Testing Library๊ฐ€ ๋”ํ•ด์ง„๋‹ค๋ฉด ๋ณด๋‹ค ๊ฒฌ๊ณ ํ•œ ์ œํ’ˆ์„ ๊ฐ–์ถฐ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ญ๋‹ˆ๋‹ค.

References