CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-puppeteer-core

A high-level API to control headless Chrome and Firefox browsers over the DevTools Protocol and WebDriver BiDi

94

1.02x
Overview
Eval results
Files

locators-selectors.mddocs/

Locators and Selectors

Advanced element location strategies including CSS selectors, XPath, text content, custom query handlers, and the modern Locator API.

Capabilities

Locator Interface

Modern element location API providing chainable, flexible element targeting with built-in waiting and error handling.

interface Locator<T> {
  /** Click the located element */
  click(options?: LocatorClickOptions): Promise<void>;
  /** Fill input with value */
  fill(value: string, options?: LocatorFillOptions): Promise<void>;
  /** Focus the element */
  focus(options?: LocatorOptions): Promise<void>;
  /** Hover over the element */
  hover(options?: LocatorHoverOptions): Promise<void>;
  /** Scroll element into view */
  scroll(options?: LocatorScrollOptions): Promise<void>;
  /** Select options from select element */
  select(values: string | string[], options?: LocatorOptions): Promise<void>;
  /** Take screenshot of element */
  screenshot(options?: LocatorScreenshotOptions): Promise<Buffer>;
  /** Wait for element to exist and return it */
  wait(options?: LocatorWaitOptions): Promise<T>;
  /** Wait for element to be in specified state */
  waitFor(options?: LocatorWaitOptions): Promise<void>;
  /** Transform locator result */
  map<U>(mapper: (value: T) => Promise<U>): Locator<U>;
  /** Filter locator results */
  filter<U extends T>(predicate: (value: T) => Promise<boolean>): Locator<U>;
  /** Chain with another locator */
  locator(selector: string): Locator<ElementHandle>;
  /** Get element count */
  count(): Promise<number>;
  /** Get nth element */
  nth(index: number): Locator<T>;
  /** Get first element */
  first(): Locator<T>;
  /** Get last element */
  last(): Locator<T>;
  /** Clone locator */
  clone(): Locator<T>;
  /** Get element handle */
  elementHandle(): Promise<T>;
  /** Get all element handles */
  elementHandles(): Promise<T[]>;
  /** Evaluate function with element */
  evaluate<R>(pageFunction: (element: T, ...args: any[]) => R, ...args: any[]): Promise<R>;
  /** Get text content */
  textContent(): Promise<string>;
  /** Get inner text */
  innerText(): Promise<string>;
  /** Get inner HTML */
  innerHTML(): Promise<string>;
  /** Get attribute value */
  getAttribute(name: string): Promise<string | null>;
  /** Get input value */
  inputValue(): Promise<string>;
  /** Check if element is checked */
  isChecked(): Promise<boolean>;
  /** Check if element is disabled */
  isDisabled(): Promise<boolean>;
  /** Check if element is editable */
  isEditable(): Promise<boolean>;
  /** Check if element is enabled */
  isEnabled(): Promise<boolean>;
  /** Check if element is hidden */
  isHidden(): Promise<boolean>;
  /** Check if element is visible */
  isVisible(): Promise<boolean>;
  /** Set checked state */
  setChecked(checked: boolean, options?: LocatorOptions): Promise<void>;
  /** Tap element (mobile) */
  tap(options?: LocatorTapOptions): Promise<void>;
  /** Type text into element */
  type(text: string, options?: LocatorTypeOptions): Promise<void>;
  /** Uncheck element */
  uncheck(options?: LocatorOptions): Promise<void>;
  /** Check element */
  check(options?: LocatorOptions): Promise<void>;
  /** Clear input */
  clear(options?: LocatorOptions): Promise<void>;
  /** Drag to another locator */
  dragTo(target: Locator<ElementHandle>, options?: LocatorDragOptions): Promise<void>;
  /** Press key */
  press(key: string, options?: LocatorPressOptions): Promise<void>;
}

interface LocatorClickOptions {
  /** Mouse button */
  button?: "left" | "right" | "middle";
  /** Click count */
  clickCount?: number;
  /** Delay between mousedown and mouseup */
  delay?: number;
  /** Force click even if not actionable */
  force?: boolean;
  /** Modifiers to press */
  modifiers?: ("Alt" | "Control" | "Meta" | "Shift")[];
  /** Position relative to element */
  position?: { x: number; y: number };
  /** Timeout */
  timeout?: number;
  /** Trial run */
  trial?: boolean;
}

interface LocatorFillOptions {
  /** Force fill even if not editable */
  force?: boolean;
  /** Timeout */
  timeout?: number;
}

interface LocatorHoverOptions {
  /** Force hover even if not actionable */
  force?: boolean;
  /** Modifiers to press */
  modifiers?: ("Alt" | "Control" | "Meta" | "Shift")[];
  /** Position relative to element */
  position?: { x: number; y: number };
  /** Timeout */
  timeout?: number;
  /** Trial run */
  trial?: boolean;
}

interface LocatorScrollOptions {
  /** Position to scroll to */
  position?: { x: number; y: number };
  /** Timeout */
  timeout?: number;
}

interface LocatorScreenshotOptions {
  /** Animation handling */
  animations?: "disabled" | "allow";
  /** Caret handling */
  caret?: "hide" | "initial";
  /** Image quality */
  quality?: number;
  /** Screenshot type */
  type?: "png" | "jpeg";
  /** Timeout */
  timeout?: number;
}

interface LocatorWaitOptions {
  /** State to wait for */
  state?: "attached" | "detached" | "visible" | "hidden";
  /** Timeout */
  timeout?: number;
}

interface LocatorOptions {
  /** Force action even if not actionable */
  force?: boolean;
  /** Timeout */
  timeout?: number;
}

interface LocatorTapOptions {
  /** Force tap even if not actionable */
  force?: boolean;
  /** Modifiers to press */
  modifiers?: ("Alt" | "Control" | "Meta" | "Shift")[];
  /** Position relative to element */
  position?: { x: number; y: number };
  /** Timeout */
  timeout?: number;
  /** Trial run */
  trial?: boolean;
}

interface LocatorTypeOptions {
  /** Delay between keystrokes */
  delay?: number;
  /** Timeout */
  timeout?: number;
}

interface LocatorDragOptions {
  /** Force drag even if not actionable */
  force?: boolean;
  /** Source position */
  sourcePosition?: { x: number; y: number };
  /** Target position */
  targetPosition?: { x: number; y: number };
  /** Timeout */
  timeout?: number;
  /** Trial run */
  trial?: boolean;
}

interface LocatorPressOptions {
  /** Delay between keydown and keyup */
  delay?: number;
  /** Timeout */
  timeout?: number;
}

Usage Examples:

import puppeteer from "puppeteer-core";

const browser = await puppeteer.launch({ executablePath: "/path/to/chrome" });
const page = await browser.newPage();
await page.goto("https://example.com");

// Basic locator usage
const button = page.locator("#submit-button");
await button.click();

const input = page.locator("input[name='username']");
await input.fill("john.doe");

const dropdown = page.locator("select#country");
await dropdown.select("US");

// Chaining locators
const form = page.locator("form#login-form");
const usernameInput = form.locator("input[name='username']");
const passwordInput = form.locator("input[name='password']");
const submitButton = form.locator("button[type='submit']");

await usernameInput.fill("user@example.com");
await passwordInput.fill("password123");
await submitButton.click();

// Locator filtering and mapping
const allButtons = page.locator("button");
const enabledButtons = allButtons.filter(async (btn) => {
  return await btn.isEnabled();
});
const firstEnabledButton = enabledButtons.first();

// Wait for elements
const dynamicContent = page.locator("#dynamic-content");
await dynamicContent.waitFor({ state: "visible" });

// Element state checks
const checkbox = page.locator("#agree-terms");
const isChecked = await checkbox.isChecked();
if (!isChecked) {
  await checkbox.check();
}

// Text content operations
const heading = page.locator("h1");
const headingText = await heading.textContent();
console.log("Page heading:", headingText);

await browser.close();

Selector Strategies

Various methods for selecting elements with different strategies:

interface SelectorStrategies {
  /** CSS selector */
  css(selector: string): Locator<ElementHandle>;
  /** XPath expression */
  xpath(expression: string): Locator<ElementHandle>;
  /** Text content matching */
  text(text: string | RegExp, options?: TextSelectorOptions): Locator<ElementHandle>;
  /** Attribute-based selection */
  attribute(name: string, value?: string | RegExp): Locator<ElementHandle>;
  /** Role-based selection (accessibility) */
  role(role: string, options?: RoleSelectorOptions): Locator<ElementHandle>;
  /** Label-based selection */
  label(text: string | RegExp): Locator<ElementHandle>;
  /** Placeholder-based selection */
  placeholder(text: string | RegExp): Locator<ElementHandle>;
  /** Test ID selection */
  testId(id: string): Locator<ElementHandle>;
  /** Title-based selection */
  title(title: string | RegExp): Locator<ElementHandle>;
  /** Alt text selection */
  alt(text: string | RegExp): Locator<ElementHandle>;
}

interface TextSelectorOptions {
  /** Exact match */
  exact?: boolean;
  /** Case sensitivity */
  ignoreCase?: boolean;
}

interface RoleSelectorOptions {
  /** Accessible name */
  name?: string | RegExp;
  /** Checked state */
  checked?: boolean;
  /** Disabled state */
  disabled?: boolean;
  /** Expanded state */
  expanded?: boolean;
  /** Include hidden elements */
  includeHidden?: boolean;
  /** Level (for headings) */
  level?: number;
  /** Pressed state */
  pressed?: boolean;
  /** Selected state */
  selected?: boolean;
}

Usage Examples:

// CSS selectors
const element = page.locator("div.container > p:nth-child(2)");
const complexSelector = page.locator("table tbody tr:has(.status.active) .name");

// XPath selectors
const byXPath = page.locator("//div[@class='content']//span[contains(text(), 'Hello')]");
const followingSibling = page.locator("//label[text()='Name']/following-sibling::input");

// Text-based selection
const byText = page.getByText("Click me");
const byPartialText = page.getByText(/Submit.*Form/);
const exactText = page.getByText("Exact Match", { exact: true });

// Attribute selection
const byAttribute = page.locator("[data-testid='user-card']");
const byMultipleAttributes = page.locator("input[type='email'][required]");

// Role-based selection (accessibility)
const submitButton = page.getByRole("button", { name: "Submit" });
const mainHeading = page.getByRole("heading", { level: 1 });
const checkedCheckbox = page.getByRole("checkbox", { checked: true });

// Label-based selection
const usernameInput = page.getByLabel("Username");
const emailField = page.getByLabel(/email/i);

// Placeholder selection
const searchInput = page.getByPlaceholder("Search...");

// Test ID selection (data-testid)
const userProfile = page.getByTestId("user-profile");

// Title attribute selection
const helpIcon = page.getByTitle("Help information");

Custom Query Handlers

Create and use custom query handlers for specialized element selection:

interface CustomQueryHandler {
  /** Query function to find single element */
  queryOne?: (element: Element, selector: string) => Element | null;
  /** Query function to find multiple elements */
  queryAll?: (element: Element, selector: string) => Element[];
}

interface QueryHandlerManager {
  /** Register custom query handler */
  registerQueryHandler(name: string, handler: CustomQueryHandler): void;
  /** Unregister query handler */
  unregisterQueryHandler(name: string): void;
  /** Get registered query handlers */
  queryHandlers(): Map<string, CustomQueryHandler>;
  /** Clear all custom query handlers */
  clearQueryHandlers(): void;
}

Usage Examples:

// Register custom query handler
puppeteer.registerQueryHandler('dataQA', {
  queryOne: (element, selector) => 
    element.querySelector(`[data-qa="${selector}"]`),
  queryAll: (element, selector) => 
    Array.from(element.querySelectorAll(`[data-qa="${selector}"]`))
});

// Use custom query handler
const customElement = await page.$("dataQA/login-button");
const customElements = await page.$$("dataQA/menu-item");

// Text-based custom handler
puppeteer.registerQueryHandler('text', {
  queryOne: (element, selector) => {
    const xpath = `.//*[contains(text(), "${selector}")]`;
    return document.evaluate(xpath, element, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as Element;
  },
  queryAll: (element, selector) => {
    const xpath = `.//*[contains(text(), "${selector}")]`;
    const result = document.evaluate(xpath, element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    const elements = [];
    for (let i = 0; i < result.snapshotLength; i++) {
      elements.push(result.snapshotItem(i) as Element);
    }
    return elements;
  }
});

// Use text handler
const textElement = await page.$("text/Click here");

// Component-based handler
puppeteer.registerQueryHandler('component', {
  queryOne: (element, selector) => {
    return element.querySelector(`[data-component="${selector}"]`);
  }
});

const headerComponent = await page.$("component/app-header");

Built-in Query Handlers

Puppeteer provides several built-in query handlers for different selection strategies:

interface BuiltInQueryHandlers {
  /** CSS selector (default) */
  css: CustomQueryHandler;
  /** XPath expression */
  xpath: CustomQueryHandler;
  /** Text content matching */
  text: CustomQueryHandler;
  /** ARIA label matching */
  aria: CustomQueryHandler;
  /** Pierce through shadow DOM */
  pierce: CustomQueryHandler;
}

Usage Examples:

// XPath with xpath/ prefix
const xpathElement = await page.$("xpath///div[@class='content']");
const xpathElements = await page.$$("xpath///button[contains(@class, 'primary')]");

// Text content with text/ prefix
const textElement = await page.$("text/Submit Form");
const partialTextElement = await page.$("text/Click");

// Pierce shadow DOM
const shadowElement = await page.$("pierce/#shadow-button");

// ARIA label
const ariaElement = await page.$("aria/Submit");

Locator Patterns and Best Practices

Common patterns for reliable element location:

interface LocatorPatterns {
  /** Wait and retry pattern */
  waitAndRetry<T>(locator: Locator<T>, maxRetries?: number): Promise<T>;
  /** Safe interaction pattern */
  safeInteraction(locator: Locator<ElementHandle>, action: string): Promise<boolean>;
  /** Multiple strategy fallback */
  multiStrategyLocator(strategies: string[]): Locator<ElementHandle>;
  /** Conditional locator based on page state */
  conditionalLocator(condition: () => Promise<boolean>, trueLocator: string, falseLocator: string): Locator<ElementHandle>;
}

Usage Examples:

// Reliable element waiting
async function waitForElementSafely(page: Page, selector: string, timeout = 30000) {
  try {
    return await page.locator(selector).wait({ timeout });
  } catch (error) {
    console.log(`Element ${selector} not found within ${timeout}ms`);
    return null;
  }
}

// Multiple selector fallback
async function findElementWithFallback(page: Page, selectors: string[]) {
  for (const selector of selectors) {
    try {
      const element = await page.locator(selector).wait({ timeout: 5000 });
      return element;
    } catch (error) {
      console.log(`Selector ${selector} failed, trying next...`);
    }
  }
  throw new Error("None of the selectors found an element");
}

// Usage
const button = await findElementWithFallback(page, [
  "#submit-btn",
  ".submit-button",
  "button[type='submit']",
  "text/Submit"
]);

// Smart locator with multiple conditions
class SmartLocator {
  constructor(private page: Page) {}

  async findButton(identifier: string) {
    // Try multiple strategies in order of preference
    const strategies = [
      `[data-testid="${identifier}"]`,
      `#${identifier}`,
      `.${identifier}`,
      `button[aria-label="${identifier}"]`,
      `text/${identifier}`
    ];

    for (const strategy of strategies) {
      try {
        const element = await this.page.locator(strategy).wait({ timeout: 2000 });
        if (await element.isVisible()) {
          return element;
        }
      } catch (error) {
        // Continue to next strategy
      }
    }

    throw new Error(`Could not find button: ${identifier}`);
  }
}

const smartLocator = new SmartLocator(page);
const submitButton = await smartLocator.findButton("submit");

Advanced Locator Features

Advanced locator capabilities for complex scenarios:

interface AdvancedLocatorFeatures {
  /** Chain multiple locators */
  chain(locators: Locator<ElementHandle>[]): Locator<ElementHandle>;
  /** Create locator union */
  union(...locators: Locator<ElementHandle>[]): Locator<ElementHandle>;
  /** Conditional locator execution */
  conditional(condition: boolean, trueLocator: Locator<ElementHandle>, falseLocator: Locator<ElementHandle>): Locator<ElementHandle>;
  /** Locator with custom validation */
  validated(locator: Locator<ElementHandle>, validator: (element: ElementHandle) => Promise<boolean>): Locator<ElementHandle>;
  /** Cached locator for performance */
  cached(locator: Locator<ElementHandle>, ttl?: number): Locator<ElementHandle>;
}

Usage Examples:

// Locator chaining for complex interactions
const form = page.locator("form#checkout");
const shippingSection = form.locator(".shipping-section");
const addressInput = shippingSection.locator("input[name='address']");

await addressInput.fill("123 Main St");

// Dynamic locator based on conditions
async function getMenuLocator(page: Page) {
  const isMobile = await page.evaluate(() => window.innerWidth < 768);
  return isMobile 
    ? page.locator("#mobile-menu") 
    : page.locator("#desktop-menu");
}

const menu = await getMenuLocator(page);
await menu.click();

// Locator with validation
async function getValidatedButton(page: Page, selector: string) {
  const locator = page.locator(selector);
  
  await locator.wait({ state: "attached" });
  
  const element = await locator.elementHandle();
  const isClickable = await element.evaluate((el) => {
    const style = window.getComputedStyle(el);
    return style.pointerEvents !== "none" && 
           style.visibility !== "hidden" && 
           style.display !== "none";
  });
  
  if (!isClickable) {
    throw new Error(`Element ${selector} is not clickable`);
  }
  
  return locator;
}

// Performance optimization with element caching
class LocatorCache {
  private cache = new Map<string, { locator: Locator<ElementHandle>, timestamp: number }>();
  private ttl = 5000; // 5 seconds

  constructor(private page: Page) {}

  getLocator(selector: string): Locator<ElementHandle> {
    const cached = this.cache.get(selector);
    const now = Date.now();
    
    if (cached && (now - cached.timestamp) < this.ttl) {
      return cached.locator;
    }
    
    const locator = this.page.locator(selector);
    this.cache.set(selector, { locator, timestamp: now });
    
    return locator;
  }
  
  clear() {
    this.cache.clear();
  }
}

const cache = new LocatorCache(page);
const button1 = cache.getLocator("#button1"); // Creates new
const button2 = cache.getLocator("#button1"); // Returns cached

Error Handling and Debugging

Common locator errors and debugging strategies:

class LocatorError extends Error {
  constructor(message: string, selector?: string);
  selector?: string;
}

class ElementNotFoundError extends LocatorError {
  constructor(selector: string);
}

class ElementNotActionableError extends LocatorError {
  constructor(selector: string, action: string);
}

interface LocatorDebugging {
  /** Debug locator visibility */
  debugVisibility(locator: Locator<ElementHandle>): Promise<VisibilityInfo>;
  /** Debug locator state */
  debugState(locator: Locator<ElementHandle>): Promise<ElementState>;
  /** Highlight element for debugging */
  highlight(locator: Locator<ElementHandle>, duration?: number): Promise<void>;
}

interface VisibilityInfo {
  exists: boolean;
  visible: boolean;
  inViewport: boolean;
  obstructed: boolean;
  dimensions: { width: number; height: number };
}

interface ElementState {
  tag: string;
  attributes: Record<string, string>;
  computedStyles: Record<string, string>;
  textContent: string;
  innerHTML: string;
}

Usage Examples:

// Robust locator operations with error handling
async function safeClick(page: Page, selector: string) {
  try {
    const locator = page.locator(selector);
    
    // Wait for element to exist
    await locator.wait({ state: "attached", timeout: 10000 });
    
    // Check if element is actionable
    const isVisible = await locator.isVisible();
    const isEnabled = await locator.isEnabled();
    
    if (!isVisible) {
      throw new ElementNotActionableError(selector, "click - element not visible");
    }
    
    if (!isEnabled) {
      throw new ElementNotActionableError(selector, "click - element disabled");
    }
    
    await locator.click({ timeout: 5000 });
    return true;
    
  } catch (error) {
    if (error instanceof ElementNotFoundError) {
      console.log(`Element not found: ${selector}`);
    } else if (error instanceof ElementNotActionableError) {
      console.log(`Element not actionable: ${error.message}`);
    } else {
      console.log(`Unexpected error clicking ${selector}:`, error.message);
    }
    return false;
  }
}

// Debugging locator issues
async function debugLocator(page: Page, selector: string) {
  const locator = page.locator(selector);
  
  try {
    const element = await locator.elementHandle();
    
    const info = await element.evaluate((el) => ({
      tagName: el.tagName,
      id: el.id,
      className: el.className,
      textContent: el.textContent?.trim(),
      offsetWidth: el.offsetWidth,
      offsetHeight: el.offsetHeight,
      offsetLeft: el.offsetLeft,
      offsetTop: el.offsetTop,
      style: window.getComputedStyle(el).cssText
    }));
    
    console.log("Element debug info:", info);
    
  } catch (error) {
    console.log(`Could not debug element ${selector}:`, error.message);
    
    // Try to find similar elements
    const similarElements = await page.$$eval(
      "*", 
      (elements, sel) => {
        return elements
          .filter(el => el.id?.includes(sel.replace("#", "")) || 
                       el.className?.includes(sel.replace(".", "")))
          .map(el => ({ tag: el.tagName, id: el.id, class: el.className }))
          .slice(0, 5);
      }, 
      selector
    );
    
    console.log("Similar elements found:", similarElements);
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-puppeteer-core

docs

browser-contexts.md

browser-management.md

element-handling.md

index.md

input-simulation.md

locators-selectors.md

network-management.md

page-interaction.md

tile.json