์ค์น, ํ ์คํธ ์์ฑ, ๋๋ฒ๊น , CI/CD ํตํฉ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ค๋ฃจ๋ ๊ฐ๋ ฅํ ์๋ ํฌ ์๋ ํ ์คํธ ํ๋ ์์ํฌ์ธ Cypress์ ๋ํ ํฌ๊ด์ ์ธ ๊ฐ์ด๋์ ๋๋ค.
Cypress: ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ์ต๊ณ ์ ์๋ ํฌ ์๋ ํ ์คํธ ๊ฐ์ด๋
์ค๋๋ ๊ธ๋ณํ๋ ์น ๊ฐ๋ฐ ํ๊ฒฝ์์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ง๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๊ฒ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์๋ ํฌ ์๋(E2E) ํ ์คํธ๋ ์ฌ์ฉ์์ ๊ด์ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ๊ตฌ์ฑ ์์๊ฐ ์ํํ๊ฒ ํจ๊ป ์๋ํ๋์ง ํ์ธํ๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. Cypress๋ ๊ฐ๋ฐ์ ์นํ์ ์ธ ๊ฒฝํ, ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ๋ฐ ๋ฐ์ด๋ ์ฑ๋ฅ์ ์ ๊ณตํ์ฌ ์ต๊ณ ์ E2E ํ ์คํธ ํ๋ ์์ํฌ๋ก ๋ถ์ํ์ต๋๋ค. ์ด ํฌ๊ด์ ์ธ ๊ฐ์ด๋์์๋ Cypress๋ฅผ ์์ํ๊ณ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํจ๊ณผ์ ์ผ๋ก ํ ์คํธํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ๊ฒ์ ์๋ดํฉ๋๋ค.
Cypress๋ ๋ฌด์์ธ๊ฐ์?
Cypress๋ ์ต์ ์น์ ์ํด ๊ตฌ์ถ๋ ์ฐจ์ธ๋ ํ๋ฐํธ ์๋ ํ ์คํธ ๋๊ตฌ์ ๋๋ค. ๋ธ๋ผ์ฐ์ ์์ ํ ์คํธ๋ฅผ ์คํํ๋ ๊ธฐ์กด ํ ์คํธ ํ๋ ์์ํฌ์ ๋ฌ๋ฆฌ Cypress๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์ ์๋ํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์์ ๋ํ ๋น๊ตํ ์ ์๋ ์ ์ด ๋ฐ ๊ฐ์์ฑ์ ์ ๊ณตํฉ๋๋ค. ๋น ๋ฅด๊ณ ์์ ์ ์ด๋ฉฐ ์ฌ์ฉํ๊ธฐ ์ฝ๋๋ก ์ค๊ณ๋์ด ์ ์ธ๊ณ ๊ฐ๋ฐ์์ QA ์์ง๋์ด ์ฌ์ด์์ ์ธ๊ธฐ ์๋ ์ ํ์ ๋๋ค. Cypress๋ JavaScript๋ก ์์ฑ๋์์ผ๋ฉฐ ๋ธ๋ผ์ฐ์ ๋ด์์ ์คํ๋๋ฏ๋ก ์ฑ๋ฅ์ด ๋งค์ฐ ๋ฐ์ด๋๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ด๋ถ ์์์ ๋ํ ํ์ํ ์ก์ธ์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
Cypress ์ฌ์ฉ์ ์ฃผ์ ์ด์
- ๊ฐ๋ฐ์ ์นํ์ : Cypress๋ ๊นจ๋ํ๊ณ ์ง๊ด์ ์ธ API๋ฅผ ์ ๊ณตํ์ฌ ํ ์คํธ๋ฅผ ์ฝ๊ฒ ์์ฑํ๊ณ ๋๋ฒ๊น ํ ์ ์๋๋ก ํฉ๋๋ค.
- ์๊ฐ ์ฌํ: Cypress๋ ๊ฐ ํ ์คํธ ๋ช ๋ น ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ์ค๋ ์ท์ ์ฐ์ด ์ธ์ ๋ ์ง ์๊ฐ์ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ ์ ํํ ์ด๋ค ์ผ์ด ๋ฐ์ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
- ์ค์๊ฐ ๋ค์ ๋ก๋: Cypress๋ ํ ์คํธ๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์๋์ผ๋ก ๋ค์ ๋ก๋ํ์ฌ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค.
- ์๋ ๋๊ธฐ: Cypress๋ ์์ ์ํ ์ ์ ์์๊ฐ ํ์๋๊ฑฐ๋ ์ํธ ์์ฉ ๊ฐ๋ฅํ๊ฒ ๋ ๋๊น์ง ์๋์ผ๋ก ๋๊ธฐํ๋ฏ๋ก ๋ช ์์ ๋๊ธฐ๊ฐ ํ์ํ์ง ์์ต๋๋ค.
- ๋คํธ์ํฌ ์ ์ด: Cypress๋ฅผ ์ฌ์ฉํ๋ฉด ๋คํธ์ํฌ ์์ฒญ ๋ฐ ์๋ต์ ์คํ ํ์ฌ ๋ค์ํ ์๋๋ฆฌ์ค๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค.
- ๋๋ฒ๊น ๊ฐ๋ฅ์ฑ: Cypress๋ ๊ฐ๋ ฅํ ๋๋ฒ๊ฑฐ์ ์์ธํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํฌํจํ์ฌ ํ๋ฅญํ ๋๋ฒ๊น ๋๊ตฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํ ์คํธ: Cypress๋ Chrome, Firefox, Edge ๋ฐ Electron์ ํฌํจํ ์ฌ๋ฌ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ง์ํฉ๋๋ค.
- ํค๋๋ฆฌ์ค ํ ์คํธ: CI/CD ํ๊ฒฝ์์ ๋ ๋น ๋ฅธ ์คํ์ ์ํด ํค๋๋ฆฌ์ค ๋ชจ๋๋ก ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค.
- ๋ด์ฅ๋ ์ด์ค์ : Cypress๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ ๋์์ ํ์ธํ๊ธฐ ์ํ ํ๋ถํ ๋ด์ฅ ์ด์ค์ ์ธํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ค์น ๋ฐ ์ค์
Cypress๋ฅผ ์์ํ๋ ๊ฒ์ ๊ฐ๋จํฉ๋๋ค. ์ค์น ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ฌ์ ์๊ตฌ ์ฌํญ: ์์คํ ์ Node.js ๋ฐ npm(Node Package Manager)์ด ์ค์น๋์ด ์๋์ง ํ์ธํฉ๋๋ค. ๊ณต์ Node.js ์น์ฌ์ดํธ์์ ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค.
- Cypress ์ค์น: ํฐ๋ฏธ๋ ๋๋ ๋ช ๋ น ํ๋กฌํํธ๋ฅผ ์ด๊ณ ํ๋ก์ ํธ ๋๋ ํ ๋ฆฌ๋ก ์ด๋ํ์ฌ ๋ค์ ๋ช ๋ น์ ์คํํฉ๋๋ค.
- Cypress ์ด๊ธฐ: ์ค์น๊ฐ ์๋ฃ๋๋ฉด ๋ค์์ ์คํํ์ฌ Cypress ํ ์คํธ ๋ฌ๋๋ฅผ ์ด ์ ์์ต๋๋ค.
npm install cypress --save-dev
npx cypress open
์ด ๋ช ๋ น์ ํ ์คํธ๋ฅผ ์คํํ๊ณ ๋๋ฒ๊น ํ๊ธฐ ์ํ ๊ทธ๋ํฝ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ Cypress ํ ์คํธ ๋ฌ๋๋ฅผ ์์ํฉ๋๋ค.
์ฒซ ๋ฒ์งธ Cypress ํ ์คํธ ์์ฑ
์น์ฌ์ดํธ์ ํ ํ์ด์ง๊ฐ ์ ๋๋ก ๋ก๋๋๋์ง ํ์ธํ๋ ๊ฐ๋จํ ํ ์คํธ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ํ๋ก์ ํธ์ `cypress/e2e` ๋๋ ํ ๋ฆฌ์ `example.cy.js`๋ผ๋ ์ ํ์ผ์ ๋ง๋ญ๋๋ค.
// cypress/e2e/example.cy.js
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
cy.url().should('include', '/commands/actions')
cy.get('.action-email')
.type('fake@email.com')
.should('have.value', 'fake@email.com')
})
})
์ด ํ ์คํธ๋ฅผ ๋ถ์ํด ๋ณด๊ฒ ์ต๋๋ค.
- `describe()`: ๊ด๋ จ ํ ์คํธ ๋ชจ์์ธ ํ ์คํธ ๋ชจ์์ ์ ์ํฉ๋๋ค.
- `it()`: ํ ์คํธ ๋ชจ์ ๋ด์์ ๊ฐ๋ณ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ ์ํฉ๋๋ค.
- `cy.visit()`: ์ง์ ๋ URL๋ก ์ด๋ํฉ๋๋ค.
- `cy.contains()`: ์ง์ ๋ ํ ์คํธ๋ฅผ ํฌํจํ๋ ์์๋ฅผ ์ฐพ์ต๋๋ค.
- `.click()`: ์ ํํ ์์๋ฅผ ํด๋ฆญํฉ๋๋ค.
- `cy.url()`: ํ์ด์ง์ ํ์ฌ URL์ ๊ฐ์ ธ์ต๋๋ค.
- `.should()`: ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ์ ๋ํ ์ด์ค์ ์ ๋ง๋ญ๋๋ค.
- `cy.get()`: CSS ์ ํ์๋ฅผ ์ฌ์ฉํ์ฌ ์์๋ฅผ ์ ํํฉ๋๋ค.
- `.type()`: ์ ํํ ์์์ ํ ์คํธ๋ฅผ ์ ๋ ฅํฉ๋๋ค.
- `.should('have.value', 'fake@email.com')`: ์์์ ๊ฐ์ด 'fake@email.com'๊ณผ ๊ฐ์์ง ์ด์ค์ ํฉ๋๋ค.
Cypress ํ ์คํธ ๋ฌ๋์์ ์ด ํ ์คํธ๋ฅผ ์คํํ์ฌ ์ค์ ๋ก ์๋ํ๋์ง ํ์ธํ์ญ์์ค. ๋ธ๋ผ์ฐ์ ๊ฐ Cypress Kitchen Sink ์น์ฌ์ดํธ๋ก ์ด๋ํ์ฌ "type" ๋งํฌ๋ฅผ ํด๋ฆญํ๊ณ URL์ ํ์ธํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
Cypress ๋ช ๋ น
Cypress๋ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์ํธ ์์ฉํ๊ธฐ ์ํ ๊ด๋ฒ์ํ ๋ช ๋ น์ ์ ๊ณตํฉ๋๋ค. ๋ค์์ ๊ฐ์ฅ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ช ๋ น ์ค ์ผ๋ถ์ ๋๋ค.
- `cy.visit(url)`: ์ง์ ๋ URL๋ก ์ด๋ํฉ๋๋ค.
- `cy.get(selector)`: CSS ์ ํ์๋ฅผ ์ฌ์ฉํ์ฌ ์์๋ฅผ ์ ํํฉ๋๋ค.
- `cy.contains(content)`: ์ง์ ๋ ํ ์คํธ๋ฅผ ํฌํจํ๋ ์์๋ฅผ ์ ํํฉ๋๋ค.
- `cy.click()`: ์ ํํ ์์๋ฅผ ํด๋ฆญํฉ๋๋ค.
- `cy.type(text)`: ์ ํํ ์์์ ํ ์คํธ๋ฅผ ์ ๋ ฅํฉ๋๋ค.
- `cy.clear()`: ์ ๋ ฅ ๋๋ ํ ์คํธ ์์ญ ์์์ ๋ด์ฉ์ ์ง์๋๋ค.
- `cy.submit()`: ์์์ ์ ์ถํฉ๋๋ค.
- `cy.check()`: ํ์ธ๋ ๋๋ ๋ผ๋์ค ๋ฒํผ์ ์ ํํฉ๋๋ค.
- `cy.uncheck()`: ํ์ธ๋ ์ ํ์ ์ทจ์ํฉ๋๋ค.
- `cy.select(value)`: ๋๋กญ๋ค์ด์์ ์ต์ ์ ์ ํํฉ๋๋ค.
- `cy.scrollTo(position)`: ํ์ด์ง๋ฅผ ์ง์ ๋ ์์น๋ก ์คํฌ๋กคํฉ๋๋ค.
- `cy.trigger(event)`: ์ ํํ ์์์์ DOM ์ด๋ฒคํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค.
- `cy.request(url, options)`: ์ง์ ๋ URL๋ก HTTP ์์ฒญ์ ๋ณด๋ ๋๋ค.
- `cy.intercept(route, handler)`: ์ง์ ๋ ๊ฒฝ๋ก์ ์ผ์นํ๋ HTTP ์์ฒญ์ ๊ฐ๋ก์ฑ๋๋ค.
- `cy.wait(time)`: ์ง์ ๋ ์๊ฐ ๋์ ๋๊ธฐํฉ๋๋ค.
- `cy.reload()`: ํ์ฌ ํ์ด์ง๋ฅผ ๋ค์ ๋ก๋ํฉ๋๋ค.
- `cy.go(direction)`: ๋ธ๋ผ์ฐ์ ๊ธฐ๋ก์์ ์ด์ ํ์ด์ง ๋๋ ๋ค์ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- `cy.url()`: ํ์ด์ง์ ํ์ฌ URL์ ๊ฐ์ ธ์ต๋๋ค.
- `cy.title()`: ํ์ด์ง์ ์ ๋ชฉ์ ๊ฐ์ ธ์ต๋๋ค.
- `cy.window()`: window ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- `cy.document()`: document ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- `cy.viewport(width, height)`: ๋ทฐํฌํธ ํฌ๊ธฐ๋ฅผ ์ค์ ํฉ๋๋ค.
์ด๊ฒ์ Cypress์์ ์ฌ์ฉํ ์ ์๋ ๋ง์ ๋ช ๋ น ์ค ์ผ๋ถ์ผ ๋ฟ์ ๋๋ค. ์ ์ฒด ๋ช ๋ น ๋ชฉ๋ก๊ณผ ํด๋น ์ต์ ์ Cypress ์ค๋ช ์๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
Cypress์ ์ด์ค์
์ด์ค์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ ๋์์ ํ์ธํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Cypress๋ ์์์ ์ํ, URL, ์ ๋ชฉ ๋ฑ์ ํ์ธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ํ๋ถํ ๋ด์ฅ ์ด์ค์ ์ธํธ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด์ค์ ์ `.should()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ Cypress ๋ช ๋ น ๋ค์ ์ฐ๊ฒฐ๋ฉ๋๋ค.
๋ค์์ ๋ช ๊ฐ์ง ์ผ๋ฐ์ ์ธ ์ด์ค์ ์์ ๋๋ค.
- `.should('be.visible')`: ์์๊ฐ ํ์๋๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('not.be.visible')`: ์์๊ฐ ํ์๋์ง ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('be.enabled')`: ์์๊ฐ ํ์ฑํ๋์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('be.disabled')`: ์์๊ฐ ๋นํ์ฑํ๋์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.text', 'expected text')`: ์์์ ์ง์ ๋ ํ ์คํธ๊ฐ ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('contain', 'expected text')`: ์์์ ์ง์ ๋ ํ ์คํธ๊ฐ ํฌํจ๋์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.value', 'expected value')`: ์์์ ์ง์ ๋ ๊ฐ์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.class', 'expected class')`: ์์์ ์ง์ ๋ ํด๋์ค๊ฐ ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.attr', 'attribute name', 'expected value')`: ์์์ ์ง์ ๋ ์์ฑ ๋ฐ ๊ฐ์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.css', 'css property', 'expected value')`: ์์์ ์ง์ ๋ CSS ์์ฑ ๋ฐ ๊ฐ์ด ์๋์ง ์ด์ค์ ํฉ๋๋ค.
- `.should('have.length', expected length)`: ์์์ ์ง์ ๋ ๊ธธ์ด(์: ๋ชฉ๋ก์ ์์ ์)๊ฐ ์๋์ง ์ด์ค์ ํฉ๋๋ค.
ํน์ ์๊ตฌ ์ฌํญ์ ๋ง๊ฒ ์ฌ์ฉ์ ์ง์ ์ด์ค์ ์ ๋ง๋ค ์๋ ์์ต๋๋ค.
Cypress ํ ์คํธ ์์ฑ ๋ชจ๋ฒ ์ฌ๋ก
๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด ๋ ์ ์ง ๊ด๋ฆฌ ๊ฐ๋ฅํ๊ณ ์์ ์ ์ด๋ฉฐ ํจ์จ์ ์ธ Cypress ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ค์์ ๋ช ๊ฐ์ง ๊ถ์ฅ ์ฌํญ์ ๋๋ค.
- ๋ช ํํ๊ณ ๊ฐ๊ฒฐํ ํ ์คํธ ์์ฑ: ๊ฐ ํ ์คํธ๋ ํน์ ๊ธฐ๋ฅ ๋๋ ์๋๋ฆฌ์ค์ ์ง์คํด์ผ ํฉ๋๋ค. ์ดํดํ๊ณ ์ ์ง ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ด ์ง๋์น๊ฒ ๋ณต์กํ ํ ์คํธ๋ฅผ ์์ฑํ์ง ๋ง์ญ์์ค.
- ์๋ฏธ ์๋ ํ ์คํธ ์ด๋ฆ ์ฌ์ฉ: ํ ์คํธํ๋ ๋ด์ฉ์ ๋ช ํํ๊ฒ ๋ํ๋ด๋ ์ค๋ช ์ ์ธ ์ด๋ฆ์ ํ ์คํธ์ ์ง์ ํฉ๋๋ค.
- ๊ฐ ํ๋์ฝ๋ฉ ๋ฐฉ์ง: ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ ์ ์๋ ๊ฐ์ ์ ์ฅํ๋ ค๋ฉด ๋ณ์ ๋๋ ๊ตฌ์ฑ ํ์ผ์ ์ฌ์ฉํฉ๋๋ค.
- ์ฌ์ฉ์ ์ง์ ๋ช ๋ น ์ฌ์ฉ: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ก์ง์ ์บก์ํํ๊ณ ํ ์คํธ๋ฅผ ๋ ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ค๋ ค๋ฉด ์ฌ์ฉ์ ์ง์ ๋ช ๋ น์ ๋ง๋ญ๋๋ค.
- ํ ์คํธ ๊ฒฉ๋ฆฌ: ๊ฐ ํ ์คํธ๋ ๋ค๋ฅธ ํ ์คํธ์ ๋ ๋ฆฝ์ ์ด์ด์ผ ํฉ๋๋ค. ์ด์ ํ ์คํธ์ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ์์กดํ์ง ๋ง์ญ์์ค.
- ํ ์คํธ ํ ์ ๋ฆฌ: ๊ฐ ํ ์คํธ ํ์ ์ ํ๋ฆฌ์ผ์ด์ ์ํ๋ฅผ ์ฌ์ค์ ํ์ฌ ํ์ ํ ์คํธ๊ฐ ๊นจ๋ํ ์ํ์์ ์์๋๋๋ก ํฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ฑ ์ฌ์ฉ: ํ ์คํธ์์ ์์๋ฅผ ์ ํํ๋ ค๋ฉด ๋ฐ์ดํฐ ์์ฑ(์: `data-testid`)์ ์ฌ์ฉํฉ๋๋ค. ๋ฐ์ดํฐ ์์ฑ์ CSS ํด๋์ค ๋๋ ID๋ณด๋ค ๋ณ๊ฒฝ๋ ๊ฐ๋ฅ์ฑ์ด ์ ์ผ๋ฏ๋ก ํ ์คํธ๊ฐ UI ๋ณ๊ฒฝ์ ๋ ํ๋ ฅ์ ์ ๋๋ค.
- ๋ช ์์ ๋๊ธฐ ๋ฐฉ์ง: Cypress๋ ์์๊ฐ ํ์๋๊ฑฐ๋ ์ํธ ์์ฉ ๊ฐ๋ฅํ๊ฒ ๋ ๋๊น์ง ์๋์ผ๋ก ๋๊ธฐํฉ๋๋ค. ๋ช ์์ ๋๊ธฐ(์: `cy.wait()`)๋ ์ ๋์ ์ผ๋ก ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํ์ญ์์ค.
- ์ฌ์ฉ์ ํ๋ฆ ํ ์คํธ: ๊ฐ๋ณ ๊ตฌ์ฑ ์์๋ณด๋ค๋ ์ฌ์ฉ์ ํ๋ฆ ํ ์คํธ์ ์ง์คํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ฌ์ฉ์์ ๊ด์ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ๋๋ก ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
- ํ ์คํธ๋ฅผ ์ ๊ธฐ์ ์ผ๋ก ์คํ: Cypress ํ ์คํธ๋ฅผ CI/CD ํ์ดํ๋ผ์ธ์ ํตํฉํ๊ณ ์ ๊ธฐ์ ์ผ๋ก ์คํํ์ฌ ๊ฐ๋ฐ ํ๋ก์ธ์ค ์ด๊ธฐ์ ๋ฒ๊ทธ๋ฅผ ํฌ์ฐฉํฉ๋๋ค.
๊ณ ๊ธ Cypress ๊ธฐ์
์คํ ๋ฐ ๋ชจํน
Cypress๋ฅผ ์ฌ์ฉํ๋ฉด ๋คํธ์ํฌ ์์ฒญ ๋ฐ ์๋ต์ ์คํ ํ์ฌ ๋ค์ํ ์๋๋ฆฌ์ค๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค. ์ด๋ ์ธ๋ถ API ๋๋ ์๋น์ค์ ์์กดํ๋ ๊ธฐ๋ฅ์ ํ ์คํธํ๋ ๋ฐ ํนํ ์ ์ฉํฉ๋๋ค.
๋คํธ์ํฌ ์์ฒญ์ ์คํ ํ๋ ค๋ฉด `cy.intercept()` ๋ช ๋ น์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์๋ ์ฝ๋๋ `/api/users`์ ๋ํ GET ์์ฒญ์ ์คํ ํ๊ณ ๋ชจ์ ์๋ต์ ๋ฐํํฉ๋๋ค.
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' }
]
}).as('getUsers')
๊ทธ๋ฐ ๋ค์ `cy.wait('@getUsers')`๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ก์ฑ ์์ฒญ์ ๊ธฐ๋ค๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ชจ์ ์๋ต์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐ ์ฟ ํค ์ฌ์ฉ
Cypress๋ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐ ์ฟ ํค์ ์ํธ ์์ฉํ๊ธฐ ์ํ ๋ช ๋ น์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ๋ช ๋ น์ ์ฌ์ฉํ์ฌ ํ ์คํธ์์ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฐ ์ฟ ํค๋ฅผ ์ค์ , ๊ฐ์ ธ์ค๊ธฐ ๋ฐ ์ง์ธ ์ ์์ต๋๋ค.
๋ก์ปฌ ์คํ ๋ฆฌ์ง ํญ๋ชฉ์ ์ค์ ํ๋ ค๋ฉด `cy.window()` ๋ช ๋ น์ ์ฌ์ฉํ์ฌ window ๊ฐ์ฒด์ ์ก์ธ์คํ ๋ค์ `localStorage.setItem()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
cy.window().then((win) => {
win.localStorage.setItem('myKey', 'myValue')
})
๋ก์ปฌ ์คํ ๋ฆฌ์ง ํญ๋ชฉ์ ๊ฐ์ ธ์ค๋ ค๋ฉด `cy.window()` ๋ช ๋ น์ ์ฌ์ฉํ ๋ค์ `localStorage.getItem()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
cy.window().then((win) => {
const value = win.localStorage.getItem('myKey')
expect(value).to.equal('myValue')
})
์ฟ ํค๋ฅผ ์ค์ ํ๋ ค๋ฉด `cy.setCookie()` ๋ช ๋ น์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
cy.setCookie('myCookie', 'myCookieValue')
์ฟ ํค๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด `cy.getCookie()` ๋ช ๋ น์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
cy.getCookie('myCookie').should('have.property', 'value', 'myCookieValue')
ํ์ผ ์ ๋ก๋ ์ฒ๋ฆฌ
Cypress๋ ํ ์คํธ์์ ํ์ผ ์ ๋ก๋๋ฅผ ๋จ์ํํ๋ `cypress-file-upload`๋ผ๋ ํ๋ฌ๊ทธ์ธ์ ์ ๊ณตํฉ๋๋ค. ํ๋ฌ๊ทธ์ธ์ ์ค์นํ๋ ค๋ฉด ๋ค์ ๋ช ๋ น์ ์คํํฉ๋๋ค.
npm install -D cypress-file-upload
๊ทธ๋ฐ ๋ค์ `cypress/support/commands.js` ํ์ผ์ ๋ค์ ์ค์ ์ถ๊ฐํฉ๋๋ค.
import 'cypress-file-upload';
๊ทธ๋ฐ ๋ค์ `cy.uploadFile()` ๋ช ๋ น์ ์ฌ์ฉํ์ฌ ํ์ผ์ ์ ๋ก๋ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
cy.get('input[type="file"]').attachFile('example.txt')
IFrames ์ฌ์ฉ
IFrames ํ ์คํธ๋ ๊น๋ค๋ก์ธ ์ ์์ง๋ง Cypress๋ IFrame๊ณผ ์ํธ ์์ฉํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. `cy.frameLoaded()` ๋ช ๋ น์ ์ฌ์ฉํ์ฌ IFrame์ด ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ๋ค์ `cy.iframe()` ๋ช ๋ น์ ์ฌ์ฉํ์ฌ IFrame์ document ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
cy.frameLoaded('#myIframe')
cy.iframe('#myIframe').find('button').click()
Cypress ๋ฐ CI/CD(์ง์์ ์ธ ํตํฉ/์ง์์ ์ธ ๋ฐฐํฌ)
์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ง์ ๋ณด์ฅํ๋ ค๋ฉด Cypress๋ฅผ CI/CD ํ์ดํ๋ผ์ธ์ ํตํฉํ๋ ๊ฒ์ด ํ์์ ์ ๋๋ค. CI/CD ํ๊ฒฝ์์ ํค๋๋ฆฌ์ค ๋ชจ๋๋ก Cypress ํ ์คํธ๋ฅผ ์คํํ ์ ์์ต๋๋ค. ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Cypress ์ค์น: Cypress๊ฐ ํ๋ก์ ํธ์ ์ข ์์ฑ์ผ๋ก ์ค์น๋์ด ์๋์ง ํ์ธํฉ๋๋ค.
- CI/CD ๊ตฌ์ฑ: ๊ฐ ๋น๋ ํ Cypress ํ ์คํธ๋ฅผ ์คํํ๋๋ก CI/CD ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํฉ๋๋ค.
- ํค๋๋ฆฌ์ค๋ก Cypress ์คํ: `cypress run` ๋ช ๋ น์ ์ฌ์ฉํ์ฌ ํค๋๋ฆฌ์ค ๋ชจ๋์์ Cypress ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค.
์์ CI/CD ๊ตฌ์ฑ(GitHub Actions ์ฌ์ฉ):
name: Cypress Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm install
- name: Cypress run
uses: cypress-io/github-action@v5
with:
start: npm start
wait-on: 'http://localhost:3000'
์ด ๊ตฌ์ฑ์ ์ฝ๋๊ฐ `main` ๋ถ๊ธฐ๋ก ํธ์๋๊ฑฐ๋ `main` ๋ถ๊ธฐ์ ๋ํด ํ ์์ฒญ์ด ์์ฑ๋ ๋๋ง๋ค Cypress ํ ์คํธ๋ฅผ ์คํํฉ๋๋ค. `cypress-io/github-action` ์์ ์ GitHub Actions์์ Cypress ํ ์คํธ๋ฅผ ์คํํ๋ ํ๋ก์ธ์ค๋ฅผ ๋จ์ํํฉ๋๋ค.
Cypress ํ ์คํธ ๋๋ฒ๊น
Cypress๋ ํ ์คํธ์์ ๋ฌธ์ ๋ฅผ ์๋ณํ๊ณ ํด๊ฒฐํ๋ ๋ฐ ๋์์ด ๋๋ ํ๋ฅญํ ๋๋ฒ๊น ๋๊ตฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. Cypress ํ ์คํธ ๋๋ฒ๊น ์ ๋ํ ๋ช ๊ฐ์ง ํ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Cypress ํ ์คํธ ๋ฌ๋ ์ฌ์ฉ: Cypress ํ ์คํธ ๋ฌ๋๋ ํ ์คํธ๋ฅผ ์คํํ๊ณ ๋๋ฒ๊น ํ๊ธฐ ์ํ ์๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ํ ๋ฒ์ ํ๋์ ๋ช ๋ น์ผ๋ก ํ ์คํธ๋ฅผ ๋จ๊ณ๋ณ๋ก ์คํํ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๊ฒ์ฌํ๊ณ , ์์ธํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
- `cy.pause()` ๋ช ๋ น ์ฌ์ฉ: `cy.pause()` ๋ช ๋ น์ ํ ์คํธ ์คํ์ ์ผ์ ์ค์งํ๊ณ ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๊ฒ์ฌํ ์ ์๋๋ก ํฉ๋๋ค.
- `cy.debug()` ๋ช ๋ น ์ฌ์ฉ: `cy.debug()` ๋ช ๋ น์ ์ ํํ ์์๋ฅผ ์ฝ์์ ์ถ๋ ฅํ์ฌ ํด๋น ์์ฑ ๋ฐ ํน์ฑ์ ๊ฒ์ฌํ ์ ์๋๋ก ํฉ๋๋ค.
- ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ ์ฌ์ฉ: ๋ธ๋ผ์ฐ์ ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ DOM, ๋คํธ์ํฌ ์์ฒญ ๋ฐ ์ฝ์ ๋ก๊ทธ๋ฅผ ํฌํจํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ํ ํ๋ถํ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ฃผ์ ๊น๊ฒ ์ฝ์ผ์ญ์์ค: Cypress๋ ์ค๋ฅ์ ์์ธ์ ์๋ณํ๋ ๋ฐ ๋์์ด ๋๋ ์์ธํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ค๋ฅ ๋ฉ์์ง์ ์คํ ์ถ์ ์ ์ฃผ์ํ์ญ์์ค.
Cypress ๋ ๋ค๋ฅธ ํ ์คํธ ํ๋ ์์ํฌ
Cypress๋ ๊ฐ๋ ฅํ ์๋ ํฌ ์๋ ํ ์คํธ ํ๋ ์์ํฌ์ด์ง๋ง ๋ค๋ฅธ ์ธ๊ธฐ ์๋ ์ต์ ๊ณผ ๋น๊ตํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ค์์ ๊ฐ๋ตํ ๊ฐ์์ ๋๋ค.
- Selenium: Selenium์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์๋ํ ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค. ์ ์ฐํ๊ณ ์ฌ๋ฌ ์ธ์ด๋ฅผ ์ง์ํ์ง๋ง ์ค์ ํ๊ณ ์ ์ง ๊ด๋ฆฌํ๊ธฐ๊ฐ ๋ณต์กํ ์ ์์ต๋๋ค. Cypress๋ ํนํ JavaScript ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ ๋ ๊ฐ๋จํ๊ณ ๊ฐ๋ฐ์ ์นํ์ ์ธ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
- Puppeteer: Puppeteer๋ ํค๋๋ฆฌ์ค Chrome ๋๋ Chromium์ ์ ์ดํ๊ธฐ ์ํ ๊ณ ๊ธ API๋ฅผ ์ ๊ณตํ๋ Node ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์คํฌ๋ํ ๋ฐ ๋ธ๋ผ์ฐ์ ์์ ์๋ํ์ ์ ํฉํ์ง๋ง ์๋ ํฌ ์๋ ํ ์คํธ์ ๊ฒฝ์ฐ Cypress์ ๋นํด ๋ ๋ง์ ์๋ ๊ตฌ์ฑ์ด ํ์ํ ์ ์์ต๋๋ค.
- Playwright: Playwright๋ Microsoft์์ ๊ฐ๋ฐํ ๋ ๋ค๋ฅธ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ์๋ํ ํ๋ ์์ํฌ์ ๋๋ค. Puppeteer์ ์ ์ฌํ์ง๋ง ๋ ๊ด๋ฒ์ํ ๋ธ๋ผ์ฐ์ ์ง์์ ์ ๊ณตํฉ๋๋ค. Cypress์๋ ๊ณ ์ ํ ์๊ฐ ์ฌํ ๋๋ฒ๊ฑฐ์ ๋ ํตํฉ๋ ํ ์คํธ ํ๊ฒฝ์ด ์์ต๋๋ค.
ํ๋ ์์ํฌ ์ ํ์ ํ๋ก์ ํธ์ ํน์ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค. Cypress๋ ๋น ๋ฅด๊ณ ์์ ์ ์ด๋ฉฐ ๊ฐ๋ฐ์ ์นํ์ ์ธ ์๋ ํฌ ์๋ ํ ์คํธ๊ฐ ํ์ํ ์ต์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉํ ์ ํ์ ๋๋ค.
์ค์ Cypress ์ฌ์ฉ ์
Cypress๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ํ ์ ํ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ ์คํธํ ์ ์๋ ๋ช ๊ฐ์ง ์ค์ ์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ ์ ์๊ฑฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ํ ์คํธ
Cypress๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ ์ ์ ์๊ฑฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ค์ํ ์ฌ์ฉ์ ํ๋ฆ์ ํ ์คํธํ ์ ์์ต๋๋ค.
- ์ ํ ๊ฒ์
- ์ฅ๋ฐ๊ตฌ๋์ ์ ํ ์ถ๊ฐ
- ๊ฒฐ์ ๋ฐ ์ฃผ๋ฌธ
- ๊ณ์ ์ค์ ๊ด๋ฆฌ
์ฌ์ฉ์๊ฐ ์ฅ๋ฐ๊ตฌ๋์ ์ ํ์ ์ฑ๊ณต์ ์ผ๋ก ์ถ๊ฐํ ์ ์๋์ง ํ์ธํ๋ Cypress ํ ์คํธ์ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
it('Adds a product to the cart', () => {
cy.visit('/products')
cy.get('.product-card').first().find('button').click()
cy.get('.cart-count').should('have.text', '1')
})
์์ ๋ฏธ๋์ด ์ ํ๋ฆฌ์ผ์ด์ ํ ์คํธ
Cypress๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ ์์ ๋ฏธ๋์ด ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ํ ์คํธํ ์ ์์ต๋๋ค.
- ์ ๊ฒ์๋ฌผ ๋ง๋ค๊ธฐ
- ๊ฒ์๋ฌผ ์ข์์
- ๊ฒ์๋ฌผ์ ๋๊ธ ๋ฌ๊ธฐ
- ๋ค๋ฅธ ์ฌ์ฉ์ ํ๋ก์ฐ
์ฌ์ฉ์๊ฐ ์ ๊ฒ์๋ฌผ์ ์ฑ๊ณต์ ์ผ๋ก ๋ง๋ค ์ ์๋์ง ํ์ธํ๋ Cypress ํ ์คํธ์ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
it('Creates a new post', () => {
cy.visit('/profile')
cy.get('#new-post-textarea').type('Hello, world!')
cy.get('#submit-post-button').click()
cy.get('.post').first().should('contain', 'Hello, world!')
})
๋ฑ ํน ์ ํ๋ฆฌ์ผ์ด์ ํ ์คํธ
๋ฑ ํน ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ Cypress๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ ์ค์ํ ๊ธฐ๋ฅ์ ํ ์คํธํ ์ ์์ต๋๋ค.
- ์์ ํ๊ฒ ๋ก๊ทธ์ธ
- ๊ณ์ ์์ก ํ์ธ
- ์๊ธ ์ด์ฒด
- ์ํ์ ๊ด๋ฆฌ
์๊ธ ์ด์ฒด๋ฅผ ํ์ธํ๋ ํ ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค(๋ณด์์ ์ํด ์ ์ ํ ์คํฐ๋น ํฌํจ).
it('Transfers funds successfully', () => {
cy.visit('/transfer')
cy.get('#recipient-account').type('1234567890')
cy.get('#amount').type('100')
cy.intercept('POST', '/api/transfer', { statusCode: 200, body: { success: true } }).as('transfer')
cy.get('#transfer-button').click()
cy.wait('@transfer')
cy.get('.success-message').should('be.visible')
})
๊ฒฐ๋ก
Cypress๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ง๊ณผ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๋ฐ ๋์์ด ๋๋ ๊ฐ๋ ฅํ๊ณ ๋ค์ฌ๋ค๋ฅํ ์๋ ํฌ ์๋ ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค. ๊ฐ๋ฐ์ ์นํ์ ์ธ API, ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ๋ฐ ๋ฐ์ด๋ ์ฑ๋ฅ์ ์ ์ธ๊ณ ๊ฐ๋ฐ์์ QA ์์ง๋์ด ์ฌ์ด์์ ์ธ๊ธฐ ์๋ ์ ํ์ ๋๋ค. ์ด ๊ฐ์ด๋์ ์ค๋ช ๋ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด ๊ฐ๋ฐ ํ๋ก์ธ์ค ์ด๊ธฐ์ ๋ฒ๊ทธ๋ฅผ ํฌ์ฐฉํ๊ณ ์ฌ์ฉ์์๊ฒ ๊ณ ํ์ง ์ํํธ์จ์ด๋ฅผ ์ ๊ณตํ๋ ๋ฐ ๋์์ด ๋๋ ํจ๊ณผ์ ์ธ Cypress ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์น ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ณ์ ๋ฐ์ ํจ์ ๋ฐ๋ผ ์๋ ํฌ ์๋ ํ ์คํธ์ ์ค์์ฑ์ ๋์ฑ ์ปค์ง ๊ฒ์ ๋๋ค. Cypress๋ฅผ ์์ฉํ๊ณ ๊ฐ๋ฐ ์ํฌํ๋ก์ ํตํฉํ๋ฉด ๋์ฑ ๊ฐ๋ ฅํ๊ณ ์์ ์ ์ด๋ฉฐ ์ฌ์ฉ์ ์นํ์ ์ธ ์น ํ๊ฒฝ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.