๐คฉ E2Eํ ์คํธ๋
๊ฐ๋ฐ๋ฌผ์ ์ฌ์ฉ์๊ด์ ์์ ํ ์คํธ ํ๋ ๋ฐฉ๋ฒ
๊ฒฐ๊ณผ๋ฌผ์ด ํ๋ฉด์ ์์ํ๋๋ก ๋์ค๋์ง ํ ์คํธํ๋๋ฐฉ๋ฒ
๐ Cypress ์ด๋
Cypress๋ Javascript E2E ํ ์คํธ ๋๊ตฌ์ค ํ๋์ด๋ค
๐ ์ค์น
์ค์น๋ ์๊ฐ๋ณด๋ค ๊ฐ๋ฅํ๋ค
npm install cypress --save-dev
๐ Cypress ํ๊ฒฝ์ค์ ์ ๋ฆฌ
cypress ํ๊ฒฝ์ค์ ์ ์ฌ๋ฌ๊ฐ์ง ํ ์ ์๋ค.
url ,ํ๊ฒฝ๋ณ์, ํ๋ฉด์ฌ์ด์ฆ ์ง์ , ํ์์์ ๋ฑ๋ฑ
ํ์ฌ๊น์ง ์์๋ณธ๊ฒ์ ์๋์ ๊ฐ๋ค
๊ธฐ๋ฅ์ค๋ช ์์
| ํ ์คํธ ๊ธฐ๋ณธ URL ์ค์ | ํ ์คํธ ์คํ ์ ๊ธฐ์ค์ด ๋๋ ๊ธฐ๋ณธ ์ฃผ์๋ฅผ ์ง์ ํฉ๋๋ค. | baseUrl: 'http://localhost:5173' |
| ํ ์คํธ ํ์ผ ๊ฒฝ๋ก ์ง์ | Cypress๊ฐ ํ ์คํธ ํ์ผ์ ์ฐพ๋ ๊ฒฝ๋ก๋ฅผ ์ ์ํฉ๋๋ค. | specPattern: 'cypress/e2e/**/*.cy.ts' |
| ํ๊ฒฝ ๋ณ์ ์ค์ | API ์ฃผ์, ํ ํฐ ๋ฑ ํ ์คํธ์์ ์ฌ์ฉํ ํ๊ฒฝ ๋ณ์๋ฅผ ์ง์ ํฉ๋๋ค. | env: { apiUrl: 'https://api.example.com' } |
| ํ๋ฌ๊ทธ์ธ ๋ฐ ์ด๋ฒคํธ ์ค์ | ํ ์คํธ ์คํ ์ ํ ์ด๋ฒคํธ๋ ํ๋ฌ๊ทธ์ธ์ ๋ฑ๋กํฉ๋๋ค. | setupNodeEvents(on, config) { ... } |
| ๋ฆฌํธ๋ผ์ด(์ฌ์๋) ํ์ ์ค์ | ํ ์คํธ ์คํจ ์ ์๋ ์ฌ์๋ ํ์๋ฅผ ์ง์ ํฉ๋๋ค. | retries: 2 |
| ๋ฆฌํฌํฐ ์ค์ | ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ํ ๋ฆฌํฌํฐ๋ก ์ถ๋ ฅํฉ๋๋ค. | reporter: 'junit' |
| ๋ฆฌํฌํฐ ์ต์ ์ค์ | ๋ฆฌํฌํฐ์ ์์ธ ์ ์ฅ ํ์์ด๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ํฉ๋๋ค. | reporterOptions: { mochaFile: 'results/test.xml' } |
| ๋ทฐํฌํธ ํฌ๊ธฐ ์ค์ | ๋ธ๋ผ์ฐ์ ์ฐฝ ํฌ๊ธฐ๋ฅผ ์ค์ ํ์ฌ ๋ฐ์ํ ํ ์คํธ์ ํ์ฉํฉ๋๋ค. | viewportWidth: 1280, viewportHeight: 720 |
| ์คํฌ๋ฆฐ์ท / ๋น๋์ค ์ค์ | ํ ์คํธ ์ค ์คํฌ๋ฆฐ์ท๊ณผ ๋น๋์ค๋ฅผ ์ ์ฅํ ์ง ์ฌ๋ถ๋ฅผ ์ค์ ํฉ๋๋ค. | video: true, screenshotOnRunFailure: true |
| ์ปค๋งจ๋ ํ์์์ ์ค์ | ๊ฐ Cypress ๋ช ๋ น์ด์ ์ต๋ ๋๊ธฐ ์๊ฐ์ ์ง์ ํฉ๋๋ค. | defaultCommandTimeout: 8000 |
๐ฟ Cypress ์ฌ์ฉ๋ฒ
ํฌ๊ฒ 4๊ฐ์ง๊ฐ ์๋ค
- ์์ ํ์ ๋ฐ ์ ํ
- ์ฌ์ฉ์ ์ ๋ ฅ ์๋ฎฌ๋ ์ด์
- ๊ฒ์ฆ (Assertions)
- ๋คํธ์ํฌ ์์ฒญ (Intercept / Wait)
์์ ํ์ ๋ฐ ์ ํ
| cy.visit(url) | ํน์ ํ์ด์ง๋ก ์ด๋ | cy.visit('/login') |
| cy.get(selector) | DOM ์์ ์ ํ | cy.get('#username') |
| cy.contains(text) | ํน์ ํ ์คํธ ํฌํจ ์์ ์ ํ | cy.contains('๋ก๊ทธ์ธ') |
| cy.find(selector) | ํน์ ์์ ๋ด๋ถ ํ์ | cy.get('.form').find('input') |
| cy.first() / cy.last() | ์ฒซ ๋ฒ์งธ / ๋ง์ง๋ง ์์ ์ ํ | cy.get('li').first() |
| cy.eq(index) | N๋ฒ์งธ ์์ ์ ํ | cy.get('li').eq(2) |
์ฌ์ฉ์ ์ ๋ ฅ ์๋ฎฌ๋ ์ด์
๋ช ๋ น์ด์ค๋ช ์์
| cy.type(text) | ํ ์คํธ ์ ๋ ฅ | cy.get('input[name=email]').type('test@test.com') |
| cy.clear() | ์ ๋ ฅ ํ๋ ๋น์ฐ๊ธฐ | cy.get('input').clear() |
| cy.click() | ํด๋ฆญ | cy.get('button').click() |
| cy.check() / cy.uncheck() | ์ฒดํฌ๋ฐ์ค ์ ํ / ํด์ | cy.get('#agree').check() |
| cy.select(value) | <select> ์ต์ ์ ํ | cy.get('select').select('Option 1') |
๊ฒ์ฆ (Assertions)
๋ช ๋ น์ด์ค๋ช ์์
| cy.should(assertion, value) | ๋จ์ผ ์ด์ค์ | cy.get('h1').should('contain', 'Welcome') |
| cy.should('have.class', 'active') | ํด๋์ค ํ์ธ | cy.get('button#submit').click().should('have.class', 'active') |
| cy.should('be.visible') / cy.should('not.exist') | ํ์/๋ถ์ฌ ์ํ ํ์ธ | cy.get('.modal').should('be.visible')cy.get('.loading').should('not.exist') |
| cy.url().should('include', value) | URL ํฌํจ ์ฌ๋ถ ๊ฒ์ฆ | cy.url().should('include', '/dashboard') |
| cy.title().should('eq', value) | ํ์ด์ง ํ์ดํ ๊ฒ์ฆ | cy.title().should('eq', 'My App') |
๋คํธ์ํฌ ์์ฒญ (Intercept / Wait)
๋ช ๋ น์ด์ค๋ช ์์
| cy.intercept(method, url) | API ์์ฒญ ๊ฐ๋ก์ฑ๊ธฐ | cy.intercept('GET', '/api/user').as('getUser') |
| cy.wait(alias) | ์์ฒญ ์๋ฃ ๋๊ธฐ | cy.wait('@getUser') |
| cy.request(url) | HTTP ์์ฒญ ๋ณด๋ด๊ธฐ | cy.request('POST', '/api/login', { id: 'a', pw: 'b' }) |
| .as(aliasName) | alias(๋ณ์นญ) ๋ฑ๋ก | cy.intercept('GET', '/api/user').as('getUser') |
| cy.get('@alias') | alias ์ฐธ์กฐ | cy.get('@submitBtn').click() |
๐ Cypress ์คํ
npx cypress open
ํด๋น ๋ช ๋ น์ ํตํด ์คํํ๋ค



๐ช ๐ Cypress.confing.ts(js)
cypressํ๊ฒฝ์ค์ ํ์ผ์ด๋ค baseUrl,env,viewportWidth ์ ๋๋ก๋ง ์ค์ ํ์๋ค.
์ธ์ ์ค์ ๋ค์ cypress๊ณต์ํํ์ด์ง์์๋ ํ์ธ์ด๊ฐ๋ฅํ๋ค
import { defineConfig } from "cypress";
import fs from "fs";
type EnvDataType = {
baseUrl: string;
};
const envName = process.env.CYPRESS_ENV || "dev";
const envPath = `cypress.env.${envName}.json`;
let envData: EnvDataType = {
baseUrl: ""
};
if (fs.existsSync(envPath)) {
envData = JSON.parse(fs.readFileSync(envPath, "utf-8"));
}
export default defineConfig({
e2e: {
baseUrl: envData.baseUrl, // ๊ธฐ๋ณธ URL
env: envData, // ํ๊ฒฝ๋ณ์
viewportWidth: 1920 // ๋๋น 1920
}
});
๐ช Cypress ์์ ์ฝ๋
๊ฐ๋จํ ํ ์คํธ ์ฝ๋ ์์ฑ์ ํ๊ณ ์ ํ๋ค.
๋จ์ ๋ฐฉ๋ฌธํ "๋ก๊ทธ์ธ"์ด ์๋์ง ํ์ธํ๊ณ ์ ํ๋ค
describe("main view E2E testing", () => {
it("๋ก๊ทธ์ธ ํ
์คํธ๊ฐ ์๋์ง ํ์ธํ๋ค", () => {
// ๋ฃจํธ ์ง์
cy.visit("/");
// ๋ก๊ทธ์ธ ํ
์คํธ๊ฐ ์๋์ง ํ์ธํ๋ค
cy.contains("๋ก๊ทธ์ธ");
});
});
export {};
๐ก ํ์๊ฐ์ ํ๋ฉด ํ ์คํธ์ฝ๋ ์์ฑ
๊ฐ๋จํ ์ ๋ ฅํผ์ด ์๋ค
์ด๋ฆ,๋์ด,๋ฒํธ,์ฑ๋ณ,๋์๊ฐ ์๊ณ
๊ทธ๋ฆฌ๊ณ submit ๋ฒํผ์ ํตํด ํด๋น๊ฐ์ ์ด๊ธฐํ ํ๋ ๊ธฐ๋ฅ์ด ์๋ค
- ์ด๋ฆ,๋์ด,๋ฒํธ,์ฑ๋ณ,๋์๋ฅผ ์ ๋ ฅ ๋ฐ ์ ํํ๋ค
- submit์ ํตํด ๊ฐ์ ์ด๊ธฐํํ๋ค
describe("ํ์๊ฐ์
ํ
์คํธ", () => {
it("ํ์๊ฐ์
ํผ ์
๋ ฅ ", () => {
cy.visit("/signup");
// ์ด๋ฆ ์
๋ ฅ
cy.get("input").eq(0).type("๊น์๋ฌด๊ฐ");
// ๋์ด
cy.get("input").eq(1).clear().type("33");
// ๋ฒํธ
cy.get("input").eq(2).clear().type("01022223333");
// radio ์ ํ
cy.get("input[type='radio'][value='man']").check();
// v-select ์ ํ
cy.get("[data-cy='city-select'] .v-field__input").click();
cy.contains(".v-list-item", "California").click();
// Submit ๋ฒํผ ํด๋ฆญ
cy.get("button[type='submit']").click();
});
});
export {};
๐ Homeํ๋ฉด ํ ์คํธ์ฝ๋ ์์ฑ
ํ ํ๋ฉด์์๋ ๋ค์ด๋ฒ ๋ด์ค๋ฅผ ๊ฒ์ํ๋ ๊ธฐ๋ฅ์ด๋ค
์ฌ๊ธฐ์๋ ํน๋ณํ๊ฒ cypress๋ฅผ ์ธํฐ์ ํฐ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋ชจํนํ๊ณ ์ ํ๋ค
- ํ ํ๋ฉด์ ์ง์ ํ๋ค
- ๋ค์ด๋ฒ ๋ด์ค๋ฅผ ์กฐํํ๋ค
- ๊ฒ์ฆ์ row ๊ฐ์๋ฅผ ํ์ธํ๋ค
describe("Home View Testing", () => {
it("๋คํธ์ํฌ ์์ฒญ (Intercept / Wait)", () => {
// ๋ค์ด๋ฒ api ์ธํฐ์
ํฐ
cy.intercept("GET", "/api/navernews*", {
total: 949,
lastBuildDate: "Thu, 20 Nov 2025 08:21:37 +0900",
display: 100,
start: 1,
items: [
{
title: "์ธ์์ ๋ชจ๋ ๊ณต๊ฐ์ PLAYํ๋ค…<b>์ดํ๋ ์ด์ฆ</b>(APLAYZ) ๋ฐฐ์ ์ง ๋ํ",
originallink: "https://www.venturesquare.net/1014661",
link: "https://www.venturesquare.net/1014661",
description: "ํ๋ซํผ, <b>์ดํ๋ ์ด์ฆ</b>(APLAYZ) ๋ฐฐ์ ์ง ๋ํ ์๋น, ์นดํ, ์ ์์ฅ, ๊ทธ๋ฆฌ๊ณ ์ฐจ๋๊น์ง. ์ด๋์๋... ๋ฎค์งํ
ํฌ ์คํํธ์
<b>์ดํ๋ ์ด์ฆ</b>(APLAYZ)๋ ๊ทธ ์ผ์์ ๋ฐฐ๊ฒฝ์์ ๋ฐ์ดํฐ์ ์ธ๊ณต์ง๋ฅ์ผ๋ก... ",
pubDate: "Tue, 18 Nov 2025 09:09:00 +0900"
},
{
title: "๊ฐ๋จ๊ตฌ, ์คํ์ธ ‘์ค๋งํธ์ํฐ ๋ฐ๋ํ’ ์ฐธ๊ฐ... 457๋ง๋ถ ๊ท๋ชจ ์๋ด ์ฑ๊ณผ",
originallink: "https://weeklytrade.co.kr/news/view.html?section=1&category=160&item=&no=97212",
link: "https://weeklytrade.co.kr/news/view.html?section=1&category=160&item=&no=97212",
description: "โฒ‘<b>์ดํ๋ ์ด์ฆ</b>’๋ ์ธ๊ณต์ง๋ฅ์ ํ์ฉํด ๊ณต๊ฐ๋ณ๋ก ์ด์ธ๋ฆฌ๋ ์์
์ ์๋์ผ๋ก ์ถ์ฒํ๋ ์์คํ
์ ์ ๋ณด์๋ค. โฒ‘ํ๋น
’์ ๊ณต๊ณต ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ํ์ฉํ ์ ์๋๋ก ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๊ธฐ๋ฅ์ด ๊ฐํ๋ ์ธ์ด์ฒ๋ฆฌ ๊ธฐ์ ์... ",
pubDate: "Tue, 11 Nov 2025 01:50:00 +0900"
}
]
}).as("naverNews");
cy.visit("/");
// ์๋ฃ๋ฅผ ๋๊ธฐํ๋ค
cy.wait("@naverNews");
// ๋ฐฉ๋ฌธ๊ฒ์ฆ
cy.url().should("include", "/");
// ์ค์ Row ๊ฐ์ ๊ฒ์ฆ
cy.get("table tbody tr").should("have.length", 2);
});
});
export {};
์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ์ ์ intercept๋ฅผ ํตํด ๋คํธ์ํฌ๋ฅผ ๊ฐ์ง๋ก ๊ตฌํํ ์ ์๋ค๋ ์ ์ด๋ค.
์ฌ๊ธฐ์๋ 2๊ฐ์ ๋ ๋ฆฌํดํ๊ฒ๋ ๊ฐ์ง๋ก ๊ตฌํํ์๊ณ ,
์ค์ ๋ก์ฐ ๊ฐ์๊ฐ 2๊ฐ์ธ์ง ๊ฒ์ฆํ๋ค
// ์ธ๋ถ ์์กด์ฑ ์๋๊ฒ๋ค์ ๊ฐ์ง๋ก ๊ตฌํํ๋ค.
cy.intercept("GET", "/api/navernews*", {
total: 949,
lastBuildDate: "Thu, 20 Nov 2025 08:21:37 +0900",
display: 100,
start: 1,
items: [
{
title: "์ธ์์ ๋ชจ๋ ๊ณต๊ฐ์ PLAYํ๋ค…<b>์ดํ๋ ์ด์ฆ</b>(APLAYZ) ๋ฐฐ์ ์ง ๋ํ",
originallink: "https://www.venturesquare.net/1014661",
link: "https://www.venturesquare.net/1014661",
description: "ํ๋ซํผ, <b>์ดํ๋ ์ด์ฆ</b>(APLAYZ) ๋ฐฐ์ ์ง ๋ํ ์๋น, ์นดํ, ์ ์์ฅ, ๊ทธ๋ฆฌ๊ณ ์ฐจ๋๊น์ง. ์ด๋์๋... ๋ฎค์งํ
ํฌ ์คํํธ์
<b>์ดํ๋ ์ด์ฆ</b>(APLAYZ)๋ ๊ทธ ์ผ์์ ๋ฐฐ๊ฒฝ์์ ๋ฐ์ดํฐ์ ์ธ๊ณต์ง๋ฅ์ผ๋ก... ",
pubDate: "Tue, 18 Nov 2025 09:09:00 +0900"
},
{
title: "๊ฐ๋จ๊ตฌ, ์คํ์ธ ‘์ค๋งํธ์ํฐ ๋ฐ๋ํ’ ์ฐธ๊ฐ... 457๋ง๋ถ ๊ท๋ชจ ์๋ด ์ฑ๊ณผ",
originallink: "https://weeklytrade.co.kr/news/view.html?section=1&category=160&item=&no=97212",
link: "https://weeklytrade.co.kr/news/view.html?section=1&category=160&item=&no=97212",
description: "โฒ‘<b>์ดํ๋ ์ด์ฆ</b>’๋ ์ธ๊ณต์ง๋ฅ์ ํ์ฉํด ๊ณต๊ฐ๋ณ๋ก ์ด์ธ๋ฆฌ๋ ์์
์ ์๋์ผ๋ก ์ถ์ฒํ๋ ์์คํ
์ ์ ๋ณด์๋ค. โฒ‘ํ๋น
’์ ๊ณต๊ณต ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ํ์ฉํ ์ ์๋๋ก ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๊ธฐ๋ฅ์ด ๊ฐํ๋ ์ธ์ด์ฒ๋ฆฌ ๊ธฐ์ ์... ",
pubDate: "Tue, 11 Nov 2025 01:50:00 +0900"
}
]
}).as("naverNews");
// ์ค์ Row ๊ฐ์ ๊ฒ์ฆ
cy.get("table tbody tr").should("have.length", 2);

๐ต CypressViewํ๋ฉด ํ ์คํธ ์ฝ๋์์ฑ
ํด๋นํ๋ฉด์์๋
* h1ํ ์คํธ ๊ฒ์ฆ
* ๋ฒํผํ์ฑํ ์์ ์์๋ณ๊ฒฝ ๊ฒ์ฆ
* ์ฒดํฌ๋ฐ์ค ํด๋ฆญ์์ h3 visible/invisible ํ๋์ง ๊ฒ์ฆ
* ํ์ด์ง title ๊ฒ์ฆ
describe("Cypress View Testing", () => {
it(" ๊ฒ์ฆ (Assertions)", () => {
cy.visit("/cypressview");
// ๋ฐฉ๋ฌธ๊ฒ์ฆ
cy.url().should("include", "/cypressview");
// h1 ํ
์คํธ ๊ฒ์ฆ
cy.get("h1").contains("H1");
// ๋ฒํผ ํ์ฑํ
cy.get("button").click().should("have.class", "bg-indigo-darken-3");
// visible/invisible
cy.get(".v-switch input[type='checkbox']").check({ force: true });
cy.get("h3").should("be.visible");
cy.get(".v-switch input[type='checkbox']").uncheck({ force: true });
cy.get("h3").should("not.exist");
// ํ์ดํ ๊ฒ์ฆ
cy.title().should("eq", "MAXGUN");
});
});
export {};
๐ command.ts
๋ฐ๋ณต์ ์ธ ์ฝ๋์ ๋ํด์ ๋ชจ๋ํํ์ฌ ์ฌ์ฌ์ฉํ๋๋ฐ ์ฌ์ฉ
// cypress/support/commands.ts
/**
* cypress login command
*/
export const login = () => {
// ๋ก๊ทธ์ธ ์ด๋
cy.visit("/login");
};
export {};
```
// cypress/e2e/spec.cy.ts
import { login } from "../support/commands";
describe("๋ก๊ทธ์ธ ๋ฐฉ๋ฌธ ํ
์คํธ", () => {
before(() => {
// ๋ฐ๋ณต์ฝ๋ ์ ์ฉ
login();
});
it("๋ก๊ทธ์ธ ์ฑ๊ณต", () => {
// ๋ก๊ทธ์ธ ํผ ์
๋ ฅ
cy.get("input").eq(0).type("cdg@aplayz.co.kr");
cy.get("input").eq(1).type("chleorjs12@");
// ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญ
cy.get("button").click();
// main ํ๋ฉด ์ด๋ ํ์ธ
cy.url().should("include", "/main");
});
});
export {};
๐ e2e.ts
e2e.ts๋ Cypress๊ฐ E2E ํ ์คํธ๋ฅผ ์คํํ ๋ ๊ฐ์ฅ ๋จผ์ ๋ก๋๋๋ ์ค์ ํ์ผ์ ๋๋ค. ์ฆ, ๋ชจ๋ ํ ์คํธ(*.cy.ts ํ์ผ ๋ฑ)๊ฐ ์คํ๋๊ธฐ ์ ์ ๊ณตํต์ ์ผ๋ก ์ ์ฉํ ์ค์ , ํ , ํ๋ฌ๊ทธ์ธ, ์ ์ญ ๋ช ๋ น์ด ๋ฑ์ ์ด๊ธฐํํ๋ ๊ณณ์ ๋๋ค.
์์๋ก beforeAll,beforeEach์ ๋ก๊ทธ๋ฅผ ์ ์ฉํด ๋ณด์๋ค
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
// ๊ณตํต ๋ก์ง ์ถ๊ฐ
before(() => {
cy.log("๐ ๋ชจ๋ ํ
์คํธ ์ ํ ๋ฒ ์คํ");
});
beforeEach(() => {
cy.log("โก๏ธ ๊ฐ ํ
์คํธ ์ ์คํ");
});

๊ธฐ๋ณธ์ ์ธ Cypressํ์ฉ๋ฒ์ ๋ํด์ ์์๋ณด์๊ณ
๋ค์์๋ cypress๋ฅผ ํตํด ์ปค๋ฒ๋ฆฌ์ง ์๋ํ N์๊ฐ๋ง๋ค ๊ฒ์ฆ ํ๋๊ฒ์ ์์๋ณผ ์์ ์ด๋ค
๋๊ธ