CSS Selector Tester

Test CSS selectors against HTML snippets

Your selector finds nothing, or finds forty things instead of twelve, and the page looks exactly like it should in DevTools. Paste the HTML your parser actually receives, try selectors against it, and see every match and its count before you run anything across a thousand pages.

HTML / XML

No input provided


How to use

  1. 1Paste the HTML your code receives, not what DevTools shows — Those are two different documents. Take the response body from Request Tester, or view-source, or Copy outerHTML if you know the fragment is already in the source. Testing against the wrong one is the single most common reason a selector passes here and fails in production.
  2. 2Write the selector — Anything the browser understands works: .product-card h2 a, div[data-id], ul.results > li:nth-child(2), nav a[href]:not([href='#']).
  3. 3Read the count first, the matches second — The number of matches tells you more than the matches do. Expected twelve products and got forty? The selector reaches outside the list. Got zero? Either the class is not in the raw HTML at all, or it is spelled differently by one character.
  4. 4Tighten it until the count is right — Add a parent, an attribute, or a structural pseudo-class and watch the number move. Matches update as you type; switch auto mode off and use Run if the HTML is large.

Examples

The selector finds nothing, though the element is right there on the page
.product-title -> 0 matches
Two usual causes. The element is added by JavaScript and is simply absent from the raw HTML. Or the class is generated at build time and the real one looks like ProductCard_title__x7Kd2. Search the pasted HTML for a word from the visible text and see what actually wraps it.
It works in DevTools and returns empty in your parser
div.price
In the console you are working with a tree the scripts have already assembled; your parser gets the response before they run. Paste the raw response here — if the selector fails on it, the difference is JavaScript, not the selector.
Copy selector from DevTools breaks on the second page
#root > div:nth-child(2) > div > div.sc-hKgILt > ul > li:nth-child(1) > a
That path describes one page's layout, not the data. One extra banner and every index shifts. Replace it with the shortest thing that is actually about the content: [data-testid='product'] a or article.product a.
Class names change on every deploy
[class^="ProductCard_"]
Styled-components, CSS modules and similar tools hash class names at build time. Anchor to the stable prefix with ^=, or better still to a data-attribute, an id, itemprop, or the tag structure — anything the build step does not touch.
Forty matches instead of twelve
ul.results > li.item
A bare .item reaches the whole document, including the sidebar and the footer. Add the parent and the direct-child combinator, then check the count again — that number is the fastest feedback this tool gives.
The same class appears in two different blocks
section.reviews .author
Reviews and comments both use .author. Scope the selector to the section you need instead of filtering the results afterwards in code.
Only real links, without dead anchors
nav a[href]:not([href='#'])
Menus are full of placeholder anchors and disabled items. Filter them out in the selector and your list of URLs comes out clean, with nothing to skip later.
Every second row, or the third cell of a table
tbody tr:nth-child(2n) td:nth-child(3)
Structural pseudo-classes work fully on static HTML, so striped tables and paginated lists can be walked without any extra logic in your code.

When to use for web scraping

CSS selectors are what most Python and Node parsers run on — BeautifulSoup, Scrapy, Cheerio and Playwright all take them directly. The trouble is that a selector verified in DevTools is verified against the wrong document. DevTools shows the DOM after scripts have executed; your parser sees the bytes that arrived. Anything a script inserted is not there, and a selector that matched forty items in the console can match zero in code. The second recurring problem is class names you cannot rely on. Styled-components, CSS modules and utility frameworks generate names like sc-hKgILt or ProductCard_title__x7Kd2, and they change whenever the site is rebuilt. A parser anchored to them works for a week. Anchor to something that is not styling instead: a data-attribute, an id, itemprop, the tag structure, or at least a stable prefix through [class^="ProductCard_"]. This tool works on an HTML string, exactly the way your parser does. It returns every matched element and, more usefully, how many there were. The count is the real test: forty where you expected twelve means the selector reached past the container, and zero means the thing you are after is not in this HTML at all. Both answers arrive in seconds instead of after a crawl. What it cannot do: it does not execute JavaScript, and dynamic pseudo-classes like :hover, :focus and :checked mean nothing in a static document — structural ones such as :nth-child, :first-of-type and :not() work fully. CSS also cannot select an element by its text or walk upwards to a parent; when you need that, use XPath Tester. To get the raw HTML in the first place, send the request through Request Tester and paste the response body here.

FAQ

Zero matches, yet the element is visibly there in the browser. What do I check first?›
Search the source for any word you can read on the page with your own eyes. If it is not in there, the content is built by JavaScript and no selector will reach it — you need a rendering tool such as Playwright or Puppeteer. If the word is there, look at what actually wraps it: the class is almost always spelled differently than you assumed, most often because it is generated at build time.
Why does my selector work in DevTools but not in my parser?›
They are looking at two different documents. The inspector shows a live DOM: the scripts have already run, and the browser has quietly repaired whatever markup was broken. Your parser gets the raw response, with neither of those applied. Fetch that response with Request Tester, paste it here, and test against it — this is the only test that predicts what your code will do.
The class names look like sc-hKgILt and change after every deploy. What do I anchor to?›
Not to styling. In order of reliability: data-attributes ([data-testid="price"]), id, itemprop and other semantic attributes, then tag structure (article > h2 > a), and only then a stable class prefix through [class^="ProductCard_"]. Hashed suffixes change on rebuild; the prefix usually survives because it is derived from the component name.
I copied the selector from DevTools and it broke on the next page. Why?›
Copy selector produces a path down one specific page: a long chain of nth-child steps and hashed class names. It encodes that page's layout, so one extra banner or one missing block shifts every index. Write the shortest selector that describes the data rather than the position, and check it against two different pages here before putting it into code.
How do I limit the search to one block when the same class appears twice?›
Scope it with a parent: section.reviews .author instead of .author. If the two blocks are only distinguishable by order, :nth-of-type on the parent works too. Doing this in the selector is better than filtering in code afterwards, because the count here tells you immediately whether the scoping worked.
How do I select an element with several classes at once?›
Chain them with no spaces: .btn.btn-primary matches an element carrying both. A space would mean a descendant instead, which is the usual mistake. Order does not matter, and you can mix in attributes the same way: a.btn[href].
Can I select an element by its text?›
Not with CSS. There is no text-matching selector in the specification, and :contains() was removed from the draft years ago — libraries that support it add their own extension, which will not work in the browser or here. When you need to match by text, or to select a parent by what it contains, use XPath Tester.
Are :hover and :focus supported?›
No, and they cannot be. Dynamic pseudo-classes describe a state that only exists in a live browser with a user in front of it — there is nothing to hover in a static document. Structural pseudo-classes are a different matter and work fully: :nth-child, :nth-of-type, :first-of-type, :last-child, :not().
CSS selectors or XPath — which should I use for parsing?›
CSS for most work: shorter, more readable, and every library supports it. XPath when you need what CSS cannot express — selecting by text, walking up to a parent, or conditions on sibling elements. In practice CSS handles the majority and you reach for XPath at the few points where it gives up.
Do these selectors work in Scrapy and BeautifulSoup?›
Yes. Scrapy's response.css() and BeautifulSoup's select() both follow CSS Selectors Level 4, the same specification the browser implements here. What passes here passes there, with the newest pseudo-classes as the only edge cases worth checking.

Related tools