Lewati ke isi

QA Suite Structure - Scola LMS

Version: 1.1
Date: January 26, 2026
Status: SSOT - Canonical Structure

⚡ Quick Reference: quick-reference.md (1-page cheat sheet for daily work)

Purpose

Define the canonical test suite organization, naming conventions, tagging strategy, and execution policies for Scola LMS E2E tests.


Directory Structure

tests/e2e/
├── flow/            # ⚡ P0: Critical end-to-end flows (2-5 min) - NEW!
├── smoke/           # P0: Fast, stable, critical path (< 2 min total)
├── critical/        # P0-P1: Business-critical workflows (< 10 min total)
├── rbac/            # P0: Security & access control (< 5 min total)
├── regression/      # P2: Feature regression (< 30 min total)
├── performance/     # P2: Performance benchmarks (< 15 min total)
├── fixtures/        # Shared fixtures (auth, data, helpers)
└── support/         # Shared utilities (selectors, API clients)

0. Flow Tests (tests/e2e/flow/) ⚡ NEW in Phase 2.1

Purpose

Critical end-to-end user journeys that validate complete workflows across multiple roles. These are the HIGHEST priority tests - if they fail, deployment is blocked.

Characteristics

  • Duration: 2-5 minutes total
  • Scope: Complete user journeys spanning multiple roles
  • Retries: 0 (must be 100% stable - failures indicate real issues)
  • Parallelization: Yes (tests use API-based auth, fully isolated)
  • Priority: P0 (blocks deployment)

Naming Convention

<module>_<role1>_<role2>_<role3>_flow.spec.ts

Examples:
- lms_student_teacher_student_flow.spec.ts
- lms_teacher_publish_idempotent_ui.spec.ts
- enrollment_admin_student_teacher_flow.spec.ts

Content Guidelines

Each flow test should: - Cover complete user journey (e.g., student submits → teacher grades → student sees grade) - Use API-based authentication (session storageState, NOT UI login) - Be resilient to existing data (check API state, not UI badges) - Include version tracking for stateful operations - Validate idempotency where applicable

Example Flow:

test.describe('LMS P0 Flow: Student → Teacher → Student @p0', () => {
  test('student submits assignment → teacher grades → student sees published grade', async ({ studentPage, teacherPage }) => {
    // Student: Submit assignment
    await studentPage.goto('/assignment/1')
    await studentPage.getByTestId('submit-button').click()
    const version = await getLatestSubmissionVersion(studentPage, 1)
    expect(version).toBeGreaterThan(0)

    // Teacher: Grade submission
    await teacherPage.goto('/faculty/lms/gradebook/1')
    await teacherPage.getByTestId('grade-input').fill('85')
    await teacherPage.getByTestId('save-grade').click()

    // Teacher: Publish grades
    await teacherPage.getByTestId('publish-grades').click()
    await expect(teacherPage.getByText('Successfully published')).toBeVisible()

    // Student: Verify published grade visible
    await studentPage.goto('/grades')
    await expect(studentPage.getByText('85')).toBeVisible()
  })
})

Tags

test.describe('LMS P0 Flow Tests @p0 @flow @lms', () => {
  // tests here
})

Fast Path Execution

# Run only P0 flow tests (2-5 minutes)
npx playwright test tests/e2e/flow --grep "@p0"

# Expected output: ✅ 11 passed

Known Issues & Solutions

See testing-guidelines.md section "Known Gotchas & Battle-Tested Solutions" for: - Assignment type field naming hell - Submission version not incrementing - Publish grade average always zero - Idempotency key format mismatch - Submission modal not visible - File upload rejecting .txt fixtures - Session auth vs UI login flakiness


1. Smoke Tests (tests/e2e/smoke/)

Purpose

Fast sanity checks to verify core system is operational. Run on every commit.

Characteristics

  • Duration: < 2 minutes total
  • Scope: Minimal happy path per module
  • Retries: 0 (must be 100% stable)
  • Parallelization: Yes (tests are isolated)

Naming Convention

<module>_smoke.spec.ts

Examples:
- lms_smoke.spec.ts
- auth_smoke.spec.ts
- navigation_smoke.spec.ts

Content Guidelines

Each smoke test should: - Login → Navigate → Verify 1 critical element visible - NO data creation (use seeded baseline) - NO complex interactions (single page validation)

Example Flow:

test('LMS smoke: teacher sees course list', async ({ page }) => {
  await page.goto('/faculty/lms')
  await expect(page.getByTestId('lms-course-card')).toBeVisible()
})

Tags

test.describe('LMS Smoke Tests @smoke @lms', () => {
  // tests here
})

2. Critical Tests (tests/e2e/critical/)

Purpose

Business-critical workflows that MUST work for system to be functional. Run before PR merge.

Characteristics

  • Duration: < 10 minutes total
  • Scope: End-to-end user journeys
  • Retries: 1 (allow flaky network recovery)
  • Parallelization: Yes (per test file)

Naming Convention

<feature>_<workflow>_flow.spec.ts

Examples:
- grade_publish_flow.spec.ts
- assignment_submission_flow.spec.ts
- student_enrollment_flow.spec.ts

Content Guidelines

Each critical test should: - Cover complete user journey (multi-step) - Verify data persistence across pages - Include assertions on notifications/side effects

Example Flow:

test('End-to-end: teacher grades and publishes', async ({ page, request }) => {
  // Step 1: Teacher creates assignment
  // Step 2: Student submits work
  // Step 3: Teacher grades submission
  // Step 4: Teacher publishes grade
  // Step 5: Student sees grade in gradebook
  // Step 6: Student receives notification
})

Tags

test.describe('Grade Publish Critical Tests @critical @lms @grade', () => {
  // tests here
})

3. RBAC Tests (tests/e2e/rbac/)

Purpose

Security gates to verify access control and data isolation. Zero-tolerance failures.

Characteristics

  • Duration: < 5 minutes total
  • Scope: Negative tests (unauthorized access)
  • Retries: 0 (security issues are blockers)
  • Parallelization: Yes (independent checks)

Naming Convention

rbac_<module>_<scenario>.spec.ts

Examples:
- rbac_no_leak.spec.ts
- rbac_student_isolation.spec.ts
- rbac_faculty_boundaries.spec.ts

Content Guidelines

Each RBAC test should: - Use multiple user contexts (storageState) - Verify DENY scenarios (403, redirect, hidden UI) - Test both UI and API layers

Example Flow:

test('Student cannot access gradebook route', async ({ page }) => {
  // Login as student
  await page.goto('/faculty/gradebook')
  // Expect: redirect to /student/home OR 403 page
  await expect(page).toHaveURL('/student/home')
})

test('Student cannot call grade publish API', async ({ request }) => {
  const response = await request.post('/api/grade/publish', { data: {...} })
  expect(response.status()).toBe(403)
})

Tags

test.describe('RBAC No Leak Tests @rbac @security @p0', () => {
  // tests here
})

4. Regression Tests (tests/e2e/regression/)

Purpose

Feature stability to catch regressions in existing functionality. Run nightly.

Characteristics

  • Duration: < 30 minutes total
  • Scope: Edge cases, UI details, integrations
  • Retries: 2 (allow intermittent failures)
  • Parallelization: Yes (by feature area)

Naming Convention

<module>_<feature>_regression.spec.ts

Examples:
- lms_ui_regression.spec.ts
- lms_notifications_regression.spec.ts
- lms_calendar_regression.spec.ts

Content Guidelines

Each regression test should: - Test non-critical but important features - Cover UI details (badges, tooltips, formatting) - Verify integrations (notifications, calendar sync)

Example Flow:

test('Assignment shows active submission badge', async ({ page }) => {
  await page.goto('/student/lms/course/1')
  const badge = page.getByTestId('active-submission-badge')
  await expect(badge).toBeVisible()
  await expect(badge).toHaveText('Active')
})

Tags

test.describe('LMS UI Regression @regression @lms @ui', () => {
  // tests here
})

5. Performance Tests (tests/e2e/performance/)

Purpose

Performance benchmarks to catch degradation. Run nightly or pre-release.

Characteristics

  • Duration: < 15 minutes total
  • Scope: Load times, API response times, caching
  • Retries: 3 (network variance acceptable)
  • Parallelization: No (avoid resource contention)

Naming Convention

<module>_performance.spec.ts

Examples:
- lms_performance.spec.ts
- gradebook_performance.spec.ts
- upload_performance.spec.ts

Content Guidelines

Each performance test should: - Measure specific metrics (ms, MB, queries) - Use consistent test data (seeded baseline) - Assert against defined SLAs

Example Flow:

test('Gradebook loads in < 500ms (10 students)', async ({ page }) => {
  const startTime = Date.now()
  await page.goto('/faculty/gradebook/1')
  await page.waitForLoadState('networkidle')
  const loadTime = Date.now() - startTime
  expect(loadTime).toBeLessThan(500)
})

Tags

test.describe('LMS Performance Tests @performance @lms @nightly', () => {
  // tests here
})

Shared Resources

Fixtures (tests/e2e/fixtures/)

Auth Fixtures

auth.fixture.ts
- teacherAuth()  → storageState for teacher1
- studentAuth()  → storageState for student1
- parentAuth()   → storageState for parent1

Data Fixtures

data.fixture.ts
- getCourse()    → seeded course data
- getAssignment() → seeded assignment
- getSubmission() → seeded submission

Support Utilities (tests/e2e/support/)

Selectors

selectors.ts
- LMS_COURSE_CARD = '[data-testid="lms-course-card"]'
- GRADEBOOK_CELL = '[data-testid="gradebook-cell"]'

API Helpers

api.ts
- publishGrade(request, payload) → Promise<Response>
- createSubmission(request, file) → Promise<Response>

Tagging Strategy

Primary Tags

Tag Purpose Run Frequency
@smoke Fast sanity Every commit
@critical Business gates Pre-PR merge
@rbac Security gates Pre-PR merge
@regression Feature stability Nightly
@performance Perf benchmarks Nightly

Secondary Tags

Tag Purpose
@lms LMS module tests
@grade Grade-related tests
@submission Submission-related
@security Security-critical
@p0 Priority 0 (blocker)
@p1 Priority 1 (critical)
@p2 Priority 2 (important)
@nightly Run in nightly suite

Usage Example

test.describe('Grade Publish Flow @critical @lms @grade @p0', () => {
  test('Teacher publishes grade successfully @smoke', async ({ page }) => {
    // ...
  })

  test('Idempotency prevents duplicate grades @security', async ({ request }) => {
    // ...
  })
})

Retry Policy

Configuration (playwright.config.ts)

export default defineConfig({
  projects: [
    {
      name: 'smoke',
      testMatch: '**/smoke/**/*.spec.ts',
      retries: 0, // Smoke must be 100% stable
    },
    {
      name: 'critical',
      testMatch: '**/critical/**/*.spec.ts',
      retries: 1, // Allow 1 retry for network flakes
    },
    {
      name: 'rbac',
      testMatch: '**/rbac/**/*.spec.ts',
      retries: 0, // Security gates are strict
    },
    {
      name: 'regression',
      testMatch: '**/regression/**/*.spec.ts',
      retries: 2, // Allow 2 retries for UI flakes
    },
    {
      name: 'performance',
      testMatch: '**/performance/**/*.spec.ts',
      retries: 3, // Network variance acceptable
    },
  ],
})

Execution Examples

Run Smoke Tests Only

npx playwright test tests/e2e/smoke --project=chromium

Run Critical + RBAC (Pre-PR)

npx playwright test tests/e2e/critical tests/e2e/rbac --project=chromium

Run by Tag

npx playwright test --grep "@smoke"
npx playwright test --grep "@p0"
npx playwright test --grep "@lms.*@grade"

Run Nightly Suite

npx playwright test tests/e2e/regression tests/e2e/performance --project=chromium

Run Full Suite

make qa-all
# OR
npx playwright test

Naming Conventions Summary

Type Pattern Example
Smoke <module>_smoke.spec.ts lms_smoke.spec.ts
Critical <feature>_<workflow>_flow.spec.ts grade_publish_flow.spec.ts
RBAC rbac_<module>_<scenario>.spec.ts rbac_no_leak.spec.ts
Regression <module>_<feature>_regression.spec.ts lms_ui_regression.spec.ts
Performance <module>_performance.spec.ts lms_performance.spec.ts
Fixture <purpose>.fixture.ts auth.fixture.ts
Support <purpose>.ts selectors.ts, api.ts

CI Integration

GitHub Actions Example

name: E2E Tests

on: [pull_request]

jobs:
  smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: make qa-reset
      - run: make qa-seed
      - run: npx playwright test tests/e2e/smoke --project=chromium

  critical:
    runs-on: ubuntu-latest
    needs: smoke
    steps:
      - uses: actions/checkout@v3
      - run: make qa-reset
      - run: make qa-seed
      - run: npx playwright test tests/e2e/critical tests/e2e/rbac --project=chromium

Document Owner: QA Architect
Review Cycle: Monthly
Last Updated: January 25, 2026