test(e2e): add 14 new web E2E test files for critical user flows
Cover auth (login, register, OAuth callbacks), search with filters, listing detail, dashboard, analytics, create listing form, admin dashboard/users/moderation/KYC, navigation routing, and responsive design. Total 91 test cases using Playwright with API route mocking. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
117
e2e/web/dashboard.spec.ts
Normal file
117
e2e/web/dashboard.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const mockMarketReport = {
|
||||
districts: [
|
||||
{ district: 'Quan 1', propertyType: 'APARTMENT', avgPriceM2: 85000000, medianPriceM2: 80000000, totalListings: 150, daysOnMarket: 45, yoyChange: 5.2 },
|
||||
{ district: 'Quan 7', propertyType: 'HOUSE', avgPriceM2: 65000000, medianPriceM2: 60000000, totalListings: 200, daysOnMarket: 60, yoyChange: -2.1 },
|
||||
],
|
||||
};
|
||||
|
||||
const mockHeatmap = {
|
||||
dataPoints: [
|
||||
{ district: 'Quan 1', avgPriceM2: 85000000, totalListings: 150, lat: 10.7769, lng: 106.7009 },
|
||||
{ district: 'Quan 7', avgPriceM2: 65000000, totalListings: 200, lat: 10.7385, lng: 106.7218 },
|
||||
],
|
||||
};
|
||||
|
||||
const mockListings = {
|
||||
data: [
|
||||
{
|
||||
id: 'l1', transactionType: 'SALE', priceVND: '5000000000', pricePerM2: 66666667,
|
||||
rentPriceMonthly: null, commissionPct: null, status: 'ACTIVE', viewCount: 120,
|
||||
saveCount: 15, inquiryCount: 8, publishedAt: '2026-01-15T00:00:00Z',
|
||||
property: {
|
||||
id: 'p1', propertyType: 'APARTMENT', title: 'Căn hộ test', description: 'Desc',
|
||||
address: '123 Test', ward: 'W1', district: 'Quận 1', city: 'Hồ Chí Minh',
|
||||
latitude: 10.77, longitude: 106.70, areaM2: 75, bedrooms: 2, bathrooms: 2,
|
||||
floors: 1, direction: 'SOUTH', yearBuilt: null, legalStatus: null,
|
||||
projectName: null, amenities: [], media: [],
|
||||
},
|
||||
seller: { id: 's1', fullName: 'Test Seller', phone: '0912345678' },
|
||||
agent: null,
|
||||
},
|
||||
],
|
||||
total: 1, page: 1, limit: 6, totalPages: 1,
|
||||
};
|
||||
|
||||
test.describe('Dashboard Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Mock all API calls
|
||||
await page.route('**/analytics/market-report**', (route) =>
|
||||
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockMarketReport) }),
|
||||
);
|
||||
await page.route('**/analytics/heatmap**', (route) =>
|
||||
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockHeatmap) }),
|
||||
);
|
||||
await page.route('**/listings**', (route) =>
|
||||
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockListings) }),
|
||||
);
|
||||
});
|
||||
|
||||
test('renders dashboard with title and post button', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Bang dieu khien' })).toBeVisible();
|
||||
await expect(page.getByText('Tong quan thi truong va tin dang cua ban')).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: /Dang tin moi/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays stat cards', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByText('Tin dang cua toi')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Luot xem')).toBeVisible();
|
||||
await expect(page.getByText('Lien he')).toBeVisible();
|
||||
await expect(page.getByText('Gia TB thi truong')).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows market summary card', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByText('Tin dang cua toi')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Tong tin dang')).toBeVisible();
|
||||
await expect(page.getByText('Gia TB/m2')).toBeVisible();
|
||||
await expect(page.getByText('Ngay TB de ban')).toBeVisible();
|
||||
await expect(page.getByText('So quan')).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows recent listings section', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByText('Tin dang gan day')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Căn hộ test')).toBeVisible();
|
||||
});
|
||||
|
||||
test('navigates to create listing page', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await expect(page.getByRole('heading', { name: 'Bang dieu khien' })).toBeVisible();
|
||||
|
||||
await page.getByRole('link', { name: /Dang tin moi/i }).click();
|
||||
await expect(page).toHaveURL(/\/listings\/new/);
|
||||
});
|
||||
|
||||
test('navigates to analytics page', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await expect(page.getByText('Xem phan tich chi tiet')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.getByText('Xem phan tich chi tiet').click();
|
||||
await expect(page).toHaveURL(/\/analytics/);
|
||||
});
|
||||
|
||||
test('handles API failures gracefully', async ({ page }) => {
|
||||
await page.route('**/analytics/market-report**', (route) =>
|
||||
route.fulfill({ status: 500, body: 'Error' }),
|
||||
);
|
||||
await page.route('**/analytics/heatmap**', (route) =>
|
||||
route.fulfill({ status: 500, body: 'Error' }),
|
||||
);
|
||||
await page.route('**/listings**', (route) =>
|
||||
route.fulfill({ status: 500, body: 'Error' }),
|
||||
);
|
||||
|
||||
await page.goto('/dashboard');
|
||||
|
||||
// Page should still render (with fallback states)
|
||||
await expect(page.getByRole('heading', { name: 'Bang dieu khien' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user