Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature #191: playwright 적용해서 e2e 테스트하기 #203

Merged
merged 12 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions apps/frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
10 changes: 8 additions & 2 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest"
"test": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"test:e2e:report": "playwright show-report"
},
"lint-staged": {
"*.{ts,tsx}": [
Expand All @@ -33,11 +37,13 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@playwright/test": "^1.49.0",
"@tanstack/react-query-devtools": "^5.59.19",
"@tanstack/router-devtools": "^1.78.3",
"@tanstack/router-plugin": "^1.78.3",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@types/node": "^20.3.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/parser": "^5.59.11",
Expand Down Expand Up @@ -67,4 +73,4 @@
"public"
]
}
}
}
35 changes: 35 additions & 0 deletions apps/frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './src/app/test/e2e',
fullyParallel: true,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry'
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
}
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173'
}
});
36 changes: 32 additions & 4 deletions apps/frontend/src/app/mock/MockRepository/MockRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,36 @@ export class MockRepository<T> {
return true;
}

private isPartialMatch(owner: Partial<T>, target: Partial<T>): boolean {
for (const key in target) {
if (!Object.prototype.hasOwnProperty.call(owner, key)) return false;

const ownerValue = owner[key as keyof T];
const targetValue = target[key as keyof T];

if (typeof ownerValue === 'boolean' && ownerValue !== targetValue) {
return false;
}

if (typeof targetValue === 'string' && !(ownerValue as string)?.includes(targetValue)) {
return false;
}
}
return true;
}

private generateId() {
return String(this._autoId++);
}

private paginate(items: Identifiable<T>[], page: number, size: number) {
const start = (page - 1) * size;
const end = start + size;
const data = items.slice(start, end);
const maxPage = Math.ceil(items.length / size);
return { data, maxPage };
}

async create(arg: T) {
const data = { ...arg, id: this.generateId() };

Expand Down Expand Up @@ -52,10 +78,7 @@ export class MockRepository<T> {

async findMany({ query, page = 1, size = 10 }: { query?: Partial<Identifiable<T>>; page?: number; size?: number }) {
const filtered = query ? this.memory.filter((item) => this.isMatch(item, query)) : this.memory;
const start = (page - 1) * size;
const end = start + size;

return filtered.slice(start, end);
return this.paginate(filtered, page, size);
}

async findOne(query: Partial<Identifiable<T>>) {
Expand All @@ -65,4 +88,9 @@ export class MockRepository<T> {

return data;
}

async search({ query, page = 1, size = 10 }: { query?: Partial<Identifiable<T>>; page?: number; size?: number }) {
const filtered = query ? this.memory.filter((item) => this.isPartialMatch(item, query)) : this.memory;
return this.paginate(filtered, page, size);
}
}
4 changes: 2 additions & 2 deletions apps/frontend/src/app/mock/gistResolvers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HttpResponse, PathParams } from 'msw';

// 사용자의 Gist 목록 조회
export const mockGetUserGistList = () => {
export const getUserGistList = () => {
return HttpResponse.json({
gists: [
{
Expand All @@ -27,7 +27,7 @@ export const mockGetUserGistList = () => {
};

// 특정 Gist 파일 조회 api
export const mockGetGistDetail = ({ params }: { params: PathParams }) => {
export const getGistDetail = ({ params }: { params: PathParams }) => {
const { gistId } = params;

if (!gistId) {
Expand Down
57 changes: 28 additions & 29 deletions apps/frontend/src/app/mock/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
import { http } from 'msw';
import { mockGetGistDetail, mockGetUserGistList } from './gistResolvers';
import { getGistDetail, getUserGistList } from './gistResolvers';
import { getHistory, getHistoryList, getTagList, postCodeRun, postTag } from './historyResolvers';
import {
mockGetHistory,
mockGetHistoryList,
mockGetTagList,
mockGetUserLotusList,
mockPostCodeRun,
mockPostTag
} from './historyResolvers';
import { deleteLotus, getLotusDetail, getPublicLotusList, patchLotus, postCreateLotus } from './lotusResolvers';
import { mockGetLogin, mockGetUserInfo, mockLogin, mockLogout, mockPatchUserInfo } from './userResolvers';
deleteLotus,
getLotusDetail,
getPublicLotusList,
getUserLotusList,
patchLotus,
postCreateLotus
} from './lotusResolvers';
import { getLogin, getUserInfo, patchUserInfo } from './userResolvers';

const apiUrl = (path: string) => `${import.meta.env.VITE_API_URL}${path}`;

export const handlers = [
// user
http.get(`/api/user`, mockGetUserInfo),
http.patch(`/api/user`, mockPatchUserInfo),
http.post(`/api/user/login`, mockLogin),
http.get(`/api/user/login`, mockGetLogin),
http.post(`/api/user/logout`, mockLogout),
http.get(`/api/user/lotus`, mockGetUserLotusList),
http.get(`/api/user/gist`, mockGetUserGistList),
http.get(`/api/user/gist/:gistId`, mockGetGistDetail),
http.get(apiUrl(`/api/user`), getUserInfo),
http.patch(apiUrl(`/api/user`), patchUserInfo),
http.get(apiUrl(`/api/user/login/callback`), getLogin),
http.get(apiUrl(`/api/user/lotus`), getUserLotusList),
http.get(apiUrl(`/api/user/gist`), getUserGistList),
http.get(apiUrl(`/api/user/gist/:gistId`), getGistDetail),
// lotus
http.get(`/api/lotus`, getPublicLotusList),
http.get(`/api/lotus/:lotusId`, getLotusDetail),
http.post(`/api/lotus`, postCreateLotus),
http.patch(`/api/lotus/:id`, patchLotus),
http.delete(`/api/lotus/:id`, deleteLotus),
http.get(apiUrl(`/api/lotus`), getPublicLotusList),
http.get(apiUrl(`/api/lotus/:lotusId`), getLotusDetail),
http.post(apiUrl(`/api/lotus`), postCreateLotus),
http.patch(apiUrl(`/api/lotus/:id`), patchLotus),
http.delete(apiUrl(`/api/lotus/:id`), deleteLotus),
// history
http.get(`/api/lotus/:lotusId/history`, mockGetHistoryList),
http.post(`/api/lotus/:lotusId/history`, mockPostCodeRun),
http.get(`/api/lotus/:lotusId/history/:historyId`, mockGetHistory),

http.get(apiUrl(`/api/lotus/:lotusId/history`), getHistoryList),
http.post(apiUrl(`/api/lotus/:lotusId/history`), postCodeRun),
http.get(apiUrl(`/api/lotus/:lotusId/history/:historyId`), getHistory),
// tag
http.get(`/api/tag`, mockGetTagList),
http.post(`/api/tag`, mockPostTag)
http.get(apiUrl(`/api/tag`), getTagList),
http.post(apiUrl(`/api/tag`), postTag)
];
Loading
Loading