How to Test EdTech Course Enrollment and Progress
Short answer
EdTech flows fail on prerequisite bypass, waitlist promotion races, progress not gating certificates, and media completion lies—not on whether enroll button works. Seed course DAGs and seat counts, probe enrollment_state and watchedSeconds, pair video completion with media player patterns, and expand waitlist scenarios when TrueCoverage shows full courses in prod.
Part of Testing Guides by industry.
Who this is for
Teams shipping LMS platforms, course marketplaces, cohort programs, or corporate training with prerequisites, seat caps, waitlists, progress tracking, quizzes, and certificates.
Why testing EdTech enrollment matters
Enrollment bugs block revenue and accreditation:
- Prerequisite bypass — user enrolls in Course B without completing A; accreditation audit fails.
- Waitlist errors — seat opens, wrong user promoted; fairness complaints and refund requests.
- Progress lies — UI 100% but probe incomplete; certificate issued incorrectly.
- Payment gating — free preview grants full content; revenue leakage.
- SCORM/xAPI mismatch — completion reported to external LRS wrong; enterprise SLA breach.
Probe enrollment_state, progress, and certificate_eligible—UI checkmarks are insufficient.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Prerequisite | Missing course A | B enroll succeeds | Probe block_reason |
| Seat cap | Full course | Over-enroll | Probe seats_remaining=0 |
| Waitlist promote | Cancel frees seat | Wrong order | Probe queue position |
| Cohort start date | Early access | Content leak | clock + probe |
| Payment pending | Content visible | Bypass paywall | Probe entitlement |
| Quiz gate | Fail quiz | Certificate issued | Probe quiz_passed |
| Video completion | Seek to end | Certificate | Probe watchedSeconds |
| Re-enroll | Expired cert | Duplicate row | Probe single active |
| Instructor preview | Student view conflated | Free access | Probe role |
| Bundle enroll | Partial failure | Orphan payment | Probe bundle state |
| Drop course | Progress retained | Policy | Probe enrollment_state |
| CE credits | Wrong hours | Compliance | Probe credit_minutes |
Prerequisite chain seed
await request.post('/api/test/seed-courses', {
data: {
runId,
courses: [
{ id: 'course-a', title: 'Intro', requires: [] },
{ id: 'course-b', title: 'Advanced', requires: ['course-a'] },
],
},
});
await request.post('/api/test/seed-user-progress', {
data: { runId, userId: `u-${runId}`, completed: [] }, // not completed A
});
Negative prerequisite
await page.goto('/courses/course-b/enroll');
await page.getByRole('button', { name: 'Enroll' }).click();
const enroll = await request.get(`/api/test/enrollment?runId=${runId}&course=course-b`).then(r => r.json());
expect(enroll.state).toBe('blocked');
expect(enroll.blockReason).toMatch(/prerequisite/i);
Waitlist promotion
await request.post('/api/test/seed-course-capacity', {
data: { runId, courseId: 'course-a', capacity: 1, enrolled: 1, waitlist: [`u-${runId}`] },
});
await request.post('/api/test/free-seat', { data: { runId, courseId: 'course-a' } }); // cancel other
await expect.poll(async () =>
(await request.get(`/api/test/enrollment?runId=${runId}&course=course-a`)).json()
).toMatchObject({ state: 'enrolled' });
Progress and media
Pair with video/audio players:
await page.goto('/lessons/lesson-1');
// play + seek pattern ...
await expect.poll(async () =>
(await request.get(`/api/test/progress?runId=${runId}&lesson=lesson-1`)).json()
).toMatchObject({ completed: true });
await page.goto('/courses/course-a/certificate');
const cert = await request.get(`/api/test/certificate?runId=${runId}&course=course-a`);
expect(cert.status()).toBe(200);
Requirement slices to cover
enrollment_state— enrolled, waitlisted, blocked, completedcourse_id— top courses by prod enrollment
Run /testchimp evolve when waitlist promotion slice undertested.
CI checklist
- Course DAG in seed data per runId
- Prerequisite negative for every required edge
- Waitlist promotion via admin/free-seat route
- Progress probe before certificate assert
- Payment entitlement probe if monetized
- Video completion uses stub clip not hour-long media
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| UI progress bar only | Lies | watchedSeconds probe |
| Skip waitlist | Race bugs | Promotion poll |
| Enroll B without A test | Accreditation | blockReason probe |
| Full video watch in CI | Slow | Seek near end |
| Shared course capacity | Parallel flake | Per-run capacity |
| Certificate without quiz | Compliance | quiz_passed probe |
Example scenario
Situation: Learner completes all lessons but fails final quiz; attempts certificate download.
Expected outcome: Certificate blocked; enrollment_state completed lessons but certificate_eligible false.
Why UI-only automation breaks: Certificate button visible; probe issues cert—accreditation violation.
- Arrange: Seed course with quiz_required; user completes lessons, fails quiz.
- Act: Click download certificate.
- Assert: Probe certificate 403; UI error; enrollment probe certificate_eligible=false.
TestChimp workflow: Track enrollment_state by course_id; evolve waitlist and prerequisite paths per prod enrollment share.
Same Arrange/Act/Assert pattern as expired-coupon checkout.
Connect scenarios to your QA workflow
Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).
Related scenarios
- Video/audio players — lesson completion
- SaaS onboarding — learner signup
- Subscriptions billing — paid courses
- Form validation — enrollment forms
External references
Frequently asked questions
How do I test waitlist promotion?
Seed full course with user on waitlist, free seat via admin/cancel route, poll enrollment probe until state=enrolled—assert queue order if product guarantees FIFO.
How do prerequisites work in E2E?
Seed course DAG; attempt enroll without completing required course; probe blockReason. Happy path: complete A via progress probe then enroll B.
How is video progress verified?
Use stub media and seek-to-end pattern; poll progress probe watchedSeconds and completed=true—do not trust UI percent alone.
How do certificates gate on quizzes?
Complete lessons, fail quiz probe, attempt certificate—expect 403. Pass quiz probe then certificate 200.
How do paid courses differ?
Probe entitlement before content URLs; attempt enroll without payment—expect blocked; after test payment seed, content probe 200.
Which courses need TrueCoverage priority?
Compare enrollment_state by course_id prod vs test—evolve waitlist and prerequisite specs for top enrollment courses.
Can ExploreChimp help complex cohort flows?
Yes for multi-week cohort UX; convert to SmartTests with course seeds and // @Scenario: for accreditation checklists.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.