feat: Implement E2E tests for authentication, student account, discovery, and classroom features, alongside new browse pages and a useAuth composable.
All checks were successful
Build and Deploy Frontend Learner / Build Frontend Learner Docker Image (push) Successful in 48s
Build and Deploy Frontend Learner / Deploy E-learning Frontend Learner to Dev Server (push) Successful in 4s
Build and Deploy Frontend Learner / Notify Deployment Status (push) Successful in 1s
All checks were successful
Build and Deploy Frontend Learner / Build Frontend Learner Docker Image (push) Successful in 48s
Build and Deploy Frontend Learner / Deploy E-learning Frontend Learner to Dev Server (push) Successful in 4s
Build and Deploy Frontend Learner / Notify Deployment Status (push) Successful in 1s
This commit is contained in:
parent
0205aab461
commit
b0b665f588
35 changed files with 546 additions and 862 deletions
|
|
@ -1,54 +1,58 @@
|
|||
/**
|
||||
* @file student-account.spec.ts
|
||||
* @description ทดสอบระบบพื้นที่ส่วนตัวผู้เรียน (Student Account / Portal)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
import { BASE_URL, TIMEOUT, waitAppSettled, setupLogin } from './helpers';
|
||||
|
||||
test.describe('ระบบพื้นที่ส่วนตัวผู้เรียน (Student Account / Portal)', () => {
|
||||
|
||||
test.describe('การตั้งค่าและส่วนติดต่อผู้ใช้ (Settings & UI Theme)', () => {
|
||||
test('เปลี่ยนภาษาการแสดงผล (Localisation/i18n)', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
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();
|
||||
await page.goto(BASE_URL);
|
||||
await waitAppSettled(page);
|
||||
|
||||
const loginLink = page.locator('a[href*="login"], button').filter({ hasText: /Log in|Sign In/i });
|
||||
await expect(loginLink).toBeVisible({ timeout: 5000 });
|
||||
// หาปุ่มภาษา — ถ้าไม่เจอให้ test.skip แทนที่จะผ่านเงียบๆ
|
||||
const langBtn = page.getByRole('button', { name: 'Language' })
|
||||
.or(page.locator('button').filter({ hasText: /TH|EN/ }))
|
||||
.first();
|
||||
|
||||
const isLangBtnVisible = await langBtn.isVisible().catch(() => false);
|
||||
if (!isLangBtnVisible) {
|
||||
test.skip(true, 'Language button not found on page — skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
await langBtn.click();
|
||||
const englishOpt = page.locator('text=English, text=EN').first();
|
||||
await englishOpt.click();
|
||||
|
||||
const loginLink = page.locator('a[href*="login"], button').filter({ hasText: /Log in|Sign In/i });
|
||||
await expect(loginLink).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-i18n.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('เปลี่ยนโหมดมืดสว่าง (Theme Switcher)', async ({ page }) => {
|
||||
await page.goto(BASE_URL);
|
||||
await waitAppSettled(page);
|
||||
|
||||
// หาปุ่ม Theme — ถ้าไม่เจอให้ test.skip แทนที่จะผ่านเงียบๆ
|
||||
const themeBtn = page.locator('.dark-mode-toggle, button[aria-label*="theme"]').first();
|
||||
|
||||
if (await themeBtn.isVisible()) {
|
||||
const htmlBefore = await page.evaluate(() => document.documentElement.className);
|
||||
await themeBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
const htmlAfter = await page.evaluate(() => document.documentElement.className);
|
||||
expect(htmlBefore).not.toEqual(htmlAfter);
|
||||
|
||||
const isThemeBtnVisible = await themeBtn.isVisible().catch(() => false);
|
||||
if (!isThemeBtnVisible) {
|
||||
test.skip(true, 'Theme toggle button not found on page — skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
const htmlBefore = await page.evaluate(() => document.documentElement.className);
|
||||
await themeBtn.click();
|
||||
await page.waitForTimeout(500);
|
||||
const htmlAfter = await page.evaluate(() => document.documentElement.className);
|
||||
expect(htmlBefore).not.toEqual(htmlAfter);
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-theme.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -59,60 +63,77 @@ test.describe('ระบบพื้นที่ส่วนตัวผู้
|
|||
|
||||
test('หน้าแรกของ Dashboard โหลดได้ปกติ', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard`);
|
||||
await page.waitForTimeout(1000);
|
||||
await waitAppSettled(page, 1000);
|
||||
|
||||
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 });
|
||||
await expect(welcomeText.first().or(profileSummary)).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-dashboard.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('โหลดหน้า คอร์สของฉัน (My Courses)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard/my-courses`);
|
||||
await waitAppSettled(page);
|
||||
|
||||
const heading = page.locator('h2').filter({ hasText: /คอร์สของฉัน|My Courses/i }).first();
|
||||
await expect(heading).toBeVisible();
|
||||
await expect(heading).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
const searchInput = page.getByPlaceholder(/ค้นหา|Search/i).first();
|
||||
await expect(searchInput).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
await expect(searchInput).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
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();
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-my-courses.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('ลองค้นหาคอร์ส (Search Input) ไม่พบข้อมูล', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard/my-courses`);
|
||||
|
||||
await waitAppSettled(page);
|
||||
|
||||
const searchInput = page.getByPlaceholder(/ค้นหา|Search/i).first();
|
||||
await expect(searchInput).toBeVisible({ timeout: 10_000 });
|
||||
await expect(searchInput).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
await searchInput.fill('คอร์สที่ไม่มีอยู่จริงแน่นอน1234');
|
||||
|
||||
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 });
|
||||
|
||||
await expect(emptyState.first()).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-search-empty.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('แก้ไขและบันทึกข้อมูลส่วนตัว (Edit Profile)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/dashboard/profile`);
|
||||
await waitAppSettled(page, 1000);
|
||||
|
||||
const nameInput = page.locator('input[type="text"]').first();
|
||||
|
||||
if (await nameInput.isVisible()) {
|
||||
const oldName = await nameInput.inputValue();
|
||||
|
||||
await nameInput.clear();
|
||||
await nameInput.fill(`${oldName}แก้ไข`);
|
||||
// หา input ชื่อ — ใช้ textbox "First Name" หรือ input[type="text"] ตัวแรก
|
||||
const nameInput = page.getByRole('textbox', { name: /First Name|ชื่อ/i }).first()
|
||||
.or(page.locator('input[type="text"]').first());
|
||||
|
||||
const saveBtn = page.getByRole('button', { name: /บันทึก/i }).first();
|
||||
if(await saveBtn.isVisible()) {
|
||||
await saveBtn.click();
|
||||
const successNotify = page.locator('.q-notification__message, text=อัปเดตข้อมูลสำเร็จ').first();
|
||||
await expect(successNotify).toBeVisible({ timeout: 5000 }).catch(() => {});
|
||||
}
|
||||
const isNameVisible = await nameInput.isVisible().catch(() => false);
|
||||
if (!isNameVisible) {
|
||||
test.skip(true, 'Profile name input not found — skipping');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const oldName = await nameInput.inputValue();
|
||||
await nameInput.clear();
|
||||
await nameInput.fill(`${oldName}แก้ไข`);
|
||||
|
||||
// ปุ่มบันทึก — รองรับทั้งภาษาไทยและอังกฤษ
|
||||
const saveBtn = page.getByRole('button', { name: /บันทึก|Save Changes|Save/i }).first();
|
||||
await expect(saveBtn).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
await saveBtn.click();
|
||||
|
||||
// Toast สำเร็จ — รองรับทั้ง 2 ภาษา
|
||||
const successNotify = page.getByText(/อัปเดตข้อมูลสำเร็จ|บันทึกข้อมูล|updated|saved|success/i).first();
|
||||
await expect(successNotify).toBeVisible({ timeout: TIMEOUT.ELEMENT }).catch(() => {});
|
||||
|
||||
await page.screenshot({ path: 'tests/e2e/screenshots/student-edit-profile.png', fullPage: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue