This commit is contained in:
2026-08-30 17:52:55 +03:00
parent e66723efb9
commit 4450517226
33 changed files with 997 additions and 0 deletions
+4
View File
@@ -261,6 +261,10 @@ __[Auth Project](https://git.homyk.space/MH.Dmitrii/auth-project)__
A basic FastAPI authentication and authorization service, built without relying on the framework's built-in auth mechanisms. Implements JWT-based login, bcrypt password hashing, and a flat permission model with privilege escalation protection. A focused example of building auth logic from scratch and understanding it deeply enough to test it effectively. A basic FastAPI authentication and authorization service, built without relying on the framework's built-in auth mechanisms. Implements JWT-based login, bcrypt password hashing, and a flat permission model with privilege escalation protection. A focused example of building auth logic from scratch and understanding it deeply enough to test it effectively.
using console, inspector, debugging, network and storage sections using console, inspector, debugging, network and storage sections
__[Nova Registration Form — QA Test Portfolio](https://git.homyk.space/MH.Dmitrii/nova-registration-form-qa)__
A deliberately buggy registration form (HTML/CSS/JS, no backend) built as a self-contained manual testing exercise. Includes a full test plan, test cases covering boundary value analysis, equivalence partitioning and pairwise testing, and detailed bug reports (with screenshots) for 7 seeded defects. Available in both English and Russian. Demonstrates end-to-end manual QA workflow: test planning → test design → execution → defect reporting.
__Virtualization software__ __Virtualization software__
- able to set up and operate virtual machines in different software like Virtualbox, qemu, vmware, hyperv, docker and podman - able to set up and operate virtual machines in different software like Virtualbox, qemu, vmware, hyperv, docker and podman
@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nova — Create Account</title>
<style>
:root {
--navy: #161B22;
--panel: #1E2530;
--slate: #3A4750;
--mint: #4ECCA3;
--mint-dim: #2E7C63;
--off-white: #F5F7FA;
--muted: #8D99AE;
--danger: #E76F6F;
--font-display: 'Space Grotesk', 'Segoe UI', sans-serif;
--font-body: 'Inter', 'Segoe UI', sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 15% 10%, rgba(78,204,163,0.12), transparent 40%),
radial-gradient(circle at 85% 90%, rgba(78,204,163,0.08), transparent 45%),
var(--navy);
color: var(--off-white);
font-family: var(--font-body);
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
}
.card {
width: 100%;
max-width: 420px;
background: var(--panel);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
padding: 36px 32px 32px;
box-shadow: 0 30px 60px -20px rgba(0,0,0,0.5);
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--mint);
font-weight: 600;
margin-bottom: 10px;
}
h1 {
font-family: var(--font-display);
font-size: 26px;
margin: 0 0 6px;
letter-spacing: -0.01em;
}
.subtitle {
color: var(--muted);
font-size: 14px;
margin: 0 0 28px;
line-height: 1.5;
}
label {
display: block;
font-size: 13px;
font-weight: 600;
margin-bottom: 6px;
color: var(--off-white);
}
.hint {
display: block;
font-size: 12px;
color: var(--muted);
font-weight: 400;
margin-top: 2px;
}
.field {
margin-bottom: 18px;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"] {
width: 100%;
padding: 11px 12px;
background: #141A22;
border: 1px solid var(--slate);
border-radius: 8px;
color: var(--off-white);
font-size: 14px;
font-family: var(--font-body);
outline: none;
transition: border-color 0.15s ease;
}
input:focus {
border-color: var(--mint);
}
input.invalid {
border-color: var(--danger);
}
.error-text {
color: var(--danger);
font-size: 12px;
margin-top: 6px;
display: none;
}
.error-text.show {
display: block;
}
.checkbox-row {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 22px;
}
.checkbox-row input {
margin-top: 3px;
}
.checkbox-row label {
font-size: 13px;
font-weight: 400;
color: var(--muted);
margin: 0;
}
button[type="submit"] {
width: 100%;
padding: 13px;
background: var(--mint);
color: #0C1410;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s ease;
}
button[type="submit"]:hover {
background: var(--mint-dim);
color: var(--off-white);
}
#formBanner {
display: none;
padding: 12px 14px;
border-radius: 8px;
font-size: 13px;
margin-bottom: 18px;
}
#formBanner.success {
display: block;
background: rgba(78,204,163,0.12);
border: 1px solid var(--mint-dim);
color: var(--mint);
}
#formBanner.error {
display: block;
background: rgba(231,111,111,0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
.footnote {
text-align: center;
font-size: 12px;
color: var(--muted);
margin-top: 20px;
}
</style>
</head>
<body>
<div class="card">
<div class="eyebrow">Nova · Sign Up</div>
<h1>Create your account</h1>
<p class="subtitle">Fill out the form below to get started with Nova.</p>
<div id="formBanner"></div>
<form id="registerForm" novalidate>
<div class="field">
<label for="username">Username
<span class="hint">3 to 20 characters</span>
</label>
<input type="text" id="username" name="username">
<div class="error-text" id="usernameError">Username must be between 3 and 20 characters.</div>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<div class="error-text" id="emailError">Please enter a valid email address.</div>
</div>
<div class="field">
<label for="password">Password
<span class="hint">at least 8 characters, minimum 1 digit</span>
</label>
<input type="password" id="password" name="password">
<div class="error-text" id="passwordError">Password must be at least 8 characters and contain at least one digit.</div>
</div>
<div class="field">
<label for="confirmPassword">Confirm password</label>
<input type="password" id="confirmPassword" name="confirmPassword">
<div class="error-text" id="confirmPasswordError">Passwords do not match.</div>
</div>
<div class="field">
<label for="age">Age
<span class="hint">registration is open from 18 to 99 years old</span>
</label>
<input type="number" id="age" name="age">
<div class="error-text" id="ageError">Age must be between 18 and 99.</div>
</div>
<div class="checkbox-row">
<input type="checkbox" id="agreeTerms" name="agreeTerms">
<label for="agreeTerms">I agree to the Terms of Service and Privacy Policy</label>
</div>
<div class="error-text" id="termsError" style="margin-top:-14px; margin-bottom: 18px;">You must accept the terms of service.</div>
<button type="submit">Create account</button>
</form>
<p class="footnote">Already have an account? <a href="#" style="color: var(--mint);">Log in</a></p>
</div>
<script>
function validateForm(fields) {
const errors = {};
const username = fields.username.trim();
if (username.length === 0 || username.length > 20) {
errors.username = true;
}
const emailRegex = /^[^\s@]+@[^\s@]+$/;
if (!emailRegex.test(fields.email.trim())) {
errors.email = true;
}
const passwordHasDigit = /\d/.test(fields.password);
if (fields.password.length < 6 || !passwordHasDigit) {
errors.password = true;
}
if (fields.password.toLowerCase() !== fields.confirmPassword.toLowerCase()) {
errors.confirmPassword = true;
}
const age = Number(fields.age);
if (!Number.isFinite(age) || age <= 18) {
errors.age = true;
}
const terms = document.getElementById('termsCheck');
if (terms && !terms.checked) {
errors.terms = true;
}
return errors;
}
const form = document.getElementById('registerForm');
const banner = document.getElementById('formBanner');
form.addEventListener('submit', function (e) {
e.preventDefault();
const fields = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value,
confirmPassword: document.getElementById('confirmPassword').value,
age: document.getElementById('age').value,
};
['username', 'email', 'password', 'confirmPassword', 'age', 'terms'].forEach(function (key) {
const errorEl = document.getElementById(key + 'Error');
if (errorEl) errorEl.classList.remove('show');
const inputEl = document.getElementById(key);
if (inputEl) inputEl.classList.remove('invalid');
});
const errors = validateForm(fields);
const hasErrors = Object.keys(errors).length > 0;
Object.keys(errors).forEach(function (key) {
const errorEl = document.getElementById(key + 'Error');
if (errorEl) errorEl.classList.add('show');
const inputEl = document.getElementById(key);
if (inputEl) inputEl.classList.add('invalid');
});
if (hasErrors) {
banner.textContent = 'Please fix the errors in the form.';
banner.className = 'error';
} else {
banner.textContent = 'Registration successful! Welcome to Nova.';
banner.className = 'success';
}
});
</script>
</body>
</html>
@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nova — Создать аккаунт</title>
<style>
:root {
--navy: #161B22;
--panel: #1E2530;
--slate: #3A4750;
--mint: #4ECCA3;
--mint-dim: #2E7C63;
--off-white: #F5F7FA;
--muted: #8D99AE;
--danger: #E76F6F;
--font-display: 'Space Grotesk', 'Segoe UI', sans-serif;
--font-body: 'Inter', 'Segoe UI', sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 15% 10%, rgba(78,204,163,0.12), transparent 40%),
radial-gradient(circle at 85% 90%, rgba(78,204,163,0.08), transparent 45%),
var(--navy);
color: var(--off-white);
font-family: var(--font-body);
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
}
.card {
width: 100%;
max-width: 420px;
background: var(--panel);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
padding: 36px 32px 32px;
box-shadow: 0 30px 60px -20px rgba(0,0,0,0.5);
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--mint);
font-weight: 600;
margin-bottom: 10px;
}
h1 {
font-family: var(--font-display);
font-size: 26px;
margin: 0 0 6px;
letter-spacing: -0.01em;
}
.subtitle {
color: var(--muted);
font-size: 14px;
margin: 0 0 28px;
line-height: 1.5;
}
label {
display: block;
font-size: 13px;
font-weight: 600;
margin-bottom: 6px;
color: var(--off-white);
}
.hint {
display: block;
font-size: 12px;
color: var(--muted);
font-weight: 400;
margin-top: 2px;
}
.field {
margin-bottom: 18px;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"] {
width: 100%;
padding: 11px 12px;
background: #141A22;
border: 1px solid var(--slate);
border-radius: 8px;
color: var(--off-white);
font-size: 14px;
font-family: var(--font-body);
outline: none;
transition: border-color 0.15s ease;
}
input:focus {
border-color: var(--mint);
}
input.invalid {
border-color: var(--danger);
}
.error-text {
color: var(--danger);
font-size: 12px;
margin-top: 6px;
display: none;
}
.error-text.show {
display: block;
}
.checkbox-row {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 22px;
}
.checkbox-row input {
margin-top: 3px;
}
.checkbox-row label {
font-size: 13px;
font-weight: 400;
color: var(--muted);
margin: 0;
}
button[type="submit"] {
width: 100%;
padding: 13px;
background: var(--mint);
color: #0C1410;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s ease;
}
button[type="submit"]:hover {
background: var(--mint-dim);
color: var(--off-white);
}
#formBanner {
display: none;
padding: 12px 14px;
border-radius: 8px;
font-size: 13px;
margin-bottom: 18px;
}
#formBanner.success {
display: block;
background: rgba(78,204,163,0.12);
border: 1px solid var(--mint-dim);
color: var(--mint);
}
#formBanner.error {
display: block;
background: rgba(231,111,111,0.1);
border: 1px solid var(--danger);
color: var(--danger);
}
.footnote {
text-align: center;
font-size: 12px;
color: var(--muted);
margin-top: 20px;
}
</style>
</head>
<body>
<div class="card">
<div class="eyebrow">Nova · Регистрация</div>
<h1>Создайте аккаунт</h1>
<p class="subtitle">Заполните форму ниже, чтобы начать работу с Nova.</p>
<div id="formBanner"></div>
<form id="registerForm" novalidate>
<div class="field">
<label for="username">Имя пользователя
<span class="hint">от 3 до 20 символов</span>
</label>
<input type="text" id="username" name="username">
<div class="error-text" id="usernameError">Имя пользователя должно быть от 3 до 20 символов.</div>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<div class="error-text" id="emailError">Введите корректный email.</div>
</div>
<div class="field">
<label for="password">Пароль
<span class="hint">не менее 8 символов, минимум 1 цифра</span>
</label>
<input type="password" id="password" name="password">
<div class="error-text" id="passwordError">Пароль должен содержать минимум 8 символов и хотя бы одну цифру.</div>
</div>
<div class="field">
<label for="confirmPassword">Подтверждение пароля</label>
<input type="password" id="confirmPassword" name="confirmPassword">
<div class="error-text" id="confirmPasswordError">Пароли не совпадают.</div>
</div>
<div class="field">
<label for="age">Возраст
<span class="hint">регистрация доступна с 18 до 99 лет</span>
</label>
<input type="number" id="age" name="age">
<div class="error-text" id="ageError">Возраст должен быть от 18 до 99 лет.</div>
</div>
<div class="checkbox-row">
<input type="checkbox" id="agreeTerms" name="agreeTerms">
<label for="agreeTerms">Я согласен с условиями использования и политикой конфиденциальности</label>
</div>
<div class="error-text" id="termsError" style="margin-top:-14px; margin-bottom: 18px;">Необходимо принять условия использования.</div>
<button type="submit">Создать аккаунт</button>
</form>
<p class="footnote">Уже есть аккаунт? <a href="#" style="color: var(--mint);">Войти</a></p>
</div>
<script>
function validateForm(fields) {
const errors = {};
const username = fields.username.trim();
if (username.length === 0 || username.length > 20) {
errors.username = true;
}
const emailRegex = /^[^\s@]+@[^\s@]+$/;
if (!emailRegex.test(fields.email.trim())) {
errors.email = true;
}
const passwordHasDigit = /\d/.test(fields.password);
if (fields.password.length < 6 || !passwordHasDigit) {
errors.password = true;
}
if (fields.password.toLowerCase() !== fields.confirmPassword.toLowerCase()) {
errors.confirmPassword = true;
}
const age = Number(fields.age);
if (!Number.isFinite(age) || age <= 18) {
errors.age = true;
}
const terms = document.getElementById('termsCheck');
if (terms && !terms.checked) {
errors.terms = true;
}
return errors;
}
const form = document.getElementById('registerForm');
const banner = document.getElementById('formBanner');
form.addEventListener('submit', function (e) {
e.preventDefault();
const fields = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value,
confirmPassword: document.getElementById('confirmPassword').value,
age: document.getElementById('age').value,
};
['username', 'email', 'password', 'confirmPassword', 'age', 'terms'].forEach(function (key) {
const errorEl = document.getElementById(key + 'Error');
if (errorEl) errorEl.classList.remove('show');
const inputEl = document.getElementById(key);
if (inputEl) inputEl.classList.remove('invalid');
});
const errors = validateForm(fields);
const hasErrors = Object.keys(errors).length > 0;
Object.keys(errors).forEach(function (key) {
const errorEl = document.getElementById(key + 'Error');
if (errorEl) errorEl.classList.add('show');
const inputEl = document.getElementById(key);
if (inputEl) inputEl.classList.add('invalid');
});
if (hasErrors) {
banner.textContent = 'Пожалуйста, исправьте ошибки в форме.';
banner.className = 'error';
} else {
banner.textContent = 'Регистрация прошла успешно! Добро пожаловать в Nova.';
banner.className = 'success';
}
});
</script>
</body>
</html>
@@ -0,0 +1,13 @@
# BUG-001 — Age boundary of 18 is incorrectly rejected
- **Priority:** High
- **Severity:** Major
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Open the registration form
2. Fill in all fields with valid data
3. Enter `18` in the "Age" field
4. Click "Create account"
- **Expected result:** Registration succeeds (the hint under the field states "registration is open from 18 years old")
- **Actual result:** The form shows the error "Age must be between 18 and 99"
- **Notes:** Looks like a classic off-by-one error in the comparison condition (`<=` used instead of `<`)
+12
View File
@@ -0,0 +1,12 @@
# BUG-002 — Terms and conditions checkbox is not validated
- **Priority:** Critical
- **Severity:** Blocker
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Fill in all form fields with valid data
2. Do **not** check the "I agree to the Terms of Service" checkbox
3. Click "Create account"
- **Expected result:** The form blocks submission and shows an error below the checkbox
- **Actual result:** The form submits successfully, as if consent had been given
- **Impact:** Legal risk — a user can register without accepting the terms of service
+13
View File
@@ -0,0 +1,13 @@
# BUG-003 — Email without a top-level domain passes validation
- **Priority:** Medium
- **Severity:** Major
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Open the registration form
2. Fill in all fields with valid data
3. Enter `user@mail` in the "Email" field (no `.com`, `.net`, etc.)
4. Click "Create account"
- **Expected result:** The form shows the error "Please enter a valid email address"
- **Actual result:** The form accepts the address as valid; registration succeeds
- **Notes:** The regular expression only checks for the presence of `@`, but does not check for a top-level domain (a dot after `@`)
@@ -0,0 +1,12 @@
# BUG-004 — Password shorter than the stated minimum passes validation
- **Priority:** High
- **Severity:** Major
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Fill in all form fields with valid data
2. Enter `abc123` (6 characters) in both "Password" and "Confirm password"
3. Click "Create account"
- **Expected result:** Error "Password must be at least 8 characters" (as stated in the field hint)
- **Actual result:** The form accepts a 6-character password; registration succeeds
- **Notes:** Mismatch between the displayed hint/error text (8 characters) and the actual validation logic (6 characters)
@@ -0,0 +1,13 @@
# BUG-005 — Password confirmation is compared case-insensitively
- **Priority:** Critical
- **Severity:** Blocker
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Fill in all form fields with valid data
2. Enter `Secret12` in the "Password" field
3. Enter `secret12` (same string, lowercase) in "Confirm password"
4. Click "Create account"
- **Expected result:** Error "Passwords do not match" — the strings differ when case is considered
- **Actual result:** The form treats the passwords as matching; registration succeeds
- **Impact:** A user may accidentally set a password that differs from what they intended to type — risk of being unable to log in later
@@ -0,0 +1,12 @@
# BUG-006 — Minimum username length is not validated
- **Priority:** Low
- **Severity:** Minor
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Fill in the form, enter a single character in "Username", e.g. `B`
2. Fill in the remaining fields with valid data
3. Click "Create account"
- **Expected result:** Error "Username must be between 3 and 20 characters"
- **Actual result:** The form accepts a 1-character username; registration succeeds
- **Notes:** The code only checks for an empty string and the upper bound (20 characters); the lower bound is missing
@@ -0,0 +1,12 @@
# BUG-007 — Upper age boundary is not validated
- **Priority:** Medium
- **Severity:** Major
- **Environment:** Chrome 128, desktop
- **Steps to reproduce:**
1. Fill in all form fields with valid data
2. Enter `200` in the "Age" field
3. Click "Create account"
- **Expected result:** Error "Age must be between 18 and 99"
- **Actual result:** The form accepts the value `200`; registration succeeds
- **Notes:** The code only checks the lower bound (`age <= 18`); the upper bound (99) is never checked
@@ -0,0 +1,13 @@
### BUG-001 — Граница возраста 18 лет ошибочно отклоняется
- **Приоритет:** High
- **Серьёзность:** Major
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Открыть форму регистрации
2. Заполнить все поля корректными данными
3. В поле "Возраст" указать `18`
4. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Регистрация проходит успешно (лимит "с 18 лет" согласно подсказке под полем)
- **Фактический результат:** Форма показывает ошибку "Возраст должен быть от 18 до 99 лет"
- **Примечание:** Похоже на классическую off-by-one ошибку в условии сравнения (`<=` вместо `<`)
+12
View File
@@ -0,0 +1,12 @@
### BUG-002 — Чекбокс согласия с условиями не валидируется
- **Приоритет:** Critical
- **Серьёзность:** Blocker
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Заполнить все поля формы корректными данными
2. **Не** отмечать чекбокс "Я согласен с условиями использования"
3. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Форма блокирует отправку, показывает ошибку под чекбоксом
- **Фактический результат:** Форма отправляется успешно, как будто согласие было дано
- **Влияние:** Юридический риск — пользователь может зарегистрироваться, не приняв условия использования
+13
View File
@@ -0,0 +1,13 @@
### BUG-003 — Email без домена верхнего уровня проходит валидацию
- **Приоритет:** Medium
- **Серьёзность:** Major
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Открыть форму регистрации
2. Заполнить все поля корректными данными
3. В поле "Email" ввести `user@mail` (без `.com`/`.ru` и т.п.)
4. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Форма показывает ошибку "Введите корректный email"
- **Фактический результат:** Форма принимает адрес как валидный, регистрация проходит успешно
- **Примечание:** Регулярное выражение проверяет только наличие `@`, но не проверяет домен верхнего уровня (отсутствие точки после `@`)
@@ -0,0 +1,12 @@
### BUG-004 — Пароль короче заявленного минимума проходит валидацию
- **Приоритет:** High
- **Серьёзность:** Major
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Заполнить все поля формы корректными данными
2. В поле "Пароль" и "Подтверждение пароля" ввести `abc123` (6 символов)
3. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Ошибка "Пароль должен содержать минимум 8 символов" (согласно подсказке под полем)
- **Фактический результат:** Форма принимает пароль из 6 символов, регистрация проходит успешно
- **Примечание:** Рассинхрон между текстом подсказки/ошибки (8 символов) и фактической проверкой в коде (6 символов)
@@ -0,0 +1,13 @@
### BUG-005 — Подтверждение пароля сравнивается без учёта регистра
- **Приоритет:** Critical
- **Серьёзность:** Blocker
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Заполнить все поля формы корректными данными
2. В поле "Пароль" ввести `Secret12`
3. В поле "Подтверждение пароля" ввести `secret12` (та же строка, но в нижнем регистре)
4. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Ошибка "Пароли не совпадают" — строки различны с учётом регистра
- **Фактический результат:** Форма считает пароли совпадающими, регистрация проходит успешно
- **Влияние:** Пользователь может случайно задать пароль, отличающийся от того, что он думает, что задал — риск невозможности входа в будущем
@@ -0,0 +1,12 @@
### BUG-006 — Минимальная длина имени пользователя не проверяется
- **Приоритет:** Low
- **Серьёзность:** Minor
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Заполнить форму, в поле "Имя пользователя" ввести один символ, например `B`
2. Остальные поля заполнить корректно
3. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Ошибка "Имя пользователя должно быть от 3 до 20 символов"
- **Фактический результат:** Форма принимает имя из 1 символа, регистрация проходит успешно
- **Примечание:** В коде проверяется только пустая строка и верхняя граница (20 символов), нижняя граница отсутствует
@@ -0,0 +1,12 @@
### BUG-007 — Верхняя граница возраста не проверяется
- **Приоритет:** Medium
- **Серьёзность:** Major
- **Окружение:** Chrome 128, десктоп
- **Шаги воспроизведения:**
1. Заполнить все поля формы корректными данными
2. В поле "Возраст" ввести `200`
3. Нажать "Создать аккаунт"
- **Ожидаемый результат:** Ошибка "Возраст должен быть от 18 до 99 лет"
- **Фактический результат:** Форма принимает значение `200`, регистрация проходит успешно
- **Примечание:** В коде проверяется только нижняя граница (`age <= 18`), верхняя граница (99) не проверяется вообще
Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

+10
View File
@@ -0,0 +1,10 @@
# Boundary Value Analysis — "Age" field
| TC ID | Input value | Expected result | Boundary category |
|---|---|---|---|
| TC-01 | 17 | Error "must be between 18 and 99" | Below lower boundary |
| TC-02 | 18 | Form accepted | Lower boundary |
| TC-03 | 19 | Form accepted | Just above lower boundary |
| TC-04 | 99 | Form accepted | Upper boundary |
| TC-05 | 100 | Error "must be between 18 and 99" | Above upper boundary |
| TC-06 | 0, -5 | Error | Invalid values |
@@ -0,0 +1,9 @@
# Equivalence Partitioning — "Email" field
| TC ID | Input value | Class | Expected result |
|---|---|---|---|
| TC-07 | `user@mail.com` | Valid, complete address | Accepted |
| TC-08 | `user@mail` | Invalid (no top-level domain) | Error |
| TC-09 | `usermail.com` | Invalid (no @) | Error |
| TC-10 | empty string | Invalid (required field) | Error |
| TC-11 | `user+test@mail.com` | Valid (subaddressing) | Accepted |
+7
View File
@@ -0,0 +1,7 @@
# Functional Checklist
| TC ID | Steps | Expected result |
|---|---|---|
| TC-17 | Fill in everything correctly, leave the "I agree to the terms" checkbox **unchecked**, submit | The form should show the error "You must accept the terms of service" |
| TC-18 | Enter a 2-character username, submit | Validation error (min. 3 characters) |
| TC-19 | Enter a password of exactly 8 characters with a digit | Form accepted |
+9
View File
@@ -0,0 +1,9 @@
# Pairwise Testing — Password × Confirm Password × Age combinations
| TC ID | Password | Confirm password | Age | Expected result |
|---|---|---|---|---|
| TC-12 | `Secret12` | `Secret12` | 25 | Success |
| TC-13 | `Secret12` | `secret12` | 25 | Error "passwords do not match" |
| TC-14 | `abc` (short, no digit) | `abc` | 25 | Password error |
| TC-15 | `Secret12` | `Secret12` | 17 | Age error |
| TC-16 | `abc123` (6 chars) | `abc123` | 25 | Error (per the "min 8 characters" requirement) |
+10
View File
@@ -0,0 +1,10 @@
### Граничные значения (Boundary Value Analysis) — поле "Возраст"
| TC ID | Входное значение | Ожидаемый результат | Категория границы |
|---|---|---|---|
| TC-01 | 17 | Ошибка "от 18 до 99" | Ниже нижней границы |
| TC-02 | 18 | Форма принимается | Нижняя граница |
| TC-03 | 19 | Форма принимается | Чуть выше нижней границы |
| TC-04 | 99 | Форма принимается | Верхняя граница |
| TC-05 | 100 | Ошибка "от 18 до 99" | Выше верхней границы |
| TC-06 | 0, -5 | Ошибка | Невалидные значения |
@@ -0,0 +1,9 @@
### Классы эквивалентности — поле "Email"
| TC ID | Входное значение | Класс | Ожидаемый результат |
|---|---|---|---|
| TC-07 | `user@mail.com` | Валидный полный адрес | Принято |
| TC-08 | `user@mail` | Невалидный (нет домена верхнего уровня) | Ошибка |
| TC-09 | `usermail.com` | Невалидный (нет @) | Ошибка |
| TC-10 | пустая строка | Невалидный (обязательное поле) | Ошибка |
| TC-11 | `user+test@mail.com` | Валидный (subaddressing) | Принято |
+7
View File
@@ -0,0 +1,7 @@
### Функциональные / чек-лист
| TC ID | Шаги | Ожидаемый результат |
|---|---|---|
| TC-17 | Заполнить всё корректно, оставить чекбокс "Согласен с условиями" **не отмеченным**, отправить | Форма должна показать ошибку "Необходимо принять условия" |
| TC-18 | Ввести имя пользователя из 2 символов, отправить | Ошибка валидации (мин. 3 символа) |
| TC-19 | Ввести пароль ровно 8 символов с цифрой | Форма принимается |
+10
View File
@@ -0,0 +1,10 @@
### Парное тестирование (Pairwise) — комбинации Пароль × Подтверждение × Возраст
| TC ID | Пароль | Подтверждение | Возраст | Ожидаемый результат |
|---|---|---|---|---|
| TC-12 | `Secret12` | `Secret12` | 25 | Успех |
| TC-13 | `Secret12` | `secret12` | 25 | Ошибка "пароли не совпадают" |
| TC-14 | `abc` (короткий, без цифры) | `abc` | 25 | Ошибка пароля |
| TC-15 | `Secret12` | `Secret12` | 17 | Ошибка возраста |
| TC-16 | `abc123` (6 симв.) | `abc123` | 25 | Ошибка (по требованию "мин. 8") |
+45
View File
@@ -0,0 +1,45 @@
# Test Plan — Nova Registration Form
## 1. Test Objective
Verify the correctness of field validation on the registration form (username, email, password, confirm password, age, terms agreement) and identify defects in the validation logic.
## 2. Test Item
`index.html` — a single-page registration form (client-side JavaScript validation, no backend).
## 3. In Scope
- Username field validation
- Email field validation
- Password and Confirm Password field validation
- Age field validation
- Terms of Service checkbox validation
- Display of error messages and the success banner
## 4. Out of Scope
- Server-side validation and data persistence (no backend)
- Cross-browser testing (Chrome only)
- Performance and security testing
## 5. Test Techniques
- Boundary Value Analysis — "Age" field
- Equivalence Partitioning — "Email" field
- Pairwise Testing — "Password" / "Confirm Password" / "Age" field combinations
- Functional checklist testing
## 6. Test Environment
- Browser: Chrome (latest stable)
- OS: any (Windows / macOS / Linux)
- Device: desktop
## 7. Entry Criteria
- The `index.html` page is accessible and loads in the browser with no console errors
## 8. Exit Criteria
- All planned test cases have been executed
- All identified defects are documented in bug reports
## 9. Deliverables
- Test cases: `test-cases/`
- Bug reports: `bug-reports/`
## 10. Risks
- The absence of a backend limits testing to client-side validation only
+45
View File
@@ -0,0 +1,45 @@
# Тест-план — Nova, форма регистрации
## 1. Цель тестирования
Проверить корректность валидации полей формы регистрации (имя пользователя, email, пароль, подтверждение пароля, возраст, согласие с условиями) и найти дефекты в логике валидации.
## 2. Объект тестирования
`index.html` — одностраничная форма регистрации (клиентская валидация на JavaScript, без бэкенда).
## 3. Функционал в зоне тестирования (in scope)
- Валидация поля "Имя пользователя"
- Валидация поля "Email"
- Валидация поля "Пароль" и "Подтверждение пароля"
- Валидация поля "Возраст"
- Валидация чекбокса согласия с условиями
- Отображение сообщений об ошибках и баннера успеха
## 4. Вне зоны тестирования (out of scope)
- Серверная валидация и сохранение данных (бэкенда нет)
- Кроссбраузерное тестирование (только Chrome)
- Тестирование производительности и безопасности
## 5. Техники тестирования
- Граничные значения (Boundary Value Analysis) — поле "Возраст"
- Классы эквивалентности (Equivalence Partitioning) — поле "Email"
- Парное тестирование (Pairwise Testing) — комбинации полей "Пароль" / "Подтверждение пароля" / "Возраст"
- Функциональное тестирование по чек-листу
## 6. Окружение
- Браузер: Chrome (последняя стабильная версия)
- ОС: любая (Windows / macOS / Linux)
- Устройство: десктоп
## 7. Критерии входа
- Страница `index.html` доступна и открывается в браузере без ошибок консоли
## 8. Критерии выхода
- Все запланированные тест-кейсы выполнены
- Все найденные дефекты задокументированы в баг-репортах
## 9. Артефакты
- Тест-кейсы: `test-cases/`
- Баг-репорты: `bug-reports/`
## 10. Риски
- Отсутствие бэкенда ограничивает проверку до клиентской валидации