test(e2e): setup Playwright testing infrastructure and comprehensive storefront & admin suites

This commit is contained in:
parsa aghaei 2026-08-25 12:56:46 +03:30
parent 7b89e141c5
commit 270e491d08
11 changed files with 643 additions and 4 deletions

6
.gitignore vendored
View File

@ -11,4 +11,8 @@ coverage/
node_modules
.next
*.tsbuildinfo
test ci trigger
test-results/
playwright-report/
playwright/.auth/
blob-report/

80
package-lock.json generated
View File

@ -8,7 +8,9 @@
"name": "caninoiran-workspace",
"version": "1.0.0",
"devDependencies": {
"concurrently": "^8.2.2"
"@playwright/test": "^1.62.1",
"concurrently": "^8.2.2",
"dotenv": "^17.4.2"
}
},
"node_modules/@babel/runtime": {
@ -21,6 +23,22 @@
"node": ">=6.9.0"
}
},
"node_modules/@playwright/test": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz",
"integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.62.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@ -157,6 +175,19 @@
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dotenv": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@ -174,6 +205,21 @@
"node": ">=6"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -211,6 +257,38 @@
"dev": true,
"license": "MIT"
},
"node_modules/playwright": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
"integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.62.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
"integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",

View File

@ -12,9 +12,14 @@
"build:frontend": "cd frontend/application && npm run build",
"build:admin": "cd frontend/admin-panel && npm run build",
"build": "npm run build:backend && npm run build:frontend && npm run build:admin",
"start": "concurrently -c \"blue,magenta,green\" -n \"backend,store,admin\" \"cd backend && npm run start:prod\" \"cd frontend/application && npm run start\" \"cd frontend/admin-panel && npm run preview\""
"start": "concurrently -c \"blue,magenta,green\" -n \"backend,store,admin\" \"cd backend && npm run start:prod\" \"cd frontend/application && npm run start\" \"cd frontend/admin-panel && npm run preview\"",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report"
},
"devDependencies": {
"concurrently": "^8.2.2"
"@playwright/test": "^1.62.1",
"concurrently": "^8.2.2",
"dotenv": "^17.4.2"
}
}

106
playwright.config.ts Normal file
View File

@ -0,0 +1,106 @@
import { defineConfig, devices } from '@playwright/test';
import * as dotenv from 'dotenv';
import * as path from 'path';
// Read from default .env.test file if present
dotenv.config({ path: path.resolve(__dirname, '.env.test') });
const STOREFRONT_URL = process.env.STOREFRONT_URL || 'http://localhost:4005';
const ADMIN_URL = process.env.ADMIN_URL || 'http://localhost:5050';
const BACKEND_URL = process.env.API_URL || 'http://localhost:4001/api';
/**
* Playwright E2E configuration for Canina Monorepo.
* Covers Next.js Storefront (port 4005) and Vite Admin Panel (port 5050).
*/
export default defineConfig({
testDir: './tests',
timeout: 60000,
expect: {
timeout: 10000,
},
fullyParallel: false,
workers: 2,
retries: 0,
reporter: [
['html', { open: 'never' }],
['list']
],
use: {
baseURL: STOREFRONT_URL,
actionTimeout: 15000,
navigationTimeout: 45000,
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'retain-on-failure',
locale: 'fa-IR',
timezoneId: 'Asia/Tehran',
},
projects: [
// 1. Setup project for Admin Authentication State
{
name: 'admin-setup',
testMatch: /fixtures\/admin-auth\.setup\.ts/,
use: {
baseURL: ADMIN_URL,
},
},
// 2. Storefront on Desktop Chromium
{
name: 'chromium-desktop',
testMatch: /storefront\/.*\.spec\.ts/,
use: {
...devices['Desktop Chrome'],
baseURL: STOREFRONT_URL,
viewport: { width: 1440, height: 900 },
},
},
// 3. Storefront on Mobile Safari (iPhone 14)
{
name: 'mobile-safari',
testMatch: /storefront\/.*\.spec\.ts/,
use: {
...devices['iPhone 14'],
baseURL: STOREFRONT_URL,
},
},
// 4. Admin Panel Flow on Desktop Chromium (Requires admin-setup)
{
name: 'admin-chromium',
testMatch: /admin\/.*\.spec\.ts/,
dependencies: ['admin-setup'],
use: {
...devices['Desktop Chrome'],
baseURL: ADMIN_URL,
viewport: { width: 1440, height: 900 },
storageState: './playwright/.auth/admin.json',
},
},
],
// Auto-start webServers if not already running on local ports
webServer: [
{
command: 'npm run dev:backend',
url: 'http://127.0.0.1:4001/api/docs',
reuseExistingServer: true,
timeout: 120000,
},
{
command: 'npm run dev:frontend',
url: 'http://localhost:4005',
reuseExistingServer: true,
timeout: 120000,
},
{
command: 'npm run dev:admin',
url: 'http://localhost:5050',
reuseExistingServer: true,
timeout: 120000,
},
],
});

View File

@ -0,0 +1,160 @@
import { test, expect } from '../../fixtures';
test.describe('سناریوی مدیریت محصول در پنل ادمین (Admin Products CRUD & SEO)', () => {
test('ورود به بخش محصولات -> باز کردن فرم ایجاد -> پر کردن فیلدها و سئو -> ذخیره', async ({ page }) => {
// Intercept and mock Admin Auth & Product API calls
await page.route('**/api/auth/admin-login*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: {
accessToken: 'mock_e2e_jwt_token',
user: { id: 'admin-1', email: 'admin@canina.ir', role: 'SUPER_ADMIN' },
},
}),
});
});
await page.route('**/api/auth/refresh*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: {
accessToken: 'mock_e2e_jwt_token',
user: { id: 'admin-1', email: 'admin@canina.ir', role: 'SUPER_ADMIN' },
},
}),
});
});
await page.route('**/api/users/profile*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: { id: 'admin-1', email: 'admin@canina.ir', role: 'SUPER_ADMIN' },
}),
});
});
await page.route('**/api/admin/products*', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: [
{
id: 'prod-1',
artNo: 'ART-001',
nameFa: 'مکمل کلسیم و مفاصل سگ',
category: { id: 'cat-1', nameFa: 'مفاصل' },
priceValue: 450000,
stockStatus: 'IN_STOCK',
},
],
meta: { total: 1, lastPage: 1 },
}),
});
} else if (route.request().method() === 'POST') {
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: { id: 'pw-mock-id', nameFa: 'محصول تستی پلی‌رایت', artNo: 'PW-TEST-001' },
message: 'محصول با موفقیت ایجاد شد',
}),
});
} else {
await route.continue();
}
});
await page.route('**/api/admin/categories*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: [{ id: 'cat-1', nameFa: 'مفاصل و استخوان' }],
}),
});
});
await page.route('**/api/orders/stats*', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { pending: 0, processing: 0 } }),
});
});
// 1. ورود به صفحه لاگین و انجام لاگین ادمین
await page.goto('/login', { waitUntil: 'domcontentloaded' });
const emailInput = page.locator('input[type="email"], input[placeholder*="ایمیل"]').first();
const passwordInput = page.locator('input[type="password"], input[placeholder*="رمز"]').first();
if (await emailInput.isVisible()) {
await emailInput.fill('admin@canina.ir');
await passwordInput.fill('Admin@123456');
const submitBtn = page.getByRole('button', { name: /ورود به پنل|ورود/i }).first();
await submitBtn.click();
await page.waitForTimeout(500);
}
// 2. رفتن به صفحه مدیریت محصولات
await page.goto('/products', { waitUntil: 'domcontentloaded' });
// 2. بررسی لود شدن ساختار صفحه مدیریت محصولات یا دکمه ایجاد محصول
const addProductBtn = page.getByRole('button', { name: /محصول جدید/i }).or(
page.locator('button').filter({ hasText: /محصول جدید/i })
).first();
await expect(addProductBtn).toBeVisible({ timeout: 15000 });
// 3. کلیک روی دکمه افزودن محصول جدید
await addProductBtn.click();
// 4. بررسی باز شدن مودال فرم ایجاد محصول
const modalTitle = page.locator('h2, h3, div').filter({ hasText: /محصول جدید|افزودن محصول|اطلاعات پایه/i }).first();
await expect(modalTitle).toBeVisible({ timeout: 10000 });
// 5. پر کردن اطلاعات پایه محصول
const nameFaInput = page.locator('input[placeholder*="نام فارسی"], input[name*="nameFa"]').or(
page.locator('input[type="text"]').first()
).first();
if (await nameFaInput.isVisible()) {
await nameFaInput.fill('محصول تستی پلی‌رایت برای بررسی سئو');
}
// 6. سوئیچ به تب یا بخش تنظیمات سئو (SEO)
const seoTab = page.locator('button, div').filter({ hasText: /سئو|SEO/i }).first();
if (await seoTab.isVisible()) {
await seoTab.click();
// پر کردن فیلدهای سئو
const metaTitleInput = page.locator('input[placeholder*="عنوان سئو"], input[name*="metaTitle"]').first();
const metaDescInput = page.locator('textarea[placeholder*="توضیحات سئو"], textarea[name*="metaDescription"]').first();
if (await metaTitleInput.isVisible()) {
await metaTitleInput.fill('خرید آنلاین محصول تستی با بهترین قیمت | کنینا ایران');
}
if (await metaDescInput.isVisible()) {
await metaDescInput.fill('توضیحات متای تستی برای ارزیابی رندرینگ پیش‌نمایش SERP گوگل در پنل مدیریت پروژه کنینا.');
}
}
// 7. کلیک روی دکمه ذخیره / ثبت
const saveBtn = page.getByRole('button', { name: /ذخیره|ثبت نهایی|ایجاد محصول/i }).or(
page.locator('button').filter({ hasText: /ذخیره|ثبت/i })
).first();
if (await saveBtn.isVisible()) {
await saveBtn.click();
}
});
});

View File

@ -0,0 +1,68 @@
import { test, expect } from '../../fixtures';
test.describe('سناریوی همکاری تجاری B2B (B2B Portal Flow)', () => {
test('ورود به /b2b -> بررسی تیتر H1 و متن‌های ارزش پیشنهادی -> ارسال فرم با اعتبارسنجی', async ({ page }) => {
// 1. ورود به /b2b
await page.goto('/b2b');
await expect(page).toHaveURL(/\/b2b/);
// 2. بررسی وجود تیتر H1
const heading = page.locator('h1');
await expect(heading).toBeVisible();
await expect(heading).toContainText(/تأمین عمده|همکاری تجاری|کنینا/i);
// بررسی متن‌های ارزش پیشنهادی (ارسال فاکتور رسمی، تخفیف، گواهی اصالت)
const valueProps = page.locator('section, div').filter({ hasText: /تخفیف|کلینیک|دامپزشکی|گواهی اصالت/i });
await expect(valueProps.first()).toBeVisible();
// 3. ارسال فرم با داده‌های خالی و بررسی نمایش خطاهای اعتبارسنجی
const submitButton = page.getByRole('button', { name: /ارسال درخواست|ثبت درخواست|درخواست همکاری/i }).first();
await expect(submitButton).toBeVisible();
// ارسال بدون وارد کردن فیلدها
await submitButton.click();
// بررسی پیام خطا یا اعتبارسنجی (toast یا متن ارور)
const toastError = page.locator('[data-sonner-toast], .toast, [role="alert"]').or(
page.getByText(/لطفاً نام مجموعه و شماره تماس را وارد کنید|الزامی است/i)
).first();
await expect(toastError).toBeVisible({ timeout: 5000 });
// 4. ارسال فرم با داده‌های معتبر (Mock کردن درخواست API برای جلوگیری از دیتای هرز)
await page.route('**/api/wholesale/apply', async (route) => {
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ success: true, message: 'درخواست همکاری با موفقیت ثبت شد' }),
});
});
// پر کردن فیلدهای فرم
const businessNameInput = page.locator('input[placeholder*="نام کلینیک"], input[placeholder*="مجموعه"]').first();
const contactNameInput = page.locator('input[placeholder*="نام و نام خانوادگی"], input[placeholder*="رابط"]').first();
const phoneInput = page.locator('input[placeholder*="۰۹"], input[placeholder*="تماس"]').first();
const cityInput = page.locator('input[placeholder*="شهر"]').first();
if (await businessNameInput.isVisible()) {
await businessNameInput.fill('پت شاپ آزمایشی پایتخت');
}
if (await contactNameInput.isVisible()) {
await contactNameInput.fill('دکتر حسینی');
}
if (await phoneInput.isVisible()) {
await phoneInput.fill('09121112233');
}
if (await cityInput.isVisible()) {
await cityInput.fill('تهران');
}
// ارسال مجدد با داده‌های پر شده
await submitButton.click();
// 5. بررسی دریافت پیام موفقیت‌آمیز
const successFeedback = page.locator('[data-sonner-toast], [role="alert"]').or(
page.getByText(/با موفقیت ثبت شد|درخواست شما ثبت شد/i)
).first();
await expect(successFeedback).toBeVisible({ timeout: 7000 });
});
});

View File

@ -0,0 +1,45 @@
import { test, expect } from '../../fixtures';
test.describe('سناریوی وبلاگ و دانشنامه (Blog & Wiki Flow)', () => {
test('ورود به /blog -> باز کردن مقاله -> بررسی اجزا -> رفتن به /wiki/[slug] و تست لینک‌ها', async ({ page }) => {
// 1. ورود به روت /blog
await page.goto('/blog');
await expect(page).toHaveURL(/\/blog/);
const blogHeading = page.locator('h1, h2').filter({ hasText: /مقالات|مجله|وبلاگ|سلامت/i }).first();
await expect(blogHeading).toBeVisible({ timeout: 10000 });
// 2. کلیک روی اولین مقاله در لیست
const firstArticleLink = page.locator('a[href^="/blog/"]').filter({ hasNotText: 'وبلاگ' }).first();
if (await firstArticleLink.isVisible()) {
await firstArticleLink.click();
// 3. بررسی لود صفحه جزئیات مقاله
await expect(page).toHaveURL(/\/blog\/.+/);
const articleHeading = page.locator('h1').first();
await expect(articleHeading).toBeVisible();
// بررسی وجود بخش‌های محتوایی یا سرفصل‌ها
const contentSection = page.locator('article, .prose, main').first();
await expect(contentSection).toBeVisible();
}
// 4. رفتن به دانشنامه /wiki
// ابتدا روت wiki را باز می‌کنیم
await page.goto('/wiki/glucosamine');
// اگر اصطلاح پیدا نشود یا نام دیگری داشته باشد، چک می‌کنیم صفحه خطای 500 نداده باشد
const wikiHeading = page.locator('h1, h2').first();
await expect(wikiHeading).toBeVisible();
// 5. بررسی وجود لینک مکمل‌ها یا محصولات مرتبط با ماده
const relatedProducts = page.locator('a[href^="/shop/"]').or(
page.locator('text=مکمل‌های حاوی').or(page.locator('text=محصولات مرتبط'))
);
if (await relatedProducts.count() > 0) {
await expect(relatedProducts.first()).toBeVisible();
}
});
});

View File

@ -0,0 +1,51 @@
import { test, expect } from '../../fixtures';
test.describe('سناریوی خرید و سبد خرید (Storefront Checkout Flow)', () => {
test('ورود به صفحه اصلی -> رفتن به فروشگاه -> انتخاب محصول -> افزودن به سبد خرید -> تسویه حساب', async ({ page }) => {
// 1. ورود به صفحه اصلی
await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 45000 });
await expect(page).toHaveTitle(/کنینا|Canina/i);
// 2. رفتن به صفحه /shop
await page.goto('/shop', { waitUntil: 'domcontentloaded', timeout: 45000 });
await expect(page).toHaveURL(/\/shop/);
// بررسی وجود ساختار کاتالوگ فروشگاه و کارت‌های محصول
const productCard = page.locator('a[href^="/shop/"]').first();
await expect(productCard).toBeVisible({ timeout: 20000 });
// 3. اعمال فیلتر دسته‌بندی یا دکمه‌های فیلتر
const categoryButton = page.locator('button, div').filter({ hasText: /مفاصل|ایمنی|ویتامین|همه|کاتالوگ/i }).first();
if (await categoryButton.isVisible()) {
await categoryButton.click();
}
// 4. افزودن محصول به سبد خرید از کارت آرشیو یا صفحه محصول
const buyButtonOnCard = page.locator('button').filter({ hasText: /خرید/i }).first();
if (await buyButtonOnCard.isVisible()) {
await buyButtonOnCard.click();
} else {
const href = await productCard.getAttribute('href');
if (href) {
await page.goto(href, { waitUntil: 'domcontentloaded' });
} else {
await productCard.click();
}
// افزودن به سبد در صفحه جزئیات محصول
const addToCartBtn = page.getByRole('button', { name: /افزودن به سبد خرید/i }).or(
page.locator('button').filter({ hasText: /افزودن به سبد/i })
).first();
await expect(addToCartBtn).toBeVisible({ timeout: 15000 });
await addToCartBtn.click();
}
// 5. رفتن به صفحه تسویه حساب (Checkout)
await page.goto('/checkout', { waitUntil: 'domcontentloaded', timeout: 30000 });
await expect(page).toHaveURL(/\/checkout/);
// 6. بررسی لود شدن ساختار تسویه‌حساب (پرداخت یا کاتالوگ یا اطلاعات خریدار)
const checkoutPageElement = page.locator('button, div, h2').filter({ hasText: /پرداخت|تسویه‌حساب|سفارش|سبد خرید|کاتالوگ/i }).first();
await expect(checkoutPageElement).toBeVisible({ timeout: 10000 });
});
});

View File

@ -0,0 +1,58 @@
import { test, expect } from '../../fixtures';
test.describe('سناریوی فرم تماس و استعلام (Contact Flow)', () => {
test('ورود به /contact -> اعتبارسنجی فیلدها -> پر کردن و ارسال پیام -> دریافت فیدبک', async ({ page }) => {
// 1. ورود به /contact
await page.goto('/contact');
await expect(page).toHaveURL(/\/contact/);
// 2. بررسی وجود تیتر صفحه تماس با ما
const contactHeading = page.locator('h1');
await expect(contactHeading).toBeVisible();
await expect(contactHeading).toContainText(/تماس با|کنینا/i);
// 3. پیدا کردن فیلدهای فرم تماس
const nameInput = page.locator('input[placeholder*="نام"]').first();
const phoneInput = page.locator('input[placeholder*="موبایل"], input[placeholder*="۰۹"]').first();
const messageInput = page.locator('textarea[placeholder*="پیام"]').first();
const submitBtn = page.getByRole('button', { name: /ارسال پیام|ثبت پیام|ارسال/i }).first();
await expect(submitBtn).toBeVisible();
// 4. تست اعتبارسنجی: ارسال فرم خالی
await submitBtn.click();
const validationError = page.locator('text=لطفاً نام، شماره موبایل و متن پیام را وارد کنید')
.or(page.locator('[data-sonner-toast]'))
.or(page.locator('[role="alert"]'))
.or(page.locator('text=معتبر نیست'))
.first();
await expect(validationError).toBeVisible({ timeout: 5000 });
// 5. Mock کردن پاسخ API ثبت پیام برای اطمینان از عدم ثبت دیتای هرز در دیتابیس اصلی
await page.route('**/api/contact', async (route) => {
await route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ success: true, message: 'پیام شما با موفقیت ثبت گردید.' }),
});
});
// 6. پر کردن فرم با دیتای معتبر
await page.locator('#contact-name').fill('کاربر تستی');
await page.locator('#contact-phone').fill('09123456789');
if (await page.locator('#contact-subject').count() > 0) {
await page.locator('#contact-subject').fill('استعلام تستی محصول');
}
await page.locator('#contact-message').fill('این یک پیام آزمایشی برای تست خودکار Playwright است.');
// 7. ارسال فرم
await submitBtn.click();
// 8. بررسی نمایش پیام موفقیت‌آمیز
const successAlert = page.locator('text=پیام شما با موفقیت ثبت شد')
.or(page.locator('text=موفقیت'))
.or(page.locator('[data-sonner-toast]'))
.first();
await expect(successAlert).toBeVisible({ timeout: 7000 });
});
});

56
tests/fixtures/admin-auth.setup.ts vendored Normal file
View File

@ -0,0 +1,56 @@
import { test as setup, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
const authFile = path.resolve(__dirname, '../../playwright/.auth/admin.json');
setup('authenticate as admin', async ({ page }) => {
const adminEmail = process.env.ADMIN_TEST_EMAIL || 'admin@canina.ir';
const adminPassword = process.env.ADMIN_TEST_PASSWORD || 'Admin@123456';
// Ensure directory exists
const authDir = path.dirname(authFile);
if (!fs.existsSync(authDir)) {
fs.mkdirSync(authDir, { recursive: true });
}
// Go to admin login page
await page.goto('/login');
// Fill in email and password
const emailInput = page.locator('input[type="email"], input[placeholder*="ایمیل"]');
const passwordInput = page.locator('input[type="password"], input[placeholder*="رمز"]');
if (await emailInput.count() > 0 && await passwordInput.count() > 0) {
await emailInput.first().fill(adminEmail);
await passwordInput.first().fill(adminPassword);
// Submit form
const submitBtn = page.getByRole('button', { name: /ورود به پنل|ورود/i });
if (await submitBtn.count() > 0) {
await submitBtn.first().click();
} else {
await page.keyboard.press('Enter');
}
// Wait for navigation or localStorage auth token setting
await page.waitForTimeout(1000);
}
// Ensure localStorage contains valid admin auth store
await page.evaluate(() => {
const authData = {
state: {
accessToken: 'mock_e2e_admin_jwt_token',
adminUser: { id: 'admin-1', email: 'admin@canina.ir', role: 'SUPER_ADMIN' },
isAuthenticated: true,
},
version: 0,
};
localStorage.setItem('canina_admin_auth', JSON.stringify(authData));
localStorage.setItem('token', 'mock_e2e_admin_jwt_token');
});
// Save authentication storage state (cookies, localStorage)
await page.context().storageState({ path: authFile });
});

8
tests/fixtures/index.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { test as base, expect } from '@playwright/test';
// Declare custom fixtures if needed
export const test = base.extend({
// Custom helpers can be attached here
});
export { expect };