/** * @file classroom.spec.ts * @description ทดสอบระบบห้องเรียนออนไลน์ และระบบแบบทดสอบ * (Classroom, Learning & Quiz System) * * รวม 2 module: * - Classroom & Learning (Layout, Access Control, Video/Quiz area) * - Quiz System (Start Screen, Pagination, Submit & Navigation) */ import { test, expect } from '@playwright/test'; import { BASE_URL, TIMEOUT, waitAppSettled, setupLogin } from './helpers'; // ========================================== // Mock: ข้อมูล Quiz สำหรับ test // ========================================== async function mockQuizData(page: any) { await page.route('**/lessons/*', async (route: any) => { const mockQuestions = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, question: { th: `คำถามข้อที่ ${i + 1}?`, en: `Question ${i + 1}?` }, text: { th: `คำถามข้อที่ ${i + 1}?`, en: `Question ${i + 1}?` }, choices: [ { id: i * 10 + 1, text: { th: 'ก', en: 'A' } }, { id: i * 10 + 2, text: { th: 'ข', en: 'B' } }, { id: i * 10 + 3, text: { th: 'ค', en: 'C' } } ] })); await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, data: { id: 17, type: 'QUIZ', quiz: { id: 99, title: { th: 'แบบทดสอบปลายภาค (Mock)', en: 'Final Exam (Mock)' }, time_limit: 30, questions: mockQuestions } }, progress: {} }) }); }); } // ========================================== // Tests // ========================================== test.describe('ระบบห้องเรียนออนไลน์และแบบทดสอบ (Classroom & Quiz)', () => { test.beforeEach(async ({ page }) => { await setupLogin(page); }); // -------------------------------------------------- // Section 1: ห้องเรียน (Classroom & Learning) // -------------------------------------------------- test.describe('ห้องเรียน (Classroom Layout & Access)', () => { test('6.1 เข้าห้องเรียนหลัก (Classroom Basic Layout)', async ({ page }) => { await page.goto(`${BASE_URL}/classroom/learning?course_id=1`); // 1. โครงร่างของหน้า — ปุ่มกลับ + ไอคอนแผงด้านข้าง const backBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'arrow_back' }) }).first(); await expect(backBtn).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD }); const menuCurriculumBtn = page.getByRole('button').filter({ has: page.locator('i.q-icon', { hasText: 'menu_open' }) }).first(); await expect(menuCurriculumBtn).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD }); // 2. Sidebar หลักสูตร 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 }) => { page.on('dialog', async dialog => { expect(dialog.message()).toBeTruthy(); await dialog.accept(); }); await page.goto(`${BASE_URL}/classroom/learning?course_id=99999`); const loadingMask = page.locator('.animate-pulse, .q-spinner'); await loadingMask.first().waitFor({ state: 'hidden', timeout: TIMEOUT.PAGE_LOAD }).catch(() => {}); }); test('6.3 การแสดงผลช่องวิดีโอ หรือ พื้นที่ทำข้อสอบ (Video / Quiz)', async ({ page }) => { await page.goto(`${BASE_URL}/classroom/learning?course_id=1`); const videoLocator = page.locator('video').first(); const quizLocator = page.getByText(/เริ่มทำแบบทดสอบ|แบบทดสอบ/i).first(); const errorLocator = page.getByText(/ไม่สามารถเข้าถึง/i).first(); try { await Promise.race([ videoLocator.waitFor({ state: 'visible', timeout: TIMEOUT.PAGE_LOAD }), quizLocator.waitFor({ state: 'visible', timeout: TIMEOUT.PAGE_LOAD }), errorLocator.waitFor({ state: 'visible', timeout: TIMEOUT.PAGE_LOAD }) ]); const isOkay = (await videoLocator.isVisible()) || (await quizLocator.isVisible()) || (await errorLocator.isVisible()); expect(isOkay).toBeTruthy(); } catch { await page.screenshot({ path: 'tests/e2e/screenshots/classroom-blank-state.png', fullPage: true }); } }); }); // -------------------------------------------------- // Section 2: แบบทดสอบ (Quiz System) // -------------------------------------------------- test.describe('แบบทดสอบ (Quiz System)', () => { test('7.1 โหลดหน้า Quiz และเริ่มทำข้อสอบได้ (Start Screen)', async ({ page }) => { await mockQuizData(page); await page.goto(`${BASE_URL}/classroom/quiz?course_id=2&lesson_id=17`); const startBtn = page.getByRole('button', { name: /เริ่มทำแบบทดสอบ|Start/i }).first(); await expect(startBtn).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD }); // กดเริ่มทำ await startBtn.click(); // เช็คว่าหน้า Taking (คำถามข้อที่ 1) โผล่มา const questionText = page.locator('h3').first(); await expect(questionText).toBeVisible({ timeout: TIMEOUT.ELEMENT }); }); test('7.2 แถบข้อสอบแบ่งหน้า (Pagination — เลื่อนซ้าย/ขวา)', async ({ page }) => { await mockQuizData(page); await page.goto(`${BASE_URL}/classroom/quiz?course_id=2&lesson_id=17`); const startBtn = page.getByRole('button', { name: /เริ่มทำแบบทดสอบ|Start/i }).first(); await expect(startBtn).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD }); await startBtn.click(); // ลูกศรเลื่อนหน้าข้อสอบ const nextPaginationPageBtn = page.locator('button').filter({ has: page.locator('i.q-icon:has-text("chevron_right")') }).first(); if (await nextPaginationPageBtn.isVisible()) { await expect(nextPaginationPageBtn).toBeEnabled(); await nextPaginationPageBtn.click(); // ข้อที่ 11 ต้องแสดง const question11Btn = page.locator('button').filter({ hasText: /^11$/ }).first(); await expect(question11Btn).toBeVisible(); } }); test('7.3 การแสดงผลปุ่มถัดไป/ส่งคำตอบ (Submit & Navigation UI)', async ({ page }) => { await mockQuizData(page); await page.goto(`${BASE_URL}/classroom/quiz?course_id=2&lesson_id=17`); const startBtn = page.getByRole('button', { name: /เริ่มทำแบบทดสอบ|Start/i }).first(); await expect(startBtn).toBeVisible({ timeout: TIMEOUT.PAGE_LOAD }); await startBtn.click(); // รอคำถามโหลดเสร็จ await expect(page.locator('h3').first()).toBeVisible({ timeout: TIMEOUT.ELEMENT }); const submitBtn = page.locator('button').filter({ hasText: /(ส่งคำตอบ|Submit)/i }).first(); const nextBtn = page.locator('button').filter({ hasText: /(ถัดไป|Next)/i }).first(); // ข้อแรกต้องมีปุ่มถัดไปหรือปุ่มส่ง await expect(submitBtn.or(nextBtn)).toBeVisible({ timeout: TIMEOUT.ELEMENT }); }); }); });