October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Select Elements with Dynamic IDs in Puppeteer

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.

Do not hard-code the changing part of an element’s id. Match the stable portion with a CSS attribute selector such as input[id^="user_"] (prefix), button[id$="_submit"] (suffix), or [id*="checkout"] (substring). Narrow that selector with a tag, stable ancestor, or semantic attribute, then use a Puppeteer locator or an explicit waitForSelector before interacting.

The selector decision order

A dynamic ID is usually generated from a random token, session value, or component instance. The complete value changes, but part of it often remains meaningful. Choose selectors in this order:

  1. Use a stable semantic hook first. Prefer an accessible role and name, visible text, a label, data-testid, or another documented attribute that expresses the element’s purpose.
  2. Match the stable ID fragment if no better hook exists. CSS attribute selectors can match a prefix, suffix, or substring without knowing the volatile characters.
  3. Make the match unique. Add an element type, stable container, or another attribute.
  4. Synchronize before acting. A locator is the recommended interaction API; use waitForSelector when you need explicit synchronization or lower-level control.
  5. Use prefixed XPath only when CSS cannot express the condition.
Need Selector Meaning
Known beginning [id^="prefix"] ID starts with prefix
Known ending [id$="suffix"] ID ends with suffix
Known fragment [id*="fragment"] ID contains fragment
Scoped match #stable-panel input[id*="email"] Only matching inputs inside a stable panel

CSS patterns for changing IDs

Prefix matching

Use ^= when the application keeps a fixed beginning and appends a changing suffix:

const save = page.locator('button[id^="save-"]');
await save.click();

This matches IDs such as save-1842 and save-a91f, but not an unrelated button whose ID starts differently.

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

Suffix matching

Use $= when the stable portion is at the end:

const submit = 'form button[id$="-submit"]';
await page.waitForSelector(submit, {visible: true});
await page.click(submit);

The tag and form scope reduce accidental matches when several components share the same suffix.

Substring matching

Use *= when the stable text can occur anywhere in the ID:

const field = page.locator('#settings-panel input[id*="email"]');
await field.fill('[email protected]');

Substring matching is the broadest option. If the fragment is common, add a stable ancestor, tag, type, or another attribute.

Combining attributes

CSS selectors can express several constraints:

const primary = page.locator(
  'section[data-testid="checkout"] button[type="submit"][id^="pay-"]'
);
await primary.click();

Use quotes around attribute values, and escape any characters that are not valid in a CSS identifier. An attribute selector avoids needing to escape a random ID suffix.

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

Prefer semantic and test selectors when available

An ID-pattern selector is a fallback, not automatically the best selector. A role, label, visible text, or test hook communicates intent and usually survives a redesign better than an implementation-generated ID.

Role and accessible name

const checkout = page.getByRole('button', {name: 'Checkout'});
await checkout.click();

Use this when the role and accessible name are stable and unique. It also tests the interface in terms closer to what an assistive-technology user perceives.

Label and test hook

await page.getByLabel('Email address').fill('[email protected]');
await page.locator('[data-testid="account-email"]').fill('[email protected]');

Only adopt a data-testid contract that the application team intends to keep stable. If the page offers no such contract, ask for one rather than building a selector around presentation-only classes.

Text selectors

Visible text can be useful for a unique action, but it may change with localization or copy edits. Scope it to a stable region when possible.

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.

Locators versus explicit waits

Locators for normal interactions

Puppeteer documentation describes locators as the recommended way to select and interact with elements. A locator waits for the element to be present and in the required state, retries the operation when needed, and makes a dynamic page less timing-sensitive:

const save = page.locator('button[id^="save-"]');
await save.click();

Use locator methods such as click() and fill() for actions that should wait for readiness.

waitForSelector for explicit synchronization

Use page.waitForSelector when you need a handle, visibility control, a custom timeout, or an abort signal. It waits for a selector to appear, works across navigations, and supports visible, hidden, timeout, and signal. Its documented default timeout is 30 seconds.

const selector = 'form button[id$="-submit"]';
await page.waitForSelector(selector, {
  visible: true,
  timeout: 15000
});
await page.click(selector);

A wait only proves that a matching node reached the requested state. It does not prove that the selector is unique or that the button represents the correct form, so still scope and inspect matches.

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

Check uniqueness before clicking

Dynamic fragments can match more than one element. Inspect the result during development or fail deliberately when the count is not what you expect.

const matches = await page.$$('input[id^="user-"]');
console.log('matched elements:', matches.length);
if (matches.length !== 1) {
  throw new Error(`Expected one user input, found ${matches.length}`);
}
await matches[0].type('alice');

page.$ returns the first matching element; page.$$ returns all matches. Selecting the first match without checking can silently operate on the wrong component.

Read values with $eval and $$eval

$eval passes one matched element to a page function and throws if none matches. $$eval passes an array of all matches and waits for an asynchronous page function:

const value = await page.$eval(
  'input[id$="-email"]',
  el => el.value
);

const ids = await page.$$eval(
  'input[id*="email"]',
  elements => elements.map(el => el.id)
);

Use these APIs for inspection or extraction; use a locator for an interaction that needs readiness and retry behavior.

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

XPath when CSS is insufficient

CSS cannot express every condition. Puppeteer supports XPath through its prefixed syntax, which uses the browser’s native Document.evaluate:

const button = await page.waitForSelector(
  '::-p-xpath(//button[starts-with(@id,"save-")])'
);
await button.click();

Keep the XPath condition specific. If an XPath expression can be replaced by a stable role, label, test hook, or CSS selector, the simpler selector is generally easier to maintain.

Dynamic rendering and navigation patterns

Wait after navigation

await page.goto('https://example.com/account', {
  waitUntil: 'domcontentloaded'
});
await page.locator('form button[id$="-submit"]').click();

For a single-page application, navigation may finish before the component renders. Wait for the stable selector, not an arbitrary sleep:

await page.goto('https://example.com/app');
await page.waitForSelector('#settings-panel input[id*="email"]', {
  visible: true
});

Handle a selector that is intentionally optional

const dismiss = 'button[id^="newsletter-dismiss-"]';
const close = await page.$(dismiss);
if (close) {
  await close.click();
}

Use an explicit, short timeout when an optional element should not hold the test for the full default timeout.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

Shadow DOM

Puppeteer supports custom selector syntax for shadow DOM as well as CSS, XPath, text, and accessibility selectors. If the dynamic-ID element is inside a web component, use Puppeteer’s shadow-DOM selector facilities or a locator that crosses the component boundary rather than assuming a document-level CSS query can reach it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

Symptom Likely cause Fix
Timeout waiting for selector Prefix, suffix, or scope is wrong; element has not rendered; or navigation is still in progress. Inspect the live DOM, verify the stable fragment, wait after the relevant navigation, and increase the timeout only after fixing synchronization.
More than one element matches The fragment is shared by repeated rows, hidden templates, or multiple panels. Add a stable ancestor, tag, role, label, or test attribute; verify with page.$$.
Click targets a hidden or covered node A template copy, overlay, or consent dialog matches first. Use a locator, require visibility, scope to the active panel, and handle the overlay explicitly.
Selector works locally but not in CI Different timing, viewport, authentication state, or feature flag changes the DOM. Wait on a meaningful state, set required cookies or headers, and capture diagnostics such as matching IDs and a screenshot.
Special characters break the selector The fragment contains CSS-significant characters. Use a quoted attribute selector with proper escaping, or choose a semantic hook/XPath.
Element is inside a component It lives in shadow DOM or an iframe. Use Puppeteer’s shadow-DOM syntax; for an iframe, obtain its frame and query within that frame.

Reliability and maintenance checklist

  • Document which part of the ID is guaranteed stable and who owns that contract.
  • Prefer one unique semantic or test selector over a long chain of incidental classes.
  • Scope repeated patterns to a stable container.
  • Assert the expected match count during development and in critical tests.
  • Wait for visible, enabled, or otherwise actionable state instead of sleeping for a fixed duration.
  • Log the selector, URL, and matched IDs when a test fails.
  • Re-check selectors after component-library, localization, and accessibility changes.

Or skip the browser setup

If your goal is a rendered page image rather than an interaction test, ScreenshotNeo returns a screenshot or PDF from one request. It accepts cookie and consent banners as a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are free, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Use the API documentation at https://screenshotneo.com/docs/ for all options, including full-page lazy-image capture, CSS-selector element capture, dark mode, device presets, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, selector/delay/network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and the OpenAPI specification.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account.

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

FAQ

Can I use a regular ID selector with a changing suffix?

Only if you know the complete value at runtime. Otherwise use an attribute selector such as [id^="prefix"], [id$="suffix"], or [id*="fragment"].

Should I use page.$ or a locator?

Use a locator for normal actions that need readiness and retries. Use page.$ when you specifically need the first matching element handle, and verify that first-match behavior is intentional.

What if the changing ID has no stable portion?

Ask for or identify another stable contract: role and accessible name, label, visible text, a test attribute, a stable ancestor, or a documented application state.

Frequently Asked Questions

Does Puppeteer support CSS prefix, suffix, and substring selectors?

Yes. Puppeteer accepts standard CSS attribute selectors: ^= for prefixes, $= for suffixes, and *= for substrings.

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

What is the default waitForSelector timeout?

The documented default is 30 seconds; you can override it with the timeout option.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.