Playwright E2E 테스트 환경 구성 및 테스터 계정 생성
- Playwright + Chromium 브라우저 테스트 환경 세팅 - 테스터 계정 4개 생성 (admin/local/shop/user, pw: test1234!) - seed SQL + Node.js 시더 스크립트 포함 - E2E 테스트 23개 작성 (전체 통과): - auth: 로그인/로그아웃/실패/회원가입 (9개) - admin: 지자체관리자/Super Admin 패널 접근 (10개) - public: 홈/로그인/회원가입/404 (4개) - CLAUDE.md: 테스트 섹션을 Playwright 기반으로 업데이트 - jobs.md: 테스트 작업 완료 기록 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
e2e/admin.spec.js
Normal file
83
e2e/admin.spec.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const { login } = require('./helpers/auth');
|
||||
|
||||
test.describe('관리자 패널 — 지자체관리자', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page, 'local');
|
||||
});
|
||||
|
||||
test('관리자 대시보드 접근', async ({ page }) => {
|
||||
await page.goto('/admin');
|
||||
await expect(page).toHaveURL(/\/admin/);
|
||||
});
|
||||
|
||||
test('회원 관리 목록 접근', async ({ page }) => {
|
||||
await page.goto('/admin/users');
|
||||
await expect(page).toHaveURL(/\/admin\/users/);
|
||||
const content = await page.content();
|
||||
expect(content).toContain('tester_');
|
||||
});
|
||||
|
||||
test('로그인 이력 접근', async ({ page }) => {
|
||||
await page.goto('/admin/access/login-history');
|
||||
await expect(page).toHaveURL(/\/admin\/access\/login-history/);
|
||||
});
|
||||
|
||||
test('승인 대기 목록 접근', async ({ page }) => {
|
||||
await page.goto('/admin/access/approvals');
|
||||
await expect(page).toHaveURL(/\/admin\/access\/approvals/);
|
||||
});
|
||||
|
||||
test('역할 목록 접근', async ({ page }) => {
|
||||
await page.goto('/admin/roles');
|
||||
await expect(page).toHaveURL(/\/admin\/roles/);
|
||||
});
|
||||
|
||||
test('메뉴 관리 접근', async ({ page }) => {
|
||||
await page.goto('/admin/menus');
|
||||
await expect(page).toHaveURL(/\/admin\/menus/);
|
||||
});
|
||||
|
||||
test('지정판매소 목록 접근', async ({ page }) => {
|
||||
await page.goto('/admin/designated-shops');
|
||||
await expect(page).toHaveURL(/\/admin\/designated-shops/);
|
||||
});
|
||||
|
||||
test('지자체 관리는 Super Admin 전용 — 지자체관리자 접근 시 리다이렉트', async ({ page }) => {
|
||||
await page.goto('/admin/local-governments');
|
||||
// Level 3는 Super Admin이 아니므로 /admin으로 리다이렉트됨
|
||||
await expect(page).toHaveURL(/\/admin$/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('관리자 패널 — Super Admin', () => {
|
||||
|
||||
test('지자체 선택 후 관리자 접근', async ({ page }) => {
|
||||
await login(page, 'admin');
|
||||
await expect(page).toHaveURL(/\/admin\/select-local-government/);
|
||||
|
||||
// radio 버튼으로 첫 번째 지자체 선택
|
||||
const radio = page.locator('input[name="lg_idx"]').first();
|
||||
await radio.check();
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// 선택 후 관리자 대시보드로 이동
|
||||
await page.waitForURL(url => !url.pathname.includes('select-local-government'), { timeout: 15000 });
|
||||
await page.goto('/admin');
|
||||
await expect(page).not.toHaveURL(/\/select-local-government/);
|
||||
});
|
||||
|
||||
test('Super Admin은 지자체 관리 접근 가능', async ({ page }) => {
|
||||
// 먼저 로그인 + 지자체 선택
|
||||
await login(page, 'admin');
|
||||
const radio = page.locator('input[name="lg_idx"]').first();
|
||||
await radio.check();
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL(url => !url.pathname.includes('select-local-government'), { timeout: 15000 });
|
||||
|
||||
await page.goto('/admin/local-governments');
|
||||
await expect(page).toHaveURL(/\/admin\/local-governments/);
|
||||
});
|
||||
});
|
||||
70
e2e/auth.spec.js
Normal file
70
e2e/auth.spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const { login, logout, TEST_ACCOUNTS } = require('./helpers/auth');
|
||||
|
||||
test.describe('인증 시스템', () => {
|
||||
|
||||
test('로그인 페이지 접속', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
await expect(page.locator('input[name="login_id"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="password"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('잘못된 ID로 로그인 실패', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[name="login_id"]', 'wrong_user');
|
||||
await page.fill('input[name="password"]', 'wrong_pass');
|
||||
await page.click('button[type="submit"]');
|
||||
// 로그인 페이지에 머물러야 함
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
|
||||
test('잘못된 비밀번호로 로그인 실패', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.fill('input[name="login_id"]', 'tester_admin');
|
||||
await page.fill('input[name="password"]', 'wrong_password');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
|
||||
test('Super Admin 로그인 성공 → 지자체 선택 페이지', async ({ page }) => {
|
||||
await login(page, 'admin');
|
||||
// Super Admin은 지자체 선택 페이지로 리다이렉트
|
||||
await expect(page).toHaveURL(/\/admin\/select-local-government/);
|
||||
});
|
||||
|
||||
test('지자체관리자 로그인 성공 → 관리자 대시보드', async ({ page }) => {
|
||||
await login(page, 'local');
|
||||
await expect(page).toHaveURL(/\/admin/);
|
||||
});
|
||||
|
||||
test('일반 사용자 로그인 성공 → 홈/대시보드', async ({ page }) => {
|
||||
await login(page, 'user');
|
||||
// 일반 사용자는 홈 또는 대시보드로 이동
|
||||
const url = page.url();
|
||||
expect(url.includes('/dashboard') || url.endsWith('/') || url.includes('/login') === false).toBeTruthy();
|
||||
});
|
||||
|
||||
test('로그아웃', async ({ page }) => {
|
||||
await login(page, 'local');
|
||||
await page.goto('/logout');
|
||||
await page.waitForURL(/\/login/, { timeout: 10000 });
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
// 로그아웃 후 관리자 접근 불가 확인
|
||||
await page.goto('/admin');
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
|
||||
test('비로그인 상태에서 관리자 페이지 접근 → 로그인으로 리다이렉트', async ({ page }) => {
|
||||
await page.goto('/admin');
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
|
||||
test('회원가입 페이지 접속', async ({ page }) => {
|
||||
await page.goto('/register');
|
||||
await expect(page.locator('input[name="mb_id"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="mb_passwd"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="mb_name"]')).toBeVisible();
|
||||
});
|
||||
});
|
||||
35
e2e/helpers/auth.js
Normal file
35
e2e/helpers/auth.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 공통 인증 헬퍼
|
||||
*/
|
||||
|
||||
const TEST_ACCOUNTS = {
|
||||
admin: { id: 'tester_admin', password: 'test1234!', level: 4 },
|
||||
local: { id: 'tester_local', password: 'test1234!', level: 3 },
|
||||
shop: { id: 'tester_shop', password: 'test1234!', level: 2 },
|
||||
user: { id: 'tester_user', password: 'test1234!', level: 1 },
|
||||
};
|
||||
|
||||
/**
|
||||
* 로그인 수행
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {'admin'|'local'|'shop'|'user'} role
|
||||
*/
|
||||
async function login(page, role = 'admin') {
|
||||
const acct = TEST_ACCOUNTS[role];
|
||||
await page.goto('/login');
|
||||
await page.fill('input[name="login_id"]', acct.id);
|
||||
await page.fill('input[name="password"]', acct.password);
|
||||
await page.click('button[type="submit"]');
|
||||
// 로그인 후 리다이렉트 대기
|
||||
await page.waitForURL(url => !url.pathname.includes('/login'), { timeout: 10000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 수행
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function logout(page) {
|
||||
await page.goto('/logout');
|
||||
}
|
||||
|
||||
module.exports = { TEST_ACCOUNTS, login, logout };
|
||||
68
e2e/helpers/db-seed.js
Normal file
68
e2e/helpers/db-seed.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* DB 시더: 테스터 계정 생성
|
||||
* 실행: node e2e/helpers/db-seed.js
|
||||
*/
|
||||
const mysql = require('mysql2/promise');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const DB_CONFIG = {
|
||||
host: '3.36.27.239',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
password: 'ssadm!#@$',
|
||||
database: 'jongryangje_dev',
|
||||
};
|
||||
|
||||
const TEST_PASSWORD = 'test1234!';
|
||||
|
||||
const TEST_ACCOUNTS = [
|
||||
{ id: 'tester_admin', name: '테스터관리자', email: 'tester_admin@test.com', phone: '010-0000-0001', level: 4, lg_idx: null },
|
||||
{ id: 'tester_local', name: '테스터지자체', email: 'tester_local@test.com', phone: '010-0000-0002', level: 3, lg_idx: 1 },
|
||||
{ id: 'tester_shop', name: '테스터판매소', email: 'tester_shop@test.com', phone: '010-0000-0003', level: 2, lg_idx: null },
|
||||
{ id: 'tester_user', name: '테스터사용자', email: 'tester_user@test.com', phone: '010-0000-0004', level: 1, lg_idx: null },
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
const conn = await mysql.createConnection(DB_CONFIG);
|
||||
const hash = bcrypt.hashSync(TEST_PASSWORD, 10).replace('$2b$', '$2y$');
|
||||
|
||||
console.log('테스터 계정 시딩 시작...');
|
||||
|
||||
for (const acct of TEST_ACCOUNTS) {
|
||||
// Upsert member
|
||||
await conn.execute(
|
||||
`INSERT INTO member (mb_id, mb_passwd, mb_name, mb_email, mb_phone, mb_lang, mb_level, mb_group, mb_lg_idx, mb_state, mb_regdate)
|
||||
VALUES (?, ?, ?, ?, ?, 'ko', ?, '', ?, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE mb_passwd = VALUES(mb_passwd), mb_state = 1, mb_level = VALUES(mb_level)`,
|
||||
[acct.id, hash, acct.name, acct.email, acct.phone, acct.level, acct.lg_idx]
|
||||
);
|
||||
|
||||
// Ensure approval request exists (approved)
|
||||
const [rows] = await conn.execute(
|
||||
'SELECT mb_idx FROM member WHERE mb_id = ?', [acct.id]
|
||||
);
|
||||
if (rows.length > 0) {
|
||||
const mbIdx = rows[0].mb_idx;
|
||||
const [existing] = await conn.execute(
|
||||
'SELECT mar_idx FROM member_approval_request WHERE mb_idx = ?', [mbIdx]
|
||||
);
|
||||
if (existing.length === 0) {
|
||||
await conn.execute(
|
||||
`INSERT INTO member_approval_request (mb_idx, mar_requested_level, mar_status, mar_request_note, mar_requested_at, mar_processed_at)
|
||||
VALUES (?, ?, 'approved', '테스트 계정 자동 승인', NOW(), NOW())`,
|
||||
[mbIdx, acct.level]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✓ ${acct.id} (Level ${acct.level})`);
|
||||
}
|
||||
|
||||
await conn.end();
|
||||
console.log('시딩 완료!');
|
||||
}
|
||||
|
||||
seed().catch(err => {
|
||||
console.error('시딩 실패:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
25
e2e/public.spec.js
Normal file
25
e2e/public.spec.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
test.describe('공개 페이지', () => {
|
||||
|
||||
test('홈페이지 접근', async ({ page }) => {
|
||||
const response = await page.goto('/');
|
||||
expect(response?.status()).toBe(200);
|
||||
});
|
||||
|
||||
test('로그인 페이지 접근', async ({ page }) => {
|
||||
const response = await page.goto('/login');
|
||||
expect(response?.status()).toBe(200);
|
||||
});
|
||||
|
||||
test('회원가입 페이지 접근', async ({ page }) => {
|
||||
const response = await page.goto('/register');
|
||||
expect(response?.status()).toBe(200);
|
||||
});
|
||||
|
||||
test('존재하지 않는 페이지 → 404', async ({ page }) => {
|
||||
const response = await page.goto('/nonexistent-page-12345');
|
||||
expect(response?.status()).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user