Добавлен UI тест для позитивной проверки восстановления пароля, также добавлена фильтрация писем для почтового сервиса

This commit is contained in:
Vlad Smykov
2026-01-30 15:25:19 +03:00
parent 257a53c97e
commit 5c40d43990
12 changed files with 127 additions and 27 deletions

18
package-lock.json generated
View File

@@ -13,6 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.57.0", "@playwright/test": "^1.57.0",
"@types/node": "^25.1.0",
"allure-playwright": "^3.4.5", "allure-playwright": "^3.4.5",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
@@ -33,6 +34,16 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@types/node": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz",
"integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/allure-js-commons": { "node_modules/allure-js-commons": {
"version": "3.4.5", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.4.5.tgz", "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.4.5.tgz",
@@ -442,6 +453,13 @@
"engines": { "engines": {
"node": ">=14.17" "node": ">=14.17"
} }
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
} }
} }
} }

View File

@@ -18,6 +18,7 @@
"type": "commonjs", "type": "commonjs",
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.57.0", "@playwright/test": "^1.57.0",
"@types/node": "^25.1.0",
"allure-playwright": "^3.4.5", "allure-playwright": "^3.4.5",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },

1
temp/user.json Normal file
View File

@@ -0,0 +1 @@
{"email":"testuser1769775617223@virgilian.com","password":"!Test123456","mailToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE3Njk3NzU2MTgsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJhZGRyZXNzIjoidGVzdHVzZXIxNzY5Nzc1NjE3MjIzQHZpcmdpbGlhbi5jb20iLCJpZCI6IjY5N2NhMjAxM2ExZjhmNWE1MDA3MTEyNSIsIm1lcmN1cmUiOnsic3Vic2NyaWJlIjpbIi9hY2NvdW50cy82OTdjYTIwMTNhMWY4ZjVhNTAwNzExMjUiXX19.t8gDCA6xtLYnAex_f3OYj5aYJgls4g8IQs2K37gc6HiqdaKkZveB1ckB31_hhYJag23Ekdi6RW9CNm0RCLsDDQ"}

View File

@@ -1,4 +1,5 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
import fs from 'fs';
import axios from 'axios'; import axios from 'axios';
import { import {
createTempEmail, createTempEmail,
@@ -41,6 +42,13 @@ test('API: регистрация абитуриента + подтвержде
const registerRes = await axios.post(`${BASE_URL}/auth/register`, registerPayload); const registerRes = await axios.post(`${BASE_URL}/auth/register`, registerPayload);
expect(registerRes.status).toBe(201); expect(registerRes.status).toBe(201);
fs.writeFileSync('temp/user.json', JSON.stringify({
email,
password: '!Test123456',
mailToken
}));
const userId = registerRes.data.id; const userId = registerRes.data.id;
console.log('🆔 ID зарегистрированного пользователя:', userId); console.log('🆔 ID зарегистрированного пользователя:', userId);
@@ -50,7 +58,7 @@ test('API: регистрация абитуриента + подтвержде
} }
console.log('📬 Ожидание письма с кодом подтверждения...'); console.log('📬 Ожидание письма с кодом подтверждения...');
const code = await waitForConfirmationCode(mailToken, 60000); const code = await waitForConfirmationCode(email, mailToken, 'register', 60000);
console.log('✅ Код получен:', code); console.log('✅ Код получен:', code);
const confirmPayload = { const confirmPayload = {

View File

@@ -50,7 +50,7 @@ test('API: регистрация выпускника + подтвержден
} }
console.log('📬 Ожидание письма с кодом подтверждения...'); console.log('📬 Ожидание письма с кодом подтверждения...');
const code = await waitForConfirmationCode(mailToken, 60000); const code = await waitForConfirmationCode(email, mailToken, 'register', 60000);
console.log('✅ Код получен:', code); console.log('✅ Код получен:', code);
const confirmPayload = { const confirmPayload = {

View File

@@ -66,7 +66,7 @@ test('API: регистрация организации + подтвержде
} }
console.log('📬 Ожидание письма с кодом подтверждения...'); console.log('📬 Ожидание письма с кодом подтверждения...');
const code = await waitForConfirmationCode(mailToken, 60000); const code = await waitForConfirmationCode(email, mailToken, 'register',60000);
console.log('✅ Код получен:', code); console.log('✅ Код получен:', code);
const confirmPayload = { const confirmPayload = {

View File

@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../../page-objects/LoginPage';
import fs from 'fs';
import {
waitForConfirmationCode
} from '../../../utils/mailTmApi';
test('UI: восстановление пароля', async ({ page }) => {
// Загружаем email пользователя из файла temp/user.json
const loginPage = new LoginPage(page);
const user = JSON.parse(fs.readFileSync('temp/user.json', 'utf-8'));
const newPassword = '!Test12345678';
// Переход на главную
await loginPage.goto();
// Нажимаем "Забыли пароль?"
await page.getByText('Забыли пароль?').click();
await expect(page).toHaveURL(/.*confirmation-code/);
// Вводим почту
await page.fill('input[name="email"]', user.email);
await page.getByRole('button', { name: 'Далее' }).click();
// Получаем код из почты
const code = await waitForConfirmationCode(user.email, user.mailToken, 'recover');
expect(code).toMatch(/^\d{6}$/);
console.log('✅ Код получен:', code);
// Вводим код
await page.fill('input[name="code"]', code);
await page.getByRole('button', { name: 'Далее' }).click();
// Вводим новый пароль
await page.fill('input[name="password"]', newPassword);
await page.fill('input[name="passwordRepeat"]', newPassword);
await page.getByRole('button', { name: 'Далее' }).click();
// Проверяем успешную авторизацию
await expect(page).toHaveURL(/.*\/account\/profile/);
// (необязательно) обновляем сохранённый пароль
//user.password = newPassword;
//fs.writeFileSync('temp/user.json', JSON.stringify(user));
});

View File

@@ -44,7 +44,7 @@ test('Полная регистрация абитуриента с подтве
await registerPage.submit(); await registerPage.submit();
await expect(page).toHaveURL(/confirmation-code/); await expect(page).toHaveURL(/confirmation-code/);
const code = await waitForConfirmationCode(token, 60000); const code = await waitForConfirmationCode(email, token, 'register', 60000);
await page.fill('input[name="code"]', code); await page.fill('input[name="code"]', code);
await page.click('button.RecoverPassword_button__5QDxM'); await page.click('button.RecoverPassword_button__5QDxM');

View File

@@ -46,7 +46,7 @@ test('Полная регистрация выпускника с подтвер
await page.waitForTimeout(5000); await page.waitForTimeout(5000);
await expect(page).toHaveURL(/confirmation-code/); await expect(page).toHaveURL(/confirmation-code/);
const code = await waitForConfirmationCode(token, 60000); const code = await waitForConfirmationCode(email, token, 'register', 60000);
await page.fill('input[name="code"]', code); await page.fill('input[name="code"]', code);
await page.click('button.RecoverPassword_button__5QDxM'); await page.click('button.RecoverPassword_button__5QDxM');

View File

@@ -78,7 +78,7 @@ test('Полная регистрация организации с подтве
await registerPage.submitGeneral(); await registerPage.submitGeneral();
await expect(page).toHaveURL(/confirmation-code/); await expect(page).toHaveURL(/confirmation-code/);
const code = await waitForConfirmationCode(token, 60000); const code = await waitForConfirmationCode(email, token, 'register', 60000);
await page.fill('input[name="code"]', code); await page.fill('input[name="code"]', code);
await page.click('button.RecoverPassword_button__5QDxM'); await page.click('button.RecoverPassword_button__5QDxM');

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"verbatimModuleSyntax": false,
"moduleResolution": "bundler",
"types": ["node", "playwright"],
"lib": ["esnext"],
"strict": true
}
}

View File

@@ -32,7 +32,8 @@ export async function createTempEmail() {
} }
export async function waitForConfirmationCode(token: string, timeout = 60000) { export async function waitForConfirmationCode(email: string, token: string, type: 'register' | 'recover', timeout = 60000) {
console.log('📧 Ожидаем код для:', email);
const start = Date.now(); const start = Date.now();
while (Date.now() - start < timeout) { while (Date.now() - start < timeout) {
@@ -42,33 +43,49 @@ export async function waitForConfirmationCode(token: string, timeout = 60000) {
const messages = res.data['hydra:member']; const messages = res.data['hydra:member'];
// ✅ Логируем полученные письма
if (messages.length === 0) { if (messages.length === 0) {
console.log('⏳ Письма пока не пришли...'); console.log('⏳ Письма пока не пришли...');
} else { } else {
console.log(`📬 Получено ${messages.length} писем:`); console.log(`📬 Получено ${messages.length} писем:`);
for (const m of messages) {
console.log(`🧾 Письмо: id=${m.id}, тема="${m.subject}"`);
}
} }
for (const message of messages) { for (const message of messages) {
console.log('🔖 Тема письма:', message.subject); console.log('🔖 Тема письма:', message.subject);
// 🔴 ФИЛЬТРАЦИЯ ЗДЕСЬ
if (
type === 'register' &&
!message.subject.toLowerCase().includes('подтверж')
) {
continue;
}
if (
type === 'recover' &&
!message.subject.toLowerCase().includes('сброс')
) {
continue;
}
// ⬇️ ТОЛЬКО НУЖНОЕ ПИСЬМО ДОХОДИТ СЮДА
const msg = await axios.get(`${MAIL_TM_BASE}/messages/${message.id}`, { const msg = await axios.get(`${MAIL_TM_BASE}/messages/${message.id}`, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
const body = msg.data.text || msg.data.html; const body = msg.data.text || msg.data.html;
console.log('📩 Тело письма:', body);
const match = body.match(/\b\d{6}\b/); const match = body.match(/\b\d{6}\b/);
if (match) { if (match) {
console.log('✅ Код подтверждения найден:', match[0]); console.log('✅ Код найден:', match[0]);
return match[0]; return match[0];
} }
} }
await new Promise(res => setTimeout(res, 2000)); // Пауза 2 сек await new Promise(res => setTimeout(res, 2000));
} }
throw new Error('Код подтверждения не получен'); throw new Error('Код подтверждения не получен');
} }