Free tools Windows power users keep installed
One-click scans. No signup required.
Use page.getByRole('button') when “button” means any control exposed to users and assistive technology. Use page.locator('button') when you specifically need native HTML <button> elements. Then call evaluateAll() to map the matches to plain data such as text, ARIA labels, titles, types, and disabled state.
Google does not publish a stable selector contract for every control in every Search surface. Scope the page you mean (product, locale, viewport, session and consent state), wait for the relevant state, and prefer semantic locators over Google’s changing class names or DOM nesting.
Choose what “button” means
The extraction strategy depends on whether you are inventorying semantic controls or HTML tags.
Accessible buttons
page.getByRole('button') matches elements Playwright exposes with the accessible button role. This includes native <button> elements and custom controls that declare that role. It reflects how a user or assistive technology perceives the control, which is usually the right definition for behavior-oriented automation.
#1 Best Overall
Native button tags
page.locator('button') selects elements whose tag is literally <button>. It will not include a clickable div, link, or other custom control that has an accessible button role.
| Requirement | Locator | What it includes |
|---|---|---|
| All controls exposed as buttons | page.getByRole('button') |
Native buttons and custom controls with the button role |
| Only native HTML elements | page.locator('button') |
Elements with the button tag |
| Only visible native buttons | page.locator('button:visible') |
Native buttons currently considered visible |
These sets are not interchangeable: semantic role, tag identity and visibility answer different questions.
Complete Playwright extraction example
The following script opens a Google page, waits for a usable page state, and extracts both semantic and native inventories. Replace the URL with the exact Google surface you need to analyze.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
locale: 'en-US'
});
await page.goto('https://www.google.com/', { waitUntil: 'domcontentloaded' });
await page.waitForLoadState('networkidle').catch(() => {});
const accessibleButtons = page.getByRole('button');
const accessibleButtonData = await accessibleButtons.evaluateAll(buttons =>
buttons.map(button => ({
tag: button.tagName.toLowerCase(),
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label'),
title: button.getAttribute('title'),
type: button.getAttribute('type'),
disabled: button.hasAttribute('disabled')
}))
);
const nativeButtons = await page.locator('button').evaluateAll(buttons =>
buttons.map(button => ({
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label'),
title: button.getAttribute('title')
}))
);
const visibleNativeButtons = await page.locator('button:visible').evaluateAll(buttons =>
buttons.map(button => ({
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label')
}))
);
console.log({ accessibleButtonData, nativeButtons, visibleNativeButtons });
await browser.close();
Install Playwright with npm install -D playwright. If your project uses the bundled browsers, run npx playwright install once. The callback passed to evaluateAll() executes in the page and can use normal DOM APIs; the returned objects are serialized into Node.js values.
Recommended Free Tools
Extract a specific Google control
For one known control, narrow a role locator by accessible name rather than collecting every match. The name must match the live page and may vary by locale or account state.
const searchButton = page.getByRole('button', { name: 'Search' });
console.log('matches:', await searchButton.count());
if (await searchButton.count() === 1) {
const details = await searchButton.evaluate(button => ({
tag: button.tagName.toLowerCase(),
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label'),
title: button.getAttribute('title'),
disabled: button.hasAttribute('disabled')
}));
console.log(details);
}
A locator used for an action is intentionally strict when multiple elements match. For an inventory, use count() or evaluateAll() instead of assuming one result.
Rank #2
Use code generation as a starting point
Playwright codegen can inspect the page and suggest role, text, or test-id locators. Treat generated code as a draft: verify the accessible name, remove unnecessary implementation details, and keep only the selector that expresses your actual requirement.
Wait for the page state you intend to measure
Google pages can render controls after navigation, interaction, consent handling, or asynchronous requests. Collecting immediately after goto() can produce an incomplete inventory.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match- Define the state. Decide whether you need the landing page, a results page, an open menu, a signed-in view, or a post-consent view.
- Navigate. Use the precise product URL, locale and viewport needed for your case.
- Trigger required UI. Click or fill controls that reveal menus or dialogs before extraction.
- Wait for a meaningful signal. Prefer a known selector or page condition. A short delay can be a fallback, but it is less deterministic.
- Extract. Run
evaluateAll()only after the intended state is ready.
Playwright locators resolve against the current DOM when used, which helps with re-rendering. However, locator.all() does not wait for matches and can be unpredictable while a list is changing. For a stable snapshot, wait for the relevant state first and then evaluate the collection.
Waiting for a known control
await page.getByRole('button', { name: 'Menu' }).waitFor({ state: 'visible' });
const menuButtons = await page.getByRole('button').evaluateAll(buttons =>
buttons.map(button => ({
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label')
}))
);
If the control is not guaranteed to exist in every context, wait for a broader page signal and handle an empty result rather than making the script fail.
Visibility, hidden controls and dynamic content
page.locator('button') can match hidden and visible native buttons. Add :visible when hidden templates, menus or responsive variants should be excluded. Visibility does not change the semantic-role distinction: a visible native query still omits custom elements exposed as buttons.
const visibleNativeButtons = await page.locator('button:visible').evaluateAll(buttons =>
buttons.map(button => ({
text: button.textContent?.trim() ?? '',
ariaLabel: button.getAttribute('aria-label'),
title: button.getAttribute('title')
}))
);
For accessibility-oriented work, do not infer visibility solely from an HTML attribute. Let Playwright’s visibility engine make that determination, and separately decide whether an off-screen but available control belongs in your dataset.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #3
Make selectors resilient on Google
Prefer user-facing contracts
Role plus accessible name describes the control’s purpose and is generally more durable than a generated class name. For example:
const voiceControl = page.getByRole('button', { name: /voice|search by voice/i });
Use a regular expression only when you have deliberately allowed known label variations. If the name is not stable across locales, make locale an explicit test parameter rather than silently broadening the match.
Use CSS or XPath only for a defined need
CSS and XPath are available when the requirement is structural, such as selecting native tags or a carefully scoped container. Google’s internal classes and nesting are implementation details; they can change without notice. XPath also does not pierce shadow roots.
Shadow DOM boundaries
Playwright locators cross open shadow roots by default. Closed-mode shadow roots are unsupported, and XPath does not cross shadow roots. If a control is rendered inside a closed root, ordinary page-level extraction cannot inspect it through that boundary.
Google-specific scope and limitations
Google’s accessibility guidance describes keyboard and screen-reader navigation for Search results, but it does not define stable HTML tags, labels or selectors for every button on the live site. Search, Lens, account, consent and regional experiences can expose different controls. A dated Google update about voice input in Lens on Android and iOS describes an app feature, not the desktop Search DOM.
Therefore, do not promise that a selector found on one visit identifies every Google button. Record the product surface, URL, locale, viewport, authentication state and consent/session state alongside your extraction. Re-run discovery when those conditions change.
Rank #4
Common failures and fixes
The result is empty
- Cause: extraction ran before dynamic content rendered. Fix: wait for a relevant role, selector, navigation state or application condition.
- Cause: the page is a different Google surface or locale. Fix: verify the final URL and accessible names in that context.
- Cause: a consent or interstitial page replaced Search. Fix: detect that state explicitly and handle it before collecting buttons.
The locator matches too many elements
Use count() to inspect the number of matches, then narrow by accessible name, container or another explicit condition. If your goal is collection rather than action, keep evaluateAll() and store all matches.
The expected custom control is missing from locator('button')
That query intentionally selects only native tags. Try getByRole('button') and inspect the returned tag names to determine whether the control is custom.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Text is blank
Icon-only controls often expose an aria-label or title instead of text content. Extract both, and do not treat an empty text string as proof that the element has no accessible name.
Selectors break after a Google update
Replace class- or nesting-dependent selectors with role and accessible-name locators where possible. If the requirement is specifically native HTML, retain locator('button') and avoid assumptions about surrounding markup.
Performance, reliability and data design
One evaluateAll() call transfers a complete collection in one browser-to-script operation, which is usually preferable to issuing one evaluation per element. Keep the mapped object small: include only fields your downstream process needs. For large or repeated runs, write newline-delimited JSON or another structured format and include capture metadata such as URL, timestamp, locale and viewport.
Network-idle waiting is useful when the page settles, but third-party requests can keep a page busy indefinitely. Use a bounded timeout and a deterministic application signal when possible. Treat bot checks, captchas, blank responses and navigation failures as separate outcomes rather than recording an empty button list as a successful extraction.
Best Value
Do not use extracted labels as a permanent API contract for Google. They are observations of a particular page state. Tests that drive a user task should assert the control needed for that task; audits that inventory controls should preserve the full context and tolerate additions or removals.
Or skip the browser setup
If you need a rendered image or PDF of the Google page rather than DOM-level button data, ScreenshotNeo provides a single screenshot API call. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Only clean shots are billed, while bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.google.com -o shot.webp
See the ScreenshotNeo documentation for options such as full-page capture, device presets, custom waits, selectors, cookies, headers, JavaScript and PDF output. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients, so an AI agent can request captures without you wiring a browser.
Equivalent Python call
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://www.google.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Equivalent Node.js call
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.google.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can Playwright extract buttons inside an iframe?
Yes, but locate the frame first with a frame locator or the matching frame, then run the role or tag locator within that frame. A page-level locator does not search an isolated frame document.
Should I save the button’s outerHTML?
Only when structural debugging requires it. For most inventories, normalized text, accessible label, title, type, tag and disabled state are easier to compare and less sensitive to irrelevant markup.
Does getByRole(‘button’) click every clickable element?
No. It matches controls exposed with the button role. Links, inputs and other interactive roles remain separate, even when a user can activate them.
Quick Recap
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

