diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx new file mode 100644 index 0000000..df05d98 --- /dev/null +++ b/docs/basic-guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` + +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` + +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` + +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` + +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all(paragraphs.map(p => p.getText())); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" }, +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: "settings" }, +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true, + }, +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole("textbox", { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (``, ``, ``); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx new file mode 100644 index 0000000..df05d98 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -0,0 +1,602 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера с помощью селекторов. Для этого используются библиотеки `WebDriverIO` и `testing-library`. Селекторы позволяют точно идентифицировать элементы интерфейса, что необходимо для автоматизации тестирования. + +## WebDriverIO + +WebDriverIO — это Node.js-библиотека для автоматизации браузеров, которая реализует протокол WebDriver (W3C стандарт) В Testplane она используется для управления браузерами и взаимодействия с веб-элементами. + +### CSS селекторы + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +// Поиск кнопки с классом "btn-primary" +const button = await browser.$(".btn-primary"); +await button.click(); + +// Поиск нескольких элементов +const menuItems = await browser.$$(".nav-item"); +console.log("Количество пунктов меню: ${menuItems.length}"); +``` + +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически; +- нужен быстрый и простой способ найти элемент; +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`); +- вы работаете с компонентными библиотеками, где классы являются частью API. + +#### По id + +Чтобы найти элемент по `id`, используйте селектор вида `"#id"`. + +```javascript +// Поиск формы id +const loginForm = await browser.$("#login-form"); +const isDisplayed = await loginForm.isDisplayed(); +``` + +Стоит использовать, если: + +- вы работаете с критически важными элементами (формы, модальные окна, главные контейнеры); +- `id` является частью публичного API компонента; +- нужна максимальная производительность селектора (`id` — самый быстрый селектор). + +#### По типу атрибута + +Для поиска элемента по атрибуту, используйте селектор вида `input[type="name"]`. + +```javascript +// Поиск всех чекбоксов +const checkboxes = await browser.$$("input[type="checkbox"]"); +for (const checkbox of checkboxes) { + await checkbox.click(); +} + +// Поиск email input +const emailInput = await browser.$("input[type="email"]"); +await emailInput.setValue("test@example.com"); + +// Поиск скрытых полей +const hiddenField = await browser.$("input[type="hidden"][name="csrf_token"]"); +const csrfValue = await hiddenField.getValue(); +``` + +Стоит использовать, если: + +- нужно найти все элементы определённого типа (все чекбоксы, все радиокнопки); +- вы тестируете формы и валидацию; +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`); +- вам нужно убедиться, что используется правильный тип поля для `accessibility`; +- вы тестируете различное поведение для разных типов полей. + +#### CSS-селекторы по атрибуту data-testid + +Для поиска элементов, которые помечены дял тестирования, используйте селекторы по атрибуту `data-testid`. + +```javascript +// Поиск элемента по data-testid +const userAvatar = await browser.$("[data-testid="user-avatar"]"); +await userAvatar.waitForDisplayed({ timeout: 5000 }); + +// Клик по кнопке с data-testid +const deleteButton = await browser.$("[data-testid="delete-user-btn"]"); +await deleteButton.click(); + +// Получение текста из элемента +const errorMessage = await browser.$("[data-testid="error-notification"]"); +const text = await errorMessage.getText(); +expect(text).toContain("Ошибка валидации"); +``` + +Стоит использовать, если: + +- создаёте селекторы специально для тестирования; +- нужна стабильность селекторов независимо от изменений UI/стилей; +- работаете в команде, где дизайнеры часто меняют классы и структуру; +- хотите явно пометить элементы, доступные для тестирования; + +#### CSS-комбинированные селекторы + +Комбинированные селекторы позволяют находить элементы в определённом контексте. + +```javascript +// Потомок: кнопка внутри модального окна +const modalButton = await browser.$(".modal .close-button"); +await modalButton.click(); + +// Прямой потомок: первый уровень вложенности +const navItem = await browser.$(".navigation > .nav-item"); + +// Соседний элемент +const errorLabel = await browser.$("input.invalid + .error-message"); +const errorText = await errorLabel.getText(); + +// Сложная комбинация +const activeTab = await browser.$("ul.tabs > li.active > a[href^="#"]"); +``` + +Стоит использовать, если: + +- нужно найти элемент в определённом контексте; +- работаете с повторяющейся структурой; +- нужно убедиться в правильной вложенности элементов; +- простые селекторы слишком неспецифичны; +- тестируете связанные элементы. + +#### CSS-псевдоселекторы + +Псевдоселекторы позволяют выбирать элементы на основе их положения или состояния. + +```javascript +// Первый элемент списка +const firstItem = await browser.$("ul.menu > li:first-child"); +await firstItem.click(); + +// Последний элемент +const lastItem = await browser.$(".breadcrumb > li:last-child"); +const currentPage = await lastItem.getText(); + +// N-ый элемент (третий пункт меню) +const thirdItem = await browser.$(".menu-item:nth-child(3)"); + +// Каждый второй элемент (чётные) +const evenRows = await browser.$$("table tr:nth-child(even)"); + +// Отключённые элементы +const disabledButtons = await browser.$$("button:disabled"); +expect(disabledButtons.length).toBe(2); + +// Проверенные чекбоксы +const checkedBoxes = await browser.$$("input[type="checkbox"]:checked"); +``` + +Стоит использовать, если: + +- тестируете позиционирование элементов; +- проверяете состояния элементов (`disabled`, `checked`, `focus`); +- работаете с таблицами и нужно выбрать определённую строку или столбец; +- тестируете паттерны (чётные/нечётные строки для `zebra-striping`); +- нужно найти элемент по его позиции, когда нет других идентификаторов. + +### XPath селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +const loginButton = await browser.$("//button[text()="Войти в систему"]"); +await loginButton.click(); + +// Частичное совпадение +const successMessage = await browser.$("//div[contains(text(), "успешно")]"); +await successMessage.waitForDisplayed(); + +// Текст с пробелами и переносами +const heading = await browser.$("//h1[normalize-space()="Добро пожаловать"]"); + +// Поиск по тексту в дочернем элементе +const card = await browser.$("//div[@class="card"][.//h3[text()="Premium план"]]"); + +// Case-insensitive поиск (XPath 2.0) +const link = await browser.$("//a[contains(translate(text(), "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"), "поиск")]"); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки); +- нет других идентификаторов (нет `data-testid`, `id`, классов); +- тестируете, что правильный текст отображается в правильном месте; +- работаете с динамически генерируемым контентом, где единственный стабильный элемент — текст; +- нужно найти элемент, содержащий определённый текст внутри себя или в дочерних элементах. + +#### По атрибутам + +Для поиска элемента по атрибуту, используйте селектор вида `//element[@type="atribute"]`. + +```javascript +// Поиск по одному атрибуту +const nameInput = await browser.$("//input[@name="username"]"); +await nameInput.setValue("john_doe"); + +// Множественные условия (AND) +const activeModal = await browser.$("//div[@class="modal" and @data-visible="true"]"); + +// Условие OR +const submitBtn = await browser.$("//button[@type="submit" or @class="btn-submit"]"); + +// Поиск по началу значения атрибута +const imageJpg = await browser.$("//img[starts-with(@src, "/images/")]"); + +// Поиск по концу значения атрибута +const pdfLink = await browser.$("//a[substring(@href, string-length(@href) - 3) = ".pdf"]"); + +// Поиск по частичному совпадению атрибута +const dataElement = await browser.$("//div[contains(@data-component, "user-profile")]"); + +// NOT условие +const notDisabledButton = await browser.$("//button[not(@disabled)]"); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов); +- работаете с динамическими атрибутами (`data`-атрибуты с переменными значениями); +- нужна гибкость в поиске (частичные совпадения, начало/конец строки); +- CSS-селекторы не могут выразить нужную логику; +- нужно найти элемент по отсутствию атрибута. + +#### Навигация по DOM + +Исползуя XPath, вы можете навигировать по DOM-дереву. + +```javascript +// Прямой родитель +const parentDiv = await browser.$("//input[@name="email"]/.."); + +// Предок с условием +const formContainer = await browser.$("//input[@name="email"]/ancestor::form[@id="registration"]"); + +// Следующий сиблинг +const errorLabel = await browser.$("//input[@class="invalid"]/following-sibling::span[@class="error"][1]"); + +// Предыдущий сиблинг +const label = await browser.$("//input[@name="password"]/preceding-sibling::label[1]"); + +// Все потомки +const allInputs = await browser.$$("//form[@id="checkout"]//input"); + +// Прямые дети +const directChildren = await browser.$$("//ul[@class="menu"]/li"); + +// Поиск «дяди» элемента (родитель -> сиблинг родителя) +const siblingSection = await browser.$("//h2[text()="Контакты"]/../following-sibling::section[1]"); +``` + +Стоит использовать, если: + +- нужно найти элемент относительно другого известного элемента; +- структура DOM сложна, но относительные позиции стабильны; +- тестируете связанные элементы (label и input, ошибка рядом с полем); +- нужно подняться вверх по DOM-дереву от найденного элемента; +- работаете с семантической структурой HTML (заголовок и следующая за ним секция). + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +// Первый элемент в результатах XPath +const firstButton = await browser.$("(//button[@class="action"])[1]"); +await firstButton.click(); + +// Последний элемент +const lastItem = await browser.$("(//li[@class="menu-item"])[last()]"); + +// Второй элемент +const secondRow = await browser.$("(//table[@id="results"]//tr)[2]"); + +// Предпоследний +const secondToLast = await browser.$("(//div[@class="card"])[last()-1]"); + +// Диапазон элементов (все кроме первого) +const allButFirst = await browser.$$("(//li[@class="item"])[position() > 1]"); + +// Каждый третий элемент +const everyThird = await browser.$$("//div[@class="grid-item"][position() mod 3 = 0]"); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов; +- тестируете пагинацию или списки с определённым порядком; +- работаете с таблицами и нужна конкретная строка; +- нужен первый или последний элемент среди нескольких одинаковых; +- тестируете сортировку (проверка, что элемент на правильной позиции). + +### Селекторы по Link Text + +Селекторы по содержащемуся внутри тексту позволяют находить ссылки `()` по их тексту. Используйте `="text"`, чтобы найти элемент с точным текстом и `*="text"` для поиска по частичному совпадению текста. + +```javascript +// Полное совпадение текста ссылки +const loginLink = await browser.$("=Войти в систему"); +await loginLink.click(); + +// Частичное совпадение +const docsLink = await browser.$("*=Документация"); +await docsLink.click(); +``` + +Стоит использовать, если: + +- работаете с навигационными ссылками с уникальным текстом; +- тестируете меню и навигацию сайта; +- текст ссылки является частью требований и не должен меняться; +- нужна простота и читаемость теста; +- тестируете наличие ссылок с правильным текстом на странице. + +### Селекторы по имени тега + +Чтобы найти элемент по их HTML-тегу, используйте селектор по имени тега. + +```javascript +// Поиск первой кнопки на странице +const button = await browser.$("button"); +await button.click(); + +// Все параграфы +const paragraphs = await browser.$$("p"); +const textsArray = await Promise.all(paragraphs.map(p => p.getText())); + +// Все изображения +const images = await browser.$$("img"); +for (const img of images) { + const alt = await img.getAttribute("alt"); + expect(alt).not.toBe(""); // проверка accessibility +} + +// Форма +const form = await browser.$("form"); +await form.waitForDisplayed(); + +// Таблица +const table = await browser.$("table"); +const rows = await table.$$("tr"); +``` + +Стоит использовать, если: + +- на странице один элемент данного типа (например, единственная форма); +- нужно получить все элементы определённого типа для массовой проверки; +- тестируете семантичность HTML (наличие правильных тегов); +- работаете с базовой HTML-структурой (`form`, `table`, `ul`); +- проводите accessibility-аудит (проверка всех `img` на наличие `alt`). + +### React селекторы + +Для поиска экомпонентов в React-приложении по их имени, используйте react-селекторы, например `react$("MyButton")` найдет компонент `MyButton`, а `react$("Button" { props: { variant: "primary", size: "large"}})` найдет кнопку с определенными параметрами. + +```javascript +// Поиск React-компонента по имени +const myButton = await browser.react$("MyButton"); +await myButton.click(); + +// С фильтрацией по параметрами +const primaryButton = await browser.react$("Button", { + props: { variant: "primary", size: "large" }, +}); + +// С фильтрацией по state +const openModal = await browser.react$("Modal", { + state: { isOpen: true, activeTab: "settings" }, +}); + +// Все экземпляры компонента +const allCards = await browser.react$$("ProductCard"); +console.log("Найдено карточек: ${allCards.length}"); + +// Вложенный поиск +const form = await browser.react$("CheckoutForm"); +const submitButton = await form.react$("SubmitButton"); + +// С комплексными параметрами +const userProfile = await browser.react$("UserProfile", { + props: { + user: { id: 123, role: "admin" }, + editable: true, + }, +}); +``` + +Стоит использовать, если: + +- работаете с React-приложением и имеете доступ к исходному коду; +- нужно найти компонент по его параметрам или `state`; +- тестируете, что компонент рендерится с правильными данными; +- структура `DOM` может меняться, но API компонента стабилен; +- нужна глубокая интеграция с React DevTools. + +### Shadow DOM селекторы + +Shadow DOM селекторы позволяют работать с элементами внутри Shadow DOM — инкапсулированной части DOM-дерева. Например, если у вас есть кастомный элемент `my-custom-element`, вы можете найти кнопку внутри его Shadow DOM с помощью `shadow$("button")`. + +```javascript +// Простой доступ в Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Множественные элементы в Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Стоит использовать, если: + +- работаете с Web Components и Custom Elements; +- приложение использует Shadow DOM для инкапсуляции стилей; +- тестируете компоненты из сторонних библиотек (Lit, Stencil, native Web Components); +- нужен доступ к элементам внутри shadow root; +- работаете с дизайн-системой на базе Web Components. + +## Testing-library + +Testing Library — это адаптер популярной философии Testing Library для Testplane. Она предоставляет селекторы, ориентированные на пользовательский опыт (как пользователи находят элементы). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `screen.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск кнопки +const submitButton = screen.getByRole("button", { name: /submit/i }); +await userEvent.click(submitButton); + +// Поиск текстового поля +const emailInput = screen.getByRole("textbox", { name: /email/i }); +await userEvent.type(emailInput, "test@example.com"); + +// Поиск чекбокса +const agreeCheckbox = screen.getByRole("checkbox", { name: /agree to terms/i }); +await userEvent.click(agreeCheckbox); +``` + +Стоит использовать, если: + +- для любых интерактивных элементов (кнопки, ссылки, поля ввода); +- для структурных элементов (`navigation`, `main`, `header`, `footer`); +- для форм и их элементов (`radio`, `checkbox`, `combobox`); +- для заголовков и важных текстовых элементов; +- для списков и таблиц. + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`), используйте метод `getByLabelText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по тексту label (полное совпадение) +const emailInput = screen.getByLabelText("Email Address"); +await userEvent.type(emailInput, "user@example.com"); + +// Поиск с регулярным выражением (частичное совпадение, case-insensitive) +const passwordInput = screen.getByLabelText(/password/i); +await userEvent.type(passwordInput, "secure123"); +``` + +Стоит использовать, если: + +- работаете с формами; +- нужно найти input, select, textarea по связанной метке; +- занимаетесь тестированием доступности (наличие `label` обязательно для доступности); +- метка уникальна и описательна. + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по placeholder +const searchInput = screen.getByPlaceholderText("Search..."); +await userEvent.type(searchInput, "testing library"); + +// С регулярным выражением +const emailInput = screen.getByPlaceholderText(/enter.*email/i); +await userEvent.type(emailInput, "test@example.com"); +``` + +Стоит использовать, если: + +- у поля нет `label`, но есть `placeholder`; +- занимаетесь тестированием legacy-кода, где `placeholder` используется вместо `label`; +- `placeholder` достаточно описателен и уникален. + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск по точному тексту +const heading = screen.getByText("Welcome to our application"); +expect(heading).toBeInTheDocument(); + +// С регулярным выражением (частичное совпадение) +const errorMessage = screen.getByText(/error.*occurred/i); +expect(errorMessage).toHaveClass("error"); +``` + +Стоит использовать, если: + +- необходимо найти неинтерактивные текстовые элементов (параграфы, заголовки, уведомления); +- занимаетесь проверкой отображения текста на странице; +- текст уникален и является частью требований. + +### ByDisplayValue + +Для поиска элемента по их текущему значению, используйте метод `getByDisplayValue`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск input с конкретным значением +const emailInput = screen.getByDisplayValue("user@example.com"); +expect(emailInput).toBeInTheDocument(); + +// Поиск с регулярным выражением +const searchInput = screen.getByDisplayValue(/search query/i); +``` + +Стоит использовать, если: + +- необходимо протестировать предзаполненных форм (edit forms, profile pages); +- нужно проверить установку корректного значения после действия; +- необходимо найти элемент по его текущему значению, а не по label. + +### ByAltText + +Для поиска изображений по тексту `alt`, используйте метод `getByAltText`. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск изображения по alt тексту +const logo = screen.getByAltText("Company Logo"); +expect(logo).toBeInTheDocument(); +expect(logo).toHaveAttribute("src", "/images/logo.png"); +``` + +Стоит использовать, если: + +- нужно работать с изображениями (``, ``, ``); +- необходимо проверить доступность изображений (наличие `alt` обязательно); +- `alt`-текст достаточно описателен и уникален. + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод getByTitle. + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Поиск элемента по title атрибуту +const closeButton = screen.getByTitle("Close dialog"); +await userEvent.click(closeButton); +``` + +Стоит использовать, если: + +- необходимо работать с элементами, которые используют `title` для `tooltips`; +- нужно протестировать `iframe`-элементы (`title` обязателен для доступности); +- нужно взаимодействовать с `SVG`-элементами (`title` внутри `SVG` для описания). + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `screen.getByTestId("submit-button")`, то найдте элемент с атрибутом `data-testid="submit-button"` + +```javascript +import { screen } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +// Базовое использование +const submitButton = screen.getByTestId("submit-button"); +await userEvent.click(submitButton); +``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx index de25099..035e2ea 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/index.mdx @@ -4,36 +4,59 @@ sidebar_position: 1 import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import Admonition from "@theme/Admonition"; -# Установка {#install} +# Установка и настройка -Запустите установщик testplane с помощью `npm`. +## Системные требования + +Чтобы начать работу с testplane, установите `Node.js` версии 18.0 и выше. + +## Установка {#install} + +Для запуска установщика testplane, с помощью `npm` выполните следующую команду: ```bash npm init testplane@latest YOUR_PROJECT_PATH ``` -Если вы не хотите использовать дефолты при инициализации проекта, а настроить его с помощью визарда, укажите опцию `-v`. +Чтобы настроить проект, а не использовать дефолты при его инициализации, укажите опцию `-v`. + +После выполнения команды установки, в директории проекта появится следующий набор файлов и папок: + +```bash +node_modules +testplane-tests + example.testplane.ts + ts.config.json +package-lock.json +package.json +testplane.config.ts +``` ## Настройка {#setup} -После выполнения команды, указанной выше, в корне проекта сгенерится файл `testplane.config.ts` с базовой настройкой. +В файле `testplane.config.ts` содержится базовый набор настроек для запуска тестов: ```typescript export default { - // https://testplane.io/ru/docs/v8/basic-guides/managing-browsers/ gridUrl: "local", baseUrl: "http://localhost", pageLoadTimeout: 0, httpTimeout: 60000, testTimeout: 90000, resetCursor: false, + + // В параметре sets содержится информация о директории, в которой находятся тесты + // и перечень браузеров, в которых они будут запускаться: sets: { desktop: { files: ["testplane-tests/**/*.testplane.(t|j)s"], browsers: ["chrome", "firefox"], }, }, + + // В поле `browsers` описана конфигурация используемых браузеров: browsers: { chrome: { headless: true, @@ -48,9 +71,9 @@ export default { }, }, }, + plugins: { "html-reporter/testplane": { - // https://github.com/gemini-testing/html-reporter enabled: true, path: "testplane-report", defaultView: "all", @@ -60,39 +83,10 @@ export default { }; ``` -Вы можете загрузить браузеры, описанные в конфиге, отдельно от запуска самого Testplane: +Чтобы загрузить браузеры, описанные в конфиге, отдельно от запуска самого Testplane, выполните команду: ```bash npx testplane install-deps ``` Без предварительного запуска команды, недостающие браузеры будут автоматически загружены с первым запуском Testplane. - -## Создание теста {#test_creation} - -Перейдите в файл `tests/example.testplane.js` с тестом. В нем вы можете посмотреть пример теста или написать свой. Например, - -```javascript -describe("github", async function () { - it("should find testplane", async function ({ browser }) { - await browser.url("https://github.com/gemini-testing/testplane"); - const elem = await browser.$("#readme h1"); - - await expect(elem).toHaveText("Testplane"); - }); -}); -``` - -## Запуск теста {#test_running} - -Теперь вы можете запустить тесты: - -```bash -npx testplane -``` - -или запустить gui-режим и запустить тест через интерфейс в браузере - -```bash -npx testplane gui -``` diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx index 2461ca2..1283c37 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/running-tests.mdx @@ -2,54 +2,174 @@ sidebar_position: 3 --- -# Запуск тестов +# Запуск и отладка -Используйте команду `npx testplane` для запуска всех тестов в вашем проекте. +## Запуск тестов -## Запуск конкретного файла +Для запуска тестов используйте команду: -Если вы хотите запустить всю группу тестов, которые находятся в конкретном файле, то укажите путь к этому файлу в качестве входного параметра для testplane: +```bash +npx testplane +``` + +Также тесты можно запускать в `gui`-режиме, для этого выполните команду: ```bash -testplane src/features/Reviews/Reviews.test/MyReview/MyReview.a11y@touch-phone.testplane.js +npx testplane gui ``` -## Опция `--grep` +### Запуск конкретного теста + +У вас имеется набор тестов и вам нужно запустить только один из них. + +```javascript +const assert = require("assert"); + +describe("tests", () => { + it("Проверка отображения главной страницы", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Проверка наличия логотипа на главной странице", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Проверка навигационного меню", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + assert.ok(menuItems.length > 0); + }); + + it("Проверка наличия поля поиска", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); +}); +``` -Если же вы хотите запустить конкретный тест, то воспользуйтесь опцией `--grep`, указав в качестве ее значения полное имя теста: +В таком случае выполните команду: ```bash -testplane --grep "Доступность Оставление отзыва" +testplane --grep "Проверка наличия поля поиска" ``` -## Директива `.only` +В кавычках вам необходимо передать содержимое скобок ключевого слова `it`. -Ещё вы можете воспользоваться директивой `.only` для набора тестов `describe` и конкретного теста `it`, аналогично тому как это реализовано в `mocha` (см. раздел [exlusive tests](https://mochajs.org/#exclusive-tests)): +### Запуск тестов в конкретных браузерах -Например: +По умолчанию тесты запускаются в тех браузерах, которые указаны в файле `testplane.config.ts`. ```javascript -describe.only("Доступность", function () { - // набор тестов... -}); +browsers: ["chrome", "firefox"]; +``` + +При выполнении команды `npx testplane` тесты запустятся в браузерах Google Chrome и Mozila Firefox. + +```bash +# Запуск во всех браузерах (по умолчанию) +testplane +``` + +Чтобы выполнить тесты в конкретном браузере, используйте команду: + +```bash +# Запуск только в Chrome +testplane --browser chrome ``` -или +Также вы можете указать конкретный браузер для работы в теле теста. ```javascript -it.only("Оставление отзыва", async function () { - // код теста... +// tests/browser-specific.test.js +describe("Browser specific tests", () => { + it("should work in all browsers", async ({ browser }) => { + await browser.url("https://example.com"); + }); + + // Пропустить тест в Safari + testplane.skip.in("safari", "Feature not supported in Safari"); + it("should work only in Chrome and Firefox", async ({ browser }) => { + await browser.url("https://example.com"); + // ... тело теста + }); + + // Запустить только в Chrome + testplane.only.in("chrome"); + it("should work only in Chrome", async ({ browser }) => { + await browser.url("https://example.com"); + // ... тело теста + }); }); ``` -## Запуск тестов несколько раз {#running_tests_multiple_times} -Иногда может быть полезным запустить один и тот же тест несколько раз — например, для проверки стабильности. Плагин [@testplane/test-repeater][testplane-test-repeater] позволяет запустить тесты заданное количество раз. +### Запуск теста из конкретного файла -После установки и включения плагина вы можете запустить тесты нужное количество раз, используя следующую команду: +Чтобы запустить тесты из конкретного файла, выполните команду: ```bash -npx testplane --test-repeater-repeat 5 --grep 'Имя теста' +# Запуск конкретного файла +testplane ../testplane-tests/example.testplane.ts +``` + +Где `../testplane-tests/example.testplane.ts` это путь к файлу с тестами. + +### Режим пользовательского интерфейса + +В Testplane вы можете работать с тестами в UI формате с помощью Testplane UI. + +![](/img/docs/html-reporter/html-reporter-demo.png) + +О процессах установки и настройки Testplane UI вы можете прочитать в разделе [UI.](..//html-reporter//overview.mdx) + +## Отладка + +### Отладка в gui-формате + +Отслеживать процесс выполнения тестов очень легко, если запустить их в `gui`-режиме. В подобном формате работы html-reporter продемонстрирует, какие тесты были успешно выполнены, а в каких присутствуют ошибки и какого они характера. + +Тут скриншот из gui testplane + +### Browser.debug() + +В Testplane имеется встроенный инструмент для отладки — `browser.debug`. + +```javascript +it("отладка с паузой", async ({ browser }) => { + // Открываем тестируемую страницу + await browser.url("/page"); + + // browser.debug() останавливает выполнение теста + // и открывает интерактивную консоль (REPL - Read-Eval-Print Loop) + await browser.debug(); + + // После вызова debug() тест приостанавливается + // В консоли можно вводить команды WebdriverIO в реальном времени: + + // Примеры команд, которые можно вводить в REPL: + // > await browser.$('.button').click() - кликнуть по кнопке + // > await browser.getTitle() - получить заголовок страницы + // > await browser.$$('.items') - найти все элементы + // > .exit - выйти из режима отладки + + // Этот код выполнится только после выхода из debug() + await browser.$(".button").click(); +}); ``` -[testplane-test-repeater]: ../../plugins/testplane-test-repeater +### Отладка через Testplane UI + +Наиболее удобным способом для работы с отладкой тестов является UI режим, в нем вы можете в реальном времени наблюдать выполнения тестов. + +![](/gif/docs/ui/run-debug.gif) + +И находить нестабильные тесты, медленные тесты или другие проблемы с помощью опций «сортировка» и «группировка». + +![](/gif/docs/ui/analytics.gif) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx index 9b4bed9..8446aa6 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/quickstart/writing-tests.mdx @@ -5,4 +5,558 @@ draft: true # Написание тестов - +## Структура теста + +Блок `describe` предназначен для группировки связанных тестов. + +```javascript +describe("Название группы тестов", () => { + it("описание того, что должно произойти", async ({ browser }) => { + // Тело теста + }); +}); +``` + +В блоке `it` описываются тестовые сценарии. + +```javascript +it("описание того, что должно произойти", async ({ browser }) => { + // Тело теста +}); +``` + +После установки testplane, вы можете ознакомиться с примером теста, для этого перейдите в папку `testplane-tests` и откройте файл `example.testplane.ts`. + +```javascript +describe("test examples", () => { + it("docs search test", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find by tag name + const navBar = await browser.$("nav"); + + // Find by aria-label + await navBar.$("aria/Search").click(); + + // Find by placeholder + const fileSearchInput = await browser.findByPlaceholderText("Search docs"); + await fileSearchInput.waitForDisplayed(); + await fileSearchInput.setValue("config"); + + // Find by id + const fileSearchResults = await browser.$("#docsearch-list"); + + // Find by role + const fileSearchResultsItems = await fileSearchResults.findAllByRole("option"); + + await expect(fileSearchResultsItems.length).toBeGreaterThan(1); + }); +}); +``` + +## Базовый синтаксис + +### Навигация + +Для перемещения по страницам используйте метод: + +```javascript +await browser.url("https://testplane.io/ru/"); +``` + +Если на странице имеются элементы, которые отображаются с задержкой, для корректного выполнения тестов укажите явное ожидание: + +```javascript +await browser.url("https://testplane.io/ru/"); +await browser.$("h1").waitForExist({ timeout: 5000 }); +const title = await browser.$("h1").getText(); +``` + +Либо используйте команду: + +```javascript +await browser.openAndWait("https://testplane.io/ru/"); +``` + +Команда `await browser.openAndWait()` по умолчанию дожидается загрузки всех необходимых элементов на странице. + +### Селекторы + +Testplane поддерживает различные стратегии поиска элементов: `CSS` селекторы (самые распространенные), текстовые селекторы (по содержимому), `XPath` для сложных запросов. Метод `$()` возвращает первый найденный элемент, а `$$()` — массив всех подходящих элементов: + +```javascript +const assert = require("assert"); + +describe("tests", () => { + it("Проверка отображения главной страницы", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Проверка наличия логотипа на главной странице", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Проверка навигационного меню", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + assert.ok(menuItems.length > 0); + }); + + it("Проверка наличия поля поиска", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); +}); +``` + +### Взаимодействия с элементами + +После знакомства с селекторами и нахождения элемента можно выполнить различные действия: клик, ввод текста, двойной клик. + +```javascript +const assert = require("assert"); + +describe("tests, () => { + + it("Пример клика - открытие поиска", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Клик по кнопке поиска + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForClickable({timeout: 5000}); + await searchButton.click(); + await browser.pause(1000); + + // Проверяем, что модальное окно поиска появилось + const searchModal = await browser.$(".DocSearch-Modal"); + const isDisplayed = await searchModal.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Пример ввода текста - поиск по документации", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Открываем поиск + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForClickable({timeout: 5000}); + await searchButton.click(); + await browser.pause(500); + + // Вводим текст в поле поиска + const searchInput = await browser.$("input.DocSearch-Input"); + await searchInput.waitForDisplayed({timeout: 5000}); + await searchInput.setValue("browser"); + await browser.pause(1000); + + // Проверяем, что текст введен + const inputValue = await searchInput.getValue(); + assert.strictEqual(inputValue, "browser"); + }); + + it("Пример двойного клика - выделение текста заголовка", async ({browser}) => { + await browser.url("https://testplane.io/ru/"); + + // Находим заголовок на главной странице + const heading = await browser.$("h1"); + await heading.waitForDisplayed({timeout: 5000}); + await heading.scrollIntoView(); + await browser.pause(500); + + // Двойной клик по заголовку + await heading.doubleClick(); + await browser.pause(500); + + // Проверяем, что элемент существует и отображается + const isDisplayed = await heading.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); +}); +``` + +### Assertions + +Testplane задействует `expect API` из `WebdriverIO` для проверки состояния элементов и страниц — это позволяет формулировать утверждения (`assertions`) о том, какими должны быть свойства элементов или страницы в целом. + +```javascript +const assert = require("assert"); + +describe("tests", () => { + it("WebdriverIO assert - проверка URL", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + // WebdriverIO expect для browser + await expect(browser).toHaveUrl("https://testplane.io/ru/"); + }); + + it("WebdriverIO assert - проверка существования элемента", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const logo = await browser.$("a.navbar__brand"); + + // WebdriverIO expect для элемента + await expect(logo).toExist(); + }); + + it("WebdriverIO assert - проверка видимости элемента", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const searchButton = await browser.$("button.DocSearch"); + + // WebdriverIO expect + await expect(searchButton).toBeDisplayed(); + }); + + // Примеры с Jest ассертами + it("Jest assert - проверка заголовка страницы", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const title = await browser.getTitle(); + + // Jest expect + expect(title).toContain("Testplane"); + }); + + it("Jest assert - проверка количества элементов", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const menuItems = await browser.$$("nav.navbar a.navbar__item"); + + // Jest expect + expect(menuItems.length).toBeGreaterThan(0); + }); + + it("Jest assert - проверка атрибута элемента", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); + const href = await docsLink.getAttribute("href"); + + // Jest expect + expect(href).toBe("/ru/docs/v8/"); + }); + + it("Jest assert - проверка URL с регулярным выражением", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const currentUrl = await browser.getUrl(); + + // Jest expect с regex + expect(currentUrl).toMatch(/testplane\.io/); + }); +}); +``` + +### Хуки + +Хуки — это специальные функции, которые автоматически выполняются в определенные моменты жизненного цикла тестов. Они позволяют подготовить окружение перед тестами и очистить его после выполнения. По умолчанию доступны два вида хуков — `beforeEach` и `afterEach`, первый выполняется перед каждым тестом, а второй после. + +```javascript +const assert = require("assert"); + +describe("Примеры работы с хуками", () => { + // beforeEach - выполняется перед каждым тестом + beforeEach(async ({ browser }) => { + console.log("--- Выполняется BEFOREEACH - перед каждым тестом ---"); + await browser.url("https://testplane.io/ru/"); + await browser.pause(500); + }); + + // afterEach - выполняется после каждого теста + afterEach(async ({ browser }) => { + console.log("--- Выполняется AFTEREACH - после каждого теста ---"); + const currentUrl = await browser.getUrl(); + console.log("Текущий URL:", currentUrl); + // Можно делать скриншоты, очищать данные и т.д. + }); + + it("Тест 1 - проверка заголовка", async ({ browser }) => { + const title = await browser.getTitle(); + assert.ok(title.includes("Testplane")); + }); + + it("Тест 2 - проверка логотипа", async ({ browser }) => { + const logo = await browser.$("a.navbar__brand"); + const isDisplayed = await logo.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Тест 3 - проверка поиска", async ({ browser }) => { + const searchButton = await browser.$("button.DocSearch"); + const isExisting = await searchButton.isExisting(); + assert.strictEqual(isExisting, true); + }); +}); + +describe("Пример вложенных describe с хуками", () => { + beforeEach(async ({ browser }) => { + console.log("--- OUTER BEFOREEACH ---"); + await browser.url("https://testplane.io/ru/"); + }); + + afterEach(async ({ browser }) => { + console.log("--- OUTER AFTEREACH ---"); + }); + + it("Внешний тест", async ({ browser }) => { + const title = await browser.getTitle(); + assert.ok(title.length > 0); + }); + + describe("Внутренний блок тестов", () => { + beforeEach(async ({ browser }) => { + console.log("--- INNER BEFOREEACH ---"); + // Сначала выполнится outer beforeEach, потом этот + await browser.url("https://testplane.io/ru/docs/v8/"); + }); + + afterEach(async ({ browser }) => { + console.log("--- INNER AFTEREACH ---"); + // Сначала выполнится этот afterEach, потом outer + }); + + it("Внутренний тест 1", async ({ browser }) => { + const currentUrl = await browser.getUrl(); + assert.ok(currentUrl.includes("docs")); + }); + + it("Внутренний тест 2", async ({ browser }) => { + const heading = await browser.$("h1"); + const isDisplayed = await heading.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + }); +}); +``` + +### Ожидания + +Явные ожидания необходимы для работы с динамическим контентом, который загружается или изменяется асинхронно. Testplane автоматически ждет появления элементов, но для сложных сценариев можно использовать специальные методы ожидания. + +```javascript +const assert = require("assert"); + +describe("Примеры ожиданий в Testplane", () => { + it("Ожидание появления и кликабельности элемента", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + // Ожидаем, что кнопка поиска появится на странице + const searchButton = await browser.$("button.DocSearch"); + await searchButton.waitForDisplayed({ + timeout: 5000, + timeoutMsg: "Кнопка поиска не появилась в течение 5 секунд", + }); + + // Ожидаем, что элемент станет кликабельным + await searchButton.waitForClickable({ + timeout: 3000, + timeoutMsg: "Кнопка поиска не стала кликабельной", + }); + + await searchButton.click(); + + // Ожидаем появления модального окна поиска + const searchModal = await browser.$(".DocSearch-Modal"); + await searchModal.waitForDisplayed({ timeout: 3000 }); + + const isDisplayed = await searchModal.isDisplayed(); + assert.strictEqual(isDisplayed, true); + }); + + it("Ожидание изменения текста элемента", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const heading = await browser.$("h1"); + + // Ожидаем, что элемент будет существовать + await heading.waitForExist({ + timeout: 5000, + timeoutMsg: "Заголовок не найден на странице", + }); + + // Ожидаем, что у элемента будет определенный текст + await heading.waitUntil( + async function () { + const text = await this.getText(); + return text.length > 0; + }, + { + timeout: 5000, + timeoutMsg: "Текст заголовка не появился", + }, + ); + + const text = await heading.getText(); + assert.ok(text.length > 0); + }); + + it("Ожидание с использованием browser.waitUntil для проверки URL", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + const docsLink = await browser.$("a[href='/ru/docs/v8/']"); + await docsLink.waitForExist({ timeout: 5000 }); + + // Используем JavaScript click для надежности + await browser.execute(el => el.click(), docsLink); + + // Ожидаем изменения URL с помощью browser.waitUntil + await browser.waitUntil( + async () => { + const currentUrl = await browser.getUrl(); + return currentUrl.includes("docs"); + }, + { + timeout: 5000, + timeoutMsg: "URL не изменился на страницу документации", + }, + ); + + const finalUrl = await browser.getUrl(); + assert.ok(finalUrl.includes("docs")); + }); +}); +``` + +### Работа с формами + +Testplane предоставляет специальные методы для работы с различными элементами форм. Чекбоксы и радио-кнопки управляются через клик. Для выпадающих списков `(