init: базовая структура АТ

This commit is contained in:
Vlad Smykov
2026-01-26 17:50:18 +03:00
commit 2cae137c8c
11 changed files with 916 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
allure-results/
allure-report/
test-results/
.env

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# Autotests: DigitalTwin Platform
Автоматизированные UI и API тесты для проекта **DigitalTwin**. Используется стек:
- [Playwright](https://playwright.dev/) + TypeScript
- Axios для API-запросов
- Allure для формирования отчётов
## 📂 Структура проекта
```
.
├── tests/
│ ├── ui/ # UI-тесты (Playwright)
│ └── api/ # API-тесты (Axios + Playwright)
├── utils/ # Генераторы данных, вспомогательные утилиты
├── package.json # Скрипты запуска
├── tsconfig.json # Конфигурация TypeScript
├── playwright.config.ts
└── .gitignore
```
## 🚀 Скрипты
| Скрипт | Назначение |
|------------------------|--------------------------------------------|
| `npm run test` | Полный запуск тестов |
| `npm run test:ui` | Только UI-тесты |
| `npm run test:api` | Только API-тесты |
| `npm run report` | Открыть HTML-отчёт Playwright |
| `npm run allure:report`| Сгенерировать и открыть Allure отчёт |
| `npm run test:clean` | Удалить старые отчёты Allure |
## 🛠️ Установка
```bash
npm install
```

447
package-lock.json generated Normal file
View File

@@ -0,0 +1,447 @@
{
"name": "digitaltwin",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "digitaltwin",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.13.3"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"allure-playwright": "^3.4.5",
"typescript": "^5.9.3"
}
},
"node_modules/@playwright/test": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/allure-js-commons": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.4.5.tgz",
"integrity": "sha512-mzAppLFva9PJqWvdI/aXn8jqr+5Am67JITdthMfEk8En6AhsrvRWZAjHZ1yUMDGEbxmivCni3lppvitJGStxFQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"md5": "^2.3.0"
},
"peerDependencies": {
"allure-playwright": "3.4.5"
},
"peerDependenciesMeta": {
"allure-playwright": {
"optional": true
}
}
},
"node_modules/allure-playwright": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-3.4.5.tgz",
"integrity": "sha512-pVewTpU9Z4qgT14VJdtYLAfF8rWROuESmvDkvyu/QnFWhRFrcDBnomynj84yx/QpXyMjJL+qu1yMU2z4Mq1YnA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"allure-js-commons": "3.4.5"
},
"peerDependencies": {
"@playwright/test": ">=1.53.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz",
"integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"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/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true,
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/playwright": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

26
package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "digitaltwin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "npx playwright test",
"test:clean": "rm -rf allure-results allure-report",
"test:ui": "npx playwright test tests/ui",
"test:api": "npx playwright test tests/api",
"report": "npx playwright show-report",
"allure:report": "allure generate ./allure-results --clean -o ./allure-report && allure open ./allure-report"
},
"keywords": [],
"author": "Vlad Smykov",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"@playwright/test": "^1.57.0",
"allure-playwright": "^3.4.5",
"typescript": "^5.9.3"
},
"dependencies": {
"axios": "^1.13.3"
}
}

View File

@@ -0,0 +1,55 @@
// page-objects/RegisterPage.ts
import { Page } from '@playwright/test';
export class RegisterPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async goto() {
await this.page.goto('/registration');
}
async fillLastName(lastName: string) {
await this.page.fill('input[name="surname"]', lastName);
}
async fillFirstName(firstName: string) {
await this.page.fill('input[name="name"]', firstName);
}
async fillMiddleName(middleName: string) {
await this.page.fill('input[name="patronymic"]', middleName);
}
async fillEmail(email: string) {
await this.page.fill('input[name="email"]', email);
}
async fillLogin(login: string) {
await this.page.fill('input[name="login"]', login);
}
async fillPhone(phone: string) {
await this.page.fill('input[name="phone"]', phone);
}
async fillPassword(password: string) {
await this.page.fill('input[name="password"]', password);
}
async fillPasswordRepeat(password: string) {
await this.page.fill('input[name="passwordRepeat"]', password);
}
async checkConsentCheckbox() {
await this.page.locator('span.Checkmark_checkmark__58fWm').click();
}
async submit() {
await this.page.locator('button[data-testid="btn-save"]').click();
}
}

File diff suppressed because one or more lines are too long

25
playwright.config.ts Normal file
View File

@@ -0,0 +1,25 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
expect: {
timeout: 5000
},
reporter: [
['html', { outputFolder: 'playwright-report', open: 'never' }],
['list'],
['allure-playwright']
],
use: {
headless: false,
launchOptions: {
slowMo: 300,
},
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
video: 'retain-on-failure',
baseURL: 'https://rumc.dev.rdcenter.ru'
}
});

View File

@@ -0,0 +1,62 @@
import { test, expect } from '@playwright/test';
import axios from 'axios';
import {
createTempEmail,
waitForConfirmationCode
} from '../../../utils/mailTmApi';
import {
generateFirstName,
generateLastName,
generateMiddleName,
generateLogin,
generatePhone,
} from '../../../utils/userGenerator';
const BASE_URL = 'https://rumc.dev.rdcenter.ru/api';
test('API: регистрация абитуриента + подтверждение почты', async () => {
const { email, token: mailToken } = await createTempEmail();
const surname = generateLastName();
const name = generateFirstName();
const patronymic = generateMiddleName();
const fullName = `${surname} ${name} ${patronymic}`;
const login = generateLogin();
const phone = '+7 (900) 000-00-00';
const registerPayload = {
role: 'APPLICANT',
type: 'INDIVIDUAL',
login,
fullName,
email,
password: '!Test123456',
phone,
approval: true
};
//const registerRes = await axios.post(`${BASE_URL}/auth/register`, registerPayload);
//expect(registerRes.status).toBe(201);
try {
const registerRes = await axios.post(`${BASE_URL}/auth/register`, registerPayload);
expect(registerRes.status).toBe(201);
} catch (error: any) {
console.error('❌ Ошибка регистрации:', error.response?.data || error.message);
throw error;
}
console.log('📬 Ожидание письма с кодом подтверждения...');
const code = await waitForConfirmationCode(mailToken, 60000);
console.log('✅ Код получен:', code);
const confirmPayload = {
email,
code
};
const confirmRes = await axios.post(`${BASE_URL}/auth/confirm`, confirmPayload);
expect(confirmRes.status).toBe(200);
console.log('🎉 Почта подтверждена успешно');
});

View File

@@ -0,0 +1,54 @@
// Позитивный тест регистрации абитуриента
// Включает в себя:
// Ввод валидных тестовых данных
// Получение кода в письме
// Ввод кода из письма (is_confirm)
// Переход на главную
import { test, expect } from '@playwright/test';
import { RegisterPage } from '../../../page-objects/RegisterPage';
import {
generateFirstName,
generateLastName,
generateMiddleName,
generateLogin,
generatePhone,
} from '../../../utils/userGenerator';
import {
createTempEmail,
waitForConfirmationCode
} from '../../../utils/mailTmApi';
test('Полная регистрация абитуриента с подтверждением почты', async ({ page }) => {
const registerPage = new RegisterPage(page);
const { email, token } = await createTempEmail();
const firstName = generateFirstName();
const lastName = generateLastName();
const middleName = generateMiddleName();
const login = generateLogin();
const phone = generatePhone();
const password = '!Test123456';
await registerPage.goto();
await registerPage.fillLastName(lastName);
await registerPage.fillFirstName(firstName);
await registerPage.fillMiddleName(middleName);
await registerPage.fillEmail(email);
await registerPage.fillLogin(login);
await registerPage.fillPhone(phone);
await registerPage.fillPassword(password);
await registerPage.fillPasswordRepeat(password);
await registerPage.checkConsentCheckbox();
await registerPage.submit();
await expect(page).toHaveURL(/confirmation-code/);
const code = await waitForConfirmationCode(token, 60000);
await page.fill('input[name="code"]', code);
await page.click('button.RecoverPassword_button__5QDxM');
await expect(page).toHaveURL('https://rumc.dev.rdcenter.ru');
});

74
utils/mailTmApi.ts Normal file
View File

@@ -0,0 +1,74 @@
// utils/mailTmApi.ts
import axios from 'axios';
const MAIL_TM_BASE = 'https://api.mail.tm';
export async function createTempEmail() {
const timestamp = Date.now();
// Получаем доступный домен
const domainRes = await axios.get(`${MAIL_TM_BASE}/domains`);
const domain = domainRes.data['hydra:member'][0].domain;
const email = `testuser${timestamp}@${domain}`;
const password = 'QwEr!1234567';
// Регистрируем
await axios.post(`${MAIL_TM_BASE}/accounts`, {
address: email,
password,
});
// Авторизуемся
const tokenRes = await axios.post(`${MAIL_TM_BASE}/token`, {
address: email,
password,
});
const token = tokenRes.data.token;
return { email, password, token };
}
export async function waitForConfirmationCode(token: string, timeout = 60000) {
const start = Date.now();
while (Date.now() - start < timeout) {
const res = await axios.get(`${MAIL_TM_BASE}/messages`, {
headers: { Authorization: `Bearer ${token}` },
});
const messages = res.data['hydra:member'];
// ✅ Логируем полученные письма
if (messages.length === 0) {
console.log('⏳ Письма пока не пришли...');
} else {
console.log(`📬 Получено ${messages.length} писем:`);
}
for (const message of messages) {
console.log('🔖 Тема письма:', message.subject);
const msg = await axios.get(`${MAIL_TM_BASE}/messages/${message.id}`, {
headers: { Authorization: `Bearer ${token}` },
});
const body = msg.data.text || msg.data.html;
console.log('📩 Тело письма:', body);
const match = body.match(/\b\d{6}\b/);
if (match) {
console.log('✅ Код подтверждения найден:', match[0]);
return match[0];
}
}
await new Promise(res => setTimeout(res, 2000)); // Пауза 2 сек
}
throw new Error('Код подтверждения не получен');
}

45
utils/userGenerator.ts Normal file
View File

@@ -0,0 +1,45 @@
// utils/userGenerator.ts
// Список русских имён, фамилий, отчеств
const firstNames = ['Алексей', 'Игорь', 'Иван', 'Владислав', 'Дмитрий', 'Константин', 'Сергей', 'Всеволод'];
const lastNames = ['Иванов', 'Петров', 'Сидоров', 'Кузнецов', 'Смирнов', 'Попов', 'Фёдоров'];
const middleNames = ['Алексеевич', 'Иванович', 'Дмитриевич', 'Андреевич', 'Сергеевич', 'Олегович'];
function getRandomFromArray(array: string[]): string {
return array[Math.floor(Math.random() * array.length)];
}
export function generateFirstName(): string {
return getRandomFromArray(firstNames);
}
export function generateLastName(): string {
return getRandomFromArray(lastNames);
}
export function generateMiddleName(): string {
return getRandomFromArray(middleNames);
}
export function generateRandomString(length: number): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
export function generateEmail(): string {
const timestamp = Date.now();
return `test_user_${timestamp}@mail.tm`;
}
export function generateLogin(): string {
return `user_${generateRandomString(6)}`;
}
export function generatePhone(): string {
const random = Math.floor(100000000 + Math.random() * 900000000); // 9 цифр
return `+790${random}`; // +79012345678
}