108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
/**
|
|
* Smoke suite — Web
|
|
*
|
|
* Runs after every deploy to verify critical frontend pages load and
|
|
* render key UI elements. Tagged with @smoke for targeted execution:
|
|
*
|
|
* npx playwright test --project=web --grep @smoke
|
|
*
|
|
* Tests avoid user interactions that require a live API/auth session so
|
|
* they remain fast and resilient in staging environments.
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
// ── Homepage ──────────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke homepage loads', async ({ page }) => {
|
|
await page.goto('/');
|
|
await expect(page).toHaveTitle(/.+/);
|
|
await expect(page.locator('main')).toBeVisible({ timeout: 10_000 });
|
|
await expect(page.getByText(/GGI HCM|Top biến động giá|Khu vực xu hướng/i).first()).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
});
|
|
|
|
// ── Auth pages ────────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke login page renders form', async ({ page }) => {
|
|
await page.goto('/login');
|
|
await expect(page.getByRole('heading', { name: /đăng nhập/i })).toBeVisible();
|
|
await expect(page.getByLabel(/số điện thoại/i)).toBeVisible();
|
|
await expect(page.getByLabel(/mật khẩu/i)).toBeVisible();
|
|
await expect(page.getByRole('button', { name: /đăng nhập/i })).toBeVisible();
|
|
});
|
|
|
|
test('@smoke register page renders form', async ({ page }) => {
|
|
await page.goto('/register');
|
|
await expect(page.getByRole('heading', { name: /đăng ký/i })).toBeVisible();
|
|
await expect(page.getByLabel(/số điện thoại/i)).toBeVisible();
|
|
});
|
|
|
|
// ── Listings ──────────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke listings page loads without JS errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('pageerror', (err) => errors.push(err.message));
|
|
|
|
await page.goto('/listings');
|
|
// Allow time for async data loading
|
|
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {
|
|
// networkidle may not be reached if polling; continue
|
|
});
|
|
|
|
// Page must not show an unhandled error boundary
|
|
const errorHeading = page.getByRole('heading', { name: /500|something went wrong|lỗi/i });
|
|
await expect(errorHeading).not.toBeVisible();
|
|
|
|
// Filter JS errors that are not related to missing env vars in test
|
|
const fatalErrors = errors.filter(
|
|
(e) => !e.includes('NEXT_PUBLIC') && !e.includes('mapbox'),
|
|
);
|
|
expect(fatalErrors).toHaveLength(0);
|
|
});
|
|
|
|
// ── Search ────────────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke search page is accessible', async ({ page }) => {
|
|
await page.goto('/search?q=apartment');
|
|
await expect(page).not.toHaveTitle(/404|not found/i);
|
|
// Be lenient — search service may be unavailable in staging
|
|
await page.waitForTimeout(3000);
|
|
// Just confirm no crash
|
|
const errorBoundary = page.getByRole('heading', { name: /500|server error/i });
|
|
await expect(errorBoundary).not.toBeVisible();
|
|
});
|
|
|
|
// ── Listing Detail ────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke listing detail page handles missing id gracefully', async ({ page }) => {
|
|
const res = await page.goto('/listings/nonexistent-smoke-test-id');
|
|
// Should render 404 page, not crash with 500
|
|
const status = res?.status();
|
|
if (status && status >= 500) {
|
|
throw new Error(`Listing detail returned ${status} for unknown ID (expected 404)`);
|
|
}
|
|
});
|
|
|
|
// ── Static / infra ────────────────────────────────────────────────────────────
|
|
|
|
test('@smoke no console errors on login page', async ({ page }) => {
|
|
const consoleErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
|
});
|
|
|
|
await page.goto('/login');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// Filter noise from third-party scripts / mapbox in test env
|
|
const meaningful = consoleErrors.filter(
|
|
(e) =>
|
|
!e.includes('mapbox') &&
|
|
!e.includes('NEXT_PUBLIC') &&
|
|
!e.includes('Failed to load resource') &&
|
|
!e.includes('net::ERR'),
|
|
);
|
|
expect(meaningful).toHaveLength(0);
|
|
});
|