|
| 1 | +import { test, expect } from '@playwright/test' |
| 2 | + |
| 3 | +const BASE_URL = 'http://code-academy.lvh.me:3000' |
| 4 | +const STUDENT_EMAIL = 'alice@student.com' |
| 5 | +const STUDENT_PASSWORD = 'password123' |
| 6 | +const COURSE_ID = 2001 |
| 7 | +const LAST_LESSON_ID = 2008 // "Modules and Project Structure" - the only uncompleted lesson |
| 8 | + |
| 9 | +test.describe('Certificate Issuance E2E', () => { |
| 10 | + test('student completes last lesson and gets certificate', async ({ page }) => { |
| 11 | + test.setTimeout(60000) |
| 12 | + |
| 13 | + // Step 1: Login |
| 14 | + await page.goto(`${BASE_URL}/en/auth/login`) |
| 15 | + await page.waitForLoadState('networkidle') |
| 16 | + await page.fill('input[type="email"]', STUDENT_EMAIL) |
| 17 | + await page.fill('input[type="password"]', STUDENT_PASSWORD) |
| 18 | + await page.click('button[type="submit"]') |
| 19 | + await page.waitForURL(/dashboard/, { timeout: 15000 }) |
| 20 | + console.log('✅ Logged in as student') |
| 21 | + |
| 22 | + // Step 2: Navigate to the last lesson |
| 23 | + await page.goto(`${BASE_URL}/en/dashboard/student/courses/${COURSE_ID}/lessons/${LAST_LESSON_ID}`) |
| 24 | + await page.waitForLoadState('networkidle') |
| 25 | + console.log('📖 Navigated to last lesson:', page.url()) |
| 26 | + |
| 27 | + // Verify we're on the lesson page |
| 28 | + const pageContent = await page.textContent('body') |
| 29 | + expect(pageContent).toContain('Modules and Project Structure') |
| 30 | + |
| 31 | + // Step 3: Click "Mark as Complete" button |
| 32 | + const completeBtn = page.locator('[data-testid="lesson-complete-toggle"]') |
| 33 | + await expect(completeBtn).toBeVisible({ timeout: 5000 }) |
| 34 | + |
| 35 | + // Check if already completed |
| 36 | + const btnText = await completeBtn.textContent() |
| 37 | + if (btnText?.toLowerCase().includes('completed') || btnText?.toLowerCase().includes('done')) { |
| 38 | + console.log('ℹ️ Lesson already completed, checking for certificate...') |
| 39 | + } else { |
| 40 | + console.log('🎯 Clicking "Mark as Complete"...') |
| 41 | + |
| 42 | + // Listen for certificate toast |
| 43 | + const toastPromise = page.waitForSelector('text=Certificate Earned', { timeout: 10000 }).catch(() => null) |
| 44 | + |
| 45 | + await page.evaluate(() => { |
| 46 | + const btn = document.querySelector('[data-testid="lesson-complete-toggle"]') as HTMLButtonElement |
| 47 | + if (btn) btn.click() |
| 48 | + }) |
| 49 | + |
| 50 | + // Wait for completion to process |
| 51 | + await page.waitForTimeout(3000) |
| 52 | + |
| 53 | + const toast = await toastPromise |
| 54 | + if (toast) { |
| 55 | + console.log('🎉 Certificate Earned toast appeared!') |
| 56 | + } else { |
| 57 | + console.log('⚠️ No certificate toast (may have auto-navigated)') |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + // Step 4: Verify certificate exists |
| 62 | + // Navigate to certificates page |
| 63 | + await page.goto(`${BASE_URL}/en/dashboard/student/certificates`) |
| 64 | + await page.waitForLoadState('networkidle') |
| 65 | + console.log('📜 Certificates page loaded') |
| 66 | + |
| 67 | + await page.screenshot({ path: '/tmp/certificates-page.png' }) |
| 68 | + |
| 69 | + const certsContent = await page.textContent('body') |
| 70 | + if (certsContent?.includes('Python Fundamentals') || certsContent?.includes('Certificate')) { |
| 71 | + console.log('✅ Certificate is visible on certificates page') |
| 72 | + } else { |
| 73 | + console.log('⚠️ Certificate not found on certificates page') |
| 74 | + console.log('Page content (first 500):', certsContent?.substring(0, 500)) |
| 75 | + } |
| 76 | + |
| 77 | + // Step 5: Also check exam result page shows certificate banner |
| 78 | + await page.goto(`${BASE_URL}/en/dashboard/student/courses/${COURSE_ID}/exams/2001/result`) |
| 79 | + await page.waitForLoadState('networkidle') |
| 80 | + console.log('📋 Exam result page loaded') |
| 81 | + |
| 82 | + await page.screenshot({ path: '/tmp/exam-result-with-cert.png' }) |
| 83 | + |
| 84 | + const resultContent = await page.textContent('body') |
| 85 | + if (resultContent?.includes('Certificate Earned')) { |
| 86 | + console.log('✅ Certificate banner shows on exam result page') |
| 87 | + } else { |
| 88 | + console.log('⚠️ No certificate banner on exam result page') |
| 89 | + } |
| 90 | + |
| 91 | + // Step 6: Find and visit the verification link |
| 92 | + const verifyLink = page.locator('a[href*="/verify/"]') |
| 93 | + const verifyCount = await verifyLink.count() |
| 94 | + if (verifyCount > 0) { |
| 95 | + const verifyHref = await verifyLink.first().getAttribute('href') |
| 96 | + console.log('🔗 Verification link found:', verifyHref) |
| 97 | + |
| 98 | + await verifyLink.first().click() |
| 99 | + await page.waitForLoadState('networkidle') |
| 100 | + console.log('📜 Verification page loaded:', page.url()) |
| 101 | + |
| 102 | + await page.screenshot({ path: '/tmp/certificate-verify.png' }) |
| 103 | + |
| 104 | + const verifyContent = await page.textContent('body') |
| 105 | + if (verifyContent?.includes('Python Fundamentals') || verifyContent?.includes('Code Academy')) { |
| 106 | + console.log('✅ Certificate verification page shows correct info') |
| 107 | + } |
| 108 | + } else { |
| 109 | + console.log('⚠️ No verification link found') |
| 110 | + } |
| 111 | + }) |
| 112 | +}) |
0 commit comments