diff --git a/.github/workflows/fe-test-e2e.yml b/.github/workflows/fe-test-e2e.yml
new file mode 100644
index 000000000..65067400d
--- /dev/null
+++ b/.github/workflows/fe-test-e2e.yml
@@ -0,0 +1,35 @@
+name: Cypress Tests
+
+on:
+ pull_request:
+ branches:
+ - develop
+ paths:
+ - frontend/**
+ - .github/**
+
+defaults:
+ run:
+ working-directory: ./frontend
+
+jobs:
+ cypress-run:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Create .env.development file
+ run: |
+ touch .env.development
+ echo "BASE_URL=${{secrets.BASE_URL_DEVELOPMENT}}" >> .env.development
+
+ - name: Cypress run
+ uses: cypress-io/github-action@v5
+ with:
+ start: yarn start
+ wait-on: 'http://localhost:3000'
+ browser: chrome
+ working-directory: ./frontend
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/frontend/.eslintrc b/frontend/.eslintrc
index 2ff137ecf..e8018f3e2 100644
--- a/frontend/.eslintrc
+++ b/frontend/.eslintrc
@@ -25,6 +25,7 @@
"plugin:import/typescript",
"plugin:storybook/recommended",
"plugin:@typescript-eslint/parser",
+ "plugin:cypress/recommended",
"prettier"
],
"rules": {
diff --git a/frontend/.gitignore b/frontend/.gitignore
index ff7c3e08c..8136e209b 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -1,4 +1,6 @@
node_modules/
dist/
yarn-error.log
-.env.*
\ No newline at end of file
+.env.*
+
+cypress/videos
\ No newline at end of file
diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts
new file mode 100644
index 000000000..e0d0e058a
--- /dev/null
+++ b/frontend/cypress.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+ e2e: {
+ baseUrl: 'http://localhost:3000',
+ setupNodeEvents(on, config) {
+ // implement node event listeners here
+ },
+ },
+});
diff --git a/frontend/cypress/e2e/main-page.cy.ts b/frontend/cypress/e2e/main-page.cy.ts
new file mode 100644
index 000000000..344d009df
--- /dev/null
+++ b/frontend/cypress/e2e/main-page.cy.ts
@@ -0,0 +1,54 @@
+describe('동글 메인 페이지', () => {
+ beforeEach(() => {
+ cy.viewport(1440, 810);
+ cy.visit(`/`);
+ });
+
+ describe('글 업로드 테스트', () => {
+ it('Add Post 버튼을 누르면 글 가져오기 모달 창이 열린다.', () => {
+ cy.findByText('Add Post').click();
+
+ cy.findByText('글 가져오기').should('exist');
+ });
+
+ it('드래그 앤 드롭으로 마크다운 파일을 업로드할 수 있다.', () => {
+ cy.findByText('Add Post').click();
+ cy.findByLabelText('파일 업로드').attachFile('markdown-test.md', {
+ subjectType: 'drag-n-drop',
+ });
+
+ cy.findByText('markdown-test').should('exist');
+ cy.findByText('e2e 테스트를 위한 마크다운 파일입니다.').should('exist');
+ cy.findByText('글 정보').should('exist');
+ cy.findByLabelText('오른쪽 사이드바 토글').should('exist');
+ });
+
+ it('기본 카테고리에서 업로드한 글을 확인할 수 있다.', () => {
+ cy.findByText('기본').click();
+
+ cy.findAllByText('markdown-test').should('exist');
+ });
+ });
+
+ describe('카테고리 테스트', () => {
+ it('카테고리 추가 버튼을 클릭하여 입력 창에 이름을 입력하고 엔터를 쳐서 카테고리를 추가할 수 있다.', () => {
+ cy.findByLabelText('카테고리 추가 입력 창 열기').click();
+ cy.findByLabelText('카테고리 추가 입력 창').focus().type('동글이{enter}');
+ cy.findByText('동글이').should('exist');
+ });
+
+ it('카테고리 이름 수정 버튼을 클릭하여 입력 창에 이름을 입력하고 엔터를 쳐서 카테고리 이름을 수정할 수 있다.', () => {
+ cy.findByText('동글이').realHover();
+ cy.findByLabelText('동글이 카테고리 이름 수정').click();
+ cy.findByLabelText('동글이 카테고리 이름 수정 입력 창').focus().type('동글동글이{enter}');
+ cy.findByText('동글이').should('not.exist');
+ cy.findByText('동글동글이').should('exist');
+ });
+
+ it('카테고리 삭제 버튼을 클릭하여 카테고리를 삭제할 수 있다.', () => {
+ cy.findByText('동글동글이').realHover();
+ cy.findByLabelText('동글동글이 카테고리 삭제').click();
+ cy.findByText('동글동글이').should('not.exist');
+ });
+ });
+});
diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json
new file mode 100644
index 000000000..02e425437
--- /dev/null
+++ b/frontend/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/frontend/cypress/fixtures/markdown-test.md b/frontend/cypress/fixtures/markdown-test.md
new file mode 100644
index 000000000..d8fc48c1c
--- /dev/null
+++ b/frontend/cypress/fixtures/markdown-test.md
@@ -0,0 +1,8 @@
+## e2e 테스트를 위한 마크다운 파일입니다.
+
+- 쿠마쿠마쿠마
+ - Kuma
+
+```javascript
+const name = 'kuma';
+```
diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts
new file mode 100644
index 000000000..44f44ad22
--- /dev/null
+++ b/frontend/cypress/support/commands.ts
@@ -0,0 +1,41 @@
+///
+///
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+//
+// declare global {
+// namespace Cypress {
+// interface Chainable {
+// login(email: string, password: string): Chainable
+// drag(subject: string, options?: Partial): Chainable
+// dismiss(subject: string, options?: Partial): Chainable
+// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
+// }
+// }
+// }
+import '@testing-library/cypress/add-commands';
+import 'cypress-real-events';
+import 'cypress-file-upload';
diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts
new file mode 100644
index 000000000..598ab5f0d
--- /dev/null
+++ b/frontend/cypress/support/e2e.ts
@@ -0,0 +1,20 @@
+// ***********************************************************
+// 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';
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/frontend/package.json b/frontend/package.json
index 282e2d3b7..e78ee69bc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,7 +12,9 @@
"start:prod": "cross-env NODE_ENV=production webpack serve --config webpack.prod.js",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
- "test": "jest --maxWorkers=50%"
+ "cy:open": "cypress open",
+ "cy:run": "cypress run",
+ "test:e2e": "start-server-and-test start http://localhost:3000 cy:run"
},
"dependencies": {
"@tanstack/react-query": "^4.32.6",
@@ -36,6 +38,7 @@
"@storybook/react-webpack5": "^7.0.26",
"@storybook/testing-library": "^0.0.14-next.2",
"@svgr/webpack": "^8.0.1",
+ "@testing-library/cypress": "^9.0.0",
"@testing-library/react": "^14.0.0",
"@types/dompurify": "^3.0.2",
"@types/jest": "^29.5.3",
@@ -45,10 +48,14 @@
"@typescript-eslint/eslint-plugin": "5.61.0",
"@typescript-eslint/parser": "5.61.0",
"cross-env": "^7.0.3",
+ "cypress": "^12.17.3",
+ "cypress-file-upload": "^5.0.8",
+ "cypress-real-events": "^1.10.0",
"dotenv-webpack": "^8.0.1",
"eslint": "8.44.0",
"eslint-config-prettier": "8.8.0",
"eslint-import-resolver-typescript": "3.5.5",
+ "eslint-plugin-cypress": "^2.14.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "7.32.2",
@@ -60,6 +67,7 @@
"msw": "^1.2.3",
"msw-storybook-addon": "^1.8.0",
"prettier": "3.0.0",
+ "start-server-and-test": "^2.0.0",
"storybook": "^7.0.26",
"storybook-addon-react-router-v6": "^1.0.2",
"ts-jest": "^29.1.1",
diff --git a/frontend/src/components/@common/FileUploader/FileUploader.tsx b/frontend/src/components/@common/FileUploader/FileUploader.tsx
index 3aa5f593b..93eb9c24f 100644
--- a/frontend/src/components/@common/FileUploader/FileUploader.tsx
+++ b/frontend/src/components/@common/FileUploader/FileUploader.tsx
@@ -21,7 +21,12 @@ const FileUploader = ({ accept = '*', width = '30rem', height = '10rem', onFileS
return (