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,40 +1,13 @@
|
|||
/**
|
||||
* @file auth.spec.ts
|
||||
* @description ทดสอบระบบยืนยันตัวตน (Authentication) — Login, Register, Forgot Password
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Helpers: Login
|
||||
// ---------------------------
|
||||
const LOGIN_EMAIL = 'studentedtest@example.com';
|
||||
const LOGIN_PASSWORD = 'admin123';
|
||||
|
||||
function loginEmailLocator(page: Page): Locator {
|
||||
return page.locator('input[type="email"]').or(page.getByRole('textbox', { name: /อีเมล|email/i })).first();
|
||||
}
|
||||
function loginPasswordLocator(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 locators became visible.');
|
||||
}
|
||||
import {
|
||||
BASE_URL, TEST_EMAIL, TEST_PASSWORD, TIMEOUT,
|
||||
waitAppSettled, expectAnyVisible,
|
||||
emailLocator, passwordLocator, loginButtonLocator,
|
||||
} from './helpers';
|
||||
|
||||
// ---------------------------
|
||||
// Helpers: Register
|
||||
|
|
@ -53,6 +26,7 @@ function regLoginLink(page: Page) { return page.getByRole('link', { name: 'เ
|
|||
function regErrorBox(page: Page) {
|
||||
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 = regPrefix(page);
|
||||
await combo.selectOption({ label: value }).catch(async () => {
|
||||
|
|
@ -60,6 +34,7 @@ async function pickPrefix(page: Page, value: 'นาย' | 'นาง' | 'นา
|
|||
await page.getByRole('option', { name: value }).click();
|
||||
});
|
||||
}
|
||||
|
||||
function uniqueUser() {
|
||||
const n = Date.now().toString().slice(-6);
|
||||
const rand8 = Math.floor(Math.random() * 1e8).toString().padStart(8, '0');
|
||||
|
|
@ -90,10 +65,12 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
test('Success Login แล้วเข้า /dashboard ได้', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await loginEmailLocator(page).fill(LOGIN_EMAIL);
|
||||
await loginPasswordLocator(page).fill(LOGIN_PASSWORD);
|
||||
|
||||
await emailLocator(page).fill(TEST_EMAIL);
|
||||
await passwordLocator(page).fill(TEST_PASSWORD);
|
||||
await loginButtonLocator(page).click();
|
||||
await page.waitForURL('**/dashboard', { timeout: 25_000 });
|
||||
|
||||
await page.waitForURL('**/dashboard', { timeout: TIMEOUT.LOGIN });
|
||||
await waitAppSettled(page);
|
||||
|
||||
const dashboardEvidence = [
|
||||
|
|
@ -102,38 +79,45 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
page.locator('img[src*="avataaars"]').first(),
|
||||
page.locator('img[alt],[alt="User Avatar"]').first()
|
||||
];
|
||||
await expectAnyVisible(page, dashboardEvidence, 20_000);
|
||||
await expectAnyVisible(page, dashboardEvidence, TIMEOUT.PAGE_LOAD);
|
||||
});
|
||||
|
||||
test('Invalid Email - Thai characters (พิมพ์ภาษาไทย)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await loginEmailLocator(page).fill('ทดสอบภาษาไทย');
|
||||
await loginPasswordLocator(page).fill(LOGIN_PASSWORD);
|
||||
const errorHint = page.getByText('ห้ามใส่ภาษาไทย');
|
||||
await expect(errorHint.first()).toBeVisible({ timeout: 12_000 });
|
||||
|
||||
await emailLocator(page).fill('ทดสอบภาษาไทย');
|
||||
await passwordLocator(page).fill(TEST_PASSWORD);
|
||||
|
||||
await expect(page.getByText('ห้ามใส่ภาษาไทย').first()).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
|
||||
test('Invalid Email Format (อีเมลผิดรูปแบบ)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await loginEmailLocator(page).fill('test@domain');
|
||||
await loginPasswordLocator(page).fill(LOGIN_PASSWORD);
|
||||
|
||||
await emailLocator(page).fill('test@domain');
|
||||
await passwordLocator(page).fill(TEST_PASSWORD);
|
||||
await loginButtonLocator(page).click();
|
||||
await waitAppSettled(page);
|
||||
const errorHint = page.getByText('กรุณากรอกอีเมลให้ถูกต้อง (you@example.com)');
|
||||
await expect(errorHint.first()).toBeVisible({ timeout: 12_000 });
|
||||
|
||||
await expect(
|
||||
page.getByText('กรุณากรอกอีเมลให้ถูกต้อง (you@example.com)').first()
|
||||
).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
|
||||
test('Wrong Password (รหัสผ่านผิด หรืออีเมลไม่ถูกต้องในระบบ)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await loginEmailLocator(page).fill(LOGIN_EMAIL);
|
||||
await loginPasswordLocator(page).fill('wrong-password-123');
|
||||
|
||||
await emailLocator(page).fill(TEST_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 expect(
|
||||
page.getByText('กรุณาเช็ค Email หรือ รหัสผ่านใหม่อีกครั้ง').first()
|
||||
).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -142,21 +126,22 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
test('หน้า Register ต้องโหลดได้ (Smoke)', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await expect(regHeading(page)).toBeVisible({ timeout: 15_000 });
|
||||
await expect(regSubmit(page)).toBeVisible({ timeout: 15_000 });
|
||||
await expect(regHeading(page)).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD });
|
||||
await expect(regSubmit(page)).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD });
|
||||
});
|
||||
|
||||
test('ลิงก์ "เข้าสู่ระบบ" ต้องกดแล้วไปหน้า login ได้', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await regLoginLink(page).click();
|
||||
await page.waitForURL('**/auth/login', { timeout: 15_000 });
|
||||
await page.waitForURL('**/auth/login', { timeout: TIMEOUT.PAGE_LOAD });
|
||||
});
|
||||
|
||||
test('สมัครสมาชิกสำเร็จ (Happy Path) → redirect ไป /auth/login', async ({ page }) => {
|
||||
const u = uniqueUser();
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await regUsername(page).fill(u.username);
|
||||
await regEmail(page).fill(u.email);
|
||||
await pickPrefix(page, 'นาย');
|
||||
|
|
@ -168,15 +153,18 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
await regSubmit(page).click();
|
||||
await waitAppSettled(page);
|
||||
|
||||
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 = regErrorBox(page).first().waitFor({ state: 'visible', timeout: 25_000 }).then(() => 'error' as const).catch(() => null);
|
||||
|
||||
// รอ 3 สัญญาณ: redirect ไป login / success toast / error
|
||||
const navToLogin = page.waitForURL('**/auth/login', { timeout: TIMEOUT.LOGIN, waitUntil: 'domcontentloaded' }).then(() => 'login' as const).catch(() => null);
|
||||
const successToast = page.getByText(/สมัครสมาชิกสำเร็จ|success/i, { exact: false }).first().waitFor({ state: 'visible', timeout: TIMEOUT.LOGIN }).then(() => 'success' as const).catch(() => null);
|
||||
const anyError = regErrorBox(page).first().waitFor({ state: 'visible', timeout: TIMEOUT.LOGIN }).then(() => 'error' as const).catch(() => null);
|
||||
|
||||
const result = await Promise.race([navToLogin, successToast, anyError]);
|
||||
if (result === 'error') {
|
||||
throw new Error('Register errors visible');
|
||||
const errs = await regErrorBox(page).allInnerTexts().catch(() => []);
|
||||
throw new Error(`Register failed with errors: ${errs.join(' | ')}`);
|
||||
}
|
||||
|
||||
// ถ้ามี toast แต่ยัง redirect ไม่ไป ให้ navigate เอง
|
||||
if (!page.url().includes('/auth/login')) {
|
||||
const hasSuccess = await page.getByText(/สมัครสมาชิกสำเร็จ/i, { exact: false }).first().isVisible().catch(() => false);
|
||||
if (hasSuccess) {
|
||||
|
|
@ -185,24 +173,28 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
}
|
||||
}
|
||||
|
||||
await expect(page).toHaveURL(/\/auth\/login/i, { timeout: 15_000 });
|
||||
await expect(page.getByRole('heading', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByRole('button', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page).toHaveURL(/\/auth\/login/i, { timeout: TIMEOUT.PAGE_LOAD });
|
||||
await expect(page.getByRole('heading', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD });
|
||||
await expect(page.getByRole('button', { name: 'เข้าสู่ระบบ' })).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD });
|
||||
});
|
||||
|
||||
test('Invalid Email - ใส่ภาษาไทย ต้องขึ้น error', async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
await regEmail(page).fill('ทดสอบภาษาไทย');
|
||||
await regUsername(page).click();
|
||||
const err = page.getByText(/ห้ามใส่ภาษาไทย|อีเมลไม่ถูกต้อง|รูปแบบอีเมล/i, { exact: false }).or(regErrorBox(page).filter({ hasText: /ไทย|อีเมล|email|invalid/i }));
|
||||
await expect(err.first()).toBeVisible({ timeout: 12_000 });
|
||||
await regUsername(page).click(); // blur trigger
|
||||
|
||||
const err = page
|
||||
.getByText(/ห้ามใส่ภาษาไทย|อีเมลไม่ถูกต้อง|รูปแบบอีเมล/i, { exact: false })
|
||||
.or(regErrorBox(page).filter({ hasText: /ไทย|อีเมล|email|invalid/i }));
|
||||
await expect(err.first()).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
|
||||
test('Password ไม่ตรงกัน ต้องขึ้น error (ต้องกดสร้างบัญชีก่อน)', async ({ page }) => {
|
||||
const u = uniqueUser();
|
||||
await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });
|
||||
await waitAppSettled(page);
|
||||
|
||||
await regUsername(page).fill(u.username);
|
||||
await regEmail(page).fill(u.email);
|
||||
await pickPrefix(page, 'นาย');
|
||||
|
|
@ -210,11 +202,14 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
await regLastName(page).fill(u.lastName);
|
||||
await regPhone(page).fill(u.phone);
|
||||
await regPassword(page).fill('Admin12345!');
|
||||
await regConfirmPassword(page).fill('Admin12345?');
|
||||
await regConfirmPassword(page).fill('Admin12345?'); // mismatch
|
||||
await regSubmit(page).click();
|
||||
await waitAppSettled(page);
|
||||
const mismatchErr = page.getByText(/รหัสผ่านไม่ตรงกัน/i, { exact: false }).or(regErrorBox(page).filter({ hasText: /รหัสผ่านไม่ตรงกัน/i }));
|
||||
await expect(mismatchErr.first()).toBeVisible({ timeout: 12_000 });
|
||||
|
||||
const mismatchErr = page
|
||||
.getByText(/รหัสผ่านไม่ตรงกัน/i, { exact: false })
|
||||
.or(regErrorBox(page).filter({ hasText: /รหัสผ่านไม่ตรงกัน/i }));
|
||||
await expect(mismatchErr.first()).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -235,13 +230,12 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
test('Validation: ใส่อีเมลภาษาไทยแล้วขึ้น Error', async ({ page }) => {
|
||||
await forgotEmail(page).fill('ฟฟฟฟ');
|
||||
await page.getByRole('heading', { name: /ลืมรหัสผ่าน/i }).click();
|
||||
const err = page.getByText(/ห้ามใส่ภาษาไทย/i).first();
|
||||
await expect(err).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByText(/ห้ามใส่ภาษาไทย/i).first()).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
});
|
||||
|
||||
test('กดลิงก์กลับไปหน้า Login ได้', async ({ page }) => {
|
||||
await forgotBackLink(page).click();
|
||||
await page.waitForURL('**/auth/login', { timeout: 10_000 });
|
||||
await page.waitForURL('**/auth/login', { timeout: TIMEOUT.ELEMENT });
|
||||
await expect(page).toHaveURL(/\/auth\/login/i);
|
||||
});
|
||||
|
||||
|
|
@ -257,9 +251,10 @@ test.describe('ระบบยืนยันตัวตน (Authentication)',
|
|||
}
|
||||
await route.continue();
|
||||
});
|
||||
|
||||
await forgotEmail(page).fill('test@gmail.com');
|
||||
await forgotSubmit(page).click();
|
||||
await expect(page.getByText(/ส่งลิงก์เรียบร้อยแล้ว/i, { exact: false })).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByText(/ส่งลิงก์เรียบร้อยแล้ว/i, { exact: false })).toBeVisible({ timeout: TIMEOUT.ELEMENT });
|
||||
await expect(page.getByText(/กรุณาตรวจสอบกล่องจดหมาย/i, { exact: false })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue