diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx new file mode 100644 index 00000000..d3ce3db6 --- /dev/null +++ b/docs/basic-guides/selectors.mdx @@ -0,0 +1,489 @@ +# Selectors + +Testplane provides multiple ways to locate elements on a web page. + +## Recommendations for using selectors + +We recommend locating elements the same way users interact with the application being tested. + +When choosing a selector, consider 3 factors: + +- User-centric — the selector reflects what the user sees and interacts with +- Stability — the selector doesn't break during refactoring, CSS changes, or page structure changes +- Uniqueness — the selector unambiguously identifies the required element + +| Selector / Selection method | Recommendation | Notes | +| -------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| By visible content — text, role, ... (`getByRole`, `getByText`, ...) | ✅ Best | Locate elements the same way users do: by text, role, label — a stable and "honest" way to find elements. | +| By `data-testid` attribute (e.g., `[data-testid="submit"]`) | ✅ Good | The `data-testid` attribute is designed specifically for automation: it doesn't affect styles/accessibility, is stable, and clearly indicates the element's purpose, but requires manual markup. | +| By ID (e.g., `#main`) | ⚠️ Rarely | IDs are more stable than classes, but can still depend on JS logic or markup changes. Use only if the ID is guaranteed to be stable. | +| By attribute type (e.g., `input[type="text"]`) | ⚠️ Rarely | Works if the attribute type is semantically meaningful and stable. However, it can be too generic (many elements of the same type). | +| By attributes (except `data-testid`, e.g., `[name="email"]`) | ⚠️ Rarely | Depends on HTML attribute semantics. Can be unstable (attributes may change or be removed). | +| XPath (e.g., `(//button)[2]`) | 🔴 Poor | Element position can change, and XPath selectors are generally hard to maintain. However, they can be useful as they provide maximum flexibility for locating elements. | +| By class (e.g., `.sidebar .list-item.state-opened-qU1azF`) | 🔴 Poor | Not related to user behavior and doesn't reflect semantics, with a hard dependency on hierarchy. | + +## WebdriverIO + +Testplane supports element location compatible with WebdriverIO syntax: CSS selectors, XPath, element text, and other methods described below. + +### By text selectors + +This type of selector allows you to find elements by the text they contain: + +- `="text"` — exact text match +- `*="text"` — partial text match +- `a*=text` — text match inside an `` tag +- `div.=text` — case-insensitive text match inside a `
` tag + +```javascript +describe("Link Text selector", () => { + it("Finding an element by exact text match", async ({ browser }) => { + await browser.url("https://testplane.io/"); + + // Exact text match for a link + const docsLink = await browser.$("=Documentation"); + const isDocsLinkFound = await docsLink.isExisting(); + console.log(`Element with exact text "Documentation" found: ${isDocsLinkFound}`); + + // Partial text match for a link + const partialLink = await browser.$("*=Docum"); + const isPartialLinkFound = await partialLink.isExisting(); + console.log(`Element with partial text "Docum" found: ${isPartialLinkFound}`); + + // Partial match with specified tag + const tagPartialLink = await browser.$("a*=Document"); + const isTagPartialLinkFound = await tagPartialLink.isExisting(); + console.log(` element with partial text "Document" found: ${isTagPartialLinkFound}`); + + // Case-insensitive search with div tag + const divCaseInsensitive = await browser.$("div.=testplane"); + const isDivCaseInsensitiveFound = await divCaseInsensitive.isExisting(); + console.log( + `
element with case-insensitive text "testplane" found: ${isDivCaseInsensitiveFound}`, + ); + }); +}); +``` + +Use this approach if: + +- the element text is stable +- you need the test to closely mimic real user scenarios + +### CSS selectors + +#### By the data-testid attribute + +This approach is suitable for finding elements that are marked with testing attributes. + +```javascript +describe("CSS selector by the data-testid attribute", () => { + it("Finding an element by data-testid", async ({ browser }) => { + // Open the page and wait for it to load + await browser.openAndWait("https://testplane.io/"); + + // Find element by the data-testid attribute + const element = await browser.$('[data-testid="main-content"]'); + + // Check if the element exists in the DOM + const isExisting = await element.isExisting(); + console.log("Element with data-testid exists:", isExisting); + }); +}); +``` + +Use this approach if: + +- you are creating selectors specifically for testing +- you need selector stability regardless of UI/style changes + +#### By class + +To find an element by class, use the `".class-name"` selector. + +```javascript +describe("CSS selector by class", () => { + it("Finding an element on the main page", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find element by class "navbar" + const navbar = await browser.$(".navbar"); + + // Check if the element is displayed on the page + const isDisplayed = await navbar.isDisplayed(); + console.log("Navbar is displayed:", isDisplayed); + }); +}); +``` + +Use this approach if: + +- the class is stable and not generated dynamically +- you need a quick and simple way to find an element +- the class semantically describes the element (e.g., `.error-message`, `.success-banner`) +- you are working with component libraries where classes are part of the API + +#### By ID + +To find an element by `id`, use a selector of the form `"#id"`. + +```javascript +describe("CSS selector by ID", () => { + it("Finding an element by ID on the main page", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find element by ID "__docusaurus" + const main = await browser.$("#__docusaurus"); + + // Check if the element is displayed on the page + const isDisplayed = await main.isDisplayed(); + console.log("Element is displayed:", isDisplayed); + }); +}); +``` + +Use this approach if: + +- you need a quick and simple way to find elements +- the `id` is part of the component's public API +- you need maximum selector performance (`id` is the fastest selector) + +#### By attribute type + +To find an element by attribute, use a selector of the form `input[type="name"]`. + +```javascript +describe("CSS selector by attribute type", () => { + it("Finding an element by attribute type", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find a button by the attribute type="button" + // Selector format: element[type="value"] + const button = await browser.$('button[type="button"]'); + + // Check if the element exists in the DOM + const isExisting = await button.isExisting(); + console.log("Button exists:", isExisting); + }); +}); +``` + +Use this approach if: + +- you need to find all elements of a certain type (all checkboxes, all radio buttons) +- you need to work with semantic HTML5 types (`email`, `tel`, `url`, `date`) + +### XPath selectors + +#### By element text + +To find an element by the text it contains, use the selector `//element[text()="text"]`. + +```javascript +// Exact text match +describe("XPath selector by element text", () => { + it("Finding an element by text", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find element by the text inside it + const link = await browser.$('//a[text()="Docs"]'); + + // Check if the element exists in the DOM + const isExisting = await link.isExisting(); + console.log("Element with text exists:", isExisting); + }); +}); +``` + +Use this approach if: + +- the element text is unique and stable (button labels, headings) +- other element-finding strategies are not applicable + +#### By attributes + +To find an element by attribute, use a selector of the form `//element[@type="attribute"]`. + +```javascript +describe("XPath selector by attribute", () => { + it("Finding an element by attribute", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find element by the type attribute + const button = await browser.$('//button[@type="button"]'); + + // Check if the element exists in the DOM + const isExisting = await button.isExisting(); + console.log("Element with attribute exists:", isExisting); + }); +}); +``` + +Use this approach if: + +- you need complex search conditions (combinations of attributes) +- you are working with dynamic attributes (`data` attributes with variable values) +- you need flexibility in searching (partial matches, start/end of string) +- CSS selectors cannot express the required logic +- you need to find an element by the absence of an attribute + +#### DOM navigation + +Using XPath, you can navigate through the DOM tree. + +```javascript +// Direct parent +const parentDiv = await browser.$("//input[@name='email']/.."); + +// Ancestor with condition +const formContainer = await browser.$("//input[@name='email']/ancestor::form[@id='registration']"); + +// Following sibling +const errorLabel = await browser.$( + "//input[@class='invalid']/following-sibling::span[@class='error'][1]", +); + +// Preceding sibling +const label = await browser.$("//input[@name='password']/preceding-sibling::label[1]"); + +// All descendants +const allInputs = await browser.$$("//form[@id='checkout']//input"); + +// Direct children +const directChildren = await browser.$$("//ul[@class='menu']/li"); + +// Finding an "uncle" of the element (parent -> parent's sibling) +const siblingSection = await browser.$("//h2[text()='Контакты']/../following-sibling::section[1]"); +``` + +This type of DOM tree navigation is not recommended due to its fragility, but it is possible. + +#### XPath: indices and positions + +XPath allows you to select elements by their position in the result set. + +```javascript +describe("XPath selector: indices and positions", () => { + it("Finding an element by index", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Find the third link element in the navigation (index starts at 1) + // Selector format: (//element)[index] + const thirdLink = await browser.$("(//a)[3]"); + + // Wait for the element to appear and be displayed + await thirdLink.waitForDisplayed({ timeout: 5000 }); + + // Check if the element exists in the DOM + const isExisting = await thirdLink.isExisting(); + console.log("Third element exists:", isExisting); + + // Get the element's text + const text = await thirdLink.getText(); + console.log("Text of the third element:", text); + }); +}); +``` + +Use this approach if: + +- you need to access an element by its position in the result set +- you are testing pagination or lists with a specific order +- you are working with tables and need a specific row +- you need the first or last element among several identical ones +- you are testing sorting (verifying that an element is in the correct position) + +### Shadow DOM selectors + +Shadow DOM selectors allow you to work with elements inside the Shadow DOM — an encapsulated part of the DOM tree. For example, if you have a custom element `my-custom-element`, you can find a button inside its Shadow DOM using `shadow$("button")`. + +```javascript +// Simple access to Shadow DOM +const customElement = await browser.$("my-custom-element"); +const button = await customElement.shadow$("button"); +await button.click(); + +// Multiple elements in Shadow DOM +const slotElements = await customElement.shadow$$(".slot-item"); +``` + +Use this approach if: + +- you are working with Web Components and Custom Elements +- the application uses Shadow DOM to encapsulate styles +- you are testing components from third‑party libraries (Lit, Stencil, native Web Components) +- you need access to elements inside the shadow root +- you are working with a design system based on Web Components + +## Testing Library + +Testing Library allows you to find elements the way users do — by text, element type, or other attributes that don't depend on your layout details. + +To use this type of selector, you need to [install Testing Library](../../guides/how-to-add-testing-library). + +### ByRole + +`getByRole` is the main method in Testing Library that lets you find elements by their ARIA roles. For example, if you use the method `browser.getByRole("button", { name: /submit/i })`, you will find a button with text containing `submit`. + +```javascript +describe("getByRole", () => { + it("Finding a button using the getByRole method", async ({ browser }) => { + await browser.url("https://testplane.io/"); + + const button = await browser.getByRole("button", { name: "Get started" }); + await button.click(); + }); +}); +``` + +### ByLabelText + +To find form elements by their label text (`label`), use the `getByLabelText` method. + +```javascript +describe("Finding an input field using the getByLabelText method", () => { + it("Find and use the search field", async ({ browser }) => { + await browser.url("https://testplane.io/docs/v8/html-reporter/overview/"); + // Find the search button + const searchButton = await browser.getByLabelText(/search|поиск/i); + // Click the button to open the search modal + await searchButton.click(); + }); +}); +``` + +### ByPlaceholderText + +To find an input field by its `placeholder` text, use the `getByPlaceholderText` selector. + +```javascript +describe("getByPlaceholderText", () => { + it("Finding an input field using the getByPlaceholderText method", async ({ browser }) => { + await browser.url("https://testplane.io/docs/v8/html-reporter/overview/"); + + // Open the search modal + const searchButton = await browser.$(".DocSearch-Button"); + await searchButton.click(); + + // Wait for the modal to appear + await browser.pause(500); + + // Find the input field by placeholder + const searchInput = await browser.getByPlaceholderText("Поиск"); + await searchInput.waitForDisplayed({ timeout: 3000 }); + + // Verify that the element is visible + await expect(searchInput).toBeDisplayed(); + }); +}); +``` + +### ByText + +To find a text element by its content, use the `getByText` method. + +```javascript +describe("getByText", () => { + it("Finding an element using the getByText method", async ({ browser }) => { + // Open the Testplane home page + await browser.url("https://testplane.io/"); + // Find the "Get started" button + const button = await browser.getByText("Get started"); + + // Verify that the element is found and visible + await expect(button).toBeDisplayed(); + await expect(button).toBeClickable(); + // Click the button + await button.click(); + + // Wait for the new page to load + await browser.pause(1000); + + // Verify navigation occurred + const url = await browser.getUrl(); + expect(url).toContain("/docs"); + }); +}); +``` + +### ByDisplayValue + +To find an element by its current value, use the `getByDisplayValue` method. + +```javascript +describe("Finding using the getByDisplayValue method", () => { + it("Find and verify the value", async ({ browser }) => { + await browser.url("https://testplane.io/docs/v8/html-reporter/overview/"); + + // Open search + const searchButton = await browser.$(".DocSearch-Button"); + await searchButton.click(); + await browser.pause(500); + + // Enter text + const searchInput = await browser.$('input[type="search"]'); + await searchInput.setValue("html-reporter"); + + // Search by full value + const input = await browser.getByDisplayValue("html-reporter"); + await expect(input).toBeDisplayed(); + + // Verify the value + const value = await input.getValue(); + expect(value).toBe("html-reporter"); + }); +}); +``` + +### ByAltText + +To find an image by its `alt` text, use the `getByAltText` method. + +```javascript +describe("Finding an element using the getByAltText method", () => { + it("Finding an element by the alt attribute", async ({ browser }) => { + // Open the page + await browser.url("https://testplane-bookstore.website.yandexcloud.net/"); + + // Find the book image by the alt attribute + const bookImage = await browser.getByAltText("The Great Gatsby"); + + // Confirm that the element is found + await expect(bookImage).toBeExisting(); + }); +}); +``` + +### ByTitle + +To find an element by the `title` attribute, use the `getByTitle` method. + +```javascript +describe("Finding an element using the getByTitle method", () => { + it("Finding an element", async ({ browser }) => { + // Open the page + await browser.url("https://testplane.io/docs/v8/"); + // Find the element by the title attribute + const linkElement = await browser.getByTitle("Direct link to Rich Debugging Capabilities"); + // Confirm that the element is found + await expect(linkElement).toBeExisting(); + }); +}); +``` + +### ByTestId + +`getByTestId` is used as a last resort when other methods are not suitable. For example, if you use `browser.getByTestId("submit-button")`, you will find an element with the attribute `data-testid="submit-button"`. + +```javascript +describe("Finding an element using the getByTestId method", () => { + it("Finding an element by the data-testid attribute", async ({ browser }) => { + // Open the page + await browser.url("https://testplane-bookstore.website.yandexcloud.net/"); + // Find the element by the data-testid attribute + const searchInput = await browser.getByTestId("search-input"); + // Confirm that the element is found + await expect(searchInput).toBeExisting(); + }); +}); +``` diff --git a/docs/quickstart/writing-tests.mdx b/docs/quickstart/writing-tests.mdx index 3a121a5d..cd7e945f 100644 --- a/docs/quickstart/writing-tests.mdx +++ b/docs/quickstart/writing-tests.mdx @@ -1,6 +1,5 @@ --- sidebar_position: 2 -draft: true --- # Writing Tests 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 00000000..ca1c2e79 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -0,0 +1,496 @@ +# Селекторы + +Testplane предоставляет множество способов для поиска элементов на странице браузера. + +## Рекомендации по использованию селекторов + +Мы рекомендуем искать элементы так же, как это делают пользователи тестируемого приложения. + +При выборе можно руководствоваться 3 факторами: + +- Ориентация на пользователя — селектор отражает то, что видит и использует пользователь +- Стабильность — селектор не ломается при рефакторинге, изменениях CSS или структуры страницы +- Уникальность — селектор однозначно идентифицирует нужный элемент + +| Селектор / Способ выбора | Рекомендация | Примечания | +| --------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| По видимому содержимому — тексту, роли, ... (`getByRole`, `getByText`, ...) | ✅ Лучший | Ищем элементы так же, как это делают пользователи: по тексту, роли, label — стабильный и "честный" способ поиска. | +| По атрибуту `data-testid` (например, `[data-testid="submit"]`) | ✅ Хорошо | Атрибут `data-testid` создан специально для автоматизации: не влияет на стили/доступность, стабилен, явно обозначает цель элемента, но требует ручной разметки такими ID. | +| По ID (например, `#main`) | ⚠️ Редко | ID стабильнее классов, но все равно может зависеть от JS‑логики или изменений в разметке. Используйте, только если ID гарантированно устойчив. | +| По типу атрибута (например, `input[type="text"]`) | ⚠️ Редко | Работает, если тип атрибута семантически значим и стабилен. Но может быть слишком общим (много элементов одного типа). | +| По атрибутам (кроме `data-testid`, например, `[name="email"]`) | ⚠️ Редко | Зависит от семантики HTML‑атрибутов. Может быть нестабильным (атрибуты меняют значение или удаляются). | +| XPath (например, `(//button)[2]`) | 🔴 Плохо | Позиция элемента может измениться, селекторы XPath как правило сложно поддерживать. Однако они могут быть полезны, так как дают максимум возможностей для поиска элементов. | +| По классу (например, `.sidebar .list-item.state-opened-qU1azF`) | 🔴 Плохо | Не связан с поведением пользователя и не отражает семантику, жесткая завязка на иерархию. | + +## WebdriverIO + +В Testplane поддерживается поиск элементов, совместимый с синтаксисом WebdriverIO: по CSS селекторам, по XPath, по тексту элементов и по другим признакам, которые описаны ниже. + +### Селекторы по тексту + +Данный вид селекторов позволяет искать элементы по содержащемуся в них тексту: + +- `="text"` — точный поиск текста +- `*="text"` — поиск по частичному совпадению текста +- `a*=text` — поиск по тексту внутри тега `` +- `div.=text` — case-insensitive поиск текста внутри тега `
` + +```javascript +describe("Селектор Link Text", () => { + it("Поиск элемента по совпадающему тексту", async ({ browser }) => { + await browser.url("https://testplane.io/ru/"); + + // Полное совпадение текста ссылки + const docsLink = await browser.$("=Документация"); + const isDocsLinkFound = await docsLink.isExisting(); + console.log(`Элемент с полным текстом "Документация" найден: ${isDocsLinkFound}`); + + // Частичное совпадение текста ссылки + const partialLink = await browser.$("*=Докум"); + const isPartialLinkFound = await partialLink.isExisting(); + console.log(`Элемент с частичным текстом "Докум" найден: ${isPartialLinkFound}`); + + // Частичное совпадение с указанием тега + const tagPartialLink = await browser.$("a*=Документ"); + const isTagPartialLinkFound = await tagPartialLink.isExisting(); + console.log(`Элемент с частичным текстом "Документ" найден: ${isTagPartialLinkFound}`); + + // Case-insensitive поиск с тегом div + const divCaseInsensitive = await browser.$("div.=testplane"); + const isDivCaseInsensitiveFound = await divCaseInsensitive.isExisting(); + console.log( + `Элемент
с case-insensitive текстом "testplane" найден: ${isDivCaseInsensitiveFound}`, + ); + }); +}); +``` + +Стоит использовать, если: + +- текст элементов стабилен +- вам необходимо, чтобы тест был максимально приближен к реальным пользовательским сценариям + +### CSS-селекторы + +#### По атрибуту data-testid + +Данный способ подходит для поиска элементов, которые размечены атрибутами для тестирования. + +```javascript +describe("CSS-селектор по атрибуту data-testid", () => { + it("Поиск элемента по data-testid", async ({ browser }) => { + // Открываем страницу и ждем ее загрузки + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по атрибуту data-testid + const element = await browser.$('[data-testid="main-content"]'); + + // Проверяем существование элемента в DOM + const isExisting = await element.isExisting(); + console.log("Элемент с data-testid существует:", isExisting); + }); +}); +``` + +Стоит использовать, если: + +- создаете селекторы специально для тестирования +- нужна стабильность селекторов независимо от изменений UI/стилей + +#### По классу + +Чтобы найти элемент на странице по классу, используйте селектор `".class-name"`. + +```javascript +describe("CSS-селектор по классу", () => { + it("Поиск элемента на главной странице", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по классу "navbar" + const navbar = await browser.$(".navbar"); + + // Проверяем, отображается ли элемент на странице + const isDisplayed = await navbar.isDisplayed(); + console.log("Навбар отображается:", isDisplayed); + }); +}); +``` + +Стоит использовать, если: + +- класс является стабильным и не генерируется динамически +- нужен быстрый и простой способ найти элемент +- класс семантически описывает элемент (например, `.error-message`, `.success-banner`) +- вы работаете с компонентными библиотеками, где классы являются частью API + +#### По id + +Чтобы найти элемент по `id` используйте селектор вида `"#id"`. + +```javascript +describe("CSS-селектор по id", () => { + it("Поиск элемента по id на главной странице", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по id "__docusaurus" + const main = await browser.$("#__docusaurus"); + + // Проверяем, отображается ли элемент на странице + const isDisplayed = await main.isDisplayed(); + console.log("Элемент отображается:", isDisplayed); + }); +}); +``` + +Стоит использовать, если: + +- вам необходим быстрый и простой способ поиска элементов +- `id` является частью публичного API компонента +- нужна максимальная производительность селектора (`id` — самый быстрый селектор) + +#### По типу атрибута + +Для поиска элемента по атрибуту используйте селектор вида `input[type="name"]`. + +```javascript +describe("CSS-селектор по типу атрибута", () => { + it("Поиск элемента по типу атрибута", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем кнопку по атрибуту type="button" + // Формат селектора: element[type="value"] + const button = await browser.$('button[type="button"]'); + + // Проверяем существование элемента в DOM + const isExisting = await button.isExisting(); + console.log("Кнопка существует:", isExisting); + }); +}); +``` + +Стоит использовать, если: + +- нужно найти все элементы определенного типа (все чекбоксы, все радиокнопки) +- нужно работать с семантическими HTML5 типами (`email`, `tel`, `url`, `date`) + +### XPath-селекторы + +#### По тексту элемента + +Чтобы найти элемент по содержащемуся в нем тексту, используйте селектор `//element[text()="text"]`. + +```javascript +// Точное совпадение текста +describe("XPath-селектор по тексту элемента", () => { + it("Поиск элемента по тексту", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по тексту внутри него + const link = await browser.$('//a[text()="Docs"]'); + + // Проверяем существование элемента в DOM + const isExisting = await link.isExisting(); + console.log("Элемент с текстом существует:", isExisting); + }); +}); +``` + +Стоит использовать, если: + +- текст элемента уникален и стабилен (названия кнопок, заголовки) +- другие стратегии поиска элементов оказались неприменимы + +#### По атрибутам + +Для поиска элемента по атрибуту используйте селектор вида `//element[@type="atribute"]`. + +```javascript +describe("XPath-селектор по атрибуту", () => { + it("Поиск элемента по атрибуту", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем элемент по атрибуту type + const button = await browser.$('//button[@type="button"]'); + + // Проверяем существование элемента в DOM + const isExisting = await button.isExisting(); + console.log("Элемент с атрибутом существует:", isExisting); + }); +}); +``` + +Стоит использовать, если: + +- нужны сложные условия поиска (комбинации атрибутов) +- работаете с динамическими атрибутами (`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]"); +``` + +Подобный вид навигации по дереву не рекомендуется использовать из-за своей хрупкости, но он возможен. + +#### XPath: индексы и позиции + +XPath позволяет выбирать элементы по их позиции в наборе результатов. + +```javascript +describe("XPath-селектор: индексы и позиции", () => { + it("Поиск элемента по индексу", async ({ browser }) => { + await browser.openAndWait("https://testplane.io/"); + + // Ищем третий элемент ссылки в навигации (индекс начинается с 1) + // Формат селектора: (//element)[index] + const thirdLink = await browser.$("(//a)[3]"); + + // Ждем появления элемента и его отображения + await thirdLink.waitForDisplayed({ timeout: 5000 }); + + // Проверяем существование элемента в DOM + const isExisting = await thirdLink.isExisting(); + console.log("Третий элемент существует:", isExisting); + + // Получаем текст элемента + const text = await thirdLink.getText(); + console.log("Текст третьего элемента:", text); + }); +}); +``` + +Стоит использовать, если: + +- нужен доступ к элементу по его позиции в наборе результатов +- тестируете пагинацию или списки с определенным порядком +- работаете с таблицами, и нужна конкретная строка +- нужен первый или последний элемент среди нескольких одинаковых +- тестируете сортировку (проверка, что элемент на правильной позиции) + +### 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](../../guides/how-to-add-testing-library). + +### ByRole + +`getByRole` — основной метод в Testing Library, который позволяет находить элементы по их ARIA-ролям. Например, если вы используете метод `browser.getByRole("button", { name: /submit/i })`, то найдете кнопку с текстом, содержащим `submit`. + +```javascript +describe("getByRole", () => { + it("Поиск кнопки с помощью метода getByRole", async ({ browser }) => { + await browser.url("https://testplane.io/"); + + const button = await browser.getByRole("button", { name: "Get started" }); + + await button.click(); + }); +}); +``` + +### ByLabelText + +Для поиска элементов форм по тексту их меток (`label`) используйте метод `getByLabelText`. + +```javascript +describe("Поиск поля ввода с помощью метода getByLabelText", () => { + it("Найти и использовать поле поиска", async ({ browser }) => { + await browser.url("https://testplane.io/ru/docs/v8/html-reporter/overview/"); + + // Находим кнопку поиска + const searchButton = await browser.getByLabelText(/search|поиск/i); + + // Кликаем на кнопку, чтобы открыть модальное окно поиска + await searchButton.click(); + }); +}); +``` + +### ByPlaceholderText + +Чтобы найти поле ввода по тексту `placeholder`, используйте селектор `getByPlaceholderText`. + +```javascript +describe("getByPlaceholderText", () => { + it("Поиск поля ввода с помощью метода getByPlaceholderText", async ({ browser }) => { + await browser.url("https://testplane.io/ru/docs/v8/html-reporter/overview/"); + + // Открываем модальное окно поиска + const searchButton = await browser.$(".DocSearch-Button"); + await searchButton.click(); + + // Ждем появления модального окна + await browser.pause(500); + + // Находим поле ввода по placeholder + const searchInput = await browser.getByPlaceholderText("Поиск"); + await searchInput.waitForDisplayed({ timeout: 3000 }); + + // Проверяем, что элемент видим + await expect(searchInput).toBeDisplayed(); + }); +}); +``` + +### ByText + +Чтобы найти текстовый элемент по его содержимому, используйте метод `getByText`. + +```javascript +describe("getByText", () => { + it("Поиск элемента с помощью метода getByText", async ({ browser }) => { + // Открываем главную страницу testplane + await browser.url("https://testplane.io/"); + + // Находим кнопку "Get started" + const button = await browser.getByText("Get started"); + + // Проверяем, что элемент найден и виден + await expect(button).toBeDisplayed(); + await expect(button).toBeClickable(); + + // Кликаем по кнопке + await button.click(); + + // Ждем загрузки новой страницы + await browser.pause(1000); + + // Проверяем, что произошла навигация + const url = await browser.getUrl(); + expect(url).toContain("/docs"); + }); +}); +``` + +### ByDisplayValue + +Для поиска элемента по их текущему значению используйте метод `getByDisplayValue`. + +```javascript +describe("Поиск с помощью метода getByDisplayValue", () => { + it("Найти и проверить значение", async ({ browser }) => { + await browser.url("https://testplane.io/ru/docs/v8/html-reporter/overview/"); + + // Открываем поиск + const searchButton = await browser.$(".DocSearch-Button"); + await searchButton.click(); + await browser.pause(500); + + // Вводим текст + const searchInput = await browser.$('input[type="search"]'); + await searchInput.setValue("html-reporter"); + + // Ищем по полному значению + const input = await browser.getByDisplayValue("html-reporter"); + await expect(input).toBeDisplayed(); + + // Проверяем значение + const value = await input.getValue(); + expect(value).toBe("html-reporter"); + }); +}); +``` + +### ByAltText + +Для поиска изображения по тексту `alt` используйте метод `getByAltText`. + +```javascript +describe("Поиск элемента с помощью метода getByAltText", () => { + it("Поиск элемента по alt атрибуту", async ({ browser }) => { + // Открываем страницу + await browser.url("https://testplane-bookstore.website.yandexcloud.net/"); + + // Находим изображение книги по атрибуту alt + const bookImage = await browser.getByAltText("The Great Gatsby"); + + // Подтверждаем, что элемент найден + await expect(bookImage).toBeExisting(); + }); +}); +``` + +### ByTitle + +Чтобы найти элемент по атрибуту title, используйте метод `getByTitle`. + +```javascript +describe("Поиск элемента с помощью метода getByTitle", () => { + it("Поиск элемента", async ({ browser }) => { + // Открываем страницу + await browser.url("https://testplane.io/docs/v8/"); + + // Находим элемент по атрибуту title + const linkElement = await browser.getByTitle("Direct link to Rich Debugging Capabilities"); + + // Подтверждаем, что элемент найден + await expect(linkElement).toBeExisting(); + }); +}); +``` + +### ByTestId + +`getByTestId` используется как последний вариант, когда другие методы не подходят. Например, если вы используете `browser.getByTestId("submit-button")`, то найдите элемент с атрибутом `data-testid="submit-button"` + +```javascript +describe("Поиск элемента с помощью метода getByTestId", () => { + it("Поиск элемента по data-testid атрибуту", async ({ browser }) => { + // Открываем страницу + await browser.url("https://testplane-bookstore.website.yandexcloud.net/"); + + // Находим элемент по атрибуту data-testid + const searchInput = await browser.getByTestId("search-input"); + + // Подтверждаем, что элемент найден + await expect(searchInput).toBeExisting(); + }); +}); +``` 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 f3ebb1fb..4e0927e3 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 @@ -1,6 +1,5 @@ --- sidebar_position: 2 -draft: true --- # Написание тестов diff --git a/src/scss/custom.scss b/src/scss/custom.scss index d9faf03e..3f6647e6 100644 --- a/src/scss/custom.scss +++ b/src/scss/custom.scss @@ -150,8 +150,7 @@ html[data-theme-override="light"] { } &.navbar--fixed-top { - @apply dark:bg-neutral-950/80; - background-color: rgba(255, 255, 255, 0.7); + @apply bg-white/70 dark:bg-neutral-950/80; &::before { content: "";