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

[FE] playwright에 자동 로그인 기능과 탭과 질문 렌더링, 체크리스트 생성과 관련된 e2e 테스트를 추가한다. #849

Merged
merged 13 commits into from
Oct 22, 2024
Merged
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
- name: Run Playwright tests
run: |
cd frontend
yarn playwright test
yarn e2e:mock
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
Expand Down
3 changes: 3 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
logs
**/*.backup.*
**/*.back.*
user.json

node_modules
dist
Expand All @@ -28,3 +29,5 @@ test-results/
playwright-report/
blob-report/
playwright/.cache/
*/.auth
.auth
5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "concurrently --names ,[TYPE] --prefix {name} \"jest\" \"tsc --noEmit\"",
"e2e": "playwright test",
"e2e:mock": "playwright test --config=playwright.mock.config.ts",
"e2e:mock-ui": "playwright test --config=playwright.mock.config.ts --ui",
"e2e:api": "playwright test --config=playwright.api.config.ts",
"e2e:api-ui": "playwright test --config=playwright.api.config.ts --ui",
"postinstall": "cd .. && husky frontend/.husky",
"format": "prettier --cache --write .",
"lint": "eslint --cache .",
Expand Down
83 changes: 83 additions & 0 deletions frontend/playwright.api.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.resolve(__dirname, '.env.development') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './playwright/tests/api',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
actionTimeout: 5000,
},

/* Configure projects for major browsers */
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: 'http://localhost:3000/',
reuseExistingServer: !process.env.CI,
},
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.resolve(__dirname, '.env.msw') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './src/e2e',
testDir: './playwright/tests/mock',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
Expand All @@ -25,12 +24,11 @@ export default defineConfig({
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
actionTimeout: 5000, // await 타임아웃 : 5초로 설정
actionTimeout: 5000,
},

/* Configure projects for major browsers */
Expand Down
16 changes: 16 additions & 0 deletions frontend/playwright/tests/api/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { test as setup } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, '../../../playwright/.auth/user.json');

setup('authenticate', async ({ page }) => {
const username = process.env.EMAIL || '';
const password = process.env.PASSWORD || '';

await page.goto('/sign-in');
await page.locator('input[name="email"]').fill(username);
await page.locator('input[name="password"]').fill(password);
await page.getByRole('button', { name: '로그인 하기' }).click();
await page.waitForURL('/home');
await page.context().storageState({ path: authFile });
});
89 changes: 89 additions & 0 deletions frontend/playwright/tests/api/postNewChecklist.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { expect, test } from '@playwright/test';

import { DefaultChecklistTabsNames, FirstCategoryQuestion } from '../constants/constants';

test.skip('체크리스트가 인풋을 채우고 제출할 수 있다.', async ({ page }) => {
await page.goto('/checklist/new');
const tabs = page.locator('.tab');
const roomInfoTab = tabs.nth(0);

await roomInfoTab.click();

await page.getByLabel('방 이름').fill('테스트 방');

await page.getByLabel('보증금').fill('1000');
await page.getByLabel('월세').fill('50');
await page.getByLabel('관리비').fill('10');

//관리비 포함 항목
await page.getByRole('button', { name: '수도' }).click();
await page.getByRole('button', { name: '인터넷' }).click();

await page.evaluate(() => window.scrollBy(0, 1000));

//층수
await page.locator('input[name="floor"]').fill('5');

// 방 구조
await page.locator('button[name="분리형 원룸"]').click();
await page.locator('button[name="오픈형 원룸"]').click();

await page.getByText('부동산 이름').scrollIntoViewIfNeeded();

//방 크기
await page.locator('input[name="size"]').fill('5');
//계약 기간
await page.locator('input[name="contractTerm"]').fill('12');

//입주 가능일
await page.locator('input[name="occupancyMonth"]').fill('10');
await page.locator('div[id="occupancyPeriod"]').click();
await page.locator('li[id="말"]').click();

//부동산
await page.locator('input[name="realEstate"]').fill('방끗 부동산');

//옵션 탭 클릭
const optionTab = tabs.nth(1);
optionTab.click();

await page.locator('button[id="refrigerator"]').click();
await page.locator('button[id="sink"]').click();

//질문 탭(방 컨디션)
const categoryTab1 = tabs.nth(2);
categoryTab1.click();
await page.getByLabel('좋아요 버튼').nth(0).click();
await page.getByLabel('좋아요 버튼').nth(1).click();

//질문 탭(방 컨디션)
const categoryTab2 = tabs.nth(3);
categoryTab2.click();
await page.getByLabel('싫어요 버튼').nth(2).click();
await page.getByLabel('좋아요 버튼').nth(2).click();

//저장
await page.getByRole('button', { name: '저장' }).click();
await page.getByRole('button', { name: '체크리스트 저장하기' }).click();

//TODO: 이후 일반 로그인이 되면 저장되는 것도 확인
//디테일 페이지 이동
await expect(page).toHaveURL(/\/detail\/\d+/);

const checklistEditButton = page.locator('button[id="checklistEditButton"]');
await checklistEditButton.click();

await expect(page.getByText('체크리스트 편집')).toBeVisible();

const checklistTabs = page.locator('.tab');
await expect(tabs).toHaveCount(7, { timeout: 3000 });

for (let i = 2; i < DefaultChecklistTabsNames.length; i++) {
await expect(checklistTabs.nth(i)).toContainText(DefaultChecklistTabsNames[i].name);
await tabs.nth(i).click();
const actualText = await page.locator('.question').nth(0).textContent();
const expectedText = FirstCategoryQuestion[i - 2].question;

expect(actualText).toBe(expectedText);
}
});
57 changes: 57 additions & 0 deletions frontend/playwright/tests/api/renderTab.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import test, { expect } from '@playwright/test';

import {
DefaultChecklistTabsNames,
DefaultQuestionSelectTabsNames,
FirstAllCategoryQuestion,
FirstCategoryQuestion,
} from '../constants/constants';

test('체크리스트 생성 페이지에 들어가면 탭과 질문들이 잘 렌더링된다.', async ({ page }) => {
await page.goto('/checklist/new');
const tabs = page.locator('.tab');
await expect(tabs).toHaveCount(6, { timeout: 3000 });

for (let i = 2; i < DefaultChecklistTabsNames.length; i++) {
await expect(tabs.nth(i)).toContainText(DefaultChecklistTabsNames[i].name);
await tabs.nth(i).click();
const actualText = await page.locator('.question').nth(0).textContent();
const expectedText = FirstCategoryQuestion[i - 2].question;

expect(actualText).toBe(expectedText);
}
});

test('체크리스트 질문 선택 페이지에 들어가면 탭과 질문들이 잘 렌더링된다.', async ({ page }) => {
await page.goto('/checklist/question-select');
const tabs = page.locator('.tab');
await expect(tabs).toHaveCount(5, { timeout: 3000 });

for (let i = 0; i < DefaultQuestionSelectTabsNames.length; i++) {
await expect(tabs.nth(i)).toContainText(DefaultQuestionSelectTabsNames[i].name);
await tabs.nth(i).click();
const actualText = await page.locator('.question').nth(0).textContent();
const expectedText = FirstAllCategoryQuestion[i].question;
expect(actualText).toContain(expectedText);
}
});

test.skip('체크리스트 편집 페이지에 들어가면 탭과 질문들이 잘 렌더링된다.', async ({ page }) => {
await page.goto('/checklist/1');
const checklistEditButton = page.locator('button[id="checklistEditButton"]');
await checklistEditButton.click();

await expect(page.getByText('체크리스트 편집')).toBeVisible();

const tabs = page.locator('.tab');
await expect(tabs).toHaveCount(6, { timeout: 3000 });

for (let i = 2; i < DefaultChecklistTabsNames.length; i++) {
await expect(tabs.nth(i)).toContainText(DefaultChecklistTabsNames[i].name);
await tabs.nth(i).click();
const actualText = await page.locator('.question').nth(0).textContent();
const expectedText = FirstCategoryQuestion[i - 2].question;

expect(actualText).toBe(expectedText);
}
});
58 changes: 58 additions & 0 deletions frontend/playwright/tests/constants/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export const DefaultChecklistTabsNames = [
{ id: 0, name: '기본 정보' },
{
id: 1,
name: '옵션',
},
{
id: 2,
name: '방 컨디션',
},
{
id: 3,
name: '창문',
},
{
id: 4,
name: '화장실',
},
{
id: 5,
name: '보안',
},
];

export const DefaultQuestionSelectTabsNames = [
{
id: 0,
name: '방 컨디션',
},
{
id: 1,
name: '창문',
},
{
id: 2,
name: '화장실',
},
{
id: 3,
name: '보안',
},
{
id: 4,
name: '외부',
},
];

export const FirstCategoryQuestion = [
{ id: 0, question: '곰팡이가 핀 곳 없이 깨끗한가요?' },
{ id: 1, question: '창 밖의 뷰가 가로막힘 없이 트여있나요?' },
{ id: 2, question: '화장실이 깨끗한가요?' },
{ id: 3, question: '잠금장치가 있는 공동 현관문이 있나요?' },
];

export const FirstAllCategoryQuestion = [
...FirstCategoryQuestion,
{ id: 4, question: '주변 도로가 밤에도 충분히 밝은가요?' },
];
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test } from '@playwright/test';

import { ROUTE_PATH } from '@/constants/routePath';

test('빈 체크리스트를 제출할 수 있다.', async ({ page }) => {
await page.goto(ROUTE_PATH.checklistNew);
await page.goto('/checklist/new');
await page.getByRole('button', { name: '저장' }).click();
await page.getByRole('button', { name: '체크리스트 저장하기' }).click();
await page.waitForURL('/checklist');
});
Loading
Loading