CSS-селекторы в Playwright

При автоматизации действий в браузере с помощью Playwright нам необходимо находить и выбирать элементы. Без возможности выбрать элемент мы не можем кликнуть по нему, заполнить форму, и даже прокрутить страницу к нужному элементу, потому что Playwright не знает, где он находится.

Если Playwright не может найти элементы на странице, он ничего не сможет сделать, кроме как открыть новые страницы и закрыть браузер.

В этой статье вы познакомитесь с базовыми и расширенными CSS-селекторами, а также научитесь применять их на практике.

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

Как найти элементы с помощью CSS-селектора

Чтобы найти элементы по CSS-селектору в Node.js с помощью Playwright, следуйте приведенному ниже скрипту:

const playwright = require("playwright");
async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com");
    //find the first h1 element
    const h1 = page.locator("h1").first();
    const h1Text = await h1.textContent();
    console.log(`H1 element: ${h1Text}`);
    //find all elements with the class "quote"
    const quotes = page.locator(".quote");
    //get the count of our quotes list
    const quoteCount = await quotes.count();
    //iterate through quotes
    for (let i=0; i<quoteCount; i++) {
        //get the text of the quote
        text = await quotes.nth(i).textContent();
        //log it to the console
        console.log(`Quote: ${text}`);
    }
    //close the browser
    await browser.close();
}

main()
  • Сначала мы открываем браузер с помощью функции launch().
  • В браузере мы создаем новую вкладку с помощью browser.newPage() и переходим по URL-адресу Quotes to Scrape.
  • Мы находим первый элемент h1 на странице с помощью page.locator("h1").first().
  • После этого мы извлекаем текстовое содержимое найденного элемента h1 с помощью h1.textContent() и выводим его в консоль.
  • Находим все элементы с классом “quote” (“цитата”) на странице, используя page.locator(".quote").
  • Выводим текстовое содержимое каждой цитаты в консоль и закрываем экземпляр браузера, используя browser.close(), чтобы освободить системные ресурсы.

Понимание CSS-селекторов

CSS-селекторы используются не только для поиска элементов, но и для стилизации веб-страницы с помощью CSS. Зачастую при оформлении веб-страницы разработчик использует классы для придания стиля группе элементов.

Для начала давайте рассмотрим пример с HTML и CSS.

Создаём новый HTML-файл:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Our Demo Page</title>
        <link rel="stylesheet" type="text/css" href="demo.css">
    </head>
    <body>
        <h1>Hello I am an HMTL File</h1>
        <p>These are some smaller words.</p>
    </body>
</html>

Если вы откроете этот файл в браузере, он будет выглядеть довольно скучно:

Теперь перейдём к классу. Давайте создадим следующий CSS-файл:

.our-new-class {
    background-color: black;
    color: white;
}

Далее мы обновим HTML-файл, добавив наш класс к тегу body.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Our Demo Page</title>
        <link rel="stylesheet" type="text/css" href="demo.css">
    </head>
    <body class="our-new-class">
        <h1>Hello I am an HMTL File</h1>
        <p>These are some smaller words.</p>
    </body>
</html>

Посмотрите, как теперь выглядит наш файл в браузере:

С помощью CSS-селекторов мы можем выбирать элементы на странице по:

  • тегу
  • id
  • классу
  • атрибуту
  • потомку
  • дочернему элементу
  • соседнему элементу
  • родственному элементу
  • псевдоэлементу
  • псевдоклассу
Тип селектораСинтаксис CSSОписание
ТегtagВыбирает элементы по их тегам.
ID#idВыбирает элемент по его идентификатору.
Класс.classВыбирает элементы по их классу.
Атрибут[attribute=value]Выбирает элементы с определенным значением атрибута.
Потомокancestor descendantВыбирает элементы-потомки внутри другого элемента.
Дочерний элементparent > childВыбирает дочерние элементы указанного родителя.
Соседний элементprevious + nextВыбирает элемент, следующий непосредственно за другим.
Родственный элементsibling ~ siblingВыбирает все элементы, имеющие общего родителя и находящиеся на одном уровне.
Псевдоклассelement:pseudo-classВыбирает элементы в определенном состоянии.

Базовые CSS-селекторы

Давайте рассмотрим некоторые базовые CSS-селекторы и их использование в Playwright.

Поиск по классу

Чтобы найти элемент по его классу, мы используем оператор . перед именем этого класса.

Если, например, нужно найти класс с именем my-custom-class, указываем в Playwright .my-custom-class.

В примере ниже показано, как найти элемент с классом tag.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com");
    //find the FIRST element with the class "tag"
    const firstTag = page.locator(".tag").first();
    //get the text
    const text = await firstTag.textContent();
    //log the text
    console.log("First tag:", text);
    //close the browser
    await browser.close();
}

main();

В этом примере:

  • Открываем браузер с помощью playwright.chromium.launch()
  • Создаем новую вкладку с помощью browser.newPage()
  • Переходим на веб-сайт, используя page.goto()
  • Находим первый элемент с классом tag с помощью page.locator(".tag").first()
  • Получаем текст из тега, используя firstTag.textContent()
  • Выводим текст в консоль console.log("First tag:", text)
  • Закрываем браузер с помощью browser.close()

Поиск по ID

Теперь найдем элемент, используя его ID.

Переходим на страницу входа в систему (“login”) и находим поле Username, используя его id с оператором # .

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/login");
    //find the FIRST element with the id "username"
    const username = page.locator("#username").first();
    //fill the box with text
    await username.fill("ScrapeOps");
    //take a screenshot
    await page.screenshot({ path: "find-by-id.png" });
    //close the browser
    await browser.close();
}

main();

В этом примере:

  • Открываем браузер с помощью playwright.chromium.launch()
  • Создаем новую вкладку, используя browser.newPage()
  • Переходим на сайт с помощью page.goto()
  • Находим поле Username по его ID с помощью page.locator("#username").first()
  • Заполняем поле текстом, используя username.fill()
  • Делаем скриншот с помощью page.screenshot()
  • Закрываем браузер, используя browser.close()

Итак, поле Username найдено по его ID и успешно заполнено текстом, что подтверждает ниже представленный скриншот.

Поиск по тегу

Теперь давайте найдем элемент, используя имя его тега. Например, чтобы найти все элементы <h1>, мы будем искать имя тега – h1.

Пример ниже практически идентичен нашему первому примеру. Попробуйте найти отличия.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com");
    //find the FIRST element with the tag "h1"
    const firstTag = page.locator("h1").first();
    //get the text
    const text = await firstTag.textContent();
    //log the text
    console.log("First h1 element by tag name:", text);
    //close the browser
    await browser.close();
}

main();

В этом примере:

  • Открываем браузер с помощью playwright.chromium.launch()
  • Создаем новую вкладку, используя browser.newPage()
  • Переходим на сайт с помощью page.goto()
  • Находим первый элемент <h1>, используя имя тега: page.locator("h1").first()
  • Получаем его текст с помощью firstTag.textContent()
  • Затем мы выводим текст в консоль и закрываем браузер

Расширенные CSS-селекторы

Далее рассмотрены методы, сочетающие в себе базовые селекторы и операторы для более точной и эффективной фильтрации элементов.

Наличие атрибута

Приведенный ниже код находит все элементы, содержащие атрибут id, и заполняет их текстом.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/login");
    //find the all elements that have an ID attribute
    const itemsWithId = page.locator("[id]");
    const itemsCount = await itemsWithId.count();
    //fill the items with text
    for (let i=0; i < itemsCount; i++) {
        await itemsWithId.nth(i).fill("scrapeops");
    }
    //take a screenshot
    await page.screenshot({ path: "find-by-attribute.png" });
    //close the browser
    await browser.close();
}

main();

В этом примере:

  • page.locator("[id]") возвращает все элементы, имеющие атрибут id
  • Наш результат – это не обычный массив. Чтобы перебрать его элементы, нам необходимо сначала получить их количество, используя itemsWithId.count()
  • Получив количество элементов, мы обращаемся к каждому из них и заполняем текстом: itemsWithId.nth(i).fill("scrapeops")
  • Далее мы делаем скриншот и закрываем браузер

Значение атрибута

В примере ниже показано, как найти все элементы с точным значением атрибута, а именно id = "username".

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/login");
    //find the all elements that have an ID attribute with the value "username"
    const itemsWithIdUsername = page.locator("[id='username']");
    const itemsCount = await itemsWithIdUsername.count();
    //fill the items with text
    for (let i=0; i < itemsCount; i++) {
        await itemsWithIdUsername.nth(i).fill("scrapeops")
    }
    //take a screenshot
    await page.screenshot({ path: "find-by-attribute-value.png" });
    //close the browser
    await browser.close();
}
main();

Единственное отличие в этом примере – использование page.locator("[id='username']") вместо page.locator("[id]").

Содержимое атрибута

Далее мы найдем элементы по содержимому их атрибутов. Приведенный ниже код находит все элементы с атрибутом href, содержащим слово “author”.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all elements with an href containing the word "author"
    const itemsWithAuthor = page.locator("[href*='author']");
    const itemsCount = await itemsWithAuthor.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsWithAuthor.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}
main();

В этом примере оператор *= указывает, что значение атрибута содержит текст 'author'.

Значение атрибута, начинающееся с определенной строки

Теперь мы найдем все элементы, у которых значение атрибута class начинается с буквы “q”.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all elements with an class starting with "q"
    const itemsWithQuote = page.locator("[class^='q']");
    const itemsCount = await itemsWithQuote.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsWithQuote.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

Здесь оператор ^= даёт команду нашему локатору искать только те элементы, у которых значение атрибута начинается с подстроки'q'.

Значение атрибута, заканчивающееся на определенную строку

В этом примере мы найдем все элементы, у которых значение атрибута class заканчивается на букву “e”.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all elements with an class ending with "e"
    const itemsWithQuote = page.locator("[class$='e']");
    const itemsCount = await itemsWithQuote.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsWithQuote.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

С помощью оператора $= мы сообщаем Playwright, что нам нужны только те элементы, у которых значение атрибута заканчивается на символ “e”.

Селектор потомков

Приведенный ниже код находит все элементы div, являющиеся потомками как минимум четырёх других элементов div.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all div elements nested within at least four other divs
    const itemsFromDivs = page.locator("div div div div div");
    const itemsCount = await itemsFromDivs.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsFromDivs.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}
main();

В этом примере page.locator("div div div div div") указывает Playwright найти все элементы div, являющихся потомками не менее четырёх других элементов div.

Дочерний селектор

Далее мы будем искать элементы с помощью оператора >. Приведенный ниже код ищет все элементы div, которые являются дочерними элементами тега body.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all div elements that are direct children of the body element
    const divsFromBody = page.locator("body > div");
    const itemsCount = await divsFromBody.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await divsFromBody.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

В этом примере:

  • page.locator("body > div") сообщает Playwright, что нам нужны все элементы div, которые являются дочерними элементами тега body.
  • При поиске элементов таким способом всегда указывайте их в следующем порядке: parentElement > childElement

Соседний селектор

Теперь для поиска мы будем использовать оператор +. Приведенный ниже код ищет все элементы div, которые следуют за другим элементом div и имеют общего родителя.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all div elements that are siblings adjacent to other divs
    const divsAdjacent = page.locator("div + div");
    const itemsCount = await divsAdjacent.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await divsAdjacent.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

В этом примере page.locator("div + div") сообщает Playwright, что нам нужны только те элементы div, которые следуют непосредственно за другим элементом div и имеют общего родителя.

Родственный селектор

Далее с помощью оператора ~ найдем все элементы div, которые находятся на том же уровне вложенности, и расположены после указанного элемента.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find the all div elements that are siblings to other divs
    const divsGeneral = page.locator("div ~ div");
    const itemsCount = await divsGeneral.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await divsGeneral.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

Здесь page.locator("div ~ div") сообщает Playwright, что нам нужны все элементы div, являющиеся последующими соседями другого элемента div с тем же родителем.

Псевдоклассы и псевдоэлементы

В отличии от псевдоклассов, псевдоэлементы отсутствуют в DOM-дереве, поэтому их нельзя найти с помощью Playwright. Приведенный ниже код находит все элементы div, которые являются первыми дочерними элементами своих родителей.

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find all div elements that are first children of any element in the DOM
    const itemsList = page.locator("div:first-child");
    const itemsCount = await itemsList.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsList.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}

main();

Обратите внимание, что при использовании псевдокласса применяется следующий синтаксис element:attribute-to-find

Комбинирование селекторов

И, наконец, мы найдем элементы, комбинируя несколько селекторов. Приведенный ниже код ищет все элементы div, которые являются первыми дочерними элементами body. При комбинировании нескольких селекторов мы просто передаем несколько селекторов в page.locator().

const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://quotes.toscrape.com/");
    //find all div elements that are first children of the body element
    const itemsList = page.locator("body > div:first-child");
    const itemsCount = await itemsList.count();
    //log the items to the console
    for (let i=0; i < itemsCount; i++) {
        text = await itemsList.nth(i).textContent()
        console.log("Text Content:", text);
    }
    //close the browser
    await browser.close();
}
main();

В этом примере page.locator("body > div:first-child") указывает Playwright найти все элементы div, которые являются первыми дочерними элементами body.


Рекомендации

При работе с CSS-селекторами в Playwright следуйте следующим рекомендациям, чтобы избежать распространенных ошибок.

Используйте DevTools

Для того, чтобы просмотреть HTML-структуру страницы и подобрать правильный селектор используйте инструменты разработчика (DevTools). Для этого просто щелкните по странице правой кнопкой мышки и выберите пункт Inspect (“Просмотреть код”) в выпадающем меню.

Пишите поддерживаемые селекторы

Когда мы пишем селекторы, важно найти баланс между эффективностью и удобством сопровождения. При написании селекторов внутри скрипта Playwright старайтесь использовать селекторы, которые легко понять и поддерживать.

Всегда используйте комментарии, когда это необходимо. Они значительно облегчают чтение и поддержку кода.

Найдите баланс между конкретикой и гибкостью

Слишком специфические селекторы могут быть отличными… поначалу. Если вы пишете код для динамической страницы, может произойти ситуация, когда селектор не найдет элемент, найденный вчера.

Чтобы избежать этого, пишите устойчивые селекторы, которые не будут ломаться при незначительных изменениях в HTML-структуре.

Не выбирайте элементы без причины

Выполняемый код потребляет ресурсы, и селекторы не являются исключением. Выбирайте только необходимые элементы на странице, иначе Playwright будет работать медленно или вовсе зависнет.

Используйте ожидания динамических элементов

Прежде чем взаимодействовать с динамическими элементами, необходимо дождаться их появления на странице. Для этого можно использовать различные методы ожидания в Playwright.

  • Фиксированное ожидание – В примере ниже, используя page.waitForTimeout(1000), мы ждем ровно одну секунду, а затем делаем скриншот страницы.
const playwright = require("playwright");

async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://www.espn.com/");
    //wait one second
    await page.waitForTimeout(1000)
    //take a screenshot
    await page.screenshot({ path: "hardcoded-wait.png"});
    //close the browser
    await browser.close();
}

main();
  • Сетевое ожидание – Приведенный ниже код выполняет ожидание на основе состояния сети. Мы используем page.waitForLoadState() и передаем ему в качестве параметра "networkidle". Это указывает Playwright на необходимость подождать, пока сеть не перейдет в режим ожидания, прежде чем продолжить выполнение скрипта.
async function main() {
    //open a browser
    const browser = await playwright.chromium.launch();
    //open a new page
    const page = await browser.newPage();
    //navigate to the site
    await page.goto("https://www.espn.com/");
    //wait until the network is idle
    await page.waitForLoadState("networkidle")
    //take a screenshot
    await page.screenshot({ path: "network-wait.png"});
    //close the browser
    await browser.close();
}

main();

Заключение

Итак, теперь вы хорошо разбираетесь в основах Playwright и CSS-селекторах. Вы определенно сможете воспользоваться этими знаниями и создать свой первый веб-скрапер с помощью Playwright.


Перевод статьи «Playwright Guide: How To Find Elements by CSS Selector».

🔥 Какой была ваша первая зарплата в QA и как вы искали первую работу? 

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Читать в телеграм

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *