feat: Establish Playwright testing infrastructure with initial tests for authentication, admin, and instructor modules, and fix instructor video and quiz lesson management pages.
All checks were successful
Build and Deploy Frontend Management to Dev Server / Build Frontend Management Docker Image (push) Successful in 1m17s
Build and Deploy Frontend Management to Dev Server / Deploy E-learning Frontend Management to Dev Server (push) Successful in 8s
Build and Deploy Frontend Management to Dev Server / Notify Deployment Status (push) Successful in 2s

This commit is contained in:
Missez 2026-03-02 15:48:47 +07:00
parent 734d922393
commit 9bc24fbe8a
18 changed files with 1344 additions and 7 deletions

View file

@ -0,0 +1,124 @@
import { test, expect } from '@playwright/test';
import { TEST_ADMIN, TEST_INSTRUCTOR, TEST_URLS } from '../fixtures/test-data';
test.describe('Login Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto(TEST_URLS.login);
});
test('should display login form', async ({ page }) => {
// Title
await expect(page.locator('h1')).toContainText('E-Learning');
// Email & password fields
await expect(page.locator('input[type="email"]')).toBeVisible();
await expect(page.locator('input[type="password"]')).toBeVisible();
// Submit button
await expect(page.locator('button[type="submit"]')).toBeVisible();
// Register link
await expect(page.locator('a[href="/register"]')).toBeVisible();
});
test('should show validation errors for empty fields', async ({ page }) => {
// Click submit without filling fields
await page.locator('button[type="submit"]').click();
// Expect validation messages in Thai
await expect(page.getByText('กรุณากรอกอีเมล')).toBeVisible();
});
test('should show validation errors for empty fields password', async ({ page }) => {
// Click submit without filling fields
await page.locator('input[type="email"]').fill('test@email.com');
await page.locator('button[type="submit"]').click();
// Expect validation messages in Thai
await expect(page.getByText('กรุณากรอกรหัสผ่าน')).toBeVisible();
});
test('should toggle password visibility', async ({ page }) => {
const passwordInput = page.locator('input[type="password"]');
await page.waitForTimeout(1000);
await passwordInput.fill('test123');
await page.waitForTimeout(1000);
// Click the visibility toggle icon
await page.locator('//i[normalize-space(text())="visibility"]').click();
// Password field should now be text type
await expect(page.locator('input[type="text"]').last()).toHaveValue('test123');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('input[type="email"]').fill('wrong@email.com');
await page.waitForTimeout(500);
await page.locator('input[type="password"]').fill('wrongpassword');
await page.locator('button[type="submit"]').click();
// Should show error notification (Quasar Notify)
await expect(page.locator('.q-notification')).toBeVisible({ timeout: 10_000 });
});
test('should login as admin and redirect to /admin', async ({ page }) => {
await page.waitForTimeout(1000);
await page.locator('input[type="email"]').fill(TEST_ADMIN.email);
await page.waitForTimeout(500);
await page.locator('input[type="password"]').fill(TEST_ADMIN.password);
await page.locator('button[type="submit"]').click();
// Should redirect to admin dashboard
await page.waitForURL('**/admin**', { timeout: 15_000 });
await expect(page).toHaveURL(/\/admin/);
});
test('should login as instructor and redirect to /instructor', async ({ page }) => {
await page.locator('input[type="email"]').fill(TEST_INSTRUCTOR.email);
await page.waitForTimeout(1000);
await page.locator('input[type="password"]').fill(TEST_INSTRUCTOR.password);
await page.waitForTimeout(1000);
await page.locator('button[type="submit"]').click();
// Should redirect to instructor dashboard
await page.waitForURL('**/instructor**', { timeout: 15_000 });
await expect(page).toHaveURL(/\/instructor/);
});
test('should open forgot password modal', async ({ page }) => {
await page.getByText('ลืมรหัสผ่าน?').click();
// Modal should be visible
await expect(page.getByText('ลืมรหัสผ่าน').nth(1)).toBeVisible();
await expect(page.locator('.q-dialog input[type="email"]')).toBeVisible();
});
test('should remember email after login with remember me checked', async ({ page }) => {
// 1. Fill credentials
await page.waitForTimeout(500);
await page.locator('input[type="email"]').fill(TEST_ADMIN.email);
await page.waitForTimeout(500);
await page.locator('input[type="password"]').fill(TEST_ADMIN.password);
await page.waitForTimeout(500);
// 2. Check "remember me"
await page.getByText('จดจำฉันไว้').click();
// 3. Login
await page.locator('button[type="submit"]').click();
await page.waitForURL('**/admin**', { timeout: 15_000 });
// 4. Logout - click avatar menu then logout
await page.getByText('ออกจากระบบ').click();
await page.waitForTimeout(1000);
// 5. Confirm logout dialog
await page.locator("(//button[@type='button'])[2]").click();
// 6. Should redirect back to login page
await page.waitForURL('**/login**', { timeout: 15_000 });
// 7. Verify the email is still pre-filled
await expect(page.locator('input[type="email"]')).toHaveValue(TEST_ADMIN.email);
});
});

View file

@ -0,0 +1,146 @@
import { test, expect } from '@playwright/test';
import { TEST_URLS } from '../fixtures/test-data';
import { faker } from '@faker-js/faker';
/**
* Register Page Tests
* Test instructor registration flow
*/
test.describe('Register Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto(TEST_URLS.register);
});
test('check display register form', async ({ page }) => {
// Header
await expect(page.getByText('ลงทะเบียนเป็นผู้สอน')).toBeVisible();
await expect(page.getByText('สร้างบัญชีเพื่อเริ่มสร้างหลักสูตร')).toBeVisible();
// Form fields
await expect(page.locator('label').filter({ hasText: 'ชื่อผู้ใช้ (Username)' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'อีเมล' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'รหัสผ่าน *' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'ยืนยันรหัสผ่าน' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'คำนำหน้า' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'ชื่อจริง' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'นามสกุล' })).toBeVisible();
await expect(page.locator('label').filter({ hasText: 'เบอร์โทรศัพท์' })).toBeVisible();
// Submit button
await expect(page.getByRole('button', { name: 'ลงทะเบียน' })).toBeVisible();
// Login link
await expect(page.getByText('มีบัญชีอยู่แล้ว?')).toBeVisible();
await expect(page.getByText('เข้าสู่ระบบ')).toBeVisible();
});
test('should show validation errors for empty fields', async ({ page }) => {
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
await expect(page.getByText('กรุณากรอก username')).toBeVisible();
await expect(page.getByText('กรุณากรอกอีเมล')).toBeVisible();
await expect(page.getByText('กรุณากรอกรหัสผ่าน')).toBeVisible();
await expect(page.getByText('กรุณายืนยันรหัสผ่าน')).toBeVisible();
await expect(page.getByText('กรุณากรอกชื่อ')).toBeVisible();
await expect(page.getByText('กรุณากรอกนามสกุล')).toBeVisible();
await expect(page.getByText('กรุณากรอกเบอร์โทร')).toBeVisible();
});
test('should show username min length validation', async ({ page }) => {
const usernameInput = page.locator('label').filter({ hasText: 'ชื่อผู้ใช้ (Username)' }).locator('input');
await usernameInput.fill('ab');
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
await expect(page.getByText('อย่างน้อย 4 ตัวอักษร')).toBeVisible();
});
test('should show email format validation', async ({ page }) => {
const emailInput = page.locator('input[type="email"]');
await emailInput.fill('invalid-email');
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
await expect(page.getByText('รูปแบบอีเมลไม่ถูกต้อง')).toBeVisible();
});
test('should show password min length validation', async ({ page }) => {
const passwordInput = page.locator('label').filter({ hasText: 'รหัสผ่าน *' }).locator('input');
await passwordInput.fill('1234');
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
await expect(page.getByText('อย่างน้อย 8 ตัวอักษร')).toBeVisible();
});
test('should show password mismatch validation', async ({ page }) => {
const passwordInput = page.locator('label').filter({ hasText: 'รหัสผ่าน *' }).locator('input');
const confirmInput = page.locator('label').filter({ hasText: 'ยืนยันรหัสผ่าน' }).locator('input');
await passwordInput.fill('password123');
await confirmInput.fill('differentpass');
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
await expect(page.getByText('รหัสผ่านไม่ตรงกัน')).toBeVisible();
});
test('should toggle password visibility', async ({ page }) => {
const passwordInput = page.locator('label').filter({ hasText: 'รหัสผ่าน *' }).locator('input');
await passwordInput.fill('test1234');
// Click visibility icon
await page.locator('label').filter({ hasText: 'รหัสผ่าน *' }).locator('.q-icon').filter({ hasText: 'visibility' }).click();
// Both password fields should now be text type
await expect(passwordInput).toHaveAttribute('type', 'text');
});
test('should navigate to login page', async ({ page }) => {
await page.getByText('เข้าสู่ระบบ').click();
await page.waitForURL('**/login**', { timeout: 10_000 });
await expect(page).toHaveURL(/\/login/);
});
test('should register successfully with valid data', async ({ page }) => {
const username = faker.internet.username().toLowerCase().replace(/[^a-z0-9]/g, '') + faker.string.alpha({ length: 4, casing: 'lower' });
const email = faker.internet.email().toLowerCase();
const password = 'Test@1234';
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const phone = `0${faker.string.numeric(9)}`;
// Fill form
const usernameInput = page.locator('label').filter({ hasText: 'ชื่อผู้ใช้ (Username)' }).locator('input');
await usernameInput.fill(username);
await page.locator('input[type="email"]').fill(email);
const passwordInput = page.locator('label').filter({ hasText: 'รหัสผ่าน *' }).locator('input');
await passwordInput.fill(password);
const confirmInput = page.locator('label').filter({ hasText: 'ยืนยันรหัสผ่าน' }).locator('input');
await confirmInput.fill(password);
// Select prefix
const prefixSelect = page.locator('label').filter({ hasText: 'คำนำหน้า' });
await prefixSelect.click();
await page.waitForTimeout(300);
await page.locator('.q-item__label').filter({ hasText: 'นาย / Mr.' }).click();
// Fill name
const firstNameInput = page.locator('label').filter({ hasText: 'ชื่อจริง' }).locator('input');
await firstNameInput.fill(firstName);
const lastNameInput = page.locator('label').filter({ hasText: 'นามสกุล' }).locator('input');
await lastNameInput.fill(lastName);
// Fill phone
const phoneInput = page.locator('label').filter({ hasText: 'เบอร์โทรศัพท์' }).locator('input');
await phoneInput.fill(phone);
// Submit
await page.getByRole('button', { name: 'ลงทะเบียน' }).click();
// Should show success notification and redirect to login
await expect(page.locator('.q-notification')).toBeVisible({ timeout: 10_000 });
await page.waitForURL('**/login**', { timeout: 15_000 });
await expect(page).toHaveURL(/\/login/);
});
});