Bỏ qua để đến nội dung

Vendor self-registration — Playwright E2E reference

Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.

This document is the technical reference for the dedicated Playwright E2E suite covering the public vendor self-registration flow (/register → POST /api/v1/auth/vendor/register). It complements the end-user guide at Đăng ký tài khoản.

Audience: engineers maintaining the vendor-web registration form, the backend VendorAuthService.register() contract, or the test infrastructure (Playwright + NestJS backend test mode).

There are two adjacent Playwright specs that already touch /register:

SpecPurposeWhere
docs-register-screenshots.spec.tsCaptures screenshots for apps/docs (gated by PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1).apps/vendor-web/e2e/vendor/getting-started/docs-register-screenshots.spec.ts:1
register.spec.ts (this doc)Asserts business behaviour of the registration flow: happy path, validation, slug derivation, slug conflict, guards.apps/vendor-web/e2e/vendor/getting-started/register.spec.ts:1

Splitting the two keeps the docs-screenshots spec fast and idempotent (it skips every test outside PLAYWRIGHT_UPDATE_DOCS_SCREENSHOTS=1), and keeps the behavioural suite small, deterministic, and safe to run on every PR.

The suite drives the frontend through /register and verifies the observable contract of POST /api/v1/auth/vendor/register:

  • Happy path: account info → store info → welcome screen → real auth via the owner login screen with the freshly minted credentials.
  • Step 1 validation: Continue stays disabled until the form is valid (full name required, email format, phone ≥ 5 chars).
  • Step 2 validation: store name required, slug rule (^[a-z][a-z0-9-]{3,40}$), terms checkbox required.
  • Slug auto-suggest: typing a store name fills the slug field, but only until the user touches the slug themselves.
  • Backend slug_taken 4xx is surfaced via the inline error banner and the form remains usable so the user can retry.
  • Already-authenticated owners are redirected from /register to the vendor dashboard root.

It does not cover:

  • Backend unit tests for VendorAuthService.register() — see apps/backend/src/modules/vendor/vendor-auth.service.spec.ts.
  • Backend e2e (Jest + supertest) — see apps/backend/test/vendor-auth.e2e-spec.ts.
  • Mobile / Expo app registration — covered separately.
  • Visual regressions — see docs-register-screenshots.spec.ts.
┌──────────────┐ GET /register ┌──────────────────┐
│ Browser │ ─────────────────────────────▶ │ Vite dev server │
│ (Playwright)│ ◀───────────────────────────── │ apps/vendor-web │
└──────┬───────┘ HTML + JS bundle └──────────────────┘
│
│ GET /api/v1/auth/vendor/register/modules
│ POST /api/v1/auth/vendor/register { f_name, l_name,
│ phone, email,
│ password, module_id? }
▼
┌──────────────────────────────────────────────┐
│ NestJS backend (apps/backend) │
│ VendorAuthController → VendorAuthService │
│ └─ register(dto) │
│ ├─ resolve moduleId (Retail default) │
│ ├─ AdminVendorsService.create(...) │
│ └─ login(dto) → owner JWT │
└──────────────────────────────────────────────┘

The frontend renders the form via apps/vendor-web/src/app/register/page.tsx. Step 1 collects f_name / l_name / phone / email; Step 2 collects store_name / store_slug / password / accept_terms. The current build no longer asks the user for module_id — the backend defaults to the active Retail module when the field is omitted.

Field rename: module_id was previously surfaced as the “Business module” dropdown and is now derived server-side. See Backend API catalog and vendor-register.dto.ts (@ApiPropertyOptional).

VerbPathWhere
GET/api/v1/healthapps/vendor-web/e2e/global-setup.ts:18 (gated)
GET/registerVite dev server (webServer in playwright.config.cjs)
GET/api/v1/auth/vendor/register/modulesOptional — only used by the docs-screenshots spec to wait for modules to load. The current register.spec.ts does not block on it because the dropdown is hidden when the backend returns a single module or none.
POST/api/v1/auth/vendor/registerapps/backend/src/modules/vendor/vendor-auth.controller.ts:104
POST/api/v1/auth/vendor/loginapps/backend/src/modules/vendor/vendor-auth.controller.ts (login handler)
GET/vendor-loginVite dev server
GET/dashboardVite dev server

POST /api/v1/auth/vendor/register accepts:

{
"f_name": "Jane",
"l_name": "Doe",
"phone": "+15550001111",
"email": "happy-abc@e2e.lionpos.local",
"password": "HappyPwd!23",
"store_name": "Happy Store",
"store_slug": "happy-abc"
}

module_id is optional. When omitted, the backend resolves it from AdminModulesService.listPublicForVendorRegistration() by matching /retail/i against the module name. If no Retail module is present, the backend still creates the vendor with no module_id set (AdminVendorsService.create already treats module_id as optional).

StatusBody code (canonical)Test
400validation_error (Zod/class-validator)step 1 validation (UI-level)
409email_taken (Laravel)duplicate email (HTTP-level via request.post)
400slug_takenserver-side slug_taken
200{ token, refresh_token, zone_wise_topic, ... }happy path, auto-suggest
>=500anyexplicitly rejected by every happy-path test
apps/vendor-web/e2e/vendor/getting-started/register.spec.ts
├── beforeEach setEnglishLocale + persistent i18nextLng
├── describe "happy path"
│ ├── signs up an owner … welcome screen + real login
│ └── omits the slug field … auto-derived slug from store name
├── describe "step 1 validation"
│ ├── Continue is disabled … full name / email / phone
│ └── Duplicate email surfaces … HTTP 4xx, no advance to step 2
├── describe "step 2 validation"
│ ├── Store name is required
│ ├── Invalid slug shows hint … Zod refine → inline error
│ └── Terms checkbox must … disabled until accepted
├── describe "slug auto-suggest …"
│ ├── Auto-suggest populates … then stops once user edits
│ └── Server-side slug_taken … banner visible, retry works
└── describe "guards"
└── Authenticated owner … /register → /dashboard redirect

Local (assumes NestJS is already running on :3000)

Section titled “Local (assumes NestJS is already running on :3000)”
Terminal window
# From the repo root or apps/vendor-web
npm run test:e2e -- vendor/getting-started/register.spec.ts
Terminal window
cd apps/backend
npm run db:test:prepare
npm run start:test &
cd ../vendor-web
npm run test:e2e -- vendor/getting-started/register.spec.ts

playwright.config.cjs starts Vite on a dedicated port (PLAYWRIGHT_VENDOR_WEB_PORT, default 3100) so reuseExistingServer does not collide with a developer session. global-setup.ts waits up to 120 s for GET /api/v1/health to respond.

VariableDefaultPurpose
PLAYWRIGHT_API_BASE_URLhttp://127.0.0.1:3100Backend base URL.
PLAYWRIGHT_VENDOR_WEB_PORT3100Vite port.
PLAYWRIGHT_SKIP_BACKEND_HEALTHunsetSkip the global-setup health probe.
PLAYWRIGHT_VENDOR_OWNER_EMAIL / _PASSWORDowner@test.nipos.local / 123123Reserved — the register spec generates fresh emails per run.
DB_ENABLEDfalseBackend flag — set true to use a real MySQL test database.

uniqueEmail(prefix) mints prefix-<base36 timestamp>-<base36 random>@e2e.lionpos.local, and uniqueSlug(prefix) does the same for the slug. Re-running the suite must not depend on previously seeded rows — and on DB_ENABLED=true, re-running against an existing seed would surface stale-data failures instead of contract failures.

The slug pattern enforces the same Zod rule as the frontend (^[a-z][a-z0-9-]{3,40}$, must start with a letter).

The register/page.tsx component reads user-visible copy from apps/vendor-web/src/en.ts / vi.ts. The label English text is the one thing Playwright can match via getByLabel(/full name/i). The beforeEach writes i18nextLng=en to localStorage before the page loads so the i18n bootstrap picks the English bundle.

If the suite is ever run against a deployment that does not ship the English bundle, the tests will fail at getByLabel resolution. Add a language negotiation step in setEnglishLocale if you need to support non-English test environments.

SymptomLikely causeFix
GET /api/v1/health times out in global-setupBackend not running, or DB_ENABLED=true and migrations not appliedSee playwright.config.cjs webServer.command and the db:test:prepare script in apps/backend/package.json.
Tests stuck on step 2 with Create account disabledacceptTerms checkbox toggled but the form thinks it is still falseLook at form2.formState.errors.acceptTerms in apps/vendor-web/src/app/register/page.tsx; assert the checkbox name matches the RHF key.
slug_taken banner missing on conflict testBackend returned a 5xx instead of 4xx — possible when AdminStoresService.create propagates an unexpected errorInspect apps/backend/src/modules/admin/admin-stores.service.ts createStoreForVendor (slug_taken branch).
Authenticated owner is redirected failsvendor-auth Zustand store name changedUpdate page.evaluate(() => window.localStorage.getItem("vendor-auth")) lookup.
Email already in use test intermittently passes/failsTest order non-deterministic; another test used the same emailAlready mitigated by uniqueEmail() per test; check the parallel/worker settings in playwright.config.cjs (fullyParallel: false, workers: 1).

When adding a new field to the registration form:

  1. Add the field to VendorRegisterDto (apps/backend/src/modules/vendor/dto/vendor-register.dto.ts).
  2. Update VendorAuthService.register() (apps/backend/src/modules/vendor/vendor-auth.service.ts:248) to derive or default it.
  3. Add a unit test in vendor-auth.service.spec.ts.
  4. Add a Playwright test in register.spec.ts that drives the form and asserts both the success path and at least one validation path.
  5. If the field changes a user-facing API contract, update apps/docs/src/content/docs/technical/backend-api-catalog.md.

When changing the slug_taken error shape, update the slug_taken test expectation — currently it only asserts the HTTP status range (4xx, not 5xx), which is intentionally lenient to survive server-side error copy changes.

  • Frontend form: apps/vendor-web/src/app/register/page.tsx
  • Backend handler: apps/backend/src/modules/vendor/vendor-auth.controller.ts
  • Backend service: apps/backend/src/modules/vendor/vendor-auth.service.ts
  • Backend DTO: apps/backend/src/modules/vendor/dto/vendor-register.dto.ts
  • Frontend types: apps/vendor-web/src/types/vendor-auth.types.ts
  • Frontend service: apps/vendor-web/src/services/vendor-auth.api.ts
  • Backend e2e (Jest): apps/backend/test/vendor-auth.e2e-spec.ts
  • Login form contract: apps/vendor-web/src/app/vendor-login/page.tsx
  • API catalog: Backend API Catalog
  • User guide: Đăng ký tài khoản