Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You cannot select a CSS ::after pseudo-element as a normal DOM node, so there is no pseudo-element handle to pass to ElementHandle.click(). Select the element that owns ::after. If the intended target is the generated visual area rather than the host element, inspect its computed styles, calculate a point inside the rendered area, verify that point, and click it with the mouse.

Puppeteer and Pyppeteer element-click methods target the matched host element, normally at its center. Coordinate clicking is therefore the reliable approach when the pseudo-element occupies a separate, hit-tested region.

Why ::after cannot be selected directly

::after creates generated content through CSS. It is addressed through the originating element’s style, not exposed as an ordinary child node in the document tree. A selector such as .control::after is useful in CSS, but it is not a selector that Puppeteer or Pyppeteer can turn into an element handle.

Use window.getComputedStyle(element, '::after') to read resolved pseudo-element values such as content, display, position, dimensions, margins and transforms. MDN documents the optional pseudo-element argument for getComputedStyle() at MDN Web Docs. The returned object is read-only information; it is not a clickable node.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the interaction that matches your goal

Click the host element

If the host owns the behavior and the pseudo-element is only an icon, decoration or enlarged visual affordance, click the host. A selector, locator or element handle is appropriate. This is usually more robust than depending on generated-content geometry.

Click only the generated region

If the host has different behavior in different areas, or an absolutely positioned ::after acts as a separate control, calculate a screen coordinate inside that region. Then call page.mouse.click(x, y). The coordinate must be based on the page’s current layout, not on a hard-coded value copied from a different viewport.

Confirm that the region is actually hit-testable

A visible pseudo-element can still be covered by another element, clipped by an ancestor, or made non-interactive with pointer-events: none. Use browser hit testing at the chosen point and check the element that receives the event before performing the click.

Puppeteer: click the host or its rendered ::after area

Install and launch

npm install puppeteer
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless: true});
  const page = await browser.newPage();
  await page.goto('https://example.com', {waitUntil: 'networkidle2'});
  // Interact here.
  await browser.close();
})();

For ordinary DOM targets, current Puppeteer documentation recommends locators. Locators wait for an element to be present and in an appropriate state, including visibility, enabled state and a stable bounding box; see Puppeteer page interactions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Inspect the pseudo-element and host geometry

const host = await page.$('.control');
if (!host) throw new Error('Host element was not found');

const info = await page.evaluate(el => {
  const pseudo = getComputedStyle(el, '::after');
  const rect = el.getBoundingClientRect();
  return {
    content: pseudo.content,
    display: pseudo.display,
    position: pseudo.position,
    width: pseudo.width,
    height: pseudo.height,
    top: pseudo.top,
    right: pseudo.right,
    bottom: pseudo.bottom,
    left: pseudo.left,
    transform: pseudo.transform,
    pointerEvents: pseudo.pointerEvents,
    host: {x: rect.x, y: rect.y, width: rect.width, height: rect.height}
  };
}, host);

console.log(info);

The host rectangle is not automatically the pseudo-element rectangle. Inline formatting, writing direction, transforms, clipping and overlapping elements can change where the generated content appears. Treat the values above as inputs for a page-specific calculation.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Calculate and verify a point

The following helper fits a common pattern: an absolutely positioned pseudo-element with explicit pixel dimensions on the right side of a relatively positioned host. Adapt the calculation when the site’s CSS uses another positioning scheme.

async function clickAfterOnRight(page, selector) {
  const host = await page.$(selector);
  if (!host) throw new Error(`No host matched ${selector}`);

  const data = await page.evaluate(el => {
    const pseudo = getComputedStyle(el, '::after');
    const rect = el.getBoundingClientRect();
    const width = parseFloat(pseudo.width);
    const height = parseFloat(pseudo.height);
    const right = parseFloat(pseudo.right);
    const top = parseFloat(pseudo.top);
    return {
      x: rect.x + rect.width - right - width / 2,
      y: rect.y + top + height / 2,
      width, height, pointerEvents: pseudo.pointerEvents
    };
  }, host);

  if (!Number.isFinite(data.x) || !Number.isFinite(data.y)) {
    throw new Error('Pseudo-element geometry is not numeric; inspect its layout and calculate a point explicitly');
  }
  if (data.pointerEvents === 'none') {
    throw new Error('The ::after pseudo-element cannot receive pointer events');
  }

  const hit = await page.evaluate(({x, y}) => {
    const target = document.elementFromPoint(x, y);
    return target ? {tag: target.tagName, className: target.className} : null;
  }, {x: data.x, y: data.y});
  console.log('Element at click point:', hit);
  await page.mouse.click(data.x, data.y);
}

await clickAfterOnRight(page, '.control');

Do not assume this formula works for every stylesheet. If the pseudo-element is inline, transformed, vertically centered with a transform, or positioned relative to another containing block, derive its actual location from the page’s layout and verify the result visually or with document.elementFromPoint().

When clicking the host is enough

const locator = page.locator('.control');
await locator.click();

This clicks the host element, not a separately selectable ::after. Puppeteer’s ElementHandle documentation describes element clicking as scrolling the element into view and using mouse input at its center.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A complete Puppeteer demonstration

This self-contained example creates a button with a right-hand generated control area. It calculates the center of that area, checks the hit target, clicks it and reports the result.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless: true});
  const page = await browser.newPage({viewport: {width: 800, height: 500}});
  await page.setContent(`
    <style>
      .control { position: relative; width: 220px; height: 48px; margin: 80px; }
      .control::after { content: '→'; position: absolute; right: 0; top: 0;
        width: 48px; height: 48px; display: grid; place-items: center;
        background: tomato; cursor: pointer; }
    </style>
    <button class="control" onclick="document.body.dataset.clicked='yes'">Next</button>`);

  const host = await page.$('.control');
  const point = await page.evaluate(el => {
    const s = getComputedStyle(el, '::after');
    const r = el.getBoundingClientRect();
    const w = parseFloat(s.width), h = parseFloat(s.height);
    return {x: r.right - parseFloat(s.right) - w / 2,
            y: r.top + parseFloat(s.top) + h / 2};
  }, host);
  await page.mouse.click(point.x, point.y);
  console.log(await page.evaluate(() => document.body.dataset.clicked));
  await browser.close();
})();

Pyppeteer: the same technique in Python

Pyppeteer is an unofficial Python port of Puppeteer’s API. The published reference is version 0.0.25, so check your Python, Chromium and package compatibility instead of assuming that an old reference reflects current browser behavior. Its API reference documents page.querySelector() (also aliased as page.J()), page.evaluate(), selector-based page.click() and page.mouse.click(x, y). See Pyppeteer’s documentation and its 0.0.25 API reference.

Install and inspect

python -m pip install pyppeteer
import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=True)
    page = await browser.newPage()
    await page.goto('https://example.com', {'waitUntil': 'networkidle2'})

    host = await page.querySelector('.control')
    if host is None:
        raise RuntimeError('Host element was not found')

    info = await page.evaluate('''el => {
      const pseudo = getComputedStyle(el, '::after');
      const rect = el.getBoundingClientRect();
      return {
        content: pseudo.content,
        display: pseudo.display,
        position: pseudo.position,
        width: pseudo.width,
        height: pseudo.height,
        top: pseudo.top,
        right: pseudo.right,
        pointerEvents: pseudo.pointerEvents,
        host: {x: rect.x, y: rect.y, width: rect.width, height: rect.height}
      };
    }''', host)
    print(info)

    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

Click a verified point

import asyncio
from pyppeteer import launch

async def click_after_on_right(page, selector):
    host = await page.querySelector(selector)
    if host is None:
        raise RuntimeError(f'No host matched {selector}')

    point = await page.evaluate('''el => {
      const s = getComputedStyle(el, '::after');
      const r = el.getBoundingClientRect();
      const w = parseFloat(s.width), h = parseFloat(s.height);
      const right = parseFloat(s.right), top = parseFloat(s.top);
      return {
        x: r.x + r.width - right - w / 2,
        y: r.y + top + h / 2,
        pointerEvents: s.pointerEvents
      };
    }''', host)

    if point['pointerEvents'] == 'none':
        raise RuntimeError('The ::after area is not hit-testable')

    hit = await page.evaluate('''p => {
      const target = document.elementFromPoint(p.x, p.y);
      return target ? {tag: target.tagName, className: target.className} : null;
    }''', point)
    print('Element at click point:', hit)
    await page.mouse.click(point['x'], point['y'])

async def main():
    browser = await launch(headless=True)
    page = await browser.newPage()
    await page.goto('https://example.com', {'waitUntil': 'networkidle2'})
    await click_after_on_right(page, '.control')
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

As with Puppeteer, page.click('.control') clicks the matched host’s center. It does not expose a separate pseudo-element handle.

Timing, layout and coordinate pitfalls

Wait for the final layout

Fonts, images, animations and client-side rendering can move the pseudo-element after you measure it. Wait for the host to be visible and stable, then disable or await relevant animations. Re-measure immediately before the mouse click if the page can reflow.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the correct coordinate space

getBoundingClientRect() and elementFromPoint() use viewport CSS pixels. Puppeteer’s mouse coordinates use the same page coordinate space. Device scale factor changes screenshot pixels, not the CSS coordinates supplied to mouse.click().

Account for scrolling and frames

Rectangles are viewport-relative. If the target is inside an iframe, obtain that frame and evaluate and click within the frame’s coordinate context. If you scroll between measurement and clicking, the point can become invalid; measure after scrolling.

Check overlays and pointer-events

Cookie dialogs, sticky headers, loading masks and transparent overlays can intercept the point. document.elementFromPoint() identifies the topmost receiving element. A pseudo-element with pointer-events: none cannot itself receive the click, although its host may still handle bubbling events.

Troubleshooting

“No node found” or a null handle

The host selector is wrong, the content is inside a frame or the page has not rendered it yet. Wait for the host, inspect the frame list, and query within the correct frame.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

The click fires the host action instead

Your coordinate landed in the host’s normal area, or the pseudo-element is decorative. Log the computed geometry, inspect the point with elementFromPoint(), and choose a point strictly inside the generated region.

The coordinate is outside the visible control

The pseudo-element may use percentage dimensions, transform, writing-mode rules or a different containing block. Do not reuse the right-edge formula; derive the transformed visual position and re-check the viewport rectangle.

The pseudo-element is visible but receives no event

Look for pointer-events: none, an ancestor overlay, clipping or a z-index relationship. Fix the page’s hit-testing conditions or click the host if that is the intended interaction.

Pyppeteer launches but fails with browser errors

Its documented API is old and unofficial. Verify the installed package, Python version and Chromium revision, or migrate the script to maintained Puppeteer-compatible tooling. The API pattern remains host selection, computed-style inspection and coordinate mouse input.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Testing and reliability checklist

  • Set a deterministic viewport and, where possible, deterministic fonts and device scale.
  • Wait for the host and final layout before measuring.
  • Record content, dimensions, position, transforms and pointer-events while diagnosing failures.
  • Use elementFromPoint() to verify the receiver at the planned coordinate.
  • Recalculate after scrolling, resizing or state changes.
  • Prefer a host locator when the pseudo-element is merely visual.
  • Use coordinate clicking only when the generated region has distinct behavior and is genuinely hit-testable.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your actual goal is a clean screenshot or PDF of the page after it renders—not a click on the pseudo-element—ScreenshotNeo can capture it through one request. It is a screenshot API and MCP server, not a replacement for event automation: it does not perform your Puppeteer click. It is useful when you need the resulting page image without maintaining a browser.

See the ScreenshotNeo API documentation for all options. A cURL request is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

The Free plan includes 1,000 screenshots per month with no card. Paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000 and Business $249 for 1,000,000; yearly billing gives two months free, and every feature is available on every plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create a free ScreenshotNeo account to use the 1,000 monthly screenshots without a card.

Frequently Asked Questions

Can I use .control::after in page.$()?

No. That selector describes CSS generated content, not a DOM node. Select .control and either click the host or calculate a mouse coordinate.

What does a computed content value of none mean?

The rule is not generating content in the current state, so there may be no visible pseudo-element to click. Check state-dependent classes, media queries and stylesheet precedence.

Is Element.pseudo() a portable solution?

MDN marks it experimental with limited availability, so it is not a dependable cross-browser automation foundation. Computed styles plus verified mouse coordinates are the broadly practical pattern.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.