tests
This commit is contained in:
@@ -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.
|
||||
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__
|
||||
|
||||
- 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 `<`)
|
||||
@@ -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
|
||||
@@ -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 ошибку в условии сравнения (`<=` вместо `<`)
|
||||
@@ -0,0 +1,12 @@
|
||||
### BUG-002 — Чекбокс согласия с условиями не валидируется
|
||||
|
||||
- **Приоритет:** Critical
|
||||
- **Серьёзность:** Blocker
|
||||
- **Окружение:** Chrome 128, десктоп
|
||||
- **Шаги воспроизведения:**
|
||||
1. Заполнить все поля формы корректными данными
|
||||
2. **Не** отмечать чекбокс "Я согласен с условиями использования"
|
||||
3. Нажать "Создать аккаунт"
|
||||
- **Ожидаемый результат:** Форма блокирует отправку, показывает ошибку под чекбоксом
|
||||
- **Фактический результат:** Форма отправляется успешно, как будто согласие было дано
|
||||
- **Влияние:** Юридический риск — пользователь может зарегистрироваться, не приняв условия использования
|
||||
@@ -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 |
@@ -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 |
|
||||
@@ -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 |
|
||||
@@ -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) |
|
||||
@@ -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) | Принято |
|
||||
@@ -0,0 +1,7 @@
|
||||
### Функциональные / чек-лист
|
||||
|
||||
| TC ID | Шаги | Ожидаемый результат |
|
||||
|---|---|---|
|
||||
| TC-17 | Заполнить всё корректно, оставить чекбокс "Согласен с условиями" **не отмеченным**, отправить | Форма должна показать ошибку "Необходимо принять условия" |
|
||||
| TC-18 | Ввести имя пользователя из 2 символов, отправить | Ошибка валидации (мин. 3 символа) |
|
||||
| TC-19 | Ввести пароль ровно 8 символов с цифрой | Форма принимается |
|
||||
@@ -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") |
|
||||
@@ -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
|
||||
@@ -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. Риски
|
||||
- Отсутствие бэкенда ограничивает проверку до клиентской валидации
|
||||
Reference in New Issue
Block a user