Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ae35e2b
feat: easy / hard 를 구분하여 테스트를 실행할 수 있도록 함
JunilHwang Jul 10, 2025
1a8c52f
init: install playwright
annkimm Jul 6, 2025
08a7e86
feat: edit test file
annkimm Oct 5, 2025
555565f
feat: add compnents
annkimm Oct 5, 2025
4fc3446
feat: add type
annkimm Oct 5, 2025
625a164
feat: add detail page
annkimm Oct 5, 2025
6a5a465
feat: add list page
annkimm Oct 5, 2025
a276dcc
feat: add router
annkimm Oct 5, 2025
e050186
feat: add state in product list page
annkimm Oct 5, 2025
c85997d
fix: router link
annkimm Oct 5, 2025
322510e
fix: to not pass test
annkimm Oct 5, 2025
a8418f2
refactor: fix to init state
annkimm Oct 5, 2025
9fca27f
refactor: to fix event delegation for utils
annkimm Oct 6, 2025
5c6d986
feat: add enter func for searching
annkimm Oct 6, 2025
1c9c98a
refactor: to fix typo
annkimm Oct 6, 2025
d8ac8d9
feat: add click product image
annkimm Oct 6, 2025
bda37a0
feat: add detail page for data loading
annkimm Oct 6, 2025
08eb319
refactor: multi click by className
annkimm Oct 6, 2025
91d7dc4
fix: to change page name
annkimm Oct 6, 2025
979fae0
feat: add to search product list by filtering category
annkimm Oct 6, 2025
a5983c3
feat: add error page
annkimm Oct 6, 2025
2514fc0
feat: move error page when not exist page move
annkimm Oct 6, 2025
0eb7c0b
fix: pass issue when all test fail
annkimm Oct 6, 2025
6bd1277
refactor: fix to two function from one function
annkimm Oct 6, 2025
9653c9b
fix: infinite loop
annkimm Oct 7, 2025
a304ddb
feat: add quantity for increasing or decreasing
annkimm Oct 8, 2025
7fa6e33
refactor: fix typo
annkimm Oct 8, 2025
5afbf03
feat: add layout
annkimm Oct 8, 2025
5d4dc35
feat: add close function for cart modal
annkimm Oct 8, 2025
354bb6c
refactor: change cartModal function
annkimm Oct 8, 2025
807df07
feat: add to show empty screen by state
annkimm Oct 8, 2025
9cbac7f
feat: add to show cart item length
annkimm Oct 8, 2025
55c68d2
feat: add, delete cart item
annkimm Oct 8, 2025
adc0542
feat: add to show selected items length
annkimm Oct 8, 2025
2f63808
feat: add delete all items
annkimm Oct 8, 2025
de175bc
fix: not pass issue about cart modal cloing
annkimm Oct 8, 2025
fce9cde
feat: add toast message
annkimm Oct 14, 2025
afc11b2
feat: add quality add or remove function
annkimm Oct 15, 2025
745da07
feat: add function for delete part cart and set checkbox
annkimm Oct 15, 2025
b5bc3f2
feat: add all checked or not checked checkbox
annkimm Oct 15, 2025
698acba
feat: add to reset open or close cart modal
annkimm Oct 15, 2025
ba5a55f
refactor: fix to 'waitFor', because rendering issue
annkimm Oct 16, 2025
10bec04
feat: add to show selected products price
annkimm Oct 16, 2025
3997e9c
feat: add to move page from breadcrumb
annkimm Oct 21, 2025
966290e
feat: add scroll event in list page
annkimm Oct 25, 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,45 @@ on:
workflow_dispatch:

jobs:
unit:
hard-basic:
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'
- 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
npx playwright install --with-deps
pnpm run test:hard:advanced
easy-basic:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -27,8 +65,9 @@ jobs:
- name: Install dependencies
run: |
pnpm install
pnpm run test
e2e:
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