import { expect, type Page, type Route } from '@playwright/test' export const CONSUMER_MOBILE = '09123456789' export const CONSUMER_MOBILE_PERSIAN = '۰۹۱۲۳۴۵۶۷۸۹' export const CONSUMER_OTP = '1234' export const DEFAULT_BIRTH_DATE_ISO = '1991-03-21' export const jsonOk = (data: unknown) => ({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, data }), }) export const installApiCatchAll = async (page: Page) => { await page.route('**/api/v1/**', (route) => route.fulfill( jsonOk({ items: [], meta: {}, response: { page: 1, pageSize: 20, totalItemsCount: 0, totalPages: 1 }, }) ) ) // Guest home client-refetches these; catch-all object shapes crash render (.map/.filter). await page.route('**/api/v1/discovery/cities**', (route) => route.fulfill(jsonOk([{ id: 1, provinceId: 1, name: 'تهران', slug: 'tehran' }])) ) await page.route('**/api/v1/discovery/categories**', (route) => route.fulfill(jsonOk([]))) await page.route('**/api/v1/discovery/home-feed**', (route) => route.fulfill(jsonOk({ popular: [], categoryPreviews: [], cityPreviews: [] })) ) } export const createConsumerAccessToken = (status: 'active' | 'pending' = 'active') => { const payload = Buffer.from( JSON.stringify({ sub: 'user-id', role: 'user', sessionId: 'session-id', status, exp: Math.floor(Date.now() / 1000) + 3600, }) ).toString('base64url') return `header.${payload}.signature` } export const mockRequestOtp = async (page: Page, purpose: 'login' | 'register', onRequest?: (body: { mobile?: string }) => void) => { await page.route('**/api/v1/auth/request-otp', async (route) => { const body = (route.request().postDataJSON() ?? {}) as { mobile?: string } onRequest?.(body) await route.fulfill(jsonOk({ purpose, expiresIn: 120, alreadySent: false })) }) } export const mockVerifyOtp = async ( page: Page, options: { status: 'active' | 'pending' onRequest?: (body: Record) => void accessToken?: string } ) => { const accessToken = options.accessToken ?? createConsumerAccessToken(options.status) const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() await page.route('**/api/v1/auth/verify-otp', async (route) => { const body = (route.request().postDataJSON() ?? {}) as Record options.onRequest?.(body) await route.fulfill( jsonOk({ accessToken, sessionId: 'session-id', expiresAt, status: options.status, }) ) }) return accessToken } export const mockCities = async (page: Page) => { await page.route('**/api/v1/geography/cities', (route) => route.fulfill(jsonOk([{ id: 1, provinceId: 1, name: 'تهران', slug: 'tehran' }])) ) } export const mockCompleteProfile = async (page: Page, onRequest?: (body: Record) => void) => { await page.route('**/api/v1/users/me/complete-profile', async (route) => { const body = (route.request().postDataJSON() ?? {}) as Record onRequest?.(body) await route.fulfill( jsonOk({ id: 'user-id', firstName: 'علی', lastName: 'رضایی', gender: 'male', status: 'active', cityId: 1, }) ) }) } /** AuthGate consumer mobile field has no label — only the placeholder. */ export const mobileField = (page: Page) => page.getByPlaceholder('مثال ۰۹۱۲۳۴۵۶۷۸۹') export const openAuthGateFromHome = async (page: Page) => { await page.goto('/?auth=1', { waitUntil: 'domcontentloaded' }) await expect(page.getByRole('heading', { name: 'سلام!' })).toBeVisible({ timeout: 15_000 }) await expect(mobileField(page)).toBeVisible() } export const submitMobile = async (page: Page, mobile = CONSUMER_MOBILE_PERSIAN) => { await mobileField(page).fill(mobile) await page.getByRole('button', { name: 'ادامه' }).click() } export const fillOtpCode = async (page: Page, code = CONSUMER_OTP) => { await page.locator('input').first().fill(code) } export const completeConsumerProfileForm = async (page: Page) => { await page.getByText('مرد', { exact: true }).click() await page.getByPlaceholder('نام', { exact: true }).fill(' علی ') await page.getByPlaceholder('نام خانوادگی', { exact: true }).fill(' رضایی ') await page.getByRole('button', { name: 'شهر' }).click() const cityPicker = page.getByRole('dialog').filter({ hasText: 'شهرتان را انتخاب کنید' }) await expect(cityPicker).toBeVisible() await cityPicker.getByRole('button', { name: 'تهران' }).click() await cityPicker.getByRole('button', { name: 'انتخاب' }).click() await page.getByRole('button', { name: 'تاریخ تولد' }).click() const birthPicker = page.getByRole('dialog').filter({ hasText: 'تاریخ تولدتان را انتخاب کنید' }) await expect(birthPicker).toBeVisible() await birthPicker.getByRole('button', { name: 'انتخاب' }).click() await page.getByRole('button', { name: 'تکمیل ثبت‌نام' }).click() } export const expectActiveSession = async (page: Page) => { const storedUser = await page.evaluate(() => JSON.parse(localStorage.getItem('user') ?? 'null')) expect(storedUser).toMatchObject({ userId: 'user-id', role: 'user', sessionId: 'session-id', status: 'active', }) expect(storedUser.refreshToken).toBeUndefined() } export const fulfillApiError = async (route: Route, code: string, message: string, status = 403) => { await route.fulfill({ status, contentType: 'application/json', body: JSON.stringify({ success: false, code, message }), }) }