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 63dc9205d..40ab10bfe 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 (