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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use page.locator('div') to create a locator for every <div> on the page, then call count() for the current number of matches. For extraction, use allInnerTexts(), allTextContents(), or evaluateAll() when you need custom fields. In tests that assert a number, prefer the retrying expect(locator).toHaveCount() assertion so Playwright waits for the page to reach the expected state.

This guide shows reliable TypeScript patterns, explains rendered text versus DOM text, handles dynamic lists, and documents the failure modes that make broad div selectors flaky.

Selecting every div

A tag selector is the clearest choice when the element type itself is the requirement:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const divs = page.locator('div');

A locator is a live description of matching elements. It is not a one-time array, so Playwright can resolve it again after the DOM changes. The selector may match zero, one, or many elements.

Count the current matches

const count = await divs.count();
console.log(`Found ${count} div elements`);

count() returns the number of elements that match at the moment it is called. It is useful for diagnostics or for branching logic. It does not, by itself, wait for an application to finish rendering.

Assert an expected count

For a test assertion, use a web-first assertion:

await expect(divs).toHaveCount(3);

toHaveCount() retries until the expected count is observed or the assertion timeout expires. This is safer than reading count() once and comparing the returned number while a list is still loading. The expected value above is illustrative; derive it from the page contract you are testing.

Extract text from all matching divs

Rendered text with allInnerTexts()

const visibleTexts = await divs.allInnerTexts();
console.log(visibleTexts);

allInnerTexts() returns an array based on each element’s innerText. It is generally the right choice when your test or scraper needs text as a user would see it, including rendering-related whitespace behavior.

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

DOM text with allTextContents()

const domTexts = await divs.allTextContents();
console.log(domTexts);

allTextContents() returns an array based on each node’s textContent. It includes text in descendants regardless of whether CSS makes that text visible. Use it when the DOM representation is the data source, such as hidden labels or template content.

Method Returns Use it when
allInnerTexts() Array of rendered innerText values You need user-visible text and layout-aware whitespace
allTextContents() Array of DOM textContent values You need text present in the DOM, including hidden descendants

Extract custom data with evaluateAll()

When text alone is insufficient, evaluateAll() runs a mapping function in the page context and passes the matched elements as an array:

const rows = await divs.evaluateAll(elements =>
  elements.map(element => ({
    text: element.textContent,
    id: element.id,
    className: element.className,
  }))
);

console.log(rows);

This pattern is suitable for attributes, dataset values, nested links, or a normalized object assembled from several descendants. Keep the callback serializable: return plain objects, arrays, and primitive values rather than DOM nodes.

Extract attributes and nested values

const cards = page.locator('div[data-card]');
const data = await cards.evaluateAll(elements =>
  elements.map(card => ({
    key: card.getAttribute('data-card'),
    heading: card.querySelector('h2')?.textContent?.trim() ?? null,
    href: card.querySelector('a')?.getAttribute('href') ?? null,
  }))
);

A narrower selector such as div[data-card] prevents unrelated layout containers from entering your result set.

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.

A complete Playwright test

import { test, expect } from '@playwright/test';

test('count and read divs', async ({ page }) => {
  await page.goto('https://example.com');

  const divs = page.locator('div');
  await expect(divs).toHaveCount(3);

  const texts = await divs.allTextContents();
  console.log(texts);
});

Install and configure Playwright in your project, then run the test with your normal Playwright command. Replace the illustrative URL and count with the page and contract under test.

Choose a locator that survives UI changes

A literal tag selector is appropriate when every div is relevant, but most pages use div for layout. A broad selector can therefore count wrappers, navigation shells, analytics containers, and unrelated components.

Prefer meaning over DOM shape

For non-interactive content, Playwright supports text locators when visible wording identifies the target. For buttons, links, checkboxes, and other controls, prefer role locators with an accessible name. User-facing attributes or explicit test IDs are also good contracts. These choices are less coupled to nesting and CSS classes than long CSS or XPath chains.

const priceCards = page.getByTestId('price-card');
const notice = page.getByText('Free trial');
const submit = page.getByRole('button', { name: 'Submit' });

If the application has no stable user-facing contract, ask developers to add a dedicated test ID rather than encoding a fragile path such as main > div:nth-child(2) > section > div.

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

Scope a tag selector

const dialogDivs = page.getByRole('dialog').locator('div');
const productDivs = page.locator('[data-testid="product-grid"] div');

Scoping reduces accidental matches while retaining the simple tag selector inside the relevant component.

Dynamic content and timing

Single reads are vulnerable to race conditions when JavaScript adds, removes, or replaces nodes. Establish a meaningful ready condition before collecting a snapshot.

Wait for a component, not an arbitrary sleep

const list = page.getByTestId('results');
await expect(list).toBeVisible();
const items = list.locator('div[data-item]');
await expect(items).toHaveCount(10);
const texts = await items.allInnerTexts();

Waiting for a selector or assertion ties the test to application behavior. A fixed waitForTimeout() can be too short on a slow run and unnecessarily long on a fast one.

Understand locator.all()

locator.all() immediately returns locators for elements currently present. It does not wait for the list to reach a particular count or finish loading. On a changing list, the returned collection can represent a transient state. Use a readiness assertion first, then call bulk extraction methods.

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

Assert text without reading too early

await expect(items).toHaveText([
  'First result',
  'Second result',
]);

For an assertion, toHaveText() retries and reports a useful mismatch. Extract with allInnerTexts() after the state is stable when you need the values for logging or further processing.

Strictness: bulk operations versus single-element operations

Bulk methods such as count(), allInnerTexts(), allTextContents(), and evaluateAll() are designed for multiple matches. Operations that imply one target are strict and throw if the locator resolves to more than one element.

const divs = page.locator('div');
// Valid for a collection:
const n = await divs.count();

// Strict: fails if several divs match:
// await divs.textContent();

If you intend one element, narrow the locator or select deliberately with first(), last(), or nth(index). Use those positional methods only when order is part of the page contract; otherwise a semantic locator is more resilient.

Common failures and fixes

The count is zero

  • Cause: the content is inside an iframe. Fix: obtain a frame locator, then select inside it, for example page.frameLocator('iframe').locator('div').
  • Cause: the application has not rendered the component. Fix: wait for a stable marker with expect(...).toBeVisible() or toHaveCount().
  • Cause: the target is inside shadow DOM or a different browsing context. Fix: inspect the component boundary and use the appropriate locator or frame.

The count is much larger than expected

  • Cause: layout uses many wrapper div elements. Fix: scope the locator and add a semantic attribute, role, text, or test ID.
  • Cause: hidden templates or duplicated responsive markup are included. Fix: decide whether DOM presence or visible content is required, then choose allTextContents() or allInnerTexts() accordingly and filter with a meaningful selector.

Text is empty or differs from what the browser shows

  • Cause: you selected textContent semantics but the visible text is generated elsewhere or hidden. Fix: try allInnerTexts() for rendered text, or inspect descendants and attributes with evaluateAll().
  • Cause: whitespace and nested nodes differ. Fix: normalize deliberately in your mapping function instead of assuming the two APIs are interchangeable.

An assertion is flaky

Replace a one-time count() comparison with await expect(locator).toHaveCount(expected), and wait on the component’s meaningful loaded state before extracting. Avoid arbitrary sleeps and selectors tied to incidental nesting.

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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and data quality

Bulk extraction is usually simpler and less chatty than looping over every element and issuing separate calls. If you only need one field, map that field in a single evaluateAll(). If you need a small, stable subset, narrow the locator first; reducing matches also reduces the amount of data serialized back to the test process.

For very large pages, avoid selecting every div unless that is genuinely the requirement. A page-wide locator can include thousands of nodes and make both assertions and returned arrays harder to interpret. Component-scoped locators document intent and are less likely to change when a framework adds a wrapper.

Keep extraction deterministic: wait for the same readiness signal on every run, define whether hidden text counts, and assert the contract before collecting a diagnostic snapshot. When a count is expected to vary by data, assert a lower-level invariant (for example, that the results container exists) and treat the returned count as data rather than a fixed test expectation.

Or skip the browser setup

If your goal is a rendered page image rather than DOM-level assertions, ScreenshotNeo returns a screenshot or PDF from one request. Its cleanup step accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. It also provides an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools.

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

Use the API documentation at https://screenshotneo.com/docs/ for authentication and options. A minimal call is:

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

Options include full-page capture with lazy images, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and page-range settings, custom CSS and JavaScript, pre-capture clicks, selector hiding, waits for selectors, delays or network idle, request and resource blocking, custom headers/cookies/user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatible parameter names used by other screenshot APIs.

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

Frequently Asked Questions

Can I count only visible div elements?

A tag locator includes matching DOM elements whether or not they are visible. Scope to the component you need and use rendered-text extraction or an explicit visibility filter when visibility is part of your requirement.

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

Should I use CSS or XPath for div extraction?

Use a simple CSS tag or attribute selector when it expresses the contract. Prefer semantic text, role, or test-ID locators when available; long CSS and XPath chains that encode DOM structure are more brittle.

How do I handle divs inside an iframe?

Create a frame locator for the iframe and then locate inside it, such as page.frameLocator('iframe').locator('div'). A page-level locator cannot see elements in a separate frame.

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.