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:
171
e2e/web/search.spec.ts
Normal file
171
e2e/web/search.spec.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const mockListings = {
|
||||
data: [
|
||||
{
|
||||
id: 'listing-1',
|
||||
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: 'prop-1',
|
||||
propertyType: 'APARTMENT',
|
||||
title: 'Căn hộ cao cấp Quận 1',
|
||||
description: 'Căn hộ đẹp view sông',
|
||||
address: '123 Nguyễn Huệ',
|
||||
ward: 'Bến Nghé',
|
||||
district: 'Quận 1',
|
||||
city: 'Hồ Chí Minh',
|
||||
latitude: 10.7769,
|
||||
longitude: 106.7009,
|
||||
areaM2: 75,
|
||||
bedrooms: 2,
|
||||
bathrooms: 2,
|
||||
floors: 1,
|
||||
direction: 'SOUTH',
|
||||
yearBuilt: null,
|
||||
legalStatus: null,
|
||||
projectName: null,
|
||||
amenities: [],
|
||||
media: [],
|
||||
},
|
||||
seller: { id: 's1', fullName: 'Nguyen Van A', phone: '0912345678' },
|
||||
agent: null,
|
||||
},
|
||||
{
|
||||
id: 'listing-2',
|
||||
transactionType: 'RENT',
|
||||
priceVND: '15000000',
|
||||
pricePerM2: null,
|
||||
rentPriceMonthly: '15000000',
|
||||
commissionPct: null,
|
||||
status: 'ACTIVE',
|
||||
viewCount: 50,
|
||||
saveCount: 5,
|
||||
inquiryCount: 3,
|
||||
publishedAt: '2026-02-01T00:00:00Z',
|
||||
property: {
|
||||
id: 'prop-2',
|
||||
propertyType: 'HOUSE',
|
||||
title: 'Nhà phố Quận 7',
|
||||
description: 'Nhà phố đẹp khu an ninh',
|
||||
address: '456 Nguyễn Thị Thập',
|
||||
ward: 'Tân Phú',
|
||||
district: 'Quận 7',
|
||||
city: 'Hồ Chí Minh',
|
||||
latitude: 10.7385,
|
||||
longitude: 106.7218,
|
||||
areaM2: 120,
|
||||
bedrooms: 4,
|
||||
bathrooms: 3,
|
||||
floors: 3,
|
||||
direction: 'EAST',
|
||||
yearBuilt: 2020,
|
||||
legalStatus: null,
|
||||
projectName: null,
|
||||
amenities: [],
|
||||
media: [],
|
||||
},
|
||||
seller: { id: 's2', fullName: 'Tran Thi B', phone: '0987654321' },
|
||||
agent: null,
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
limit: 12,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
test.describe('Search Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Mock the listings API to return consistent data
|
||||
await page.route('**/listings**', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockListings),
|
||||
});
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
});
|
||||
|
||||
test('renders search page with title and filters', async ({ page }) => {
|
||||
await page.goto('/search');
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Tìm kiếm bất động sản' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('Tìm bất động sản phù hợp với nhu cầu của bạn'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays view mode toggle buttons', async ({ page }) => {
|
||||
await page.goto('/search');
|
||||
|
||||
await expect(page.getByRole('button', { name: /Danh sách/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /Bản đồ/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays listing results', async ({ page }) => {
|
||||
await page.goto('/search');
|
||||
|
||||
await expect(page.getByText('Căn hộ cao cấp Quận 1')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Nhà phố Quận 7')).toBeVisible();
|
||||
});
|
||||
|
||||
test('switches to map view mode', async ({ page }) => {
|
||||
await page.goto('/search');
|
||||
await page.getByRole('button', { name: /Bản đồ/i }).click();
|
||||
|
||||
// Map view should be active — list results should not be visible
|
||||
await expect(page.getByRole('button', { name: /Bản đồ/i })).toHaveAttribute(
|
||||
'data-state',
|
||||
/.*/,
|
||||
);
|
||||
});
|
||||
|
||||
test('syncs filters to URL query parameters', async ({ page }) => {
|
||||
await page.goto('/search?transactionType=SALE');
|
||||
|
||||
// The URL should contain the filter
|
||||
await expect(page).toHaveURL(/transactionType=SALE/);
|
||||
});
|
||||
|
||||
test('shows error state on API failure', async ({ page }) => {
|
||||
await page.route('**/listings**', (route) =>
|
||||
route.fulfill({ status: 500, body: 'Internal Server Error' }),
|
||||
);
|
||||
|
||||
await page.goto('/search');
|
||||
|
||||
// Should show some error indication
|
||||
await page.waitForTimeout(2000);
|
||||
// The page should still be navigable (not crash)
|
||||
await expect(page.getByRole('heading', { name: 'Tìm kiếm bất động sản' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows loading spinner initially', async ({ page }) => {
|
||||
await page.route('**/listings**', async (route) => {
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockListings),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/search');
|
||||
|
||||
// Should show loading indication (spinner or skeleton)
|
||||
const spinner = page.locator('.animate-spin, .animate-pulse');
|
||||
await expect(spinner.first()).toBeVisible({ timeout: 3000 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user