From e5212176d94828751bb4e7abe606654e3a0420e6 Mon Sep 17 00:00:00 2001 From: Nikolaengel Date: Mon, 29 Dec 2025 21:23:50 +0300 Subject: [PATCH 1/2] docs: selectors --- docs/basic-guides/selectors.mdx | 477 +++++++++++++++++ .../current/basic-guides/selectors.mdx | 484 ++++++++++++++++++ 2 files changed, 961 insertions(+) create mode 100644 docs/basic-guides/selectors.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx new file mode 100644 index 00000000..54468c6d --- /dev/null +++ b/docs/basic-guides/selectors.mdx @@ -0,0 +1,477 @@ +# Selectors + +Testplane provides multiple ways to locate elements on a web page. + +## Recommendations for using selectors + +| Selector / Selection method | Recommendation | Notes | +| ------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| By class (e.g., `.btn`) | 🚨 Poor | Not related to user behavior and doesn’t reflect semantics, but is usually stable. | +| 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). | +| CSS selectors by `data-testid` attribute (e.g., `[data-testid="submit"]`) | βœ… Good | The best choice for tests. The `data-testid` attribute is designed specifically for automation: it doesn’t affect styles/accessibility, is stable, and clearly indicates the element’s purpose. | +| By element text (e.g., `*=Submit`) | βœ… Good | Reflects user interaction. However, requires attention to translations and dynamic content. Use locale files to prevent tests from breaking when text changes. | +| By attributes (except `data-testid`, e.g., `[name="email"]`) | ⚠️ Rarely | Depends on HTML attribute semantics. Can be unstable (attributes may change or be removed). | +| DOM navigation (e.g., `parent().find('button')`) | 🚨 Poor | Extremely fragile. Depends on markup structure: any change in nesting breaks the selector. Hard to maintain. | +| XPath: indices and positions (e.g., `(//button)[2]`) | 🚨 Poor | Very fragile. Element position can change when adjacent elements are added/removed. Doesn’t reflect the element’s meaning. | +| Link Text selectors (e.g., `:link("More details")`) | βœ… Good | Natural for users. However, like element text, requires consideration of translations and dynamic changes. | +| Shadow DOM selectors (e.g., `::shadow .inner-element`) | ⚠️ Rarely | Specific to web components. Can be unstable when libraries/components are updated. Use only if there’s no alternative. | + +## WebDriverIO + +Testplane supports element location compatible with WebdriverIO syntax: CSS selectors, XPath, element text, and other methods described below. + +### CSS selectors + +#### 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`). + +#### CSS selectors by the data-testid attribute + +To find elements that are marked for testing, use selectors based on the `data-testid` attribute. + +```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. + +### 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). + +### Link Text selectors + +Link Text selectors allow you to find links (``) by their inner text. Use `="text"` to find an element with exact text match and `*="text"` for partial text matching. + +```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. + +### 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. + +### 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 `screen.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 `screen.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/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..d5d26fb9 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -0,0 +1,484 @@ +# Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ + +Testplane прСдоставляСт мноТСство способов для поиска элСмСнтов Π½Π° страницС Π±Ρ€Π°ΡƒΠ·Π΅Ρ€Π°. + +## Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ ΠΏΠΎ использованию сСлСкторов + +| Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€ / Бпособ Π²Ρ‹Π±ΠΎΡ€Π° | РСкомСндация | ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ | +| ---------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| По классу (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `.btn`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | НС связан с ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ΠΌ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ Π½Π΅ ΠΎΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ сСмантику, Π½ΠΎ Ρ‡Π°Ρ‰Π΅ всСго стабилСн. | +| По ID (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `#main`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | ID ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Π΅Π΅ классов, Π½ΠΎ всС Ρ€Π°Π²Π½ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π²ΠΈΡΠ΅Ρ‚ΡŒ ΠΎΡ‚ JS‑логики ΠΈΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π² Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠ΅. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ID Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎ устойчив. | +| По Ρ‚ΠΈΠΏΡƒ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `input[type="text"]`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚, Ссли Ρ‚ΠΈΠΏ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° сСмантичСски Π·Π½Π°Ρ‡ΠΈΠΌ ΠΈ стабилСн. Но ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ слишком ΠΎΠ±Ρ‰ΠΈΠΌ (ΠΌΠ½ΠΎΠ³ΠΎ элСмСнтов ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ°). | +| CSS‑сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ `data-testid` (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `[data-testid="submit"]`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | Π›ΡƒΡ‡ΡˆΠΈΠΉ Π²Ρ‹Π±ΠΎΡ€ для тСстов. Атрибут `data-testid` создан ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ для Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·Π°Ρ†ΠΈΠΈ: Π½Π΅ влияСт Π½Π° стили/Π΄ΠΎΡΡ‚ΡƒΠΏΠ½ΠΎΡΡ‚ΡŒ, стабилСн, явно ΠΎΠ±ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚ Ρ†Π΅Π»ΡŒ элСмСнта. | +| По тСксту элСмСнта (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `*=ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | ΠžΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ΅ взаимодСйствиС. Но Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ внимания ΠΊ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π°ΠΌ ΠΈ динамичСскому ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Ρƒ. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ„Π°ΠΉΠ»Ρ‹ Π»ΠΎΠΊΠ°Π»Π΅ΠΉ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ тСсты Π½Π΅ ломались ΠΏΡ€ΠΈ смСнС тСкста. | +| По Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌ (ΠΊΡ€ΠΎΠΌΠ΅ `data-testid`, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `[name="email"]`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Зависит ΠΎΡ‚ сСмантики HTML‑атрибутов. ΠœΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ (Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ ΠΌΠ΅Π½ΡΡŽΡ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»ΡΡŽΡ‚ΡΡ). | +| Навигация ΠΏΠΎ DOM (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `parent().find('button')`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | ΠšΡ€Π°ΠΉΠ½Π΅ Ρ…Ρ€ΡƒΠΏΠΊΠΈΠΉ. Зависит ΠΎΡ‚ структуры Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ: любоС ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ влоТСнности Π»ΠΎΠΌΠ°Π΅Ρ‚ сСлСктор. Π’Ρ€ΡƒΠ΄Π½ΠΎ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ. | +| XPath: индСксы ΠΈ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `(//button)[2]`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | ΠžΡ‡Π΅Π½ΡŒ Ρ…Ρ€ΡƒΠΏΠΊΠΈΠΉ. ΠŸΠΎΠ·ΠΈΡ†ΠΈΡ элСмСнта ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒΡΡ ΠΏΡ€ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ/ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ сосСдних элСмСнтов. НС ΠΎΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ смысл элСмСнта. | +| Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ Link Text (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `:link("ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅")`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | ЕстСствСнСн для ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ. Но, ΠΊΠ°ΠΊ ΠΈ с тСкстом элСмСнта, Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ ΡƒΡ‡Π΅Ρ‚Π° ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΎΠ² ΠΈ динамичСских ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ. | +| Shadow DOM сСлСкторы (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `::shadow .inner-element`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Π‘ΠΏΠ΅Ρ†ΠΈΡ„ΠΈΡ‡Π½Ρ‹ для вСб‑компонСнтов. ΠœΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌΠΈ ΠΏΡ€ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ/ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ². Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π½Π΅Ρ‚ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Ρ‹. | + +## WebDriverIO + +Π’ Testplane поддСрТиваСтся поиск элСмСнтов, совмСстимый с синтаксисом WebdriverIO: ΠΏΠΎ CSS сСлСкторам, ΠΏΠΎ XPath, ΠΏΠΎ тСксту элСмСнтов ΠΈ ΠΏΠΎ Π΄Ρ€ΡƒΠ³ΠΈΠΌ ΠΏΡ€ΠΈΠ·Π½Π°ΠΊΠ°ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ описаны Π½ΠΈΠΆΠ΅. + +### CSS-сСлСкторы + +#### По классу + +Π§Ρ‚ΠΎΠ±Ρ‹ Π½Π°ΠΉΡ‚ΠΈ элСмСнт Π½Π° страницС ΠΏΠΎ классу, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ сСлСктор `".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`). + +#### CSS-сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ data-testid + +Для поиска элСмСнтов, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΠΎΠΌΠ΅Ρ‡Π΅Π½Ρ‹ для тСстирования, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ `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/стилСй. + +### 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); + }); +}); +``` + +Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: + +- Π½ΡƒΠΆΠ΅Π½ доступ ΠΊ элСмСнту ΠΏΠΎ Π΅Π³ΠΎ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ Π² Π½Π°Π±ΠΎΡ€Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ²; +- тСстируСтС ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ ΠΈΠ»ΠΈ списки с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ порядком; +- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Ρ‚Π°Π±Π»ΠΈΡ†Π°ΠΌΠΈ, ΠΈ Π½ΡƒΠΆΠ½Π° конкрСтная строка; +- Π½ΡƒΠΆΠ΅Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈΠ»ΠΈ послСдний элСмСнт срСди Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ…; +- тСстируСтС сортировку (ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ°, Ρ‡Ρ‚ΠΎ элСмСнт Π½Π° ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ). + +### Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ Link Text + +Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ содСрТащСмуся Π²Π½ΡƒΡ‚Ρ€ΠΈ тСксту ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ ссылки `()` ΠΏΠΎ ΠΈΡ… тСксту. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `="text"`, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π°ΠΉΡ‚ΠΈ элСмСнт с Ρ‚ΠΎΡ‡Π½Ρ‹ΠΌ тСкстом ΠΈ `*="text"` для поиска ΠΏΠΎ частичному совпадСнию тСкста. + +```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}`, + ); + }); +}); +``` + +Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: + +- тСкст элСмСнтов стабилСн; +- Π²Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ тСст Π±Ρ‹Π» максимально ΠΏΡ€ΠΈΠ±Π»ΠΈΠΆΠ΅Π½ ΠΊ Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹ΠΌ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΌ сцСнариям. + +### 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 позволяСт ΠΈΡΠΊΠ°Ρ‚ΡŒ элСмСнты Ρ‚Π°ΠΊ, ΠΊΠ°ΠΊ ΠΈΡ… ΠΈΡ‰ΡƒΡ‚ Π½Π° страницС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ β€” ΠΏΠΎ тСксту, Ρ‚ΠΈΠΏΡƒ элСмСнта ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠΌ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ зависят ΠΎΡ‚ Π΄Π΅Ρ‚Π°Π»Π΅ΠΉ вашСй вСрстки. + +### ByRole + +`getByRole` β€” основной ΠΌΠ΅Ρ‚ΠΎΠ΄ Π² Testing Library, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ элСмСнты ΠΏΠΎ ΠΈΡ… ARIA-ролям. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄ `screen.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` ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΊΠ°ΠΊ послСдний Π²Π°Ρ€ΠΈΠ°Π½Ρ‚, ΠΊΠΎΠ³Π΄Π° Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Π½Π΅ подходят. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ `screen.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(); + }); +}); +``` From 33a79e3134e720e7b27168a60536f4b496da0c82 Mon Sep 17 00:00:00 2001 From: shadowusr Date: Wed, 18 Mar 2026 02:25:53 +0300 Subject: [PATCH 2/2] docs: final fixes of selectors articles --- docs/basic-guides/selectors.mdx | 226 +++++++++--------- docs/quickstart/writing-tests.mdx | 1 - .../current/basic-guides/selectors.mdx | 226 +++++++++--------- .../current/quickstart/writing-tests.mdx | 1 - src/scss/custom.scss | 3 +- 5 files changed, 239 insertions(+), 218 deletions(-) diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx index 54468c6d..d3ce3db6 100644 --- a/docs/basic-guides/selectors.mdx +++ b/docs/basic-guides/selectors.mdx @@ -4,25 +4,99 @@ Testplane provides multiple ways to locate elements on a web page. ## Recommendations for using selectors -| Selector / Selection method | Recommendation | Notes | -| ------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| By class (e.g., `.btn`) | 🚨 Poor | Not related to user behavior and doesn’t reflect semantics, but is usually stable. | -| 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). | -| CSS selectors by `data-testid` attribute (e.g., `[data-testid="submit"]`) | βœ… Good | The best choice for tests. The `data-testid` attribute is designed specifically for automation: it doesn’t affect styles/accessibility, is stable, and clearly indicates the element’s purpose. | -| By element text (e.g., `*=Submit`) | βœ… Good | Reflects user interaction. However, requires attention to translations and dynamic content. Use locale files to prevent tests from breaking when text changes. | -| By attributes (except `data-testid`, e.g., `[name="email"]`) | ⚠️ Rarely | Depends on HTML attribute semantics. Can be unstable (attributes may change or be removed). | -| DOM navigation (e.g., `parent().find('button')`) | 🚨 Poor | Extremely fragile. Depends on markup structure: any change in nesting breaks the selector. Hard to maintain. | -| XPath: indices and positions (e.g., `(//button)[2]`) | 🚨 Poor | Very fragile. Element position can change when adjacent elements are added/removed. Doesn’t reflect the element’s meaning. | -| Link Text selectors (e.g., `:link("More details")`) | βœ… Good | Natural for users. However, like element text, requires consideration of translations and dynamic changes. | -| Shadow DOM selectors (e.g., `::shadow .inner-element`) | ⚠️ Rarely | Specific to web components. Can be unstable when libraries/components are updated. Use only if there’s no alternative. | - -## WebDriverIO +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. @@ -44,10 +118,10 @@ describe("CSS selector by class", () => { 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. +- 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 @@ -70,9 +144,9 @@ describe("CSS selector by ID", () => { 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). +- 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 @@ -96,33 +170,8 @@ describe("CSS selector by attribute type", () => { 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`). - -#### CSS selectors by the data-testid attribute - -To find elements that are marked for testing, use selectors based on the `data-testid` attribute. - -```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. +- 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 @@ -148,8 +197,8 @@ describe("XPath selector by element text", () => { Use this approach if: -- the element text is unique and stable (button labels, headings); -- other element-finding strategies are not applicable. +- the element text is unique and stable (button labels, headings) +- other element-finding strategies are not applicable #### By attributes @@ -172,11 +221,11 @@ describe("XPath selector by attribute", () => { 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. +- 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 @@ -238,50 +287,11 @@ describe("XPath selector: indices and positions", () => { 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). - -### Link Text selectors - -Link Text selectors allow you to find links (``) by their inner text. Use `="text"` to find an element with exact text match and `*="text"` for partial text matching. - -```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. +- 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 @@ -299,19 +309,21 @@ 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. +- 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. +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 `screen.getByRole("button", { name: /submit/i })`, you will find a button with text containing `submit`. +`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", () => { @@ -461,7 +473,7 @@ describe("Finding an element using the getByTitle method", () => { ### ByTestId -`getByTestId` is used as a last resort when other methods are not suitable. For example, if you use `screen.getByTestId("submit-button")`, you will find an element with the attribute `data-testid="submit-button"`. +`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", () => { 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 index d5d26fb9..ca1c2e79 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -4,25 +4,99 @@ Testplane прСдоставляСт мноТСство способов для ## Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ ΠΏΠΎ использованию сСлСкторов -| Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€ / Бпособ Π²Ρ‹Π±ΠΎΡ€Π° | РСкомСндация | ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΡ | -| ---------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| По классу (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `.btn`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | НС связан с ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ΠΌ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ Π½Π΅ ΠΎΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ сСмантику, Π½ΠΎ Ρ‡Π°Ρ‰Π΅ всСго стабилСн. | -| По ID (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `#main`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | ID ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Π΅Π΅ классов, Π½ΠΎ всС Ρ€Π°Π²Π½ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ Π·Π°Π²ΠΈΡΠ΅Ρ‚ΡŒ ΠΎΡ‚ JS‑логики ΠΈΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π² Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠ΅. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ID Π³Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎ устойчив. | -| По Ρ‚ΠΈΠΏΡƒ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `input[type="text"]`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚, Ссли Ρ‚ΠΈΠΏ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° сСмантичСски Π·Π½Π°Ρ‡ΠΈΠΌ ΠΈ стабилСн. Но ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ слишком ΠΎΠ±Ρ‰ΠΈΠΌ (ΠΌΠ½ΠΎΠ³ΠΎ элСмСнтов ΠΎΠ΄Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ°). | -| CSS‑сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ `data-testid` (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `[data-testid="submit"]`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | Π›ΡƒΡ‡ΡˆΠΈΠΉ Π²Ρ‹Π±ΠΎΡ€ для тСстов. Атрибут `data-testid` создан ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎ для Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·Π°Ρ†ΠΈΠΈ: Π½Π΅ влияСт Π½Π° стили/Π΄ΠΎΡΡ‚ΡƒΠΏΠ½ΠΎΡΡ‚ΡŒ, стабилСн, явно ΠΎΠ±ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚ Ρ†Π΅Π»ΡŒ элСмСнта. | -| По тСксту элСмСнта (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `*=ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | ΠžΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ΅ взаимодСйствиС. Но Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ внимания ΠΊ ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄Π°ΠΌ ΠΈ динамичСскому ΠΊΠΎΠ½Ρ‚Π΅Π½Ρ‚Ρƒ. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ„Π°ΠΉΠ»Ρ‹ Π»ΠΎΠΊΠ°Π»Π΅ΠΉ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ тСсты Π½Π΅ ломались ΠΏΡ€ΠΈ смСнС тСкста. | -| По Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌ (ΠΊΡ€ΠΎΠΌΠ΅ `data-testid`, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `[name="email"]`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Зависит ΠΎΡ‚ сСмантики HTML‑атрибутов. ΠœΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ (Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ ΠΌΠ΅Π½ΡΡŽΡ‚ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»ΡΡŽΡ‚ΡΡ). | -| Навигация ΠΏΠΎ DOM (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `parent().find('button')`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | ΠšΡ€Π°ΠΉΠ½Π΅ Ρ…Ρ€ΡƒΠΏΠΊΠΈΠΉ. Зависит ΠΎΡ‚ структуры Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ: любоС ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ влоТСнности Π»ΠΎΠΌΠ°Π΅Ρ‚ сСлСктор. Π’Ρ€ΡƒΠ΄Π½ΠΎ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Ρ‚ΡŒ. | -| XPath: индСксы ΠΈ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `(//button)[2]`) | 🚨 ΠŸΠ»ΠΎΡ…ΠΎ | ΠžΡ‡Π΅Π½ΡŒ Ρ…Ρ€ΡƒΠΏΠΊΠΈΠΉ. ΠŸΠΎΠ·ΠΈΡ†ΠΈΡ элСмСнта ΠΌΠΎΠΆΠ΅Ρ‚ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒΡΡ ΠΏΡ€ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ/ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ сосСдних элСмСнтов. НС ΠΎΡ‚Ρ€Π°ΠΆΠ°Π΅Ρ‚ смысл элСмСнта. | -| Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ Link Text (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `:link("ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Π΅Π΅")`) | βœ… Π₯ΠΎΡ€ΠΎΡˆΠΎ | ЕстСствСнСн для ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ. Но, ΠΊΠ°ΠΊ ΠΈ с тСкстом элСмСнта, Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ ΡƒΡ‡Π΅Ρ‚Π° ΠΏΠ΅Ρ€Π΅Π²ΠΎΠ΄ΠΎΠ² ΠΈ динамичСских ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ. | -| Shadow DOM сСлСкторы (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `::shadow .inner-element`) | ⚠️ Π Π΅Π΄ΠΊΠΎ | Π‘ΠΏΠ΅Ρ†ΠΈΡ„ΠΈΡ‡Π½Ρ‹ для вСб‑компонСнтов. ΠœΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ Π½Π΅ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌΠΈ ΠΏΡ€ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ/ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ². Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π½Π΅Ρ‚ Π°Π»ΡŒΡ‚Π΅Ρ€Π½Π°Ρ‚ΠΈΠ²Ρ‹. | - -## WebDriverIO +ΠœΡ‹ Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡƒΠ΅ΠΌ ΠΈΡΠΊΠ°Ρ‚ΡŒ элСмСнты Ρ‚Π°ΠΊ ΠΆΠ΅, ΠΊΠ°ΠΊ это Π΄Π΅Π»Π°ΡŽΡ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ тСстируСмого прилоТСния. + +ΠŸΡ€ΠΈ Π²Ρ‹Π±ΠΎΡ€Π΅ ΠΌΠΎΠΆΠ½ΠΎ Ρ€ΡƒΠΊΠΎΠ²ΠΎΠ΄ΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒΡΡ 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"`. @@ -44,10 +118,10 @@ describe("CSS-сСлСктор ΠΏΠΎ классу", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- класс являСтся ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ ΠΈ Π½Π΅ гСнСрируСтся динамичСски; -- Π½ΡƒΠΆΠ΅Π½ быстрый ΠΈ простой способ Π½Π°ΠΉΡ‚ΠΈ элСмСнт; -- класс сСмантичСски описываСт элСмСнт (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `.error-message`, `.success-banner`); -- Π²Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π½Ρ‹ΠΌΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ°ΠΌΠΈ, Π³Π΄Π΅ классы ΡΠ²Π»ΡΡŽΡ‚ΡΡ Ρ‡Π°ΡΡ‚ΡŒΡŽ API. +- класс являСтся ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹ΠΌ ΠΈ Π½Π΅ гСнСрируСтся динамичСски +- Π½ΡƒΠΆΠ΅Π½ быстрый ΠΈ простой способ Π½Π°ΠΉΡ‚ΠΈ элСмСнт +- класс сСмантичСски описываСт элСмСнт (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, `.error-message`, `.success-banner`) +- Π²Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π½Ρ‹ΠΌΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ°ΠΌΠΈ, Π³Π΄Π΅ классы ΡΠ²Π»ΡΡŽΡ‚ΡΡ Ρ‡Π°ΡΡ‚ΡŒΡŽ API #### По id @@ -70,9 +144,9 @@ describe("CSS-сСлСктор ΠΏΠΎ id", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- Π²Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌ быстрый ΠΈ простой способ поиска элСмСнтов; -- `id` являСтся Ρ‡Π°ΡΡ‚ΡŒΡŽ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ³ΠΎ API ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°; -- Π½ΡƒΠΆΠ½Π° максимальная ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ сСлСктора (`id` β€” самый быстрый сСлСктор). +- Π²Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌ быстрый ΠΈ простой способ поиска элСмСнтов +- `id` являСтся Ρ‡Π°ΡΡ‚ΡŒΡŽ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ³ΠΎ API ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π° +- Π½ΡƒΠΆΠ½Π° максимальная ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ сСлСктора (`id` β€” самый быстрый сСлСктор) #### По Ρ‚ΠΈΠΏΡƒ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° @@ -96,33 +170,8 @@ describe("CSS-сСлСктор ΠΏΠΎ Ρ‚ΠΈΠΏΡƒ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- Π½ΡƒΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ всС элСмСнты ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° (всС чСкбоксы, всС Ρ€Π°Π΄ΠΈΠΎΠΊΠ½ΠΎΠΏΠΊΠΈ); -- Π½ΡƒΠΆΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с сСмантичСскими HTML5 Ρ‚ΠΈΠΏΠ°ΠΌΠΈ (`email`, `tel`, `url`, `date`). - -#### CSS-сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ data-testid - -Для поиска элСмСнтов, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΠΎΠΌΠ΅Ρ‡Π΅Π½Ρ‹ для тСстирования, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ сСлСкторы ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ `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/стилСй. +- Π½ΡƒΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ всС элСмСнты ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ Ρ‚ΠΈΠΏΠ° (всС чСкбоксы, всС Ρ€Π°Π΄ΠΈΠΎΠΊΠ½ΠΎΠΏΠΊΠΈ) +- Π½ΡƒΠΆΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с сСмантичСскими HTML5 Ρ‚ΠΈΠΏΠ°ΠΌΠΈ (`email`, `tel`, `url`, `date`) ### XPath-сСлСкторы @@ -148,8 +197,8 @@ describe("XPath-сСлСктор ΠΏΠΎ тСксту элСмСнта", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- тСкст элСмСнта ΡƒΠ½ΠΈΠΊΠ°Π»Π΅Π½ ΠΈ стабилСн (названия ΠΊΠ½ΠΎΠΏΠΎΠΊ, Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ); -- Π΄Ρ€ΡƒΠ³ΠΈΠ΅ стратСгии поиска элСмСнтов оказались Π½Π΅ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΠΌΡ‹. +- тСкст элСмСнта ΡƒΠ½ΠΈΠΊΠ°Π»Π΅Π½ ΠΈ стабилСн (названия ΠΊΠ½ΠΎΠΏΠΎΠΊ, Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ) +- Π΄Ρ€ΡƒΠ³ΠΈΠ΅ стратСгии поиска элСмСнтов оказались Π½Π΅ΠΏΡ€ΠΈΠΌΠ΅Π½ΠΈΠΌΡ‹ #### По Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌ @@ -172,11 +221,11 @@ describe("XPath-сСлСктор ΠΏΠΎ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρƒ", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- Π½ΡƒΠΆΠ½Ρ‹ слоТныС условия поиска (ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΈ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ²); -- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с динамичСскими Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌΠΈ (`data`-Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ с ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌΠΈ значСниями); -- Π½ΡƒΠΆΠ½Π° Π³ΠΈΠ±ΠΊΠΎΡΡ‚ΡŒ Π² поискС (частичныС совпадСния, Π½Π°Ρ‡Π°Π»ΠΎ/ΠΊΠΎΠ½Π΅Ρ† строки); -- CSS-сСлСкторы Π½Π΅ ΠΌΠΎΠ³ΡƒΡ‚ Π²Ρ‹Ρ€Π°Π·ΠΈΡ‚ΡŒ Π½ΡƒΠΆΠ½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ; -- Π½ΡƒΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ элСмСнт ΠΏΠΎ ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΠΈΡŽ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°. +- Π½ΡƒΠΆΠ½Ρ‹ слоТныС условия поиска (ΠΊΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΈ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ²) +- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с динамичСскими Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌΠΈ (`data`-Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ с ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹ΠΌΠΈ значСниями) +- Π½ΡƒΠΆΠ½Π° Π³ΠΈΠ±ΠΊΠΎΡΡ‚ΡŒ Π² поискС (частичныС совпадСния, Π½Π°Ρ‡Π°Π»ΠΎ/ΠΊΠΎΠ½Π΅Ρ† строки) +- CSS-сСлСкторы Π½Π΅ ΠΌΠΎΠ³ΡƒΡ‚ Π²Ρ‹Ρ€Π°Π·ΠΈΡ‚ΡŒ Π½ΡƒΠΆΠ½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ +- Π½ΡƒΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ элСмСнт ΠΏΠΎ ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΠΈΡŽ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π° #### Навигация ΠΏΠΎ DOM @@ -236,50 +285,11 @@ describe("XPath-сСлСктор: индСксы ΠΈ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ", () => { Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- Π½ΡƒΠΆΠ΅Π½ доступ ΠΊ элСмСнту ΠΏΠΎ Π΅Π³ΠΎ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ Π² Π½Π°Π±ΠΎΡ€Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ²; -- тСстируСтС ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ ΠΈΠ»ΠΈ списки с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ порядком; -- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Ρ‚Π°Π±Π»ΠΈΡ†Π°ΠΌΠΈ, ΠΈ Π½ΡƒΠΆΠ½Π° конкрСтная строка; -- Π½ΡƒΠΆΠ΅Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈΠ»ΠΈ послСдний элСмСнт срСди Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ…; -- тСстируСтС сортировку (ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ°, Ρ‡Ρ‚ΠΎ элСмСнт Π½Π° ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ). - -### Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ Link Text - -Π‘Π΅Π»Π΅ΠΊΡ‚ΠΎΡ€Ρ‹ ΠΏΠΎ содСрТащСмуся Π²Π½ΡƒΡ‚Ρ€ΠΈ тСксту ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ ссылки `()` ΠΏΠΎ ΠΈΡ… тСксту. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `="text"`, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π°ΠΉΡ‚ΠΈ элСмСнт с Ρ‚ΠΎΡ‡Π½Ρ‹ΠΌ тСкстом ΠΈ `*="text"` для поиска ΠΏΠΎ частичному совпадСнию тСкста. - -```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}`, - ); - }); -}); -``` - -Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: - -- тСкст элСмСнтов стабилСн; -- Π²Π°ΠΌ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ тСст Π±Ρ‹Π» максимально ΠΏΡ€ΠΈΠ±Π»ΠΈΠΆΠ΅Π½ ΠΊ Ρ€Π΅Π°Π»ΡŒΠ½Ρ‹ΠΌ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠΌ сцСнариям. +- Π½ΡƒΠΆΠ΅Π½ доступ ΠΊ элСмСнту ΠΏΠΎ Π΅Π³ΠΎ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ Π² Π½Π°Π±ΠΎΡ€Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² +- тСстируСтС ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΡŽ ΠΈΠ»ΠΈ списки с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ порядком +- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Ρ‚Π°Π±Π»ΠΈΡ†Π°ΠΌΠΈ, ΠΈ Π½ΡƒΠΆΠ½Π° конкрСтная строка +- Π½ΡƒΠΆΠ΅Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ ΠΈΠ»ΠΈ послСдний элСмСнт срСди Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ… +- тСстируСтС сортировку (ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ°, Ρ‡Ρ‚ΠΎ элСмСнт Π½Π° ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠΉ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ) ### Shadow-DOM-сСлСкторы @@ -297,19 +307,21 @@ const slotElements = await customElement.shadow$$(".slot-item"); Π‘Ρ‚ΠΎΠΈΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ, Ссли: -- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Web Components ΠΈ Custom Elements; -- ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ Shadow DOM для инкапсуляции стилСй; -- тСстируСтС ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ ΠΈΠ· сторонних Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ (Lit, Stencil, native Web Components); -- Π½ΡƒΠΆΠ΅Π½ доступ ΠΊ элСмСнтам Π²Π½ΡƒΡ‚Ρ€ΠΈ shadow root; -- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Π΄ΠΈΠ·Π°ΠΉΠ½-систСмой Π½Π° Π±Π°Π·Π΅ Web Components. +- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Web Components ΠΈ Custom Elements +- ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ Shadow DOM для инкапсуляции стилСй +- тСстируСтС ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ ΠΈΠ· сторонних Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ (Lit, Stencil, native Web Components) +- Π½ΡƒΠΆΠ΅Π½ доступ ΠΊ элСмСнтам Π²Π½ΡƒΡ‚Ρ€ΠΈ shadow root +- Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅ с Π΄ΠΈΠ·Π°ΠΉΠ½-систСмой Π½Π° Π±Π°Π·Π΅ Web Components -## Testing-library +## Testing Library Testing Library позволяСт ΠΈΡΠΊΠ°Ρ‚ΡŒ элСмСнты Ρ‚Π°ΠΊ, ΠΊΠ°ΠΊ ΠΈΡ… ΠΈΡ‰ΡƒΡ‚ Π½Π° страницС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ β€” ΠΏΠΎ тСксту, Ρ‚ΠΈΠΏΡƒ элСмСнта ΠΈΠ»ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠΌ Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°ΠΌ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ зависят ΠΎΡ‚ Π΄Π΅Ρ‚Π°Π»Π΅ΠΉ вашСй вСрстки. +Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹ΠΉ Π²ΠΈΠ΄ сСлСкторов, Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ [ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Testing Library](../../guides/how-to-add-testing-library). + ### ByRole -`getByRole` β€” основной ΠΌΠ΅Ρ‚ΠΎΠ΄ Π² Testing Library, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ элСмСнты ΠΏΠΎ ΠΈΡ… ARIA-ролям. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄ `screen.getByRole("button", { name: /submit/i })`, Ρ‚ΠΎ Π½Π°ΠΉΠ΄Π΅Ρ‚Π΅ ΠΊΠ½ΠΎΠΏΠΊΡƒ с тСкстом, содСрТащим `submit`. +`getByRole` β€” основной ΠΌΠ΅Ρ‚ΠΎΠ΄ Π² Testing Library, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт Π½Π°Ρ…ΠΎΠ΄ΠΈΡ‚ΡŒ элСмСнты ΠΏΠΎ ΠΈΡ… ARIA-ролям. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄ `browser.getByRole("button", { name: /submit/i })`, Ρ‚ΠΎ Π½Π°ΠΉΠ΄Π΅Ρ‚Π΅ ΠΊΠ½ΠΎΠΏΠΊΡƒ с тСкстом, содСрТащим `submit`. ```javascript describe("getByRole", () => { @@ -466,7 +478,7 @@ describe("Поиск элСмСнта с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° getByTitle" ### ByTestId -`getByTestId` ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΊΠ°ΠΊ послСдний Π²Π°Ρ€ΠΈΠ°Π½Ρ‚, ΠΊΠΎΠ³Π΄Π° Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Π½Π΅ подходят. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ `screen.getByTestId("submit-button")`, Ρ‚ΠΎ Π½Π°ΠΉΠ΄ΠΈΡ‚Π΅ элСмСнт с Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠΌ `data-testid="submit-button"` +`getByTestId` ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΊΠ°ΠΊ послСдний Π²Π°Ρ€ΠΈΠ°Π½Ρ‚, ΠΊΠΎΠ³Π΄Π° Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Π½Π΅ подходят. НапримСр, Ссли Π²Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚Π΅ `browser.getByTestId("submit-button")`, Ρ‚ΠΎ Π½Π°ΠΉΠ΄ΠΈΡ‚Π΅ элСмСнт с Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠΌ `data-testid="submit-button"` ```javascript describe("Поиск элСмСнта с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° getByTestId", () => { 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: "";