Выделение текста — стандартное взаимодействие с пользователем, связанное с контекстно-зависимыми действиями, такими как копирование, обмен или поиск выделенного фрагмента. Могут ли Playwright, Selenium и Cypress эффективно эмулировать и верифицировать выделение текста? В этой статье мы рассмотрим, как автоматизировать выделение текста в веб-приложении, проверить его поведение и устранить возникающие при этом проблемы.
Демо-приложение
Наше демо-приложение с функцией интеллектуального выделения текста, которое распознает типы текста (электронные адреса, номера телефонов, адреса и даты), и предоставляет контекстные действия после выделения фрагмента.
Когда пользователь выделяет фрагмент текста, JavaScript извлекает его содержимое и определяет, соответствует ли оно заданному формату. Если совпадение найдено, рядом с выделением появляется небольшое меню действий, например:
- Электронная почта: «Отправить письмо»
- Номер телефона: «Позвонить по номеру» или «Добавить в контакты»
- Адрес: «Посмотреть на карте» или «Получить маршрут в точку»
- Дата: «Добавить в календарь»
Эта функциональность имитирует действия пользователя в мобильных операционных системах и веб-браузерах. Наша цель при автоматизации — проверить, что эта функция работает так, как ожидается, имитируя выделение текста и проверяя правильность последующих действий.
Автоматизировать выделение текста может быть непросто, поскольку большинство инструментов автоматизации взаимодействуют с элементами, а не с отдельными фрагментами текста. Однако Playwright, Selenium и Cypress предлагают методы выделения текста и проверке контекстного меню действий. Ниже приведены краткие примеры кода для каждого инструмента.
Playwright
В Playwright мы имитируем выделение текста, сначала находя нужный текст в абзаце, определяя его точную позицию, а затем выполняя JavaScript для создания выделения. Мы вызываем событие mouseup
для имитации взаимодействия с пользователем, обеспечивая появление контекстного меню действий. Наконец, мы проверяем, что меню содержит ожидаемую кнопку действия. Далее разберем это пошагово.
Начнем с открытия демо-приложения, и убедимся, что на странице присутствует абзац, содержащий текст, который мы хотим выделить.
import { test, expect } from "@playwright/test"; test("Select text and verify action menu", async ({ page }) => { await page.goto("http://localhost:3000"); await page.waitForSelector("#demo-text");
Далее мы получаем текст абзаца и определяем положение конкретной даты в нем.
const paragraphText = await page.locator("#demo-text").innerText(); const dateText = "March 15, 2025"; const datePosition = paragraphText.indexOf(dateText); expect(datePosition).toBeGreaterThan(-1);
Теперь мы выполняем JavaScript в браузере, чтобы найти текстовый фрагмент (нод), содержащий целевую дату, и создать диапазон выделения.
await page.evaluate((dateText) => { const textNodes = []; function getTextNodes(node) { if (node.nodeType === Node.TEXT_NODE) { textNodes.push(node); } else { for (let i = 0; i < node.childNodes.length; i++) { getTextNodes(node.childNodes[i]); } } } getTextNodes(document.body);
Теперь пройдемся по этим нодам, чтобы найти тот, который содержит нашу дату:
let targetNode = null; let targetOffset = -1; for (const node of textNodes) { const index = node.textContent.indexOf(dateText); if (index >= 0) { targetNode = node; targetOffset = index; break; } }
Получив целевой нод и позицию, мы создаем диапазон выделения и вызываем событие мыши, чтобы имитировать взаимодействие с пользователем.
if (targetNode) { const range = document.createRange(); range.setStart(targetNode, targetOffset); range.setEnd(targetNode, targetOffset + dateText.length); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range);
Далее мы моделируем событие mouseup
для запуска контекстного меню действий:
const mouseupEvent = new MouseEvent("mouseup", { bubbles: true, cancelable: true, view: window, }); document.dispatchEvent(mouseupEvent); } }, dateText);
Здесь отправляется событие MouseEvent
, имитирующее отпускание пользователем кнопки мыши после выделения текста. Это обеспечивает активацию логики выделения в приложении и отображение меню действий. После выделения текста мы проверяем, становится ли меню действий видимым.
await page.waitForSelector('#action-menu[style*="display: block"]', { timeout: 5000, });
Наконец, мы проверяем, что контекстное меню содержит ожидаемую кнопку «Добавить в календарь».
const actionButton = page.locator("#action-menu button"); await expect(actionButton).toBeVisible(); await expect(actionButton).toHaveText("Add to Calendar"); });
Selenium
Начнем с инициализации Selenium WebDriver и перехода на целевую веб-страницу.
from selenium import webdriver driver = webdriver.Chrome() driver.get("http://localhost:3000")
WebDriverWait
в Selenium проверяет, что элемент абзаца доступен, прежде чем произойдет взаимодействие с ним.
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) paragraph = wait.until(EC.presence_of_element_located((By.ID, "demo-text")))
Далее мы извлекаем текст абзаца и проверяем наличие ожидаемой даты.
paragraph_text = paragraph.text date_text = "March 15, 2025" if date_text not in paragraph_text: raise Exception(f"'{date_text}' not found in the paragraph text")
Поскольку логика выполнения JavaScript идентична примеру Playwright, она здесь не повторяется. Сценарий использует driver.execute_script()
для запуска JavaScript, который выбирает дату и запускает событие mouseup
.
После выбора текста мы некоторое время ждем, пока меню действий станет видимым.
import time time.sleep(0.5) action_menu = wait.until(EC.visibility_of_element_located((By.ID, "action-menu"))) assert action_menu.is_displayed(), "Action menu is not displayed after text selection"
Наконец, мы находим кнопку внутри меню действий и валидируем ее текст.
action_button = action_menu.find_element(By.TAG_NAME, "button") assert action_button.text == "Add to Calendar", f"Expected 'Add to Calendar' button, got '{action_button.text}'"
Для очистки мы закрываем сессию браузера после завершения теста.
print("Text selection and action menu verification successful!") driver.quit()
Cypress
Общий ход выполнения этого теста Cypress похож на примеры в Playwright и Selenium.
Cypress предоставляет простой метод cy.visit()
для открытия целевой веб-страницы.
describe("Text Selection Test", () => { it("should select text and verify action menu appears", () => { cy.visit("http://localhost:3000"); }); });
Cypress использует cy.get(selector).should("be.visible")
, чтобы убедиться, что абзац существует и отображается.
cy.get("#demo-text").should("be.visible");
Сохраняем текст абзаца и проверяем, содержит ли он ожидаемую дату.
const dateText = "March 15, 2025"; cy.get("#demo-text").then(($paragraph) => { const paragraphText = $paragraph.text(); expect(paragraphText).to.include(dateText); });
Поскольку логика JavaScript для выбора текста идентична предыдущим примерам, мы не будем повторять ее здесь. Cypress выполняет JavaScript через cy.window().then((win) => { ... })
для имитации процесса выделения.
После выбора текста мы убеждаемся, что меню действий становится видимым.
cy.get("#action-menu") .should("be.visible") .and("have.css", "display", "block");
Мы также проверяем, что кнопка действий внутри меню содержит правильный текст.
cy.get("#action-menu button") .should("be.visible") .and("have.text", "Add to Calendar");
Проблемы и решения
Автоматизация выделения текста и проверка контекстных меню могут вызвать ряд сложностей, например с управлением фокусом, нечетким выделением и кроссбраузерным поведением. Ниже приведены возможные решения.
1. Потеря фокуса после выделения
Проблема:
Некоторые браузеры или веб-приложения могут сбрасывать выделение, если тестовый сценарий взаимодействует с другим элементом или запускает событие JavaScript.
Это может помешать меню действий отобразиться так, как ожидалось.
Решение:
После выполнения сценария выделения явно перефокусируйтесь на целевом элементе перед проверкой меню действий.
Используйте JavaScript для проверки того, что выделение все еще активно:
cy.window().then((win) => { const selection = win.getSelection(); expect(selection.rangeCount).to.be.greaterThan(0); });
В Selenium добавление короткого ожидания перед взаимодействием с меню может помочь предотвратить преждевременную потерю фокуса:
time.sleep(0.5) # Give the UI a moment to render the action menu
2. Непоследовательное поведение при выделении текста
Проблема:
Некоторые браузеры могут не распознавать текстовые выделения, созданные с помощью JavaScript, не позволяя срабатывать ожидаемым событиям.
Это может произойти, если выделение чисто визуальное и не вызывает внутреннего события выделения.
Решение:
После выделения текста отправьте событие mouseup вручную, чтобы убедиться в срабатывании обработчиков выделения:
cy.window().then((win) => { const mouseupEvent = new MouseEvent("mouseup", { bubbles: true, cancelable: true, view: win, }); win.document.dispatchEvent(mouseupEvent); });
В Selenium убедитесь, что скрипт явно вызывает addRange(range)
после установки выделения:
selection.removeAllRanges(); selection.addRange(range);
3. Кроссбраузерные проблемы
Проблема:
Поведение при выделении текста может отличаться в Chrome, Firefox и Safari.
Некоторые браузеры (например, Safari) могут не вызывать события выделения, если выделение не происходит при взаимодействии с пользователем.
Решение:
При необходимости используйте условия для конкретного браузера:
cy.window().then((win) => { const userAgent = win.navigator.userAgent; if (userAgent.includes("Safari") && !userAgent.includes("Chrome")) { cy.log("Safari detected - applying workaround"); // Adjust selection handling for Safari } });
В Playwright активируйте реальное взаимодействие с пользователем после выделения:
await page.mouse.up(); // Mimic the user releasing the mouse
4. Выделение снимается после прокрутки или клика в другом месте
Проблема:
Если страница прокручивается или скрипт кликает на другом элементе, выделение может быть потеряно.
Некоторые приложения автоматически очищают текстовые выделения при переключении фокуса.
Решение:
Повторно активируйте выделение перед работой с меню действий.
Вместо того чтобы щелкать в другом месте, используйте сочетания клавиш (например, нажатие Enter) для работы с меню.
Заключение
У каждого фреймворка автоматизации есть свои сильные стороны, но основной подход остается неизменным: выделите текст программно, запустите все необходимые события выделения и проверьте полученные изменения пользовательского интерфейса. Полные примеры кода вместе с демо-приложением вы можете найти на нашей GitHub-странице.