Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
d790dbb
feat :: View 화면 위한 클래스 분리 및 mount 테스트
eveneul Jul 6, 2025
6d84747
Merge branch 'main' of https://github.com/hanghae-plus/front_6th_chap…
eveneul Jul 7, 2025
78b89bb
chore: 원본 저장소 CI 수정사항 동기화
eveneul Jul 7, 2025
01711de
chore: 원본 저장소 CI 수정사항 동기화
eveneul Jul 7, 2025
b032117
feat: 라우터 클래스, View 베이스 클래스 생성 및 컴포넌트 분리
eveneul Jul 7, 2025
e51e0a9
feat: 상품 목록 fetching 및 Search 컴포넌트 분리, render 함수 추가
eveneul Jul 7, 2025
3683f35
refactor: 기존 코드 전면 수정 및 재구성
eveneul Jul 9, 2025
53a160c
feat: useRender Hook, Layout 컴포넌트 생성
eveneul Jul 9, 2025
416b5d1
Merge branch 'main' of https://github.com/hanghae-plus/front_6th_chap…
eveneul Jul 9, 2025
9dcc2d0
feat: 전역 상태 관리 커스텀 훅 추가
eveneul Jul 9, 2025
23ef1d5
fix: 화면 렌더링 함수 오류 수정
eveneul Jul 9, 2025
2d6d9ac
feat: 로딩 UI Component 추가
eveneul Jul 9, 2025
239fb11
feat: products API 호출 및 컴포넌트 정리
eveneul Jul 9, 2025
53a2667
feat: store set 함수에서 중첩 키 지원 추가
eveneul Jul 9, 2025
bd4c84c
feat: store watch 변경
eveneul Jul 9, 2025
614e88a
feat: 상품 검색 컴포넌트 생성
eveneul Jul 9, 2025
f709021
fix: 검색값에 따른 상품 목록 리렌더링 오류 수정
eveneul Jul 9, 2025
51fe4a2
Merge branch 'main' of https://github.com/hanghae-plus/front_6th_chap…
eveneul Jul 9, 2025
542c4ca
feat: 무한 스크롤 구현 중
eveneul Jul 9, 2025
f1f700a
feat: 상품 카드 클릭 이벤트 및 장바구니 추가 기능 구현
eveneul Jul 9, 2025
63e6bbc
feat: 상품 상세 페이지 로딩 및 URL 변경 이벤트 처리 추가
eveneul Jul 9, 2025
ae35e2b
feat: easy / hard 를 구분하여 테스트를 실행할 수 있도록 함
JunilHwang Jul 10, 2025
60a4682
feat: 상품 상세 페이지 및 목록 기능 개선
eveneul Jul 10, 2025
e9ad578
feat: E2E 테스트 및 CI 구성 개선
eveneul Jul 10, 2025
c0b5e44
feat: useNavigate 훅 리팩토링 및 반환 구조 변경
eveneul Jul 10, 2025
c457811
feat: Home 및 Search 컴포넌트 개선
eveneul Jul 10, 2025
1add259
feat: Search 컴포넌트에서 파라미터 초기화 및 주석 제거
eveneul Jul 10, 2025
be3a29b
feat: Search 및 Home 컴포넌트 개선
eveneul Jul 10, 2025
1142de2
feat: main.js 및 관련 컴포넌트 리팩토링
eveneul Jul 10, 2025
31a1e48
feat: Home 및 Product 컴포넌트 로딩 상태 관리 개선
eveneul Jul 10, 2025
7335559
feat: Header 컴포넌트 상품 상세 페이지 처리 및 Layout 리팩토링
eveneul Jul 10, 2025
5e89717
feat: Search 및 Home 컴포넌트에서 파라미터 관리 개선
eveneul Jul 10, 2025
1767994
feat: Home 컴포넌트에서 무한 스크롤 및 로딩 상태 개선
eveneul Jul 10, 2025
fd69172
fix: 검색 기능 수정
eveneul Jul 10, 2025
5f0c62c
feat: ProductList 컴포넌트에서 불필요한 메시지 제거
eveneul Jul 10, 2025
ecbc932
feat: Toast 컴포넌트 추가 및 장바구니 추가 시 알림 기능 구현
eveneul Jul 10, 2025
a22d512
feat: ProductList 및 Search 컴포넌트 개선
eveneul Jul 11, 2025
8c5e259
feat: Product 및 Search 컴포넌트 개선
eveneul Jul 11, 2025
a595946
feat: Toast 컴포넌트 이벤트 핸들링 개선
eveneul Jul 11, 2025
e6828bb
feat: ProductCard 컴포넌트 내비게이션 및 이벤트 핸들링 개선
eveneul Jul 11, 2025
4205c6f
feat: Search 및 Home 컴포넌트 개선
eveneul Jul 11, 2025
972880a
feat: Breadcrumb 컴포넌트 추가 및 Search, Home에서 통합
eveneul Jul 11, 2025
dac711b
feat: ProductCard 컴포넌트 가격 포맷팅 및 브랜드 정보 표시 개선
eveneul Jul 11, 2025
aa348f5
feat: Search 및 Home 컴포넌트에서 URL 파라미터 처리 개선
eveneul Jul 11, 2025
9653718
feat: NotFound 컴포넌트 개선 및 404 페이지 디자인 추가
eveneul Jul 11, 2025
fe4592a
feat: Home 컴포넌트 스크롤 이벤트 핸들링 개선
eveneul Jul 11, 2025
a3aab89
feat: 장바구니 기능 추가 및 헤더 컴포넌트 통합
eveneul Jul 11, 2025
fb3100e
feat: 빌드 및 배포 스크립트 추가 및 Vite 설정 개선
eveneul Jul 11, 2025
162a51c
feat: gh-pages 패키지 추가 및 배포 스크립트 수정
eveneul Jul 11, 2025
3b29340
feat: 서비스 워커 설정 추가
eveneul Jul 11, 2025
c6797ab
fix: Vite 설정에서 기본 경로 수정
eveneul Jul 11, 2025
5243133
feat: 404 페이지 추가 및 디자인 구현
eveneul Jul 11, 2025
f15d4eb
feat: 배포 스크립트 수정 및 GitHub Actions 워크플로우 추가
eveneul Jul 11, 2025
54307d9
feat: HTML 구조 개선 및 JavaScript 모킹 기능 수정
eveneul Jul 11, 2025
b58f3ff
feat: 앱 경로 처리 유틸리티 함수 추가 및 관련 컴포넌트 수정
eveneul Jul 11, 2025
4b31745
index.html에서 불필요한 스크립트 및 스타일시트 링크 제거
eveneul Jul 11, 2025
56fdcc8
메인페이지에서만 스크롤 이벤트 처리 가능하도록 주석 추가
eveneul Jul 11, 2025
c9c813c
배포 오류 수정 중
eveneul Jul 11, 2025
9dd544f
배포 오류 수정 중
eveneul Jul 11, 2025
6931266
배포 테스트
eveneul Jul 12, 2025
2a5014c
fix: 무한 스크롤 수정
eveneul Jul 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
workflow_dispatch:

jobs:
unit:
hard-basic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -24,11 +24,50 @@ jobs:
with:
node-version: 22
cache: 'pnpm'
- run: |
pnpm install
pnpm run test:hard:basic
hard-advanced:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: |
pnpm install
pnpm run test
e2e:
npx playwright install --with-deps
pnpm run test:hard:advanced
easy-basic:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: |
pnpm install
npx playwright install --with-deps
pnpm run test:easy:basic
easy-advanced:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
Expand All @@ -47,4 +86,4 @@ jobs:
run: |
pnpm install
npx playwright install --with-deps
pnpm run test:e2e
pnpm run test:easy:advanced
305 changes: 305 additions & 0 deletions e2e/e2e-easy.advanced.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
import { expect, test } from "@playwright/test";

// 테스트 설정
test.describe.configure({ mode: "serial" });

// 헬퍼 함수들
class E2EHelpers {
constructor(page) {
this.page = page;
}

// 페이지 로딩 대기
async waitForPageLoad() {
await this.page.waitForSelector('[data-testid="products-grid"], #products-grid', { timeout: 10000 });
await this.page.waitForFunction(() => {
const text = document.body.textContent;
return text.includes("총") && text.includes("개");
});
}

// 상품을 장바구니에 추가
async addProductToCart(productName) {
await this.page.click(
`text=${productName} >> xpath=ancestor::*[contains(@class, 'product-card')] >> .add-to-cart-btn`,
);
await this.page.waitForSelector("text=장바구니에 추가되었습니다", { timeout: 5000 });
}

// 장바구니 모달 열기
async openCartModal() {
await this.page.click("#cart-icon-btn");
await this.page.waitForSelector(".cart-modal-overlay", { timeout: 5000 });
}
}

test.describe("E2E: 쇼핑몰 전체 사용자 시나리오", () => {
test.beforeEach(async ({ page }) => {
// 로컬 스토리지 초기화
await page.goto("/");
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
});

test.describe("1. 검색 및 필터링 기능이 URL과 연동된다.", () => {
test("검색어 입력 후 Enter 키로 검색하고 URL이 업데이트된다", async ({ page }) => {
const helpers = new E2EHelpers(page);
await helpers.waitForPageLoad();

// 검색어 입력
await page.fill("#search-input", "젤리");
await page.press("#search-input", "Enter");

// URL 업데이트 확인
await expect(page).toHaveURL(/search=%EC%A0%A4%EB%A6%AC/);

// 검색 결과 확인
await expect(page.locator("text=3개")).toBeVisible();

// 검색어가 검색창에 유지되는지 확인
await expect(page.locator("#search-input")).toHaveValue("젤리");

// 검색어 입력
await page.fill("#search-input", "아이패드");
await page.press("#search-input", "Enter");

// URL 업데이트 확인
await expect(page).toHaveURL(/search=%EC%95%84%EC%9D%B4%ED%8C%A8%EB%93%9C/);

// 검색 결과 확인
await expect(page.locator("text=21개")).toBeVisible();

// 새로고침을 해도 유지 되는지 확인
await page.reload();
await helpers.waitForPageLoad();
await expect(page.locator("text=21개")).toBeVisible();
});

test("정렬 옵션 변경 시 URL이 업데이트된다", async ({ page }) => {
const helpers = new E2EHelpers(page);
await helpers.waitForPageLoad();

// 가격 높은순으로 정렬
await page.selectOption("#sort-select", "price_desc");

// 첫 번째 상품 이 가격 높은 순으로 정렬되었는지 확인
await expect(page.locator(".product-card").first()).toMatchAriaSnapshot(`
- img "ASUS ROG Flow Z13 GZ302EA-RU110W 64GB, 1TB"
- heading "ASUS ROG Flow Z13 GZ302EA-RU110W 64GB, 1TB" [level=3]
- paragraph: ASUS
- paragraph: 3,749,000원
- button "장바구니 담기"
`);

await page.selectOption("#sort-select", "name_asc");
await expect(page.locator(".product-card").nth(1)).toMatchAriaSnapshot(`
- img "[매일출발]유로블루플러스 차량용 요소수 국내산 Adblue 호스포함"
- heading "[매일출발]유로블루플러스 차량용 요소수 국내산 Adblue 호스포함" [level=3]
- paragraph: 유로블루플러스
- paragraph: 8,700원
- button "장바구니 담기"
`);

await page.selectOption("#sort-select", "name_desc");
await expect(page.locator(".product-card").nth(1)).toMatchAriaSnapshot(`
- img "P&G 다우니 울트라 섬유유연제 에이프릴 프레쉬, 5.03L, 1개"
- heading "P&G 다우니 울트라 섬유유연제 에이프릴 프레쉬, 5.03L, 1개" [level=3]
- paragraph: 다우니
- paragraph: 16,610원
- button "장바구니 담기"
`);

await page.reload();
await helpers.waitForPageLoad();
await expect(page.locator(".product-card").nth(1)).toMatchAriaSnapshot(`
- img "P&G 다우니 울트라 섬유유연제 에이프릴 프레쉬, 5.03L, 1개"
- heading "P&G 다우니 울트라 섬유유연제 에이프릴 프레쉬, 5.03L, 1개" [level=3]
- paragraph: 다우니
- paragraph: 16,610원
- button "장바구니 담기"
`);
});

test("페이지당 상품 수 변경 시 URL이 업데이트된다", async ({ page }) => {
const helpers = new E2EHelpers(page);
await helpers.waitForPageLoad();

// 10개로 변경
await page.selectOption("#limit-select", "10");
await expect(page).toHaveURL(/limit=10/);
await page.waitForFunction(() => {
return document.querySelectorAll(".product-card").length === 10;
});
await expect(page.locator(".product-card").last()).toMatchAriaSnapshot(
`- heading "탈부착 방충망 자석쫄대 방풍비닐 창문방충망 셀프시공 DIY 백색 100cm" [level=3]`,
);

await page.selectOption("#limit-select", "20");
await expect(page).toHaveURL(/limit=20/);
await page.waitForFunction(() => {
return document.querySelectorAll(".product-card").length === 20;
});
await expect(page.locator(".product-card").last()).toMatchAriaSnapshot(
`- heading "고양이 난간 안전망 복층 베란다 방묘창 방묘문 방충망 캣도어 일반형검정1mx1m" [level=3]`,
);

await page.selectOption("#limit-select", "50");
await expect(page).toHaveURL(/limit=50/);
await page.waitForFunction(() => {
return document.querySelectorAll(".product-card").length === 50;
});
await expect(page.locator(".product-card").last()).toMatchAriaSnapshot(
`- heading "강아지 고양이 아이스팩 파우치 여름 베개 젤리곰 M사이즈" [level=3]`,
);

await page.selectOption("#limit-select", "100");
await expect(page).toHaveURL(/limit=100/);
await page.waitForFunction(() => {
return document.querySelectorAll(".product-card").length === 100;
});
await expect(page.locator(".product-card").last()).toMatchAriaSnapshot(
`- heading "고양이 스크래쳐 숨숨집 하우스 대형 원목 스크레쳐 A type" [level=3]`,
);

await page.reload();
await helpers.waitForPageLoad();
await expect(page.locator(".product-card").last()).toMatchAriaSnapshot(
`- heading "고양이 스크래쳐 숨숨집 하우스 대형 원목 스크레쳐 A type" [level=3]`,
);
});

test("검색어와 필터 조건이 URL에서 복원된다", async ({ page }) => {
const helpers = new E2EHelpers(page);

await page.goto("/?search=젤리&sort=price_desc&limit=10");
await helpers.waitForPageLoad();

await expect(page.locator("#search-input")).toHaveValue("젤리");
await expect(page.locator("#sort-select")).toHaveValue("price_desc");
await expect(page.locator("#limit-select")).toHaveValue("10");
await expect(page.getByRole("main")).toMatchAriaSnapshot(`- text: /총 3개의 상품/`);

await page.goto("/?search=고양이&sort=name_desc&limit=50");
await helpers.waitForPageLoad();

await expect(page.locator("#search-input")).toHaveValue("고양이");
await expect(page.locator("#sort-select")).toHaveValue("name_desc");
await expect(page.locator("#limit-select")).toHaveValue("50");
await expect(page.getByRole("main")).toMatchAriaSnapshot(`- text: /총 84개의 상품/`);
});
});

test.describe("2. 상품 상세 페이지와 URL이 연동된다.", () => {
test("상품 클릭부터 관련 상품 이동까지 전체 플로우", async ({ page }) => {
await page.evaluate(() => {
window.loadFlag = true;
window.history.pushState({}, "", "/product/85067212996");
window.dispatchEvent(new Event("popstate"));
});

// 상세 페이지 로딩 확인
await expect(page.locator("text=상품 상세")).toBeVisible();

// h1 태그에 상품명 확인
await expect(
page.locator('h1:text("PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장")'),
).toBeVisible();

// 관련 상품 섹션 확인
await expect(page.locator("text=관련 상품")).toBeVisible();
const relatedProducts = page.locator(".related-product-card");
await expect(relatedProducts.first()).toBeVisible();

// 첫 번째 관련 상품 클릭
await relatedProducts.first().click();

// 다른 상품의 상세 페이지로 이동했는지 확인
await expect(page).toHaveURL("/product/86940857379");
await expect(
page.locator('h1:text("샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이")'),
).toBeVisible();

await expect(await page.evaluate(() => window.loadFlag)).toBe(true);

await page.reload();

await expect(
page.locator('h1:text("샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이")'),
).toBeVisible();

await expect(await page.evaluate(() => window.loadFlag)).toBe(undefined);
});
});

test.describe("3. SPA 네비게이션", () => {
test("브라우저 뒤로가기/앞으로가기가 올바르게 작동한다", async ({ page }) => {
const helpers = new E2EHelpers(page);
await page.evaluate(() => {
window.loadFlag = true;
});
await helpers.waitForPageLoad();

// 상품 상세 페이지로 이동
const productCard = page
.locator("text=PVC 투명 젤리 쇼핑백")
.locator('xpath=ancestor::*[contains(@class, "product-card")]');
await productCard.locator("img").click();

await expect(page).toHaveURL("/product/85067212996");
await expect(
page.locator('h1:text("PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장")'),
).toBeVisible();
await expect(page.locator("text=관련 상품")).toBeVisible();
const relatedProducts = page.locator(".related-product-card");
await relatedProducts.first().click();

await expect(page).toHaveURL("/product/86940857379");
await expect(
page.locator('h1:text("샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이")'),
).toBeVisible();

// 브라우저 뒤로가기
await page.goBack();
await expect(page).toHaveURL("/product/85067212996");
await expect(
page.locator('h1:text("PVC 투명 젤리 쇼핑백 1호 와인 답례품 구디백 비닐 손잡이 미니 간식 선물포장")'),
).toBeVisible();

// 브라우저 앞으로가기
await page.goForward();
await expect(page).toHaveURL("/product/86940857379");
await expect(
page.locator('h1:text("샷시 풍지판 창문 바람막이 베란다 문 틈막이 창틀 벌레 차단 샤시 방충망 틈새막이")'),
).toBeVisible();

await page.goBack();
await page.goBack();
await expect(page).toHaveURL("/");
const firstProductCard = page.locator(".product-card").first();
await expect(firstProductCard.locator("img")).toBeVisible();

expect(await page.evaluate(() => window.loadFlag)).toBe(true);

await page.reload();
expect(
await page.evaluate(() => {
return window.loadFlag;
}),
).toBe(undefined);
});

// 404 페이지 테스트
test("존재하지 않는 페이지 접근 시 404 페이지가 표시된다", async ({ page }) => {
// 존재하지 않는 경로로 이동
await page.goto("/non-existent-page");

// 404 페이지 확인
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
- img: /404 페이지를 찾을 수 없습니다/
- link "홈으로"
`);
});
});
});
Loading
Loading