Or press ESC to close.

Automating Text Selection in Web Apps

Mar 2nd 2025 14 min read
medium
python3.13.1
javascriptES6
playwright1.50.1
cypress14.1.0
selenium4.29.0
ui
web

Text selection is a common user interaction, often triggering context-aware actions like copying, sharing, or searching selected content. But can automation tools like Playwright, Selenium, and Cypress effectively simulate and verify text selection? In this blog post, we'll explore how to automate text selection in a web app, assert its behavior, and tackle the challenges that come with it. Let's dive in!

Web App Overview

Our demo web app showcases a smart text selection feature that detects specific types of text—such as emails, phone numbers, addresses, and dates—and presents contextual actions based on the selection.

Smart Text Selection Demo

Smart Text Selection Demo App

When a user selects a piece of text, JavaScript extracts its content and determines if it matches a predefined format. If a match is found, a small action menu appears near the selection, offering relevant options like:

This functionality mimics how mobile operating systems and modern browsers enhance user experience by providing intelligent actions on selected content. Our goal in automation is to verify that this feature works as expected by simulating text selection and asserting the correct actions appear.

Simulating Text Selection in Automation

Automating text selection can be tricky since most automation tools interact with elements rather than individual text fragments. However, Playwright, Selenium, and Cypress offer different approaches to simulate text selection and verify the contextual action menu appears correctly. Below are concise code examples for each tool.

Playwright

In Playwright, we simulate text selection by first locating the desired text inside a paragraph, finding its exact position, and then executing JavaScript to create a selection. We trigger a mouseup event to simulate user interaction, ensuring the contextual action menu appears. Finally, we verify the menu contains the expected action button. Let's break this down step by step.

We start by opening the demo app and ensuring the paragraph containing the text we want to select is present on the page.

                
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");
                

Next, we retrieve the paragraph's text and determine the position of the specific date within it.

                
const paragraphText = await page.locator("#demo-text").innerText();

const dateText = "March 15, 2025";
const datePosition = paragraphText.indexOf(dateText);
expect(datePosition).toBeGreaterThan(-1);
                

Now, we execute JavaScript in the browser to find the text node that contains our target date and create a selection range.

                
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);
                

Now, we loop through these nodes to find the one containing our date:

                
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;
  }
}
                

Once we have the target node and position, we create a selection range and trigger a mouse event to simulate user interaction.

                
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);
                

Next, we simulate a mouseup event to trigger the contextual action menu:

                
    const mouseupEvent = new MouseEvent("mouseup", {
      bubbles: true,
      cancelable: true,
      view: window,
    });
    document.dispatchEvent(mouseupEvent);
  }
}, dateText);
                

Here, a MouseEvent is dispatched to mimic a user releasing their mouse button after selecting text. This ensures the app's selection logic is activated, displaying the action menu. After the text selection, we check if the action menu becomes visible.

                
await page.waitForSelector('#action-menu[style*="display: block"]', {
  timeout: 5000,
});
                

Finally, we verify that the contextual menu contains the expected "Add to Calendar" button.

                
  const actionButton = page.locator("#action-menu button");
  await expect(actionButton).toBeVisible();
  await expect(actionButton).toHaveText("Add to Calendar");
});
                
Selenium

We start by initializing the Selenium WebDriver and navigating to the target webpage.

                
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("http://localhost:3000")
                

Selenium's WebDriverWait ensures that the paragraph element is available before interacting with it.

                
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")))
                

Next, we extract the paragraph's text and check whether the expected date is present.

                
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")
                

Since the JavaScript execution logic is identical to the Playwright example, it is not repeated here. The script uses driver.execute_script() to run JavaScript that selects the date and triggers a mouseup event.

After text selection, we wait briefly for the action menu to become visible.

                
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"
                

Finally, we locate the button inside the action menu and validate its text.

                
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}'"
                

To clean up, we close the browser session after the test completes.

                
print("Text selection and action menu verification successful!")

driver.quit()
                
Cypress

The overall flow in this Cypress test is similar to the Playwright and Selenium examples.

Cypress provides a simple cy.visit() method to open the target webpage.

                
describe("Text Selection Test", () => {
  it("should select text and verify action menu appears", () => {
    cy.visit("http://localhost:3000");
  });
});
                

Cypress uses cy.get(selector).should("be.visible") to ensure the paragraph is present and displayed.

                
cy.get("#demo-text").should("be.visible");
                

We store the paragraph's text and check whether it contains the expected date.

                
const dateText = "March 15, 2025";
                    
cy.get("#demo-text").then(($paragraph) => {
  const paragraphText = $paragraph.text();
  expect(paragraphText).to.include(dateText);
});
                

Since the JavaScript logic for text selection is identical to the previous examples, we won't repeat it here. Cypress executes JavaScript via cy.window().then((win) => { ... }) to simulate the selection process.

After text selection, we ensure the action menu becomes visible.

                
cy.get("#action-menu")
  .should("be.visible")
  .and("have.css", "display", "block");
                

We also verify that the action button inside the menu has the correct text.

                
cy.get("#action-menu button")
  .should("be.visible")
  .and("have.text", "Add to Calendar");
                

Challenges and Workarounds

Automating text selection and verifying contextual menus can present several challenges, particularly with focus management, inconsistent selections, and cross-browser behavior. Below are some common issues and potential solutions.

1. Focus Loss After Selection
Problem: Workaround:
2. Inconsistent Text Selection Behavior
Problem: Workaround:
3. Cross-Browser Quirks
Problem: Workaround:
4. Selection Disappearing After Scrolling or Clicking Elsewhere
Problem: Workaround:

Conclusion

Automating text selection and verifying contextual menus can be a tricky process due to focus loss, inconsistent selection behavior, and cross-browser differences. However, using Playwright, Selenium, and Cypress effectively, we can simulate user interactions, ensure selections persist, and verify that the expected UI elements appear.

Each automation framework has its strengths, but the core approach remains the same—select the text programmatically, trigger any necessary selection events, and validate the resulting UI changes. By handling common challenges such as selection disappearing, inconsistent event triggers, and browser-specific quirks, we can make our tests more reliable.

If you're interested in seeing the complete code examples along with a working demo application, you can find them on our GitHub page. Happy testing! 🧪