feat: Add Playwright end-to-end testing setup and initial test suites for various application flows.
1
Frontend-Learner/.nuxtrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
setups.@nuxt/test-utils="4.0.0"
|
||||
1551
Frontend-Learner/package-lock.json
generated
|
|
@ -19,9 +19,14 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint-config": "^1.12.1",
|
||||
"@nuxt/test-utils": "^4.0.0",
|
||||
"@nuxtjs/i18n": "^10.2.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/node": "^22.19.8",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"eslint": "^9.39.2",
|
||||
"typescript": "^5.4.5"
|
||||
"jsdom": "^28.1.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "47"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "43"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "41"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "21"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "52"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "27"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "29"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
85
Frontend-Learner/playwright-report/index.html
Normal file
57
Frontend-Learner/playwright.config.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* @file playwright.config.ts
|
||||
* @description ไฟล์ตั้งค่าสำหรับการทำ Automated E2E Testing ด้วย Playwright
|
||||
*/
|
||||
export default defineConfig({
|
||||
// โฟลเดอร์ที่เก็บไฟล์เทส (ชี้ไปที่โฟลเดอร์ปลายทางที่เราสร้าง)
|
||||
testDir: './tests/e2e',
|
||||
|
||||
// รันเทสแบบขนาน (พร้อมๆ กันหลายไฟล์) เพื่อให้เสร็จเร็วขึ้น
|
||||
fullyParallel: true,
|
||||
|
||||
// หากการรันเทสบน CI/CD ล้มเหลว ให้ลองรันซ้ำ 2 ครั้ง
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
|
||||
// จำนวน Worker ที่ใช้รันเทส
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
// รูปแบบการแสดงผลลัพธ์ (Reporter)
|
||||
reporter: 'html',
|
||||
|
||||
use: {
|
||||
// กำหนดล่วงหน้าว่าเว็บที่เรากำลังจะพุ่งไปหาคือ URL อะไร (พอร์ต 3000 ของ Nuxt)
|
||||
baseURL: 'http://localhost:3000',
|
||||
|
||||
// ตั้งค่าให้เก็บประวัติแบบติดตามผล (Trace) ถ้าระบบพัง จะได้กลับมาดูได้
|
||||
trace: 'on-first-retry',
|
||||
|
||||
// ตั้งค่าเก็บรูปภาพหน้าจอเมื่อพัง (Screenshot on failure)
|
||||
screenshot: 'only-on-failure'
|
||||
},
|
||||
|
||||
// ตั้งค่าอุปกรณ์ที่ใช้ทดสอบ (Browsers)
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
// หากต้องการเทสบน Firefox หรือ Safari สามารถเปิดคอมเมนต์บรรทัดถัดไปได้เลย
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
],
|
||||
|
||||
// (Optional) หากต้องการให้เปิด Local Server อัตโนมัติก่อนรันเทส สามารถเอาคอมเมนต์ออกได้
|
||||
// webServer: {
|
||||
// command: 'npm run dev',
|
||||
// url: 'http://localhost:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "52"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "41"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "47"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "43"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "21"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "27"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
|
@ -0,0 +1,65 @@
|
|||
# Page snapshot
|
||||
|
||||
```yaml
|
||||
- generic [active] [ref=e1]:
|
||||
- generic [ref=e5]:
|
||||
- generic [ref=e6]:
|
||||
- generic [ref=e8]: E
|
||||
- heading "เข้าสู่ระบบ" [level=1] [ref=e9]
|
||||
- paragraph [ref=e10]: ยินดีต้อนรับกลับมา! กรุณากรอกข้อมูลของคุณ
|
||||
- generic [ref=e11]:
|
||||
- generic [ref=e12]:
|
||||
- generic [ref=e13]:
|
||||
- generic [ref=e14]: อีเมล
|
||||
- generic [ref=e15]:
|
||||
- generic:
|
||||
- generic: email
|
||||
- textbox [ref=e16]
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]: รหัสผ่าน
|
||||
- generic [ref=e19]:
|
||||
- generic:
|
||||
- generic: lock
|
||||
- textbox [ref=e20]
|
||||
- button "visibility" [ref=e21] [cursor=pointer]:
|
||||
- generic [ref=e22]: visibility
|
||||
- generic [ref=e23]:
|
||||
- generic [ref=e24] [cursor=pointer]:
|
||||
- checkbox "จดจำฉัน" [ref=e26]
|
||||
- generic [ref=e28]: จดจำฉัน
|
||||
- link "ลืมรหัสผ่าน?" [ref=e29] [cursor=pointer]:
|
||||
- /url: /auth/forgot-password
|
||||
- button "เข้าสู่ระบบ" [ref=e30] [cursor=pointer]:
|
||||
- generic [ref=e31]: เข้าสู่ระบบ
|
||||
- generic [ref=e32]:
|
||||
- generic [ref=e33]: บัญชีสำหรับทดสอบ (Test Account)
|
||||
- generic [ref=e34]:
|
||||
- generic [ref=e35]: studentedtest@example.com
|
||||
- generic [ref=e36]:
|
||||
- generic [ref=e37]: "Password:"
|
||||
- generic [ref=e38]: admin123
|
||||
- paragraph [ref=e40]:
|
||||
- text: ยังไม่มีบัญชีสมาชิก?
|
||||
- link "สมัครสมาชิกฟรี" [ref=e41] [cursor=pointer]:
|
||||
- /url: /auth/register
|
||||
- link "← กลับไปหน้าแรก" [ref=e43] [cursor=pointer]:
|
||||
- /url: /
|
||||
- generic [ref=e44]: ←
|
||||
- text: กลับไปหน้าแรก
|
||||
- generic:
|
||||
- img
|
||||
- generic:
|
||||
- generic:
|
||||
- generic:
|
||||
- button "Go to parent" [disabled]
|
||||
- button "Open in editor"
|
||||
- button "Close"
|
||||
- generic [ref=e45]:
|
||||
- button "Toggle Nuxt DevTools" [ref=e46] [cursor=pointer]:
|
||||
- img [ref=e47]
|
||||
- generic "Page load time" [ref=e50]:
|
||||
- generic [ref=e51]: "29"
|
||||
- generic [ref=e52]: ms
|
||||
- button "Toggle Component Inspector" [ref=e54] [cursor=pointer]:
|
||||
- img [ref=e55]
|
||||
```
|
||||
|
After Width: | Height: | Size: 41 KiB |
51
Frontend-Learner/tests/e2e/browse.spec.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('หมวดหน้าค้นหาคอร์สและผลลัพธ์ (Discovery & Browse)', () => {
|
||||
|
||||
test('2.1 หน้าแรก (Landing Page) โหลดได้ปกติ', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// ยืนยันว่าหน้าเว็บขึ้นจริงๆ ด้วยการจับ Element พื้นฐานอย่าง Logo หรือ Footer หรือแบนเนอร์
|
||||
// ตัวอย่างเช่นหาคำว่า "เริ่มเรียนฟรี" หรือหัวข้อแคมเปญที่คุณวางไว้หน้า Landing
|
||||
const heroTitle = page.locator('h1, h2, .hero-title').first();
|
||||
await expect(heroTitle).toBeVisible();
|
||||
|
||||
// เช็คว่าเมนูด้านบน (Navbar) หน้าหลักแสดงผล โดยเช็คจากโลโก้หรือส่วนหัว
|
||||
const navBar = page.getByRole('link', { name: 'EduLearn Platform' }).first();
|
||||
await expect(navBar).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('2.2 ค้นหาหลักสูตร (Search Course)', async ({ page }) => {
|
||||
// ไปที่หน้ารวมคอร์ส
|
||||
await page.goto('/browse');
|
||||
|
||||
// หาช่องค้นหา ด้วย placeholder
|
||||
const searchInput = page.locator('input[placeholder="ค้นหาคอร์ส..."]').first();
|
||||
|
||||
// พิมพ์คำค้นหา
|
||||
await searchInput.fill('การเขียนโปรแกรม');
|
||||
await searchInput.press('Enter');
|
||||
|
||||
// 1) รอให้เว็บโหลดผลการค้นหา และตรวจสอบว่ามีการ์ดคอร์สขึ้น (Course Card)
|
||||
const searchResults = page.locator('a[href^="/course/"]').first();
|
||||
await expect(searchResults).toBeVisible();
|
||||
});
|
||||
|
||||
test('2.3 ตัวกรองหมวดหมู่คอร์ส (Category Filter)', async ({ page }) => {
|
||||
await page.goto('/browse');
|
||||
|
||||
// คลิกลองเลือกระบุหมวดหมู่ เช่น การออกแบบ
|
||||
const categoryButton = page.locator('button').filter({ hasText: 'การออกแบบ' }).first();
|
||||
|
||||
if (await categoryButton.isVisible()) {
|
||||
await categoryButton.click();
|
||||
|
||||
// ดูผลลัพธ์ว่าหน้าเว็บยังแสดงผลการ์ดอยู่ (อาจไม่ได้เปลี่ยน URL)
|
||||
// ลบเช็ค toHaveURL ออกเพราะระบบอาจจะ filter ด้วย Client-Side แทน
|
||||
|
||||
const courseCard = page.locator('a[href^="/course/"]').first();
|
||||
await expect(courseCard).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
95
Frontend-Learner/tests/e2e/classroom.spec.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||
|
||||
async function waitAppSettled(page: any) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
// ฟังก์ชันจำลองล็อกอิน
|
||||
async function setupLogin(page: any) {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await page.locator('input[type="email"]').or(page.getByRole('textbox', { name: /อีเมล|email/i })).first().fill('studentedtest@example.com');
|
||||
await page.locator('input[type="password"]').or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i })).first().fill('admin123');
|
||||
await page.getByRole('button', { name: /เข้าสู่ระบบ|login/i }).or(page.locator('button[type="submit"]')).first().click();
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: 15_000 }).catch(() => {});
|
||||
await waitAppSettled(page);
|
||||
}
|
||||
|
||||
test.describe('ระบบห้องเรียนออนไลน์ (Classroom & Learning)', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupLogin(page);
|
||||
});
|
||||
|
||||
test('6.1 เข้าห้องเรียนหลัก (Classroom Basic Layout)', async ({ page }) => {
|
||||
// สมมติว่ามี Course ID: 1 ทดสอบแบบเปิดหน้าตรงๆ
|
||||
await page.goto(`${BASE_URL}/classroom/learning?course_id=1`);
|
||||
|
||||
// 1. โครงร่างของหน้า (Top Bar) ควรมีปุ่มกลับ กับไอคอนแผงด้านข้าง
|
||||
const backBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'arrow_back' }) }).first();
|
||||
await expect(backBtn).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
const menuCurriculumBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'menu_open' }) }).first();
|
||||
await expect(menuCurriculumBtn).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// 2. เช็คว่ามีพื้นที่ Sidebar หลักสูตร (CurriculumSidebar Component) โผล่ขึ้นมาหรือมีอยู่ใน DOM
|
||||
const sidebar = page.locator('.q-drawer').first();
|
||||
if (!await sidebar.isVisible()) {
|
||||
await menuCurriculumBtn.click();
|
||||
}
|
||||
await expect(sidebar).toBeVisible();
|
||||
});
|
||||
|
||||
test('6.2 เช็คสถานะการเข้าถึงเนื้อหา (Access Control)', async ({ page }) => {
|
||||
// ลองสุ่ม Course ID สูงๆ ที่อาจจะไม่อนุญาตให้เรียน (ไม่มีสิทธิ์) ควรรองรับกล่องแจ้งเตือนด้วย Alert ของระบบ
|
||||
// ใน learning.vue จะมีการสั่ง `alert(msg)` แต่อาจจะต้องพึ่งกลไก Intercepter
|
||||
|
||||
page.on('dialog', async dialog => {
|
||||
// หน้าต่าง Alert ถ้ามีสิทธิ์ไม่อนุญาตมันจะเด้งอันนี้
|
||||
expect(dialog.message()).toBeTruthy();
|
||||
await dialog.accept();
|
||||
});
|
||||
|
||||
await page.goto(`${BASE_URL}/classroom/learning?course_id=99999`);
|
||||
|
||||
// รอดู Loading หายไป
|
||||
const loadingMask = page.locator('.animate-pulse, .q-spinner');
|
||||
await loadingMask.first().waitFor({ state: 'hidden', timeout: 20_000 }).catch(() => {});
|
||||
});
|
||||
|
||||
test('6.3 การแสดงผลช่องวิดีโอ (Video Player) หรือ พื้นที่ทำข้อสอบ (Quiz)', async ({ page }) => {
|
||||
// เข้าหน้าห้องเรียน Course id: 1
|
||||
await page.goto(`${BASE_URL}/classroom/learning?course_id=1`);
|
||||
|
||||
// กรณีที่ 1: อาจแสดง Video ถ้าเป็นบทเรียนวิดีโอ
|
||||
const videoLocator = page.locator('video').first();
|
||||
|
||||
// กรณีที่ 2: ถ้าบทแรกเป็น Quiz จะแสดงไอคอนแบบทดสอบ
|
||||
const quizLocator = page.getByText(/เริ่มทำแบบทดสอบ|แบบทดสอบ/i).first();
|
||||
|
||||
// กรณีที่ 3: ไม่มีบทเรียนเนื้อหาใดๆ เลยให้แสดง
|
||||
const errorLocator = page.getByText(/ไม่สามารถเข้าถึง/i).first();
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
videoLocator.waitFor({ state: 'visible', timeout: 20_000 }),
|
||||
quizLocator.waitFor({ state: 'visible', timeout: 20_000 }),
|
||||
errorLocator.waitFor({ state: 'visible', timeout: 20_000 })
|
||||
]);
|
||||
|
||||
const isOkay = (await videoLocator.isVisible()) || (await quizLocator.isVisible()) || (await errorLocator.isVisible());
|
||||
expect(isOkay).toBeTruthy();
|
||||
} catch {
|
||||
// ถ้าไม่มีเลยใน 20 วิ ถือว่าหน้าอาจจะล้มเหลว หรือเป็น Content เปล่า
|
||||
// ให้ลอง Capture เพื่อเก็บข้อมูลไปใช้งาน
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/classroom-blank-state.png', fullPage: true });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
92
Frontend-Learner/tests/e2e/dashboard.spec.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||
|
||||
async function waitAppSettled(page: any) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
// ฟังก์ชันจำลองล็อกอิน (เพื่อที่จะเข้า Dashboard ได้)
|
||||
async function setupLogin(page: any) {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await page.locator('input[type="email"]').or(page.getByRole('textbox', { name: /อีเมล|email/i })).first().fill('studentedtest@example.com');
|
||||
await page.locator('input[type="password"]').or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i })).first().fill('admin123');
|
||||
await page.getByRole('button', { name: /เข้าสู่ระบบ|login/i }).or(page.locator('button[type="submit"]')).first().click();
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: 15_000 }).catch(() => {});
|
||||
await waitAppSettled(page);
|
||||
}
|
||||
|
||||
test.describe('ระบบหน้าแดชบอร์ดนักเรียน (Dashboard & My Courses)', () => {
|
||||
|
||||
// บังคับให้ Test ทุกข้อในกลุ่มนี้ ล็อกอินก่อนเสมอ
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupLogin(page);
|
||||
});
|
||||
|
||||
test('5.1 หน้าแรกของ Dashboard โหลดได้ปกติ', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard`);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// ตรวจสอบว่ามี UI เบื้องต้น เช่น คำต้อนรับ หรือสรุปสถิติ (ถ้ามี)
|
||||
const welcomeText = page.getByText(/ยินดีต้อนรับกลับ/i, { exact: false });
|
||||
const profileSummary = page.locator('.q-avatar, img[alt*="Profile"], img[src*="avatar"]').first();
|
||||
|
||||
await expect(welcomeText.first().or(profileSummary)).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test('5.2 โหลดหน้า คอร์สของฉัน (My Courses)', async ({ page }) => {
|
||||
// ลองกดเมนูด้านซ้ายเพื่อนำทาง
|
||||
const myCoursesMenu = page.getByRole('link', { name: /คอร์สของฉัน|My Courses/i }).first()
|
||||
.or(page.locator('a[href="/dashboard/my-courses"]').first());
|
||||
|
||||
await myCoursesMenu.click();
|
||||
await page.waitForURL('**/dashboard/my-courses', { timeout: 10_000 });
|
||||
|
||||
// ตรวจสอบโครงสร้างว่าโหลดเรียบร้อย
|
||||
const heading = page.locator('h2').filter({ hasText: /คอร์สของฉัน|My Courses/i }).first();
|
||||
await expect(heading).toBeVisible();
|
||||
|
||||
// เช็คว่ามีช่องค้นหาคอร์ส
|
||||
const searchInput = page.locator('input[placeholder*="ค้นหา"]').first();
|
||||
await expect(searchInput).toBeVisible();
|
||||
|
||||
// เช็คปุ่มเปลี่ยนมุมมอง Grid/List (ดูจากไอคอน)
|
||||
await expect(page.locator('i.q-icon').filter({ hasText: 'grid_view' }).first()).toBeVisible();
|
||||
await expect(page.locator('i.q-icon').filter({ hasText: 'view_list' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('5.3 กรองคอร์สด้วยเมนูหมวดหมู่ (Category Filter) ใน My Courses', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard/my-courses`);
|
||||
await page.waitForTimeout(1500); // รอ API หรือ UI Mount นิดนึง
|
||||
|
||||
// มองหาปุ่มตัวกรองทั้งหมด
|
||||
const filterAllBtn = page.getByRole('button').filter({ hasText: /ทั้งหมด|All/i }).first();
|
||||
|
||||
if (await filterAllBtn.isVisible()) {
|
||||
await filterAllBtn.click();
|
||||
// เช็คว่าข้อมูลโหลด (หาองค์ประกอบ Loading spinner หรือรอ Layout นิ่ง)
|
||||
await page.locator('.q-spinner').waitFor({ state: 'hidden', timeout: 5000 }).catch(() => {});
|
||||
}
|
||||
});
|
||||
|
||||
test('5.4 ลองค้นหาคอร์ส (Search Input) ไม่พบข้อมูล', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard/my-courses`);
|
||||
|
||||
const searchInput = page.locator('input[placeholder*="ค้นหา"]').first();
|
||||
await expect(searchInput).toBeVisible();
|
||||
|
||||
// ลองพิมพ์ชื่อมั่วๆ
|
||||
await searchInput.fill('คอร์สที่ไม่มีอยู่จริงแน่นอน1234');
|
||||
|
||||
// ตรวจสอบว่ามีกล่อง Empty State ขึ้นบอกอธิบาย
|
||||
const emptyState = page.locator('h3').filter({ hasText: /ไม่พบ|ไม่เจอ|No result/i }).first()
|
||||
.or(page.locator('i.q-icon').filter({ hasText: 'search_off' }));
|
||||
|
||||
await expect(emptyState.first()).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
102
Frontend-Learner/tests/e2e/forgot-password.spec.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||
|
||||
// ✅ หน้าจริงคือ /auth/forgot-password (อ้างอิงจากรูป)
|
||||
const FORGOT_URL = `${BASE_URL}/auth/forgot-password`;
|
||||
|
||||
async function waitAppSettled(page: Page) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
function emailInput(page: Page) {
|
||||
// เผื่อบางที input ไม่ได้ type=email แต่เป็น textbox ธรรมดา
|
||||
return page.locator('input[type="email"]').or(page.getByRole('textbox')).first();
|
||||
}
|
||||
|
||||
function submitBtn(page: Page) {
|
||||
// ปุ่มในรูปเป็น “ส่งลิงก์รีเซ็ต”
|
||||
return page.getByRole('button', { name: /ส่งลิงก์รีเซ็ต/i }).first();
|
||||
}
|
||||
|
||||
function backToLoginLink(page: Page) {
|
||||
// ในรูปเป็นลิงก์ “กลับไปหน้าเข้าสู่ระบบ”
|
||||
return page.getByRole('link', { name: /กลับไปหน้าเข้าสู่ระบบ/i }).first();
|
||||
}
|
||||
|
||||
test.describe('หน้าลืมรหัสผ่าน (Forgot Password)', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(FORGOT_URL, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
});
|
||||
|
||||
test('3.1 โหลดหน้าลืมรหัสผ่านได้ครบถ้วน (Smoke Test)', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: /ลืมรหัสผ่าน/i })).toBeVisible();
|
||||
await expect(emailInput(page)).toBeVisible();
|
||||
await expect(submitBtn(page)).toBeVisible();
|
||||
await expect(backToLoginLink(page)).toBeVisible();
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/forgot-01-smoke.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('3.2 Validation: ใส่อีเมลภาษาไทยแล้วขึ้น Error', async ({ page }) => {
|
||||
await emailInput(page).fill('ฟฟฟฟ');
|
||||
|
||||
// trigger blur
|
||||
await page.getByRole('heading', { name: /ลืมรหัสผ่าน/i }).click();
|
||||
|
||||
// ข้อความจริงในระบบ “ห้ามใส่ภาษาไทย”
|
||||
const err = page.getByText(/ห้ามใส่ภาษาไทย/i).first();
|
||||
await expect(err).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/forgot-02-thai-email.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('3.3 กดลิงก์กลับไปหน้า Login ได้', async ({ page }) => {
|
||||
await backToLoginLink(page).click();
|
||||
await page.waitForURL('**/auth/login', { timeout: 10_000 });
|
||||
await expect(page).toHaveURL(/\/auth\/login/i);
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/forgot-03-back-login.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('3.4 ทดลองส่งลิงก์รีเซ็ตรหัสผ่าน (API Mock)', async ({ page }) => {
|
||||
// ✅ ดัก request แบบกว้างขึ้น: POST ที่ URL มี forgot/reset
|
||||
await page.route('**/*', async (route) => {
|
||||
const req = route.request();
|
||||
const url = req.url();
|
||||
const method = req.method();
|
||||
|
||||
const looksLikeForgotApi =
|
||||
method === 'POST' &&
|
||||
/forgot|reset/i.test(url) &&
|
||||
// กันไม่ให้ไป intercept asset
|
||||
!/\.(png|jpg|jpeg|webp|svg|css|js|map)$/i.test(url);
|
||||
|
||||
if (looksLikeForgotApi) {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, data: { message: 'Reset link sent' } }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.continue();
|
||||
});
|
||||
|
||||
await emailInput(page).fill('test@gmail.com');
|
||||
await submitBtn(page).click();
|
||||
|
||||
// ✅ ตรวจหน้าสำเร็จตามที่คุณคาดหวัง
|
||||
await expect(page.getByText(/ส่งลิงก์เรียบร้อยแล้ว/i, { exact: false })).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByText(/กรุณาตรวจสอบกล่องจดหมาย/i, { exact: false })).toBeVisible();
|
||||
|
||||
// ปุ่ม “ส่งอีกครั้ง” (ถ้ามี)
|
||||
await expect(page.getByRole('button', { name: /ส่งอีกครั้ง/i })).toBeVisible({ timeout: 10_000 }).catch(() => {});
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/forgot-04-mock-success.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
122
Frontend-Learner/tests/e2e/login.spec.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { test, expect, type Page, type Locator } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||
|
||||
// ใช้ account ตามที่คุณให้มา
|
||||
const EMAIL = 'studentedtest@example.com';
|
||||
const PASSWORD = 'admin123';
|
||||
|
||||
async function waitAppSettled(page: Page) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
function emailLocator(page: Page): Locator {
|
||||
return page
|
||||
.locator('input[type="email"]')
|
||||
.or(page.getByRole('textbox', { name: /อีเมล|email/i }))
|
||||
.first();
|
||||
}
|
||||
|
||||
function passwordLocator(page: Page): Locator {
|
||||
return page
|
||||
.locator('input[type="password"]')
|
||||
.or(page.getByRole('textbox', { name: /รหัสผ่าน|password/i }))
|
||||
.first();
|
||||
}
|
||||
|
||||
function loginButtonLocator(page: Page): Locator {
|
||||
return page
|
||||
.getByRole('button', { name: /เข้าสู่ระบบ|login/i })
|
||||
.or(page.locator('button[type="submit"]'))
|
||||
.first();
|
||||
}
|
||||
|
||||
async function expectAnyVisible(page: Page, locators: Locator[], timeout = 20_000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
for (const loc of locators) {
|
||||
try {
|
||||
if (await loc.isVisible()) return;
|
||||
} catch {}
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
throw new Error('None of the expected dashboard locators became visible.');
|
||||
}
|
||||
|
||||
test.describe('Login -> Dashboard', () => {
|
||||
test('Success Login แล้วเข้า /dashboard ได้', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await emailLocator(page).fill(EMAIL);
|
||||
await passwordLocator(page).fill(PASSWORD);
|
||||
await loginButtonLocator(page).click();
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: 25_000 });
|
||||
await waitAppSettled(page);
|
||||
|
||||
// ✅ ใช้ Locator ที่พบเจอแน่นอนใน Layout/Page โดยไม่ยึดติดกับภาษาปัจจุบัน (I18n)
|
||||
const dashboardEvidence = [
|
||||
// มองหา Layout container ฝั่ง Dashboard
|
||||
page.locator('.q-page-container').first(),
|
||||
page.locator('.q-drawer').first(),
|
||||
// มองหารูปโปรไฟล์ (UserAvatar)
|
||||
page.locator('img[src*="avataaars"]').first(),
|
||||
page.locator('img[alt],[alt="User Avatar"]').first()
|
||||
];
|
||||
|
||||
await expectAnyVisible(page, dashboardEvidence, 20_000);
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/login-to-dashboard.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Invalid Email - Thai characters (พิมพ์ภาษาไทย)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await emailLocator(page).fill('ทดสอบภาษาไทย');
|
||||
await passwordLocator(page).fill(PASSWORD);
|
||||
|
||||
const errorHint = page.getByText('ห้ามใส่ภาษาไทย');
|
||||
|
||||
await expect(errorHint.first()).toBeVisible({ timeout: 12_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/login-thai-email.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Invalid Email Format (อีเมลผิดรูปแบบ)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
// *สำคัญ*: HTML5 จะดักจับ invalid-email-format ตั้งแต่กด Submit (native validation)
|
||||
// ทำให้ Vue Form ไม่เริ่มทำงาน
|
||||
// ดังนั้นเพื่อให้ทดสอบเจอ Error จาก useFormValidation จริงๆ เราใช้ 'test@domain'
|
||||
// ซึ่ง HTML5 <input type="email"> ปล่อยผ่าน แต่ /regex/ ของระบบตรวจเจอว่าไม่มี .com
|
||||
await emailLocator(page).fill('test@domain');
|
||||
await passwordLocator(page).fill(PASSWORD);
|
||||
await loginButtonLocator(page).click();
|
||||
await waitAppSettled(page);
|
||||
|
||||
const errorHint = page.getByText('กรุณากรอกอีเมลให้ถูกต้อง (you@example.com)');
|
||||
|
||||
await expect(errorHint.first()).toBeVisible({ timeout: 12_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/login-invalid-email.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Wrong Password (รหัสผ่านผิด หรืออีเมลไม่ถูกต้องในระบบ)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await emailLocator(page).fill(EMAIL);
|
||||
await passwordLocator(page).fill('wrong-password-123');
|
||||
await loginButtonLocator(page).click();
|
||||
await waitAppSettled(page);
|
||||
|
||||
const errorHint = page.getByText('กรุณาเช็ค Email หรือ รหัสผ่านใหม่อีกครั้ง');
|
||||
|
||||
await expect(errorHint.first()).toBeVisible({ timeout: 12_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/login-wrong-password.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
241
Frontend-Learner/tests/e2e/register.spec.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { test, expect, type Page, type Locator } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
|
||||
|
||||
async function waitAppSettled(page: Page) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle').catch(() => {});
|
||||
await page.waitForTimeout(250);
|
||||
}
|
||||
|
||||
// ===== Anchors / Scope =====
|
||||
function headingRegister(page: Page) {
|
||||
return page.getByRole('heading', { name: 'สร้างบัญชีผู้ใช้งาน' });
|
||||
}
|
||||
|
||||
// ===== Inputs (ตาม snapshot ที่คุณส่งมา) =====
|
||||
function usernameInput(page: Page): Locator {
|
||||
// snapshot: textbox "username"
|
||||
return page.getByRole('textbox', { name: 'username' }).first();
|
||||
}
|
||||
|
||||
function emailInput(page: Page): Locator {
|
||||
// snapshot: textbox "student@example.com"
|
||||
return page.getByRole('textbox', { name: 'student@example.com' }).first();
|
||||
}
|
||||
|
||||
function prefixCombobox(page: Page): Locator {
|
||||
// snapshot: combobox มี option นาย/นาง/นางสาว
|
||||
return page.getByRole('combobox').first();
|
||||
}
|
||||
|
||||
function firstNameInput(page: Page): Locator {
|
||||
// snapshot: label "ชื่อ *" + textbox
|
||||
return page.getByText(/^ชื่อ\s*\*$/).locator('..').getByRole('textbox').first();
|
||||
}
|
||||
|
||||
function lastNameInput(page: Page): Locator {
|
||||
return page.getByText(/^นามสกุล\s*\*$/).locator('..').getByRole('textbox').first();
|
||||
}
|
||||
|
||||
function phoneInput(page: Page): Locator {
|
||||
return page.getByText(/^เบอร์โทรศัพท์\s*\*$/).locator('..').getByRole('textbox').first();
|
||||
}
|
||||
|
||||
function passwordInput(page: Page): Locator {
|
||||
// snapshot: label "รหัสผ่าน *" + textbox (มีปุ่ม visibility อยู่ข้างๆ)
|
||||
return page.getByText(/^รหัสผ่าน\s*\*$/).locator('..').getByRole('textbox').first();
|
||||
}
|
||||
|
||||
function confirmPasswordInput(page: Page): Locator {
|
||||
return page.getByText(/^ยืนยันรหัสผ่าน\s*\*$/).locator('..').getByRole('textbox').first();
|
||||
}
|
||||
|
||||
function submitButton(page: Page): Locator {
|
||||
return page.getByRole('button', { name: 'สร้างบัญชี' });
|
||||
}
|
||||
|
||||
function loginLink(page: Page): Locator {
|
||||
return page.getByRole('link', { name: 'เข้าสู่ระบบ' });
|
||||
}
|
||||
|
||||
function errorBox(page: Page): Locator {
|
||||
// ทั้ง field message และ notification/toast/alert
|
||||
return page.locator(
|
||||
['.q-field__messages', '.q-field__bottom', '.text-negative', '.q-notification', '.q-banner', '[role="alert"]'].join(
|
||||
', '
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function pickPrefix(page: Page, value: 'นาย' | 'นาง' | 'นางสาว' = 'นาย') {
|
||||
const combo = prefixCombobox(page);
|
||||
|
||||
// ถ้าเป็น <select> จริง selectOption จะเวิร์คทันที
|
||||
await combo.selectOption({ label: value }).catch(async () => {
|
||||
// fallback: คลิกแล้วเลือก option
|
||||
await combo.click();
|
||||
await page.getByRole('option', { name: value }).click();
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Test data =====
|
||||
function uniqueUser() {
|
||||
const n = Date.now().toString().slice(-6);
|
||||
|
||||
// ✅ แก้ปัญหา "Phone number already exists" ด้วยเบอร์สุ่มไม่ซ้ำ
|
||||
// รูปแบบ 09xxxxxxxx (10 หลัก)
|
||||
const rand8 = Math.floor(Math.random() * 1e8).toString().padStart(8, '0');
|
||||
const phone = `09${rand8}`;
|
||||
|
||||
return {
|
||||
username: `e2e_user_${n}`,
|
||||
email: `e2e_${n}@example.com`,
|
||||
firstName: 'ทดสอบ',
|
||||
lastName: 'ระบบ',
|
||||
phone,
|
||||
password: 'Admin12345!',
|
||||
};
|
||||
}
|
||||
|
||||
// ================== TESTS ==================
|
||||
test.describe('Register Page (auth/register)', () => {
|
||||
test('หน้า Register ต้องโหลดได้ (Smoke)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await expect(headingRegister(page)).toBeVisible({ timeout: 15_000 });
|
||||
await expect(submitButton(page)).toBeVisible({ timeout: 15_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-page.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('ลิงก์ "เข้าสู่ระบบ" ต้องกดแล้วไปหน้า login ได้', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await loginLink(page).click();
|
||||
await page.waitForURL('**/auth/login', { timeout: 15_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-go-login.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('สมัครสมาชิกสำเร็จ (Happy Path) → redirect ไป /auth/login', async ({ page }) => {
|
||||
const u = uniqueUser();
|
||||
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await usernameInput(page).fill(u.username);
|
||||
await emailInput(page).fill(u.email);
|
||||
await pickPrefix(page, 'นาย');
|
||||
|
||||
await firstNameInput(page).fill(u.firstName);
|
||||
await lastNameInput(page).fill(u.lastName);
|
||||
await phoneInput(page).fill(u.phone);
|
||||
|
||||
await passwordInput(page).fill(u.password);
|
||||
await confirmPasswordInput(page).fill(u.password);
|
||||
|
||||
await submitButton(page).click();
|
||||
await waitAppSettled(page);
|
||||
|
||||
// ✅ รอ 3 อย่าง: ไป login / success toast / error
|
||||
const navToLogin = page
|
||||
.waitForURL('**/auth/login', { timeout: 25_000, waitUntil: 'domcontentloaded' })
|
||||
.then(() => 'login' as const)
|
||||
.catch(() => null);
|
||||
|
||||
const successToast = page
|
||||
.getByText(/สมัครสมาชิกสำเร็จ|success/i, { exact: false })
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 25_000 })
|
||||
.then(() => 'success' as const)
|
||||
.catch(() => null);
|
||||
|
||||
const anyError = errorBox(page)
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 25_000 })
|
||||
.then(() => 'error' as const)
|
||||
.catch(() => null);
|
||||
|
||||
const result = await Promise.race([navToLogin, successToast, anyError]);
|
||||
|
||||
// ถ้ามี error ให้ fail พร้อม log ชัดๆ
|
||||
if (result === 'error') {
|
||||
const errs = await errorBox(page).allInnerTexts().catch(() => []);
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-happy-error.png', fullPage: true });
|
||||
throw new Error(`Register did not redirect. Errors: ${errs.join(' | ')}`);
|
||||
}
|
||||
|
||||
// ถ้ามีแต่ toast success แต่ยังไม่ redirect ให้ไปหน้า login เอง (ตาม flow ที่คุณต้องการ)
|
||||
if (!page.url().includes('/auth/login')) {
|
||||
const hasSuccess = await page
|
||||
.getByText(/สมัครสมาชิกสำเร็จ/i, { exact: false })
|
||||
.first()
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
if (hasSuccess) {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ สุดท้ายต้องอยู่หน้า /auth/login แน่นอน
|
||||
await expect(page).toHaveURL(/\/auth\/login/i, { timeout: 15_000 });
|
||||
|
||||
// ✅ แก้ strict mode: ระบุให้ชัดว่าเป็น heading และ button
|
||||
await expect(page.getByRole('heading', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByRole('button', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// optional: การ์ด TEST ACCOUNT (ถ้ามี)
|
||||
await expect(page.getByText(/TEST ACCOUNT/i, { exact: false }))
|
||||
.toBeVisible({ timeout: 10_000 })
|
||||
.catch(() => {});
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-redirect-login.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Invalid Email - ใส่ภาษาไทย ต้องขึ้น error', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await emailInput(page).fill('ทดสอบภาษาไทย');
|
||||
await usernameInput(page).click(); // blur trigger
|
||||
|
||||
const err = page
|
||||
.getByText(/ห้ามใส่ภาษาไทย|อีเมลไม่ถูกต้อง|รูปแบบอีเมล/i, { exact: false })
|
||||
.or(errorBox(page).filter({ hasText: /ไทย|อีเมล|email|invalid/i }));
|
||||
|
||||
await expect(err.first()).toBeVisible({ timeout: 12_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-invalid-email-thai.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Password ไม่ตรงกัน ต้องขึ้น error (ต้องกดสร้างบัญชีก่อน)', async ({ page }) => {
|
||||
const u = uniqueUser();
|
||||
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await usernameInput(page).fill(u.username);
|
||||
await emailInput(page).fill(u.email);
|
||||
await pickPrefix(page, 'นาย');
|
||||
|
||||
await firstNameInput(page).fill(u.firstName);
|
||||
await lastNameInput(page).fill(u.lastName);
|
||||
await phoneInput(page).fill(u.phone);
|
||||
|
||||
await passwordInput(page).fill('Admin12345!');
|
||||
await confirmPasswordInput(page).fill('Admin12345?'); // mismatch
|
||||
|
||||
// ✅ ต้อง submit ก่อน error ถึงขึ้น
|
||||
await submitButton(page).click();
|
||||
await waitAppSettled(page);
|
||||
|
||||
const mismatchErr = page
|
||||
.getByText(/รหัสผ่านไม่ตรงกัน/i, { exact: false })
|
||||
.or(errorBox(page).filter({ hasText: /รหัสผ่านไม่ตรงกัน/i }));
|
||||
|
||||
await expect(mismatchErr.first()).toBeVisible({ timeout: 12_000 });
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/register-password-mismatch.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
86
Frontend-Learner/tests/e2e/settings.spec.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('หมวดการตั้งค่าและส่วนติดต่อผู้ใช้ (Settings & UI Theme)', () => {
|
||||
|
||||
test('5.1 เปลี่ยนภาษาการแสดงผล (Localisation/i18n)', async ({ page }) => {
|
||||
await page.goto('/'); // ไปที่หน้าแรกปกติ
|
||||
|
||||
// หาสวิตช์เปลี่ยนภาษา
|
||||
const langBtn = page.getByRole('button', { name: 'Language' }).or(page.locator('button').filter({ hasText: /TH|EN/ })).first();
|
||||
|
||||
// ลองกดสลับถ้ามีปุ่ม
|
||||
if (await langBtn.isVisible()) {
|
||||
await langBtn.click();
|
||||
|
||||
// เลือกเปลี่ยนเป็นภาษาอังกฤษ (สมมติตั้งต้นเป็นไทย)
|
||||
const englishOpt = page.locator('text=English, text=EN').first();
|
||||
await englishOpt.click();
|
||||
|
||||
// ตรวจสอบว่าคำว่า "เข้าสู่ระบบ" (ไทย) ถูกแปลเป็น "Log in" หรือเมนูอื่นเป็นอังกฤษไปแล้ว
|
||||
const loginLink = page.locator('a[href*="login"], button').filter({ hasText: /Log in|Sign In/i });
|
||||
await expect(loginLink).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('5.2 เปลี่ยนโหมดมืดสว่าง (Theme Switcher)', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// หาปุ่มสลับ Dark Mode / Light Mode (มักจะเป็นรูปพระอาทิตย์/พระจันทร์)
|
||||
const themeBtn = page.locator('.dark-mode-toggle, button[aria-label*="theme"]').first();
|
||||
|
||||
// ถ้ารองรับการเปลี่ยนตีม
|
||||
if (await themeBtn.isVisible()) {
|
||||
// ดึงคลาสของ <html> ก่อนกด (เช่น '<html class="dark">')
|
||||
const htmlBefore = await page.evaluate(() => document.documentElement.className);
|
||||
|
||||
await themeBtn.click();
|
||||
await page.waitForTimeout(500); // รอ animation เฟดนิดนึง
|
||||
|
||||
// ดึงคลาส <html> หลังกดอีกที
|
||||
const htmlAfter = await page.evaluate(() => document.documentElement.className);
|
||||
|
||||
// คลาสของ HTML ต้องเปลี่ยนไป (เช่น เคยมี 'dark' แล้วโดนลบออก)
|
||||
expect(htmlBefore).not.toEqual(htmlAfter);
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('การตั้งค่าบัญชี (Profile Settings)', () => {
|
||||
// แยกหมวดเล็กต้องล็อกอินเอาไว้ทดสอบหน้า My Profile
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/auth/login');
|
||||
await page.waitForTimeout(1500);
|
||||
await page.locator('input[type="email"]').fill('studentedtest@example.com');
|
||||
await page.locator('input[type="password"]').fill('admin123');
|
||||
await page.getByRole('button', { name: 'เข้าสู่ระบบ', exact: true }).click();
|
||||
await page.waitForURL('**/dashboard', { timeout: 15000 });
|
||||
});
|
||||
|
||||
test('5.3 แก้ไขและบันทึกข้อมูลส่วนตัว (Edit Profile)', async ({ page }) => {
|
||||
// เข้าไปที่หน้าการตั้งค่า หรือ Profile
|
||||
await page.goto('/dashboard/profile');
|
||||
|
||||
// ตรวจสอบว่ามีช่องกรอกชื่อ
|
||||
const nameInput = page.locator('input[type="text"]').first();
|
||||
|
||||
if (await nameInput.isVisible()) {
|
||||
const oldName = await nameInput.inputValue();
|
||||
|
||||
// ทดลองพิมพ์ตัวเลขต่อท้ายเข้าไปเพื่อจำลองการแก้ไข
|
||||
await nameInput.clear();
|
||||
await nameInput.fill(`${oldName}แก้ไข`);
|
||||
|
||||
// กดปุ่มบันทึกข้อมูล
|
||||
const saveBtn = page.getByRole('button', { name: 'บันทึกการเปลี่ยนแปลง', exact: false })
|
||||
.or(page.locator('button').filter({ hasText: 'บันทึก' })).first();
|
||||
await saveBtn.click();
|
||||
|
||||
// ตรวจสอบว่ามีแถบ Notification เด้งว่า "อัปเดตเรียบร้อย"
|
||||
const successNotify = page.locator('.q-notification__message, text=อัปเดตข้อมูลสำเร็จ').first();
|
||||
await expect(successNotify).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// (Clean up) อาจจะพิมพ์ชื่อเดิมกลับลงไปเพื่อไม่ให้ข้อมูลพัง
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
ควบคุมการทำงานและการนำทาง (Routing) ของผู้ใช้งานแต่ละหน้า โดยจะเรียกใช้ `components/` และ `composables/` มาทำงานร่วมกัน
|
||||
|
||||
- **`pages/index.vue` (หน้า Landing Page):**
|
||||
- **หน้าที่:** หน้าแรกสุดของระบบสำหรับผู้เยี่ยมชม (Guest) นำเสนอคอร์สเด่นและจุดเด่นของระบบ
|
||||
- **การเชื่อมโยง:** ใช้ Layout `landing`, เชื่อมต่อ `useCourse`, `useCategory` และ components เช่น `CourseCard`, `LandingHeader`, `LandingFooter`
|
||||
- **หน้าที่:** หน้าแรกสุดของระบบสำหรับผู้เยี่ยมชม นำเสนอคอร์สเด่น รายละเอียดสถิติ และรีวิวล่าสุด โดยดึงข้อมูลจริงจาก API (Dynamic Data) ขับเคลื่อน UI ทันสมัย
|
||||
- **การเชื่อมโยง:** ใช้ Layout `landing`, เชื่อมต่อ `useCourse`, `useCategory` และ components พื้นฐานเช่น `CourseCard`, `LandingHeader`, `LandingFooter`, `StarIcon`, และ `NuxtTime`
|
||||
- **`pages/auth/` (กลุ่มหน้าเข้าสู่ระบบและสมัครสมาชิก):**
|
||||
- **`login.vue`:** หน้าเข้าสู่ระบบ (เชื่อม `useAuth` -> `login()`)
|
||||
- **`register.vue`:** หน้าลงทะเบียนผู้ใช้ใหม่ (เชื่อม `useAuth` -> `register()`)
|
||||
|
|
@ -34,12 +34,12 @@
|
|||
|
||||
---
|
||||
|
||||
## 2. 🧩 ตัวจัดการตรรกะและข้อมูล (Composables Directory: `composables/`)
|
||||
## 2. 🧩 ตัวจัดการข้อมูล (Composables Directory: `composables/`)
|
||||
|
||||
เปรียบเสมือน "สมอง" ของระบบ แยกการจัดการข้อมูล (State) และการเรียก API (Fetch) ออกจากหน้าจอ (UI)
|
||||
|
||||
- **`useAuth.ts`:**
|
||||
- **หน้าที่:** จัดการ Login, Register, Logout, ระบบ Token (Access & Refresh), ดึง Data ของผู้ใช้
|
||||
- **หน้าที่:** จัดการ Login, Register, Request Reset Password, อัปโหลดรูปโปรไฟล์ (Avatar), ระบบ Token (มีกลไก Refresh Token อัตโนมัติเมื่อพบ 401), และดึง/อัปเดตข้อมูลผู้ใช้
|
||||
- **ถูกเรียกใช้โดย:** แทบทุกหน้าจอและ `middleware/auth.ts`
|
||||
- **`useCourse.ts`:**
|
||||
- **หน้าที่:** ศูนย์รวม API คอร์สเรียน (ดึงรายการคอร์ส, รายละเอียด, รายชื่อบทเรียน, การลงทะเบียน, ส่งความคืบหน้า, โหลดหน้าวิดีโอ)
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
|
||||
### ภาพรวมการหมุนเวียนข้อมูล (Data Flow Example)
|
||||
|
||||
1. **ล็อกอิน:** หน้า `login.vue` กดยืนยัน -> เรียก `useAuth.login()` -> ได้ Token และ Role ยัดลง Local Cache (Cookie)
|
||||
1. **ล็อกอินและจัดการ Session:** หน้า `login.vue` กดยืนยัน -> เรียก `useAuth.login()` -> ได้ Token และรายละเอียดผู้ใช้ยัดลง Cookie -> _หากดึงโปรไฟล์แล้ว API หมดอายุ (401)_ ระบบจะเรียก `refreshAccessToken()` อัตโนมัติเพื่อใช้ต่อ
|
||||
2. **เข้าห้องเรียน:** หน้า `index.vue` กดคอร์ส -> ส่งเข้า `middleware/auth.ts` เช็คผ่านเข้าหน้าเรียน -> `pages/course/[id].vue` เรียก `useCourse.fetchCourseById(id)` -> ส่งข้อมูลให้ UI
|
||||
3. **เปิดคลิปเรียน:** `learning.vue` ส่ง URL ให้ `<VideoPlayer />` เล่น
|
||||
4. **บันทึกเวลาเรียน:** `VideoPlayer` เฝ้าดูเวลา (TimeUpdate) -> รายงานตัวเลขกลับมายังฟังก์ชัน `handleVideoTimeUpdate()` ที่อยู่ใน `learning.vue` -> เรียก `useCourse.saveVideoProgress()` ส่ง API กลับ Database
|
||||
4. **บันทึกเวลาเรียน:** `VideoPlayer` เฝ้าดูเวลา (TimeUpdate) -> รายงานตัวเลขกลับมายังบัญชีผู้ใช้ใน `learning.vue` -> เรียก `useCourse.saveVideoProgress()` ส่ง API กลับ Database พร้อมเช็คสถานะเนื้อหาว่าสำเร็จหรือไม่
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
เอกสารฉบับนี้สรุปรายละเอียดทางเทคนิค โครงสร้างโค้ด และกลไกการทำงานของระบบ **Frontend-Learner (ฝั่งผู้เรียน)**
|
||||
ใช้เป็นคู่มือสำหรับการพัฒนา บำรุงรักษา และขยายระบบต่อไป
|
||||
|
||||
> อัปเดตล่าสุด: 27 กุมภาพันธ์ 2026 (อัปเดตคอมเมนต์ภาษาไทย & แก้ไข TypeScript)
|
||||
> อัปเดตล่าสุด: 2 มีนาคม 2026 (อัปเดตระบบแปลภาษา UI & ปรับปรุงหน้า Landing Page เป็น Dynamic Data)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -39,10 +39,9 @@
|
|||
### 1.2 Security & Authentication
|
||||
|
||||
- **Token Management:** ใช้ JWT (Access & Refresh Tokens) จัดเก็บผ่าน `useCookie`
|
||||
โดยตั้งค่าความปลอดภัยระดับ **HTTP-only** และ **SameSite**
|
||||
โดยมีกลไกสกัดจับ (Interceptors) ภายใน `useAuth` เช่นเมื่อโหลดโปรไฟล์หรือ **อัปโหลดรูปภาพ** หากโดน API ดีดกลับสถานะ 401 จะทำการต่ออายุ (Refresh) Token อัตโนมัติทันที
|
||||
- **Middleware:** `auth.ts` ตรวจสอบสิทธิ์การเข้าถึงหน้า Dashboard และ Classroom แบบ Real-time
|
||||
- **Persistence:** ระบบ Remember Me (จดจำอีเมล) ใช้ `localStorage` แยกส่วนจาก Session
|
||||
เพื่อความปลอดภัยและสะดวกสำหรับผู้ใช้
|
||||
- **Persistence:** ระบบ Remember Me (จดจำอีเมล) และธีมจัดเก็บผ่าน `localStorage` แยกวงจรจาก Session (จะไม่ลบหายไปตอนสั่ง Logout)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -123,6 +122,7 @@
|
|||
ที่จำสถานะตามผู้ใช้งาน
|
||||
- **Interactive Quizzes:** ระบบสอบที่สลับคำถามอัตโนมัติ พร้อมโหมดเฉลย (Answer Review) ที่ชัดเจน
|
||||
- **Certificate Automation:** ระบบตรวจสอบสิทธิ์ความสำเร็จและออกใบประกาศนียบัตรได้ทันที
|
||||
- **Dynamic Landing Page:** หน้าแรกเชื่อมต่อข้อมูลจริงจาก API ครบถ้วน (คอร์สเรียน, รีวิว) ขจัดข้อมูล Mock-up รกๆ ออก ใช้คอมโพเนนต์เสริมที่สร้างเองเช่น `StarIcon` ควบคู่กับ `@nuxt/time` เพื่อจัดฟอร์แมตวันที่แบบมือโปร
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ _(หมายเหตุ: Nuxt จะอ่านค่า `NUXT_PUBLIC_API_BA
|
|||
|
||||
ระบบมี 2 รูปแบบการแปลภาษาคือ:
|
||||
|
||||
- **UI Elements แบบ Static:** แปลผ่านไฟล์หรือแท็กในระบบ `@nuxtjs/i18n` (การสลับภาษาทำผ่านแฮมเบอร์เกอร์เมนูด้านบนขวา แจ้งสถานะผ่าน `useI18n()`)
|
||||
- **UI Elements แบบ Static:** แปลผ่านไฟล์หรือแท็กในระบบ `@nuxtjs/i18n` อย่างสมบูรณ์ (เช่น Component `AppHeader` ที่รองรับการสลับภาษา, ข้อความบอกโหมด Light/Dark, และชื่อสิทธิ์ User Roles จะถูกเรียกใช้ผ่าน i18n keys ทั้งหมดโดยไม่มีการ Hardcode)
|
||||
- **API Content แบบ Dynamic:** ในตาราง Course หรือ Quiz จากหลังบ้าน จะใช้โครงสร้างแบบคู่ (เช่น `title: { th: "...", en: "..." }`) โดยในทุกตรรกะหน้าเรียน (Composables) จะมีฟังก์ชันช่วยอย่าง `getLocalizedText()` ไว้คอยแปลงก้อน JSON นี้เป็นภาษาที่ผู้ใช้เลือกในปัจจุบันอัตโนมัติ
|
||||
|
||||
---
|
||||
|
|
|
|||
BIN
tests/e2e/screenshots/forgot-01-smoke.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tests/e2e/screenshots/forgot-02-thai-email.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tests/e2e/screenshots/forgot-03-back-login.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
tests/e2e/screenshots/forgot-04-mock-success.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
tests/e2e/screenshots/login-invalid-email.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
tests/e2e/screenshots/login-thai-email.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
tests/e2e/screenshots/login-to-dashboard.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
tests/e2e/screenshots/login-wrong-password.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
tests/e2e/screenshots/register-go-login.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
tests/e2e/screenshots/register-happy-error.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tests/e2e/screenshots/register-invalid-email-thai.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
tests/e2e/screenshots/register-page.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
tests/e2e/screenshots/register-password-mismatch.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tests/e2e/screenshots/register-redirect-login.png
Normal file
|
After Width: | Height: | Size: 42 KiB |