- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Playwright
- Prettier
- Block Rebound
E2E testing uses Playwright. Multi-environment, multi-tenant aware, and integrated with Playwright MCP for browser-driven AI flows.
Quick start
pnpm exec playwright install # One-time browser install
pnpm test:e2e # All e2e tests, local dev
TEST_ENV=production pnpm test:e2e --project=production-chromium # Against productionCommands
| Command | Use |
|---|---|
pnpm test:e2e | All tests, default Chromium project |
pnpm test:e2e:ui | Interactive UI runner |
pnpm test:e2e:debug | Debug with the Playwright inspector |
pnpm test:e2e:report | View the last HTML report |
pnpm test:e2e --project=chromium | Chromium only |
pnpm test:e2e --project=firefox | Firefox only |
pnpm test:e2e --headed | Show the browser window |
pnpm test:e2e tests/login.spec.ts | Single file |
Test credentials
All accounts use password 1234. See accounts for full details.
| Role | Notes | |
|---|---|---|
dev@balqalam.com | DEVELOPER | Platform admin, no schoolId |
user@balqalam.com | USER | Fresh user — onboarding tests |
admin@balqalam.com | ADMIN | Demo school |
teacher@balqalam.com | TEACHER | Demo school |
student@balqalam.com | STUDENT | Demo school |
parent@balqalam.com | GUARDIAN | Demo school |
accountant@balqalam.com | ACCOUNTANT | Demo school |
staff@balqalam.com | STAFF | Demo school |
Bulk: teacher1@ … teacher99@ (100), student1@ … student999@ (1,000), parent1@ … parent1999@ (2,000).
If user@balqalam.com mutates during a run, reset:
pnpm db:reset-test-user # or: pnpm db:seed:single usersMulti-tenant URLs
| Environment | Main domain | School subdomain |
|---|---|---|
| Local | http://localhost:3000/en/... | http://demo.localhost:3000/en/... |
| Production | https://ed.databayt.org/en/... | https://demo.databayt.org/en/... |
| Preview | https://branch.vercel.app | https://tenant---branch.vercel.app |
Scenarios
| Scenario | Credential | Domain | Expected |
|---|---|---|---|
| SaaS dashboard | dev@balqalam.com | localhost:3000 | Access /dashboard, /tenants |
| School dashboard | admin@balqalam.com | demo.localhost:3000 | Access school platform |
| Onboarding | user@balqalam.com | localhost:3000 | Get Started → wizard |
| RBAC | teacher@balqalam.com | demo.localhost:3000 | Blocked from /admin, /settings |
| Tenant isolation | admin@balqalam.com | other.localhost:3000 | Access denied (wrong school) |
| Cross-subdomain SSO | Any logged-in user | Main → subdomain | Session persists |
Example test
import { expect, test } from "@playwright/test"
test("admin can access school dashboard on subdomain", async ({ page }) => {
await page.goto("http://demo.localhost:3000/en/login")
await page.fill('[name="email"]', "admin@balqalam.com")
await page.fill('[name="password"]', "1234")
await page.click('[type="submit"]')
await page.waitForURL(/\/dashboard$/)
await expect(page.getByRole("heading", { name: /dashboard/i })).toBeVisible()
})
test("teacher cannot access /admin", async ({ page }) => {
await loginAs(page, "teacher@balqalam.com")
await page.goto("/en/admin")
await expect(page).not.toHaveURL(/\/admin$/)
})For shared auth state, save storageState once and reuse via test.use({ storageState }).
Configuration
playwright.config.ts levers:
TEST_ENV—local(default) orproduction. Switches base URL and timeout.- Per-project base URL:
local→http://localhost:3000,production→https://ed.databayt.org. webServerbootspnpm devfor local runs; skipped for production.- Headless GPU acceleration enabled by default — ~30% faster than non-GPU headless.
Browser modes
Default project is chromium; firefox available for cross-engine spot checks. --headed watches the run.
GPU-accelerated headless uses Chromium with --enable-gpu and --use-gl=angle. Verify:
import { chromium } from "@playwright/test"
const b = await chromium.launch({ args: ["--enable-gpu", "--use-gl=angle"] })
const ctx = await b.newContext()
const p = await ctx.newPage()
await p.goto("chrome://gpu")
console.log(await p.textContent("body"))Playwright MCP
AI-assisted browser bridge. Configure in your MCP-aware client:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-playwright"]
}
}
}Recommended local setup uses headless Chromium with GPU acceleration and a small persistent profile so cookies survive across calls. See .claude/settings.json and playwright.config.ts for the pinned config.
Debugging
pnpm test:e2e:debug— Playwright Inspectornpx playwright show-trace trace.zip— replay recorded trace- Failed tests automatically capture screenshot + video to
test-results/
CI
GitHub Actions runs pnpm test:e2e on PRs against the local server. Production smoke runs pnpm test:e2e:prod:smoke against ed.databayt.org after deploys.