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:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -163,5 +163,13 @@ _modules/*
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
|
||||
# Playwright
|
||||
playwright-report/
|
||||
test-results/
|
||||
blob-report/
|
||||
|
||||
/results/
|
||||
/phpunit*.xml
|
||||
|
||||
48
CLAUDE.md
48
CLAUDE.md
@@ -73,31 +73,49 @@ assets/ # 기획 문서 (엑셀)
|
||||
php spark serve --port=8045
|
||||
```
|
||||
|
||||
## 테스트
|
||||
## 테스트 (Playwright E2E)
|
||||
|
||||
모든 작업 완료 후 반드시 테스트를 수행합니다.
|
||||
모든 작업 완료 후 반드시 Playwright 브라우저 테스트를 수행합니다.
|
||||
|
||||
```bash
|
||||
# 전체 테스트
|
||||
vendor/bin/phpunit
|
||||
npm test
|
||||
|
||||
# 특정 테스트 스위트
|
||||
vendor/bin/phpunit --testsuite App
|
||||
# headed 모드 (브라우저 표시)
|
||||
npm run test:headed
|
||||
|
||||
# 특정 테스트 파일
|
||||
vendor/bin/phpunit tests/unit/SomeTest.php
|
||||
# 특정 파일만
|
||||
npx playwright test e2e/auth.spec.js
|
||||
|
||||
# 필터로 특정 메서드만
|
||||
vendor/bin/phpunit --filter testMethodName
|
||||
# 특정 테스트만
|
||||
npx playwright test -g "로그인 페이지"
|
||||
```
|
||||
|
||||
### 테스트 구조
|
||||
```
|
||||
e2e/
|
||||
├── helpers/
|
||||
│ ├── auth.js # 로그인/로그아웃 헬퍼, 테스터 계정 정보
|
||||
│ └── db-seed.js # 테스터 계정 DB 시딩 스크립트
|
||||
├── auth.spec.js # 인증 테스트 (로그인/로그아웃/회원가입)
|
||||
├── admin.spec.js # 관리자 패널 테스트 (지자체관리자/Super Admin)
|
||||
└── public.spec.js # 공개 페이지 테스트
|
||||
```
|
||||
|
||||
### 테스터 계정 (비밀번호: `test1234!`)
|
||||
| ID | 역할 | Level |
|
||||
|----|------|-------|
|
||||
| `tester_admin` | Super Admin | 4 |
|
||||
| `tester_local` | 지자체관리자 (대구) | 3 |
|
||||
| `tester_shop` | 지정판매소 | 2 |
|
||||
| `tester_user` | 일반 사용자 | 1 |
|
||||
|
||||
### 테스트 규칙
|
||||
- **작업 완료 시 해당 기능의 테스트를 반드시 작성/실행**
|
||||
- 테스트 디렉토리: `tests/unit/`, `tests/database/`, `tests/session/`
|
||||
- CI4 TestCase 사용: `CodeIgniter\Test\CIUnitTestCase`, `CodeIgniter\Test\DatabaseTestTrait`
|
||||
- 테스트 파일명: `{ClassName}Test.php` (PascalCase)
|
||||
- 테스트 메서드명: `test{행위}` (camelCase)
|
||||
- DB 테스트 시 `DatabaseTestTrait` + `$seed` 속성 활용
|
||||
- **작업 완료 시 해당 기능의 E2E 테스트를 반드시 작성/실행**
|
||||
- 테스트 파일: `e2e/{기능명}.spec.js`
|
||||
- 공통 로그인은 `e2e/helpers/auth.js`의 `login(page, role)` 사용
|
||||
- 새 기능 추가 시 해당 페이지 접근 + 주요 CRUD 동작 테스트 작성
|
||||
- 테스터 계정 시딩: `node e2e/helpers/db-seed.js`
|
||||
|
||||
## DB 초기화 순서
|
||||
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
4
jobs.md
4
jobs.md
@@ -144,6 +144,7 @@
|
||||
| INIT-09 | 지정판매소 관리 CRUD | 2026-01 | `4e557d4` |
|
||||
| INIT-10 | PII 암호화 (이메일/전화번호) | 2026-01 | `4e557d4` |
|
||||
| DOC-01 | README 개발현황 정리 + CLAUDE.md | 2026-03-25 | `c07261a` |
|
||||
| TEST-01 | Playwright E2E 테스트 환경 + 테스터 계정 + 23개 테스트 | 2026-03-25 | — |
|
||||
|
||||
---
|
||||
|
||||
@@ -153,6 +154,9 @@
|
||||
|
||||
### 2026-03-25
|
||||
|
||||
- **TEST-01** Playwright E2E 테스트 환경 구성 (Chromium)
|
||||
- **TEST-01** 테스터 계정 4개 생성 (admin/local/shop/user, 비밀번호: test1234!)
|
||||
- **TEST-01** E2E 테스트 23개 작성 및 전체 통과 (auth 9, admin 10, public 4)
|
||||
- **DOC-01** README.md 개발현황 상세 정리 (63개 웹 + 15개 앱 항목별 분석)
|
||||
- **DOC-01** CLAUDE.md 생성 (Claude Code 프로젝트 가이드)
|
||||
- **DOC-01** jobs.md 생성 (작업 관리 파일)
|
||||
|
||||
245
package-lock.json
generated
Normal file
245
package-lock.json
generated
Normal file
@@ -0,0 +1,245 @@
|
||||
{
|
||||
"name": "jongryangje",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jongryangje",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"mysql2": "^3.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
|
||||
"integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"bin": {
|
||||
"bcrypt": "bin/bcrypt"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
|
||||
"integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=1.30.0",
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.20.0.tgz",
|
||||
"integrity": "sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.2",
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.7.2",
|
||||
"long": "^5.3.2",
|
||||
"lru.min": "^1.1.4",
|
||||
"named-placeholders": "^1.1.6",
|
||||
"sql-escaper": "^1.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
|
||||
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru.min": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sql-escaper": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
|
||||
"integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=2.0.0",
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "jongryangje",
|
||||
"version": "1.0.0",
|
||||
"description": "종량제 쓰레기봉투 물류시스템",
|
||||
"scripts": {
|
||||
"test": "npx playwright test",
|
||||
"test:ui": "npx playwright test --ui",
|
||||
"test:headed": "npx playwright test --headed"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wixon-associates/jongryangje.git"
|
||||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"mysql2": "^3.20.0"
|
||||
}
|
||||
}
|
||||
26
playwright.config.js
Normal file
26
playwright.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// @ts-check
|
||||
const { defineConfig } = require('@playwright/test');
|
||||
|
||||
module.exports = defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: false,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: 1,
|
||||
reporter: [['html', { open: 'never' }], ['list']],
|
||||
timeout: 30000,
|
||||
|
||||
use: {
|
||||
baseURL: 'http://localhost:8045',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
locale: 'ko-KR',
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { browserName: 'chromium' },
|
||||
},
|
||||
],
|
||||
});
|
||||
32
writable/database/seed_test_accounts.sql
Normal file
32
writable/database/seed_test_accounts.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- ============================================
|
||||
-- 테스터 계정 시드 데이터
|
||||
-- 비밀번호: test1234! (bcrypt hash)
|
||||
-- 실행: mysql -u root -p jongryangje_dev < seed_test_accounts.sql
|
||||
-- ============================================
|
||||
|
||||
-- Super Admin (Level 4)
|
||||
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 ('tester_admin', '$2y$10$t8FlP9uqDux942Chm1WO3uFhJ5M9G8O9inY20rwTRgLru2ae.t.xS', '테스터관리자', 'tester_admin@test.com', '010-0000-0001', 'ko', 4, '', NULL, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE `mb_passwd` = VALUES(`mb_passwd`), `mb_state` = 1;
|
||||
|
||||
-- 지자체관리자 (Level 3) — lg_idx=1 (대구)
|
||||
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 ('tester_local', '$2y$10$t8FlP9uqDux942Chm1WO3uFhJ5M9G8O9inY20rwTRgLru2ae.t.xS', '테스터지자체', 'tester_local@test.com', '010-0000-0002', 'ko', 3, '', 1, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE `mb_passwd` = VALUES(`mb_passwd`), `mb_state` = 1;
|
||||
|
||||
-- 지정판매소 (Level 2)
|
||||
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 ('tester_shop', '$2y$10$t8FlP9uqDux942Chm1WO3uFhJ5M9G8O9inY20rwTRgLru2ae.t.xS', '테스터판매소', 'tester_shop@test.com', '010-0000-0003', 'ko', 2, '', NULL, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE `mb_passwd` = VALUES(`mb_passwd`), `mb_state` = 1;
|
||||
|
||||
-- 일반 사용자 (Level 1)
|
||||
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 ('tester_user', '$2y$10$t8FlP9uqDux942Chm1WO3uFhJ5M9G8O9inY20rwTRgLru2ae.t.xS', '테스터사용자', 'tester_user@test.com', '010-0000-0004', 'ko', 1, '', NULL, 1, NOW())
|
||||
ON DUPLICATE KEY UPDATE `mb_passwd` = VALUES(`mb_passwd`), `mb_state` = 1;
|
||||
|
||||
-- 승인 요청도 approved 상태로 생성 (tester 계정이 정상 로그인 가능하도록)
|
||||
INSERT INTO `member_approval_request` (`mb_idx`, `mar_requested_level`, `mar_status`, `mar_request_note`, `mar_requested_at`, `mar_processed_at`)
|
||||
SELECT mb_idx, mb_level, 'approved', '테스트 계정 자동 승인', NOW(), NOW()
|
||||
FROM `member`
|
||||
WHERE `mb_id` IN ('tester_admin', 'tester_local', 'tester_shop', 'tester_user')
|
||||
AND `mb_idx` NOT IN (SELECT mb_idx FROM `member_approval_request`);
|
||||
Reference in New Issue
Block a user