feat: добавил тесты с использованием Playwright
Frontend CI / build-and-check (push) Failing after 19s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 20s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 3s

This commit is contained in:
2026-05-28 19:17:11 +03:00
parent cb80b35ba6
commit 88146f22b6
10 changed files with 419 additions and 2 deletions
+9
View File
@@ -0,0 +1,9 @@
import { test, expect } from '@playwright/test'
import { mockApi } from './support/mockApi'
test('redirects unauthenticated user to login', async ({ page }) => {
await mockApi(page, { authenticated: false })
await page.goto('/catalog')
await expect(page).toHaveURL(/\/login/)
await expect(page.getByText('Войти через ЮФУ')).toBeVisible()
})
+23
View File
@@ -0,0 +1,23 @@
import { expect, test } from '@playwright/test'
import { mockApi } from './support/mockApi'
test.beforeEach(async ({ page }) => {
await mockApi(page, { authenticated: true })
})
test('renders catalog items from mock api', async ({ page }) => {
await page.goto('/catalog')
await expect(page.getByRole('heading', { name: 'Каталог открытых лекций' })).toBeVisible()
await expect(page.getByText('Введение в ML')).toBeVisible()
await expect(page.getByText('Квантовые вычисления')).toBeVisible()
})
test('register button works for available lecture', async ({ page }) => {
await page.goto('/catalog')
const firstRegisterButton = page.getByRole('button', { name: 'Записаться' }).first()
await firstRegisterButton.click()
await expect(page.getByText('Вы записаны на лекцию.')).toBeVisible()
})
+74
View File
@@ -0,0 +1,74 @@
import { Page } from '@playwright/test'
import { mockAuthResponse, mockCurrentUser, mockLectures, mockUserStats } from '../../mocks/fixtures'
export async function mockApi(page: Page, options?: { authenticated?: boolean }) {
const authenticated = options?.authenticated ?? false
await page.route('**/api/v1/auth/refresh', async (route) => {
if (!authenticated) {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ message: 'Unauthorized' }),
})
return
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockAuthResponse),
})
})
await page.route('**/api/v1/auth/me', async (route) => {
if (!authenticated) {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ message: 'Unauthorized' }),
})
return
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockCurrentUser),
})
})
await page.route('**/api/v1/users/me/stats', async (route) => {
if (!authenticated) {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ message: 'Unauthorized' }),
})
return
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockUserStats),
})
})
await page.route('**/api/v1/lectures/*/enroll', async (route) => {
await route.fulfill({ status: 204 })
})
await page.route(/\/api\/v1\/lectures(\?.*)?$/, async (route) => {
if (route.request().method() !== 'GET') {
await route.continue()
return
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: mockLectures }),
})
})
}
+80
View File
@@ -0,0 +1,80 @@
export const mockAuthResponse = {
accessToken: 'fake-token',
expiresAt: '2099-01-01T00:00:00.000Z',
user: {
id: 1,
email: 'student@example.com',
displayName: 'Test User',
roles: ['Student'],
},
}
export const mockCurrentUser = {
id: 1,
email: 'student@example.com',
displayName: 'Test User',
roles: ['Student'],
avatarUrl: null,
xp: 120,
coins: 10,
level: 2,
createdAt: '2026-01-01T00:00:00.000Z',
}
export const mockUserStats = {
totalLectures: 0,
attendedLectures: 0,
totalReviews: 0,
xp: 120,
coins: 10,
level: 2,
achievementsCount: 0,
currentLevelXp: 20,
nextLevelXp: 100,
activeEnrollments: 0,
enrollmentSlotLimit: 3,
enrollmentSlotRules: [],
}
export const mockLectures = [
{
id: 1,
courseId: 101,
courseName: 'ML',
teacherId: 201,
teacherName: 'Иванов И.И.',
locationId: 301,
locationName: 'B-1 / 101',
title: 'Введение в ML',
description: 'База машинного обучения',
format: 'Offline',
startsAt: '2026-06-12T10:00:00.000Z',
endsAt: '2026-06-12T11:30:00.000Z',
isOpen: true,
maxEnrollments: 30,
enrollmentsCount: 10,
onlineUrl: null,
createdAt: '2026-01-01T00:00:00.000Z',
isEnrolled: false,
},
{
id: 2,
courseId: 102,
courseName: 'квантовые-вычисления',
teacherId: 202,
teacherName: 'Петров П.П.',
locationId: null,
locationName: null,
title: 'Квантовые вычисления',
description: 'Кубиты и алгоритмы',
format: 'Online',
startsAt: '2026-06-13T10:00:00.000Z',
endsAt: '2026-06-13T11:00:00.000Z',
isOpen: false,
maxEnrollments: 50,
enrollmentsCount: 50,
onlineUrl: 'https://example.com/meet',
createdAt: '2026-01-01T00:00:00.000Z',
isEnrolled: false,
},
]