Одна из основных составляющих современного интернета — формы. Они используются во многих сценариях:
- Заказ товара
- Заказ услуги
- Подписка на рассылку
- Отправка сообщений в чат
- Прохождение тестов
- Регистрация и вход на сайт
Это лишь часть того, где используются формы. Это единственный способ прямого взаимодействия с пользователем на сайте или в приложении. По этой причине тестирование форм выходит на передний план, так как невозможность пользоваться сайтом критичнее, чем небольшая ошибка в стилях.
В этом уроке разберем, из чего состоят формы, и как их тестировать. Важно понимать, что невозможно протестировать все возможные варианты значений в большой форме, например, из 20-30 полей. Количество комбинаций разных вариантов в таких формах может быть очень большим.
По этой причине мы будем рассматривать общие рекомендации и самые частые проблемы, которые позволят не тестировать форму на все возможные комбинации, а сразу находить проблемные места.
Формы в HTML
Формы в HTML — большая тема, которая включает десятки тегов и атрибутов. Но мы остановимся на общей концепции элементов и том, чем они отличаются друг от друга. В этом разделе рассмотрим следующие элементы:
- Поля ввода
- Чекбоксы и радиокнопки
- Список
- Кнопка
Если вы заинтересованы в более подробном изучении работы этих элементов, а также в вёрстке, для вас в конце урока будет ссылка на интенсив по вёрстке веб-приложений.
Рассмотрим поля формы на примере формы заказа товара:
Поля ввода
Базовый элемент формы — поле для ввода текста. Внешне они могут быть разными, но их принцип один — дать пользователю однострочное поле для ввода информации. Такие поля используются для ввода небольшой информации: имя, фамилия, Email, номер телефона.
В современном стандарте HTML5 появилось много типов полей, которыми можно пользоваться:
- Текст
- Email
- Числовое значение
- Дата
- Пароль
Эти поля не просто позволяют определить, что нужно ввести. Кроме того, по ним браузеры автоматически проверяют корректность введенных значений. Такая проверка называется валидацией или validation. Если значение в поле не соответствует ожидаемому, то высвечивается ошибка — форма не прошла валидацию.
Представим, что в нашей форме поле Email обязательное и для него указана правильная HTML-разметка. Теперь проверку поля забирает на себя браузер. Если не ввести информацию или ввести некорректный Email, то получится такой результат:
Это касается всей формы. Она размечена с учетом всех правил HTML, поэтому валидируется браузером перед отправкой на сервер. Это удобно, так как отнимает меньше сил по проверке каждого обязательного поля.
Иногда в целях дизайна разработчики указывают специальные атрибуты, чтобы не валидировать форму встроенными средствами браузера. Тогда все валидации и вывод ошибок разработчики делают сами. Это позволяет точнее настроить внешний вид ошибок и их описание. Например, вот как выглядит форма с отключенной валидацией браузером:
Такой подход позволяет лучше контролировать ошибки. Но это накладывает больше ответственности на разработчика, так как для всех типов полей необходимо самостоятельно написать обработчик, который проверит корректность данных. С другой стороны можно проверить абсурдные варианты, например, имя «asjfhalskdfhlkhasdf», так как браузер его пропустит, ведь это текст и поле не пустое.
При тестировании полей для ввода определите, какой элемент перед вами и исходя из этого тестируйте невозможные варианты:
- Для числового поля попробуйте ввести буквы
- В полях для ввода имени введите Email
- В поле для телефона введите имя
Некоторые поля изначально рассчитаны на определенное количество символов. Например, мобильный номер телефона состоит из 11 цифр. Попробуйте ввести большее или меньшее количество цифр.
Всегда старайтесь вводить нелогичные комбинации:
- Поле для ввода цены — введите отрицательное значение
- Адрес доставки — введите адрес из вымышленной вселенной
Чекбоксы и радиокнопки
Это похожие по структуре элементы, у которых есть одно важное отличие: чекбоксы позволяют выбрать несколько вариантов, а радиокнопки — только один:
В нашей форме с помощью чекбоксов сделаны варианты:
- Связаться за день до доставки
- Сохранить информацию для будущих заказов
Пользователь может выбрать оба варианта, один или ни одного. Для чекбоксов отсутствие выбора — это тоже выбор.
Радиокнопки позволяют выбрать только один вариант ответа. В данном случае мы можем выбрать между оплатой карты и оплатой наличными. Их валидация происходит чаще, так как пользователю нужно выбрать один из вариантов взаимодействия. В практике часто встречается ситуация, что одна из радиокнопок сразу выбрана при загрузке формы.
В этом случае четких правил по валидации нет. В вашем проекте может быть обязательный выбор чекбокса или нескольких и отсутствовать валидация у радиокнопок.
При тестировании нужно обращать внимание на тип элемента:
- Если в форме чекбокс, то должна быть возможность выбрать несколько вариантов
- Если в форме радиокнопка, то вариант всегда один. Если разработчик неправильно сверстает форму, то возможна ситуация одновременной оплаты картой и наличными
При тестировании стоит смотреть не только на поведение конкретных элементов, но и на их окружение. В форме радиокнопка отвечает на тип оплаты. Если оплата происходит за наличные, то и части формы с вводом банковской карты быть не должно. Тестировщик всегда смотрит на продукт не только как работник, но и как обычный пользователь. Ищите нелогичности.
Список
Списки — одна из вариаций чекбоксов и радиокнопок, только в другом внешнем представлении. Разработчик может заложить в них один из двух вариантов:
- Множественный выбор из списка
- Один вариант
При тестировании списков можно придерживаться рекомендаций из раздела про чекбоксы и радиокнопки.
В нашей форме есть два списка: «Страна» и «Город». Вот как выглядит выбор стран, если кликнуть по списку:
Здесь видно, что один список серый — он заблокирован. И в этом есть своя логика: нельзя выбрать город до тех пор, пока не выбрана страна. Если же выбрать страну, то открывается возможность выбрать город:
Следите за логикой полей — возможно ли выбрать что-то, если не сделаны предварительные шаги. Так как здесь невозможно вписать свое значение, то основа тестирования строится на логике поведения элементов вокруг списка.
Кнопка
Кнопка — последний в списке, но самый первый по значению элемент. Форму невозможно отправить без нужной кнопки. Когда пользователь нажимает на нее, происходит валидация всех значений в форме. Если всё в порядке, то происходит отправка на сервер, иначе показываются ошибки, как на примерах выше.
Кнопка должна реагировать на текущее состоянии формы. Если пользователь исправил некоторые ошибки, то после отправки должны исчезнуть предупреждения на полях, которые были исправлены:
Иногда форма валидируется сразу, то есть все ошибки пропадают или появляются еще до нажатия на кнопку «Отправить». В этом случае кнопка не должна ничего делать до того момента, как все ошибки не будут исправлены.
При тестировании старайтесь использовать разные сценарии. Проверьте:
- Форма не отправляется со всеми пустыми полями
- С незаполненным одним обязательным полем
- Если форма валидируется в моменте, то не должно происходить отправки в момент исправления последней ошибки
В одном из следующих уроков мы поговорим и о безопасности отправки данных в формах, что является одним из главных пунктов любого тестирования.
Выводы
В этом уроке на примере формы отправки заказа мы рассмотрели, из чего состоит форма, и каким образом ее можно протестировать. Узнали о понятии валидации и как она должна работать.
Вот основные пункты при тестировании форм:
- Проверяйте поля для ввода на невозможных данных. Например: отрицательная цена, буквы в поле для цифр, невозможные имена
- Чекбоксы должны иметь возможность множественного выбора. Если это не так, то функционал должен быть представлен в виде радиокнопки
- Радиокнопки используются для выбора только одного пункта из множества
- Списки повторяют функционал радиокнопок и чекбоксов, только в другом обличии. Их тестирование не отличается
- Старайтесь проверять не только поле формы, но и то, что происходит вокруг него. Представьте себя не в виде тестировщика, а в виде клиента
- Все валидируемые поля должны быть понятны и отображать ошибки, если это необходимо
В будущих уроках мы еще вернемся к форме, когда будем изучать тему безопасности.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 20:52:41 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="heCY1LRUrguZQ5gHmfmlOiI1PEc8Zn3l_PpjcxPOWkJqMVPjRioDay8AvJ-V9lVN4jwR7TRRg0dBGvknQcm9LA";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Формы | Введение в тестирование веб-приложений</title>
<meta name="description" content="Формы / Введение в тестирование веб-приложений: Изучаем работу форм и валидацию, а также распространенные ошибки">
<link rel="canonical" href="https://ru.hexlet.io/courses/web-testing-basics/lessons/forms/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Формы">
<meta property="og:title" content="Введение в тестирование веб-приложений">
<meta property="og:description" content="Формы / Введение в тестирование веб-приложений: Изучаем работу форм и валидацию, а также распространенные ошибки">
<meta property="og:url" content="https://ru.hexlet.io/courses/web-testing-basics/lessons/forms/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ZBBxsUkGFFxT4-JLDYQIIq7XR23e3FobqARkvYlrvBiLwbqGu3i5POWgxtMBi_hVbt5qx9brpLkV5P7p22xbdg" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MCwicHVyIjoiYmxvYl9pZCJ9fQ==--4a16fe638654fb8d5ae09d7e8ab8e16ff228214f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-amico.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--f60f9dfdd11bed62e5573394ea442764a862e2c8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Mobile%20testing-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T20:52:41.647Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"_Z43gR985yLLkEexYC5BJrXj-klmCBs56IRgsa7SvDMST_y27QJKQn3TYylsIbFRderX424_5ZtVZPrl_NVbXQ","topics":[{"id":94989,"title":"Почему в правильных ответах не указано \"При добавлении товара в поле названия товара можно ввести любое значение\" Если я могу указать любые знаки (цифры, буквы, символы, пробел)?","plain_title":"Почему в правильных ответах не указано \"При добавлении товара в поле названия товара можно ввести любое значение\" Если я могу указать любые знаки (цифры, буквы, символы, пробел)? ","creator":{"public_name":"Антон","id":731286,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":185588,"body":"**Антон**, здравствуйте. Проверил, там указан этот пункт.","topic_id":94989}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":90175,"title":"Подскажите, вариант: \"При добавлении товара в поле цены товара можно ввести только корректное число цены товара\" почему неподходит? Если, действительно, форма не дает мне ввести символы, а только цифры от руки или при прокрутки правого ползунка? ","plain_title":"Подскажите, вариант: \"При добавлении товара в поле цены товара можно ввести только корректное число цены товара\" почему неподходит? Если, действительно, форма не дает мне ввести символы, а только цифры от руки или при прокрутки правого ползунка? ","creator":{"public_name":"Наталия","id":527207,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":179104,"body":"А что произойдёт при попытке добавить товар без цены или если в поле цена внести пробелы или всё-таки символы? Должен ли добавляться такой товар?","topic_id":90175}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":83060,"title":"И снова привет!\nВ этом упражнении так же линтер недоволен файлами `/usr/src/app/src/components/AddProductForm.jsx` и `/usr/src/app/src/components/Product.jsx`. Исправьте, пожалуйста, его капризы :-)","plain_title":"И снова привет! В этом упражнении так же линтер недоволен файлами /usr/src/app/src/components/AddProductForm.jsx и /usr/src/app/src/components/Product.jsx. Исправьте, пожалуйста, его капризы :-) ","creator":{"public_name":"Елена","id":399943,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":168886,"body":"Приветствую! Спасибо, что написали! Исправил ошибки.","topic_id":83060},{"creator":{"public_name":"Anastasia Glazova","id":587471,"is_tutor":false},"id":168847,"body":"Тоже выполнила упражнение, но линтер ворчит","topic_id":83060}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":87722,"title":"Что имеется в ввиду под спиннером. Значок перезагрузки или что-то другое?\n\"При редактировании товара после отправки формы, отображается спиннер\"\nУ меня ничего лишнего при редактировании товара не отображается. Только значок перезагрузки. Или это и есть спиннер? Если это так, то название варианта не совсем ясное.","plain_title":"Что имеется в ввиду под спиннером. Значок перезагрузки или что-то другое? \"При редактировании товара после отправки формы, отображается спиннер\" У меня ничего лишнего при редактировании товара не отображается. Только значок перезагрузки. Или это и есть спиннер? Если это так, то название варианта не совсем ясное. ","creator":{"public_name":"Петр","id":618081,"is_tutor":false},"comments":[{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":175713,"body":"**Петр**, \n\nпримеры спиннеров можно посмотреть [тут](https://getbootstrap.com/docs/5.0/components/spinners/) ","topic_id":87722},{"creator":{"public_name":"Olga Pejenkova","id":364375,"is_tutor":true},"id":175809,"body":"**Петр**, \n\nСпасибо, хорошее предложение! Я добавила уточнение в описание задания.","topic_id":87722},{"creator":{"public_name":"Петр","id":618081,"is_tutor":false},"id":175735,"body":"А нельзя ли написать просто, что \"проводится перезагрузка формы\". Как и писали в предыдущих уроках. Или же где-то ранее или тут же пояснить, что спиннер - это значок перезагрузки или загрузки.","topic_id":87722}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":81337,"title":"Во всех предложениях содержащих части вида \"При загрузке страницы, \", \"При (добавлении|редактировании) (нового)? товара, \", \"В форме редактирования товара, \", \"При сохранении отредактированного товара,\" запятая не нужна.\n\n\"На время загрузки списка товаров, отображается спиннер и больше ничего\" - Во время загрузки списка товаров отображается только спиннер, и больше ничего.\n\n\"При редактировании товара, после отправки формы, отображается спиннер\" - обе запятые лишние.\n","plain_title":"Во всех предложениях содержащих части вида \"При загрузке страницы, \", \"При (добавлении|редактировании) (нового)? товара, \", \"В форме редактирования товара, \", \"При сохранении отредактированного товара,\" запятая не нужна. \"На время загрузки списка товаров, отображается спиннер и больше ничего\" - Во время загрузки списка товаров отображается только спиннер, и больше ничего. \"При редактировании товара, после отправки формы, отображается спиннер\" - обе запятые лишние. ","creator":{"public_name":"Дмитрий Бражников","id":560112,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":166368,"body":"**Дмитрий Бражников**, здравствуйте. Спасибо, поправил. Если что, у нас есть для таких ошибок специальная форма отправки. [Она открывается по нажатию на ctrl+enter после выделения текста.](https://help.hexlet.io/ru/articles/111160-otpravit-soobshchenie-ob-oshibke) ","topic_id":81337}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":88125,"title":"Подскажите, как проверить отправились ли мои данные при заполнении формы на сайт , после нажатия кнопки","plain_title":"Подскажите, как проверить отправились ли мои данные при заполнении формы на сайт , после нажатия кнопки ","creator":{"public_name":"Екатерина Шикина","id":634989,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":176226,"body":"**Екатерина Шикина**, в этом задании достаточно проверять список товаров. Если там появился добавленный товар, значит форма отработала.","topic_id":88125}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":90362,"title":"Добрый день.\nУрок \"Формы\".\nПри попытке перехода к заданию (кнопка \"WEB доступ\") выходит ответ \"404 page not found\"","plain_title":"Добрый день. Урок \"Формы\". При попытке перехода к заданию (кнопка \"WEB доступ\") выходит ответ \"404 page not found\" ","creator":{"public_name":"Andrey","id":664884,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksandr Litvinov","id":117182,"is_tutor":true},"id":179501,"body":"Подскажите, пожалуйста, сейчас проблема у вас сохраняется? Пробовали сбросить упражнение?","topic_id":90362},{"creator":{"public_name":"Evgenii Iuzhakov","id":675596,"is_tutor":false},"id":184041,"body":"**Aleksandr Litvinov**, Александр, добрый день, у меня такая же ошибка, в разных браузерах пробовал, нигде не удалось загрузить страницу.","topic_id":90362},{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":184116,"body":"**Evgenii Iuzhakov**, здравствуйте. Пробовали сделать сброс упражнения? В самом упражнении не пишет никаких ошибок?","topic_id":90362}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":94035,"title":"В разделе теории о формах говорится, что без кнопки невозможно отправить на сервер данные из формы и что это один и важнеших элементов формы. В то же время в последующих вопросах на проверку ответ на вопрос \"обязательна ли кнопка в форме\" ответ \"да\" обозначается как ошибка и правильным ответом считается \"нет, форму можно отправить нажав Enter\". Как там в итоге?","plain_title":"В разделе теории о формах говорится, что без кнопки невозможно отправить на сервер данные из формы и что это один и важнеших элементов формы. В то же время в последующих вопросах на проверку ответ на вопрос \"обязательна ли кнопка в форме\" ответ \"да\" обозначается как ошибка и правильным ответом считается \"нет, форму можно отправить нажав Enter\". Как там в итоге? ","creator":{"public_name":"Anlvk","id":659470,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":184349,"body":"**Anlvk**, здравствуйте! В тесте ошибка. Я заменил этот вопрос. Спасибо, что обратили на это внимание!","topic_id":94035}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":95702,"title":"Не думаю, что ответ \"В форме редактирования товара поля изначально заполнены текущими данными\" правильный. Изначально я его не ставил и никак не мог завершить задание. Пришлось ковырять ваш закодированный pbase64 файл с ответами, чтобы понять, где я застрял. Могу пояснить, почему я не считаю этот ответ правильным. Достаточно ввести товар с пустым названием и пустой ценой, а потом попробовать отредактировать его - и можно увидеть, что поля Название и Цена не пустые, а заполнены подсказкой. Да, с формы в качестве ответа это не уйдет, но я как ТУПОЙ ПОТРЕБИТЕЛЬ не должен думать, так ли это.","plain_title":"Не думаю, что ответ \"В форме редактирования товара поля изначально заполнены текущими данными\" правильный. Изначально я его не ставил и никак не мог завершить задание. Пришлось ковырять ваш закодированный pbase64 файл с ответами, чтобы понять, где я застрял. Могу пояснить, почему я не считаю этот ответ правильным. Достаточно ввести товар с пустым названием и пустой ценой, а потом попробовать отредактировать его - и можно увидеть, что поля Название и Цена не пустые, а заполнены подсказкой. Да, с формы в качестве ответа это не уйдет, но я как ТУПОЙ ПОТРЕБИТЕЛЬ не должен думать, так ли это. ","creator":{"public_name":"Игорь Пустошило","id":744978,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":186456,"body":"**Игорь Пустошило**, здравствуйте. Поля не заполнены подсказкой. Если поле пустое, то название поля увеличивает шрифт, если нет фокуса на этом поле. Точно так же это происходит, если удалить значение в поле. ","topic_id":95702}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}},{"id":96707,"title":"Такой вопрос: \n\"При сохранении отредактированного товара открывается список товаров без перезагрузки страницы\"\nДля меня если крутится спинер - это равно перезагрузке страницы.. или это не так? Можно пожалуйста пояснить этот момент. Я не совсем понимаю","plain_title":"Такой вопрос: \"При сохранении отредактированного товара открывается список товаров без перезагрузки страницы\" Для меня если крутится спинер - это равно перезагрузке страницы.. или это не так? Можно пожалуйста пояснить этот момент. Я не совсем понимаю ","creator":{"public_name":"Alice Yanchevskaya","id":113343,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Gagarinov","id":75907,"is_tutor":true},"id":187674,"body":"**Alice Yanchevskaya**, здравствуйте. Спиннер не означает перегрузку страницы. Перегрузка страницы вызывает за собой перегрузку всех ресурсов: хтмл, javascript-кода, картинок и т.д., как если бы вы нажали перезагрузить страницу в браузере. Обычно перезагрузку можно отследить через статус самой вкладки в браузере, либо через вкладку Networks в devtools.","topic_id":96707}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Формы","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2123,"slug":"web_testing_basics_forms_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"В этом задании вам нужно протестировать SPA-приложение с каталогом товаров и новым функционалом. При загрузке страницы появляется список товаров. В таблице товара нужно проверить форму добавления нового товара, заполняется название товара и цена. В списке товаров есть кнопка редактирования товаров и удаления.\n\nПриложение должно соответствовать нескольким требованиям:\n\n* На время загрузки списка товаров на странице отображается [спиннер](https://getbootstrap.com/docs/5.0/components/spinners/)(значок загрузки) и больше ничего\n* При добавлении товара в поле названия товара можно ввести любое значение\n* При добавлении товара в поле цены товара можно ввести только корректное число цены товара\n* При добавлении нового товара форма добавления блокируется (кнопка добавления товара заблокирована), пока товар не будет добавлен в список\n* Переход в редактирование товара происходит без перезагрузки страницы\n* В форме редактирования товара поля изначально заполнены текущими данными\n* В форме редактирования работает валидация, нельзя указать пустое название товара и цену\n* При редактировании товара в поле названия товара можно ввести любое значение\n* При редактировании товара в поле цены товара можно ввести только корректное число цены товара\n* При редактировании товара после отправки формы, отображается спиннер\n* При сохранении отредактированного товара открывается список товаров без перезагрузки страницы\n* Удаление товара происходит с подтверждением\n\n## solution\n\nЗапишите в файл _solution_ все требования, которые соблюдаются. Помните, что одно различие — одна строчка. Сохраняйте заглавные буквы и кавычки. Самый простой способ — скопировать правильные варианты в файл.\n\nПример файла с решением:\n\n```\nНа время загрузки списка товаров отображается спиннер и больше ничего\nПри добавлении нового товара форма добавления блокируется, пока товар не будет добавлен в список\n```\n","prepared_readme":"В этом задании вам нужно протестировать SPA-приложение с каталогом товаров и новым функционалом. При загрузке страницы появляется список товаров. В таблице товара нужно проверить форму добавления нового товара, заполняется название товара и цена. В списке товаров есть кнопка редактирования товаров и удаления.\n\nПриложение должно соответствовать нескольким требованиям:\n\n* На время загрузки списка товаров на странице отображается [спиннер](https://getbootstrap.com/docs/5.0/components/spinners/)(значок загрузки) и больше ничего\n* При добавлении товара в поле названия товара можно ввести любое значение\n* При добавлении товара в поле цены товара можно ввести только корректное число цены товара\n* При добавлении нового товара форма добавления блокируется (кнопка добавления товара заблокирована), пока товар не будет добавлен в список\n* Переход в редактирование товара происходит без перезагрузки страницы\n* В форме редактирования товара поля изначально заполнены текущими данными\n* В форме редактирования работает валидация, нельзя указать пустое название товара и цену\n* При редактировании товара в поле названия товара можно ввести любое значение\n* При редактировании товара в поле цены товара можно ввести только корректное число цены товара\n* При редактировании товара после отправки формы, отображается спиннер\n* При сохранении отредактированного товара открывается список товаров без перезагрузки страницы\n* Удаление товара происходит с подтверждением\n\n## solution\n\nЗапишите в файл _solution_ все требования, которые соблюдаются. Помните, что одно различие — одна строчка. Сохраняйте заглавные буквы и кавычки. Самый простой способ — скопировать правильные варианты в файл.\n\nПример файла с решением:\n\n```\nНа время загрузки списка товаров отображается спиннер и больше ничего\nПри добавлении нового товара форма добавления блокируется, пока товар не будет добавлен в список\n```\n","has_solution":true,"entity_name":"Формы"},"units":[{"id":7287,"name":"theory","url":"/courses/web-testing-basics/lessons/forms/theory_unit"},{"id":7519,"name":"quiz","url":"/courses/web-testing-basics/lessons/forms/quiz_unit"},{"id":7380,"name":"exercise","url":"/courses/web-testing-basics/lessons/forms/exercise_unit"}],"links":[{"id":423123,"name":"Чек-лист по тестированию веб-форм","url":"https://ru.hexlet.io/blog/posts/chek-list-po-testirovaniyu-web-form"}],"ordered_units":[{"id":7287,"name":"theory","url":"/courses/web-testing-basics/lessons/forms/theory_unit"},{"id":7519,"name":"quiz","url":"/courses/web-testing-basics/lessons/forms/quiz_unit"},{"id":7380,"name":"exercise","url":"/courses/web-testing-basics/lessons/forms/exercise_unit"}],"id":3275,"slug":"forms","state":"approved","name":"Формы","course_order":600,"goal":"Изучаем работу форм и валидацию, а также распространенные ошибки","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"774792692","theory":"Одна из основных составляющих современного интернета — формы. Они используются во многих сценариях:\n\n* Заказ товара\n* Заказ услуги\n* Подписка на рассылку\n* Отправка сообщений в чат\n* Прохождение тестов\n* Регистрация и вход на сайт\n\nЭто лишь часть того, где используются формы. Это единственный способ прямого взаимодействия с пользователем на сайте или в приложении. По этой причине тестирование форм выходит на передний план, так как невозможность пользоваться сайтом критичнее, чем небольшая ошибка в стилях.\n\nВ этом уроке разберем, из чего состоят формы, и как их тестировать. Важно понимать, что невозможно протестировать все возможные варианты значений в большой форме, например, из 20-30 полей. Количество комбинаций разных вариантов в таких формах может быть очень большим.\n\nПо этой причине мы будем рассматривать общие рекомендации и самые частые проблемы, которые позволят не тестировать форму на все возможные комбинации, а сразу находить проблемные места.\n\n## Формы в HTML\n\nФормы в HTML — большая тема, которая включает десятки тегов и атрибутов. Но мы остановимся на общей концепции элементов и том, чем они отличаются друг от друга. В этом разделе рассмотрим следующие элементы:\n\n* Поля ввода\n* Чекбоксы и радиокнопки\n* Список\n* Кнопка\n\nЕсли вы заинтересованы в более подробном изучении работы этих элементов, а также в вёрстке, для вас в конце урока будет ссылка на интенсив по вёрстке веб-приложений.\n\nРассмотрим поля формы на примере формы заказа товара:\n\n\n\n### Поля ввода\n\nБазовый элемент формы — поле для ввода текста. Внешне они могут быть разными, но их принцип один — дать пользователю однострочное поле для ввода информации. Такие поля используются для ввода небольшой информации: имя, фамилия, Email, номер телефона.\n\nВ современном стандарте HTML5 появилось много типов полей, которыми можно пользоваться:\n\n* Текст\n* Email\n* Числовое значение\n* Дата\n* Пароль\n\nЭти поля не просто позволяют определить, что нужно ввести. Кроме того, по ним браузеры автоматически проверяют корректность введенных значений. Такая проверка называется **валидацией** или **validation**. Если значение в поле не соответствует ожидаемому, то высвечивается ошибка — форма не прошла валидацию.\n\nПредставим, что в нашей форме поле Email обязательное и для него указана правильная HTML-разметка. Теперь проверку поля забирает на себя браузер. Если не ввести информацию или ввести некорректный Email, то получится такой результат:\n\n\n\nЭто касается всей формы. Она размечена с учетом всех правил HTML, поэтому валидируется браузером перед отправкой на сервер. Это удобно, так как отнимает меньше сил по проверке каждого обязательного поля.\n\nИногда в целях дизайна разработчики указывают специальные атрибуты, чтобы не валидировать форму встроенными средствами браузера. Тогда все валидации и вывод ошибок разработчики делают сами. Это позволяет точнее настроить внешний вид ошибок и их описание. Например, вот как выглядит форма с отключенной валидацией браузером:\n\n\n\nТакой подход позволяет лучше контролировать ошибки. Но это накладывает больше ответственности на разработчика, так как для всех типов полей необходимо самостоятельно написать обработчик, который проверит корректность данных. С другой стороны можно проверить абсурдные варианты, например, имя «asjfhalskdfhlkhasdf», так как браузер его пропустит, ведь это текст и поле не пустое.\n\nПри тестировании полей для ввода определите, какой элемент перед вами и исходя из этого тестируйте невозможные варианты:\n\n* Для числового поля попробуйте ввести буквы\n* В полях для ввода имени введите Email\n* В поле для телефона введите имя\n\nНекоторые поля изначально рассчитаны на определенное количество символов. Например, мобильный номер телефона состоит из 11 цифр. Попробуйте ввести большее или меньшее количество цифр.\n\nВсегда старайтесь вводить нелогичные комбинации:\n\n* Поле для ввода цены — введите отрицательное значение\n* Адрес доставки — введите адрес из вымышленной вселенной\n\n### Чекбоксы и радиокнопки\n\nЭто похожие по структуре элементы, у которых есть одно важное отличие: чекбоксы позволяют выбрать несколько вариантов, а радиокнопки — только один:\n\n\n\nВ нашей форме с помощью чекбоксов сделаны варианты:\n\n* Связаться за день до доставки\n* Сохранить информацию для будущих заказов\n\nПользователь может выбрать оба варианта, один или ни одного. Для чекбоксов отсутствие выбора — это тоже выбор.\n\nРадиокнопки позволяют выбрать только один вариант ответа. В данном случае мы можем выбрать между оплатой карты и оплатой наличными. Их валидация происходит чаще, так как пользователю нужно выбрать один из вариантов взаимодействия. В практике часто встречается ситуация, что одна из радиокнопок сразу выбрана при загрузке формы.\n\nВ этом случае четких правил по валидации нет. В вашем проекте может быть обязательный выбор чекбокса или нескольких и отсутствовать валидация у радиокнопок.\n\nПри тестировании нужно обращать внимание на тип элемента:\n\n* Если в форме чекбокс, то должна быть возможность выбрать несколько вариантов\n* Если в форме радиокнопка, то вариант всегда один. Если разработчик неправильно сверстает форму, то возможна ситуация одновременной оплаты картой и наличными\n\nПри тестировании стоит смотреть не только на поведение конкретных элементов, но и на их окружение. В форме радиокнопка отвечает на тип оплаты. Если оплата происходит за наличные, то и части формы с вводом банковской карты быть не должно. Тестировщик всегда смотрит на продукт не только как работник, но и как обычный пользователь. Ищите нелогичности.\n\n### Список\n\nСписки — одна из вариаций чекбоксов и радиокнопок, только в другом внешнем представлении. Разработчик может заложить в них один из двух вариантов:\n\n* Множественный выбор из списка\n* Один вариант\n\nПри тестировании списков можно придерживаться рекомендаций из раздела про чекбоксы и радиокнопки.\n\nВ нашей форме есть два списка: «Страна» и «Город». Вот как выглядит выбор стран, если кликнуть по списку:\n\n\n\nЗдесь видно, что один список серый — он заблокирован. И в этом есть своя логика: нельзя выбрать город до тех пор, пока не выбрана страна. Если же выбрать страну, то открывается возможность выбрать город:\n\n\n\nСледите за логикой полей — возможно ли выбрать что-то, если не сделаны предварительные шаги. Так как здесь невозможно вписать свое значение, то основа тестирования строится на логике поведения элементов вокруг списка.\n\n### Кнопка\n\nКнопка — последний в списке, но самый первый по значению элемент. Форму невозможно отправить без нужной кнопки. Когда пользователь нажимает на нее, происходит валидация всех значений в форме. Если всё в порядке, то происходит отправка на сервер, иначе показываются ошибки, как на примерах выше.\n\nКнопка должна реагировать на текущее состоянии формы. Если пользователь исправил некоторые ошибки, то после отправки должны исчезнуть предупреждения на полях, которые были исправлены:\n\n\n\nИногда форма валидируется сразу, то есть все ошибки пропадают или появляются еще до нажатия на кнопку «Отправить». В этом случае кнопка не должна ничего делать до того момента, как все ошибки не будут исправлены.\n\nПри тестировании старайтесь использовать разные сценарии. Проверьте:\n\n* Форма не отправляется со всеми пустыми полями\n* С незаполненным одним обязательным полем\n* Если форма валидируется в моменте, то не должно происходить отправки в момент исправления последней ошибки\n\nВ одном из следующих уроков мы поговорим и о безопасности отправки данных в формах, что является одним из главных пунктов любого тестирования.\n\n## Выводы\n\nВ этом уроке на примере формы отправки заказа мы рассмотрели, из чего состоит форма, и каким образом ее можно протестировать. Узнали о понятии валидации и как она должна работать.\n\nВот основные пункты при тестировании форм:\n\n* Проверяйте поля для ввода на невозможных данных. Например: отрицательная цена, буквы в поле для цифр, невозможные имена\n* Чекбоксы должны иметь возможность множественного выбора. Если это не так, то функционал должен быть представлен в виде радиокнопки\n* Радиокнопки используются для выбора только одного пункта из множества\n* Списки повторяют функционал радиокнопок и чекбоксов, только в другом обличии. Их тестирование не отличается\n* Старайтесь проверять не только поле формы, но и то, что происходит вокруг него. Представьте себя не в виде тестировщика, а в виде клиента\n* Все валидируемые поля должны быть понятны и отображать ошибки, если это необходимо\n\nВ будущих уроках мы еще вернемся к форме, когда будем изучать тему безопасности.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":7261,"name":"theory","url":"/courses/web-testing-basics/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":7261,"name":"theory","url":"/courses/web-testing-basics/lessons/intro/theory_unit"}],"id":3262,"slug":"intro","state":"approved","name":"Введение","course_order":60,"goal":"Знакомимся с темой курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В этом курсе мы поговорим о ручном тестировании: из чего оно состоит, какие инструменты используются и на что обращать внимание при тестировании.\n\nНесмотря на то, что курс является вводным, он полезен как для разработчиков, так и для новичков:\n\n* **Новичкам в тестировании** — познакомиться с основами тестирования, выяснить, из чего оно состоит, и какие аспекты приложения или сайта требует\n* **Начинающим разработчикам** — выявить типичные проблемы при создании сайта, которых можно избежать уже на этапе постановки задачи. Например, как ускорить загрузку сайта, как обеспечить базовую защиту от атак и так далее\n* **Кто уже тестирует** — структурировать информацию, найти новые подходы к тестированию и почерпнуть новые темы, которые пригодятся в работе\n\nПроцесс тестирования в крупных приложениях неразрывно связан с разработкой, и на это есть три причины.\n\n### Качество\n\nБольшинство проектов в интернете рассчитано на массовое использование, будь то сайт с рецептами или приложение для просмотра сериалов. Такие проекты постоянно вводят новые функции и разделы, из-за чего сложно отслеживать их работоспособность.\n\nПредставьте свои ощущения, если вместо рецепта ваших любимых кексиков откроется страница с контактами или количество ингредиентов будет рассчитываться неправильно.\n\nТак как разработчики заняты разработкой продукта, то главными героями являются тестировщики. Их задача — проверить новый функционал и убедиться, что старый работает исправно. После этого продукт можно открывать для всех пользователей.\n\n### Понятность\n\nРазработчики сделали возможность изменения языка озвучки для сериала. Для компании важно, чтобы этот функционал не просто работал, но и был доступен для понимания. Он не должен быть скрыт за кучей настроек или расположен в том месте, где его не ожидаешь.\n\nТестировщики, как фокус-группа, не просто находят технические проблемы, но и следят, чтобы функционал был понятен и прост для использования. Такая область, как понятность и удобность использования, еще называется юзабилити, от английского usability — удобство использования.\n\n### Безопасность\n\nСайт или приложение = большое количество кода. Чем больше проект и больше в нем данных, тем больше встает вопрос о безопасности. Если продукт можно взломать простыми способами, которые написаны на первой странице поисковой системы, то будьте уверены — кто-то воспользуется этим.\n\nЕсли злоумышленник получит персональные данные, то может пострадать сайт, приложение, репутация или пользователи.\n\nОдна из задач тестировщика — проверить базовые виды атак, которые можно исправить сразу. Например, добавление кода на JavaScript в поля формы.\n\n## Что изучается в курсе\n\nВ процессе прохождения курса мы изучим, что тестируют специалисты, какие используют инструменты и на что стоит обращать внимание при заходе на сайт. Также изучим:\n\n* Как проверить сайт на разных устройствах: компьютер, планшет, смартфоны\n* Почему сайт может отличаться в разных программах и устройствах\n* Из чего состоит сайт, что такое HTML и CSS\n* Какой инструмент в браузерах необходим для тестировщиков\n* С какими проблемами безопасности можно встретиться и как их выявлять\n* Какие типы тестирования существуют\n"},"id":245,"slug":"web-testing-basics","challenges_count":4,"name":"Введение в тестирование веб-приложений","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"free","description":"На этом курсе вы узнаете о веб-приложениях и изучите основы их тестирования. \nВ процессе прохождения курса мы изучим, что тестируют специалисты, какие используют инструменты и на что стоит обращать внимание при заходе на сайт. Вы узнаете, как на работоспособность веб-приложения влияет многообразие устройств, скорость соединения и другие условия. Вы познакомитесь с основами юзабилити и доступности. Вы научитесь находить уязвимости, ошибки верстки и логики работы веб-приложения. Вы научитесь диагностировать работу сайта с помощью инструментов Devtools. \nОсвоить тестирование веб-приложений с нуля непросто, поэтому с первого же урока вы будете подкреплять теорию практикой. В упражнениях курса вы будете проводить тестирование различных страниц, каталогов, форм и приложений.","kind":"basic","updated_at":"2026-01-20T11:40:26.695Z","language":"other","duration_cache":62880,"skills":["Использовать DevTools для диагностики запросов","Обнаруживать ошибки в верстке и UX приложения","Тестировать SPA-приложения","Находить уязвимости веб-приложений, например, XSS"],"keywords":[],"lessons_count":15,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjIyMSwicHVyIjoiYmxvYl9pZCJ9fQ==--23e3df9feea39eaed8d150557abd4377bc99b425/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":56,"slug":"qa-engineer","title":"Инженер по тестированию","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":40,"duration_in_months":4},"id":100,"slug":"qa-engineer","title":"Инженер по ручному тестированию","subtitle":"Изучите виды тестирования, тест-кейсы, DevTools, Postman, SQL, Git и HTTP/HTTPS.","subtitle_for_lists":"Изучите виды тестирования, тест-кейсы, DevTools, Postman, SQL, Git и HTTP/HTTPS.","locale":"ru","current":true,"duration_in_months_text":"4 месяца","stack_slug":"qa-engineer","price_text":"от 3 368 ₽","duration_text":"4 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MCwicHVyIjoiYmxvYl9pZCJ9fQ==--4a16fe638654fb8d5ae09d7e8ab8e16ff228214f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-amico.png"},{"stack":{"id":121,"slug":"testing-basics-free","title":"Основы тестирования","audience":"for_beginners","start_type":"anytime","pricing_model":"free","priority":"low","kind":"track","state":"published","stack_state":"finished","order":1000,"duration_in_months":1},"id":200,"slug":"testing-basics-free","title":"Основы тестирования","subtitle":"Изучите основы тестирование, HTML/CSS, DevTools и веб-стандарты","subtitle_for_lists":"","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"testing-basics-free","price_text":"Бесплатно","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--f60f9dfdd11bed62e5573394ea442764a862e2c8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Mobile%20testing-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":true,"accessToCourseExists":true},"url":"/courses/web-testing-basics/lessons/forms/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Введение в тестирование веб-приложений</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Формы</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Формы","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Введение в тестирование веб-приложений"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div style="margin-bottom:var(--mantine-spacing-xl)" class=""><div class="ratio ratio-16x9"><iframe width="100%" height="auto" src="//player.vimeo.com/video/774792692" loading="lazy" allowFullScreen="" title="video"></iframe></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Одна из основных составляющих современного интернета — формы. Они используются во многих сценариях:</p>
<ul>
<li>Заказ товара</li>
<li>Заказ услуги</li>
<li>Подписка на рассылку</li>
<li>Отправка сообщений в чат</li>
<li>Прохождение тестов</li>
<li>Регистрация и вход на сайт</li>
</ul>
<p>Это лишь часть того, где используются формы. Это единственный способ прямого взаимодействия с пользователем на сайте или в приложении. По этой причине тестирование форм выходит на передний план, так как невозможность пользоваться сайтом критичнее, чем небольшая ошибка в стилях.</p>
<p>В этом уроке разберем, из чего состоят формы, и как их тестировать. Важно понимать, что невозможно протестировать все возможные варианты значений в большой форме, например, из 20-30 полей. Количество комбинаций разных вариантов в таких формах может быть очень большим.</p>
<p>По этой причине мы будем рассматривать общие рекомендации и самые частые проблемы, которые позволят не тестировать форму на все возможные комбинации, а сразу находить проблемные места.</p>
<h2 id="heading-2-1">Формы в HTML</h2>
<p>Формы в HTML — большая тема, которая включает десятки тегов и атрибутов. Но мы остановимся на общей концепции элементов и том, чем они отличаются друг от друга. В этом разделе рассмотрим следующие элементы:</p>
<ul>
<li>Поля ввода</li>
<li>Чекбоксы и радиокнопки</li>
<li>Список</li>
<li>Кнопка</li>
</ul>
<p>Если вы заинтересованы в более подробном изучении работы этих элементов, а также в вёрстке, для вас в конце урока будет ссылка на интенсив по вёрстке веб-приложений.</p>
<p>Рассмотрим поля формы на примере формы заказа товара:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQxMSwicHVyIjoiYmxvYl9pZCJ9fQ==--5bceef8c53da0522fcb043a703e1a5116bbc5de1/form.png" alt="Пример формы на HTML" loading="lazy"/></p>
<h3 id="heading-3-2">Поля ввода</h3>
<p>Базовый элемент формы — поле для ввода текста. Внешне они могут быть разными, но их принцип один — дать пользователю однострочное поле для ввода информации. Такие поля используются для ввода небольшой информации: имя, фамилия, Email, номер телефона.</p>
<p>В современном стандарте HTML5 появилось много типов полей, которыми можно пользоваться:</p>
<ul>
<li>Текст</li>
<li>Email</li>
<li>Числовое значение</li>
<li>Дата</li>
<li>Пароль</li>
</ul>
<p>Эти поля не просто позволяют определить, что нужно ввести. Кроме того, по ним браузеры автоматически проверяют корректность введенных значений. Такая проверка называется <strong>валидацией</strong> или <strong>validation</strong>. Если значение в поле не соответствует ожидаемому, то высвечивается ошибка — форма не прошла валидацию.</p>
<p>Представим, что в нашей форме поле Email обязательное и для него указана правильная HTML-разметка. Теперь проверку поля забирает на себя браузер. Если не ввести информацию или ввести некорректный Email, то получится такой результат:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQxNCwicHVyIjoiYmxvYl9pZCJ9fQ==--fc5db73099708bbe38ff74533d5d45fa901a3f33/form-email-valid.jpg" alt="Валидация Email" loading="lazy"/></p>
<p>Это касается всей формы. Она размечена с учетом всех правил HTML, поэтому валидируется браузером перед отправкой на сервер. Это удобно, так как отнимает меньше сил по проверке каждого обязательного поля.</p>
<p>Иногда в целях дизайна разработчики указывают специальные атрибуты, чтобы не валидировать форму встроенными средствами браузера. Тогда все валидации и вывод ошибок разработчики делают сами. Это позволяет точнее настроить внешний вид ошибок и их описание. Например, вот как выглядит форма с отключенной валидацией браузером:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQxOSwicHVyIjoiYmxvYl9pZCJ9fQ==--b1abc88462acb1c07aeb66d4bcce6030dde8ca7f/form-error.png" alt="Валидация формы Bootstrap" loading="lazy"/></p>
<p>Такой подход позволяет лучше контролировать ошибки. Но это накладывает больше ответственности на разработчика, так как для всех типов полей необходимо самостоятельно написать обработчик, который проверит корректность данных. С другой стороны можно проверить абсурдные варианты, например, имя «asjfhalskdfhlkhasdf», так как браузер его пропустит, ведь это текст и поле не пустое.</p>
<p>При тестировании полей для ввода определите, какой элемент перед вами и исходя из этого тестируйте невозможные варианты:</p>
<ul>
<li>Для числового поля попробуйте ввести буквы</li>
<li>В полях для ввода имени введите Email</li>
<li>В поле для телефона введите имя</li>
</ul>
<p>Некоторые поля изначально рассчитаны на определенное количество символов. Например, мобильный номер телефона состоит из 11 цифр. Попробуйте ввести большее или меньшее количество цифр.</p>
<p>Всегда старайтесь вводить нелогичные комбинации:</p>
<ul>
<li>Поле для ввода цены — введите отрицательное значение</li>
<li>Адрес доставки — введите адрес из вымышленной вселенной</li>
</ul>
<h3 id="heading-3-3">Чекбоксы и радиокнопки</h3>
<p>Это похожие по структуре элементы, у которых есть одно важное отличие: чекбоксы позволяют выбрать несколько вариантов, а радиокнопки — только один:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQyMywicHVyIjoiYmxvYl9pZCJ9fQ==--40695f15880293c9d3563c1c21e2b8c9b81e30c2/form-money.png" alt="Радиокнопка в форме" loading="lazy"/></p>
<p>В нашей форме с помощью чекбоксов сделаны варианты:</p>
<ul>
<li>Связаться за день до доставки</li>
<li>Сохранить информацию для будущих заказов</li>
</ul>
<p>Пользователь может выбрать оба варианта, один или ни одного. Для чекбоксов отсутствие выбора — это тоже выбор.</p>
<p>Радиокнопки позволяют выбрать только один вариант ответа. В данном случае мы можем выбрать между оплатой карты и оплатой наличными. Их валидация происходит чаще, так как пользователю нужно выбрать один из вариантов взаимодействия. В практике часто встречается ситуация, что одна из радиокнопок сразу выбрана при загрузке формы.</p>
<p>В этом случае четких правил по валидации нет. В вашем проекте может быть обязательный выбор чекбокса или нескольких и отсутствовать валидация у радиокнопок.</p>
<p>При тестировании нужно обращать внимание на тип элемента:</p>
<ul>
<li>Если в форме чекбокс, то должна быть возможность выбрать несколько вариантов</li>
<li>Если в форме радиокнопка, то вариант всегда один. Если разработчик неправильно сверстает форму, то возможна ситуация одновременной оплаты картой и наличными</li>
</ul>
<p>При тестировании стоит смотреть не только на поведение конкретных элементов, но и на их окружение. В форме радиокнопка отвечает на тип оплаты. Если оплата происходит за наличные, то и части формы с вводом банковской карты быть не должно. Тестировщик всегда смотрит на продукт не только как работник, но и как обычный пользователь. Ищите нелогичности.</p>
<h3 id="heading-3-4">Список</h3>
<p>Списки — одна из вариаций чекбоксов и радиокнопок, только в другом внешнем представлении. Разработчик может заложить в них один из двух вариантов:</p>
<ul>
<li>Множественный выбор из списка</li>
<li>Один вариант</li>
</ul>
<p>При тестировании списков можно придерживаться рекомендаций из раздела про чекбоксы и радиокнопки.</p>
<p>В нашей форме есть два списка: «Страна» и «Город». Вот как выглядит выбор стран, если кликнуть по списку:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--7e15eeece86eefab704834838088acb8344b816e/form-select.png" alt="Список в форме" loading="lazy"/></p>
<p>Здесь видно, что один список серый — он заблокирован. И в этом есть своя логика: нельзя выбрать город до тех пор, пока не выбрана страна. Если же выбрать страну, то открывается возможность выбрать город:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQzMCwicHVyIjoiYmxvYl9pZCJ9fQ==--60ce9e793f62a3d2a584594c4a91d8f7f5cc5585/form-select-2.png" alt="Список в форме" loading="lazy"/></p>
<p>Следите за логикой полей — возможно ли выбрать что-то, если не сделаны предварительные шаги. Так как здесь невозможно вписать свое значение, то основа тестирования строится на логике поведения элементов вокруг списка.</p>
<h3 id="heading-3-5">Кнопка</h3>
<p>Кнопка — последний в списке, но самый первый по значению элемент. Форму невозможно отправить без нужной кнопки. Когда пользователь нажимает на нее, происходит валидация всех значений в форме. Если всё в порядке, то происходит отправка на сервер, иначе показываются ошибки, как на примерах выше.</p>
<p>Кнопка должна реагировать на текущее состоянии формы. Если пользователь исправил некоторые ошибки, то после отправки должны исчезнуть предупреждения на полях, которые были исправлены:</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NjQzMywicHVyIjoiYmxvYl9pZCJ9fQ==--f84a0fa5f21a00a73892480612e2e05061997646/form-error-3.png" alt="Валидация формы Bootstrap" loading="lazy"/></p>
<p>Иногда форма валидируется сразу, то есть все ошибки пропадают или появляются еще до нажатия на кнопку «Отправить». В этом случае кнопка не должна ничего делать до того момента, как все ошибки не будут исправлены.</p>
<p>При тестировании старайтесь использовать разные сценарии. Проверьте:</p>
<ul>
<li>Форма не отправляется со всеми пустыми полями</li>
<li>С незаполненным одним обязательным полем</li>
<li>Если форма валидируется в моменте, то не должно происходить отправки в момент исправления последней ошибки</li>
</ul>
<p>В одном из следующих уроков мы поговорим и о безопасности отправки данных в формах, что является одним из главных пунктов любого тестирования.</p>
<h2 id="heading-2-6">Выводы</h2>
<p>В этом уроке на примере формы отправки заказа мы рассмотрели, из чего состоит форма, и каким образом ее можно протестировать. Узнали о понятии валидации и как она должна работать.</p>
<p>Вот основные пункты при тестировании форм:</p>
<ul>
<li>Проверяйте поля для ввода на невозможных данных. Например: отрицательная цена, буквы в поле для цифр, невозможные имена</li>
<li>Чекбоксы должны иметь возможность множественного выбора. Если это не так, то функционал должен быть представлен в виде радиокнопки</li>
<li>Радиокнопки используются для выбора только одного пункта из множества</li>
<li>Списки повторяют функционал радиокнопок и чекбоксов, только в другом обличии. Их тестирование не отличается</li>
<li>Старайтесь проверять не только поле формы, но и то, что происходит вокруг него. Представьте себя не в виде тестировщика, а в виде клиента</li>
<li>Все валидируемые поля должны быть понятны и отображать ошибки, если это необходимо</li>
</ul>
<p>В будущих уроках мы еще вернемся к форме, когда будем изучать тему безопасности.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/qa-engineer?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">4 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Инженер по ручному тестированию</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите виды тестирования, тест-кейсы, DevTools, Postman, SQL, Git и HTTP/HTTPS.</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk1MCwicHVyIjoiYmxvYl9pZCJ9fQ==--4a16fe638654fb8d5ae09d7e8ab8e16ff228214f/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Bug%20fixing-amico.png" alt="Инженер по ручному тестированию" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 368 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/testing-basics-free?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Основы тестирования</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root"></p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzkyOCwicHVyIjoiYmxvYl9pZCJ9fQ==--f60f9dfdd11bed62e5573394ea442764a862e2c8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Mobile%20testing-bro.png" alt="Основы тестирования" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Бесплатно</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/web-testing-basics/lessons/forms/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 15</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/web-testing-basics/lessons/forms/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>