diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a52b619..66e4bb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,7 +149,7 @@ jobs: name: E2E Tests needs: ci runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 45 env: CI: true diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 50c2c75..cd48771 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -14,7 +14,7 @@ jobs: e2e: name: Playwright E2E runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 45 env: CI: true diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 9f6b3a9..52eea59 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -3,6 +3,14 @@ const createNextIntlPlugin = require('next-intl/plugin'); const withNextIntl = createNextIntlPlugin('./i18n/request.ts'); +function getPublicApiOrigin() { + try { + return process.env.NEXT_PUBLIC_API_URL ? new URL(process.env.NEXT_PUBLIC_API_URL).origin : ''; + } catch { + return ''; + } +} + /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, @@ -52,7 +60,7 @@ const nextConfig = { "style-src 'self' 'unsafe-inline' https://api.mapbox.com", "img-src 'self' data: blob: https://*.mapbox.com https://*.tiles.mapbox.com https:", "font-src 'self' data:", - `connect-src 'self' https://*.mapbox.com https://api.mapbox.com https://events.mapbox.com https://api.goodgo.vn${process.env.NODE_ENV !== 'production' ? ' http://localhost:3001 http://localhost:3011 http://localhost:3200 http://localhost:3201 http://localhost:9000 ws://localhost:3001 ws://localhost:3011 ws://localhost:3200 ws://localhost:3201' : ''}`, + `connect-src 'self' https://*.mapbox.com https://api.mapbox.com https://events.mapbox.com https://api.goodgo.vn${process.env.NODE_ENV !== 'production' ? ` ${getPublicApiOrigin()} http://localhost:3001 http://localhost:3011 http://localhost:3200 http://localhost:3201 http://localhost:9000 ws://localhost:3001 ws://localhost:3011 ws://localhost:3200 ws://localhost:3201` : ''}`, "worker-src 'self' blob:", "child-src 'self' blob:", "frame-ancestors 'none'", diff --git a/e2e/web/auth-register.spec.ts b/e2e/web/auth-register.spec.ts index 3980880..e4b1653 100644 --- a/e2e/web/auth-register.spec.ts +++ b/e2e/web/auth-register.spec.ts @@ -1,4 +1,20 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, type Route } from '@playwright/test'; + +async function fulfillJson(route: Route, status: number, body: unknown) { + const origin = route.request().headers()['origin'] ?? '*'; + + await route.fulfill({ + status, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Allow-Headers': 'content-type,x-csrf-token', + 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', + }, + body: JSON.stringify(body), + }); +} test.describe('Register Page', () => { test.beforeEach(async ({ page }) => { @@ -6,12 +22,12 @@ test.describe('Register Page', () => { }); test('renders registration form with all fields', async ({ page }) => { - await expect(page.getByRole('heading', { name: 'Tạo tài khoản' })).toBeVisible(); - await expect(page.getByText('Nhập thông tin để đăng ký tài khoản GoodGo')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Đăng ký' })).toBeVisible(); + await expect(page.getByText('Tạo tài khoản mới để bắt đầu sử dụng GoodGo')).toBeVisible(); await expect(page.getByLabel('Họ và tên')).toBeVisible(); await expect(page.getByLabel('Số điện thoại')).toBeVisible(); - await expect(page.getByLabel('Email (tùy chọn)')).toBeVisible(); + await expect(page.getByLabel('Email')).toBeVisible(); await expect(page.getByLabel('Mật khẩu', { exact: false }).first()).toBeVisible(); await expect(page.getByLabel('Xác nhận mật khẩu')).toBeVisible(); await expect(page.getByRole('button', { name: 'Đăng ký' })).toBeVisible(); @@ -73,13 +89,19 @@ test.describe('Register Page', () => { test('successful registration redirects to home', async ({ page }) => { await page.route('**/auth/register', (route) => - route.fulfill({ - status: 201, - contentType: 'application/json', - body: JSON.stringify({ - accessToken: 'fake-access-token', - refreshToken: 'fake-refresh-token', - }), + fulfillJson(route, 201, { message: 'Registered successfully' }), + ); + await page.route('**/auth/profile', (route) => + fulfillJson(route, 200, { + id: 'test-user-id', + email: null, + phone: '0912345678', + fullName: 'Test User', + avatarUrl: null, + role: 'USER', + kycStatus: 'NOT_SUBMITTED', + isActive: true, + createdAt: new Date().toISOString(), }), ); @@ -94,11 +116,7 @@ test.describe('Register Page', () => { test('displays server error on failed registration', async ({ page }) => { await page.route('**/auth/register', (route) => - route.fulfill({ - status: 409, - contentType: 'application/json', - body: JSON.stringify({ message: 'Số điện thoại đã được đăng ký' }), - }), + fulfillJson(route, 409, { message: 'Số điện thoại đã được đăng ký' }), ); await page.getByLabel('Họ và tên').fill('Test User'); diff --git a/playwright.config.ts b/playwright.config.ts index 7d65a49..c2b990b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,6 +13,12 @@ const API_PORT = process.env.API_PORT ?? '3001'; const WEB_PORT = process.env.WEB_PORT ?? '3000'; const SERVER_STARTUP_TIMEOUT_MS = process.env.CI ? 300_000 : 60_000; +const VIETNAMESE_BROWSER_CONTEXT = { + locale: 'vi-VN', + extraHTTPHeaders: { + 'Accept-Language': 'vi-VN,vi;q=0.9,en;q=0.8', + }, +}; /** * Playwright E2E configuration for Goodgo Platform. @@ -57,6 +63,7 @@ export default defineConfig({ testDir: './e2e/web', use: { ...devices['Desktop Chrome'], + ...VIETNAMESE_BROWSER_CONTEXT, baseURL: process.env.WEB_BASE_URL ?? `http://localhost:${WEB_PORT}`, }, }, @@ -75,6 +82,7 @@ export default defineConfig({ grep: /@smoke/, use: { ...devices['Desktop Chrome'], + ...VIETNAMESE_BROWSER_CONTEXT, baseURL: process.env.WEB_BASE_URL ?? `http://localhost:${WEB_PORT}`, }, }, @@ -84,6 +92,7 @@ export default defineConfig({ testDir: './e2e/a11y', use: { ...devices['Desktop Chrome'], + ...VIETNAMESE_BROWSER_CONTEXT, baseURL: process.env.WEB_BASE_URL ?? `http://localhost:${WEB_PORT}`, }, },