QA: unit tests for API logic, Playwright smoke tests, pre-push check, manual QA checklist
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# کپی کنید به e2e/.env (در git ثبت نمیشود) — اینجا رمز ننویسید
|
||||
E2E_USER=
|
||||
E2E_PASSWORD=
|
||||
# اگر کاربر عضو چند سازمان است، نام سازمان
|
||||
E2E_ORG=
|
||||
# پیشفرض: سرور لوکال (npm run dev:web که خودش به سرور اصلی وصل است)
|
||||
E2E_BASE_URL=http://127.0.0.1:5173
|
||||
# اختیاری: مشتری دارای حساب تهحساب برای تست اسناد
|
||||
E2E_PERSON_ID=
|
||||
# اختیاری: توکن یک فرم عمومی لید
|
||||
E2E_FORM_TOKEN=
|
||||
@@ -0,0 +1,36 @@
|
||||
import { test as setup, expect } from '@playwright/test';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
|
||||
const STATE = 'e2e/.auth/user.json';
|
||||
|
||||
setup('ورود به پنل', async ({ page }) => {
|
||||
const user = process.env.E2E_USER;
|
||||
const password = process.env.E2E_PASSWORD;
|
||||
if (!user || !password) {
|
||||
throw new Error('E2E_USER و E2E_PASSWORD را در e2e/.env تنظیم کنید (نمونه: e2e/.env.example).');
|
||||
}
|
||||
|
||||
await page.goto('/login');
|
||||
await page.locator('#identifier').fill(user);
|
||||
await page.locator('#password').fill(password);
|
||||
await page.getByRole('button', { name: 'ورود', exact: true }).click();
|
||||
|
||||
const orgCards = page.locator('.org-card');
|
||||
await expect(async () => {
|
||||
const onOrgPicker = await orgCards.first().isVisible();
|
||||
const leftLogin = !page.url().includes('/login');
|
||||
expect(onOrgPicker || leftLogin).toBe(true);
|
||||
}).toPass({ timeout: 30_000 });
|
||||
|
||||
if (await orgCards.first().isVisible()) {
|
||||
const orgName = process.env.E2E_ORG;
|
||||
const card = orgName ? orgCards.filter({ hasText: orgName }).first() : orgCards.first();
|
||||
await card.click();
|
||||
}
|
||||
|
||||
await expect(page).not.toHaveURL(/\/login/, { timeout: 30_000 });
|
||||
await expect(page.locator('main.content')).toBeVisible();
|
||||
|
||||
mkdirSync('e2e/.auth', { recursive: true });
|
||||
await page.context().storageState({ path: STATE });
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { test as base, expect, type Page } from '@playwright/test';
|
||||
|
||||
type Problems = { list: string[] };
|
||||
|
||||
/** هر خطای جاوااسکریپت یا پاسخ 5xx از API در طول تست، تست را رد میکند */
|
||||
export const test = base.extend<{ problems: Problems }>({
|
||||
problems: [
|
||||
async ({ page }, use) => {
|
||||
const problems: Problems = { list: [] };
|
||||
page.on('pageerror', (err) => problems.list.push(`JS error: ${err.message}`));
|
||||
page.on('response', (res) => {
|
||||
if (res.url().includes('/api/') && res.status() >= 500) {
|
||||
problems.list.push(`API ${res.status()}: ${res.request().method()} ${res.url()}`);
|
||||
}
|
||||
});
|
||||
await use(problems);
|
||||
expect(problems.list, 'خطا در صفحه').toEqual([]);
|
||||
},
|
||||
{ auto: true },
|
||||
],
|
||||
});
|
||||
|
||||
export { expect };
|
||||
|
||||
export async function waitForIdle(page: Page) {
|
||||
await page.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => undefined);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { test, expect, waitForIdle } from './fixtures';
|
||||
|
||||
/** فقط خواندنی: هیچ رکوردی ساخته، ویرایش یا حذف نمیشود؛ اجرا روی سرور اصلی بیخطر است */
|
||||
const PAGES: Array<[string, string]> = [
|
||||
['/', 'داشبورد'],
|
||||
['/contacts', 'مشتریان'],
|
||||
['/leads', 'لیدها'],
|
||||
['/pipeline', 'فروش'],
|
||||
['/activities', 'کارها'],
|
||||
['/activities/calendar', 'تقویم کارها'],
|
||||
['/routines', 'کارهای روتین'],
|
||||
['/conversations', 'گفتگو'],
|
||||
['/notes', 'یادداشتها'],
|
||||
['/tickets', 'پشتیبانی'],
|
||||
['/loyalty', 'باشگاه مشتریان'],
|
||||
['/reports', 'گزارشها'],
|
||||
['/invoices', 'فاکتورها'],
|
||||
['/quotes', 'پیشفاکتورها'],
|
||||
['/cheques', 'چکها'],
|
||||
['/projects', 'پروژهها'],
|
||||
['/profile', 'پروفایل'],
|
||||
['/settings/team', 'تنظیمات تیم'],
|
||||
['/settings/lead-forms', 'فرمهای لید'],
|
||||
['/settings/message-automations', 'اتوماسیون پیام'],
|
||||
['/settings/tahesab', 'تنظیمات تهحساب'],
|
||||
];
|
||||
|
||||
for (const [path, title] of PAGES) {
|
||||
test(`صفحهٔ ${title} بدون خطا باز میشود (${path})`, async ({ page, problems }) => {
|
||||
await page.goto(path);
|
||||
await expect(page).not.toHaveURL(/\/login/);
|
||||
await expect(page.locator('main.content')).toBeVisible();
|
||||
await waitForIdle(page);
|
||||
expect(problems.list).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
test('جستجوی سراسری باز میشود و نتیجه میدهد', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
const search = page.getByPlaceholder(/جستجو در مشتریان/);
|
||||
await expect(search).toBeVisible();
|
||||
await search.fill('a');
|
||||
await waitForIdle(page);
|
||||
});
|
||||
|
||||
test('داراییها و اسناد تهحساب مشتری', async ({ page }) => {
|
||||
const personId = process.env.E2E_PERSON_ID;
|
||||
test.skip(!personId, 'E2E_PERSON_ID تنظیم نشده');
|
||||
await page.goto(`/contacts/people/${personId}?tab=assets`);
|
||||
await expect(page.getByText('مانده حساب')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: 'جزئیات حساب', exact: true }).click();
|
||||
await expect(page.locator('.invoice, .asnad-grid .sanad-card').first().or(page.getByText('در این بازه سندی یافت نشد'))).toBeVisible({ timeout: 60_000 });
|
||||
const invoice = page.locator('.invoice').first();
|
||||
if (await invoice.isVisible()) {
|
||||
await expect(invoice.locator('.inv-head')).toContainText('سند شماره');
|
||||
await expect(invoice.locator('.inv-table tbody tr').first()).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('فرم عمومی لید بدون لاگین باز میشود', async ({ browser }) => {
|
||||
const token = process.env.E2E_FORM_TOKEN;
|
||||
test.skip(!token, 'E2E_FORM_TOKEN تنظیم نشده');
|
||||
const ctx = await browser.newContext({ storageState: undefined });
|
||||
const page = await ctx.newPage();
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', (e) => errors.push(e.message));
|
||||
await page.goto(`/f/${token}`);
|
||||
await expect(page.locator('form')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /ارسال|ثبت/ })).toBeVisible();
|
||||
expect(errors).toEqual([]);
|
||||
await ctx.close();
|
||||
});
|
||||
Reference in New Issue
Block a user