Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
126 lines
5.0 KiB
TypeScript
126 lines
5.0 KiB
TypeScript
import AxeBuilder from '@axe-core/playwright'
|
|
import { expect, test, type Page } from '@playwright/test'
|
|
|
|
import { authenticateAs, authenticateConsumer } from '@/e2e/fixtures/session'
|
|
|
|
const consumerProfile = {
|
|
id: 'user-id',
|
|
mobile: '09123456789',
|
|
firstName: 'علی',
|
|
lastName: 'رضایی',
|
|
gender: 'male',
|
|
cityId: null,
|
|
bio: 'کاربر تست',
|
|
avatarUrl: null,
|
|
status: 'active',
|
|
identityStatus: 'verified',
|
|
role: 'user',
|
|
}
|
|
|
|
const consumerViewports = [
|
|
{ name: 'mobile', width: 360, height: 800 },
|
|
{ name: 'tablet', width: 768, height: 1024 },
|
|
{ name: 'desktop', width: 1440, height: 900 },
|
|
] as const
|
|
|
|
const expectNoSeriousViolations = async (page: Page) => {
|
|
await page.waitForLoadState('domcontentloaded')
|
|
await page.locator('body').waitFor({ state: 'visible' })
|
|
|
|
let violations
|
|
|
|
try {
|
|
;({ violations } = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze())
|
|
} catch (error) {
|
|
if (!(error instanceof Error) || !error.message.includes('Execution context was destroyed')) throw error
|
|
await page.waitForLoadState('domcontentloaded')
|
|
;({ violations } = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze())
|
|
}
|
|
|
|
const blocking = violations.filter(({ impact }) => impact === 'critical' || impact === 'serious')
|
|
|
|
expect(blocking, blocking.map(({ id, help }) => `${id}: ${help}`).join('\n')).toEqual([])
|
|
}
|
|
|
|
test('auth route has no serious automated accessibility violations', async ({ page }) => {
|
|
await page.goto('/auth')
|
|
await expectNoSeriousViolations(page)
|
|
})
|
|
|
|
test('public discovery route has no serious automated accessibility violations', async ({ page }) => {
|
|
await page.route('**/api/v1/**', (route) => route.fulfill({ json: { success: true, data: { items: [], meta: {} } } }))
|
|
await page.goto('/')
|
|
await expectNoSeriousViolations(page)
|
|
})
|
|
|
|
test('admin shell has no serious automated accessibility violations', async ({ context, page }) => {
|
|
await authenticateAs(context, page, 'admin')
|
|
await page.route('**/api/v1/**', (route) => route.fulfill({ json: { success: true, data: {} } }))
|
|
await page.route('**/api/v1/admin/reports/overview', (route) =>
|
|
route.fulfill({
|
|
json: {
|
|
success: true,
|
|
data: {
|
|
users: { total: 0, active: 0, pending: 0, suspended: 0 },
|
|
events: {
|
|
total: 0,
|
|
active: 0,
|
|
totalHeld: 0,
|
|
byStatus: { draft: 0, pending_review: 0, published: 0, full: 0, cancelled: 0, completed: 0 },
|
|
},
|
|
bookings: { total: 0, byStatus: { pending_payment: 0, confirmed: 0, cancelled: 0, expired: 0, refunded: 0, no_show: 0 } },
|
|
identityVerifications: { none: 0, pending: 0, verified: 0, rejected: 0 },
|
|
financial: { totalCommissionCollected: '0', totalWalletBalance: '0' },
|
|
},
|
|
},
|
|
})
|
|
)
|
|
await page.goto('/dashboard')
|
|
await expect(page.getByText('کل کاربران').first()).toBeVisible()
|
|
await expect(page.locator('#mainContent')).toHaveCSS('opacity', '1')
|
|
await expectNoSeriousViolations(page)
|
|
})
|
|
|
|
for (const viewport of consumerViewports) {
|
|
test(`consumer shell has no serious violations or horizontal overflow at ${viewport.name} width`, async ({ context, page }) => {
|
|
await page.setViewportSize({ width: viewport.width, height: viewport.height })
|
|
await authenticateConsumer(context, page)
|
|
await page.route('**/api/v1/users/me', (route) => route.fulfill({ json: { success: true, data: consumerProfile } }))
|
|
await page.route('**/api/v1/users/me/wallet', (route) =>
|
|
route.fulfill({ json: { success: true, data: { id: 'wallet-1', balance: 250000 } } })
|
|
)
|
|
await page.route('**/api/v1/organizers/me/following?**', (route) =>
|
|
route.fulfill({
|
|
json: { success: true, data: { items: [], pagination: { page: 1, pageSize: 1, totalItemsCount: 0, totalPagesCount: 0 } } },
|
|
})
|
|
)
|
|
await page.route('**/api/v1/conversations/me/unread-count', (route) =>
|
|
route.fulfill({ json: { success: true, data: { totalUnread: 0 } } })
|
|
)
|
|
await page.route('**/api/v1/notifications/me/unread-count', (route) =>
|
|
route.fulfill({ json: { success: true, data: { totalUnread: 0 } } })
|
|
)
|
|
|
|
await page.goto('/profile')
|
|
await expect(page.getByRole('heading', { level: 1, name: 'پروفایل من' })).toBeVisible()
|
|
await expect(page.getByRole('main')).toBeVisible()
|
|
const bottomNavigation = page.getByRole('navigation', { name: 'ناوبری پایین' })
|
|
|
|
await expect(bottomNavigation).toBeVisible()
|
|
|
|
const navigationItemSizes = await bottomNavigation.getByRole('link').evaluateAll((links) =>
|
|
links.map((link) => {
|
|
const bounds = link.getBoundingClientRect()
|
|
|
|
return { height: bounds.height, width: bounds.width }
|
|
})
|
|
)
|
|
|
|
const hasHorizontalOverflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth)
|
|
|
|
expect(hasHorizontalOverflow).toBe(false)
|
|
expect(navigationItemSizes.every(({ height, width }) => height >= 44 && width >= 44)).toBe(true)
|
|
await expectNoSeriousViolations(page)
|
|
})
|
|
}
|