Homemade Masala Chai
A warming spiced tea recipe, perfect for monsoon evenings.
Serves 2. Prep time: 10 minutes. Total time: 15 minutes.
Jump to full recipe View commentsNot every source has an API — sometimes the data only lives in a web page. Twelve challenges across five parts, from a single tag to combined real-world patterns. Type a real CSS selector, run it against live sample HTML, and see instantly what it matches — the same skill that powers extraction in n8n's HTML node and Power Query's Html.Table.
How to use this: read the task, inspect the raw HTML if you need to, then write your own selector before opening the hint. The hint teaches the underlying rule in general terms — the Solution box gives the exact answer once you're ready.
Tag, class, id — the primitives every selector is built from.
Task A: select every paragraph in the article below — not the heading, not the link.
Task B: select every link in the article.
A warming spiced tea recipe, perfect for monsoon evenings.
Serves 2. Prep time: 10 minutes. Total time: 15 minutes.
Jump to full recipe View comments<article>
<h1>Homemade Masala Chai</h1>
<p>A warming spiced tea recipe, perfect for monsoon evenings.</p>
<p>Serves 2. Prep time: 10 minutes. Total time: 15 minutes.</p>
<a href="#recipe">Jump to full recipe</a>
<a href="#comments">View comments</a>
</article>Task A: select every price across all three cards.
Task B: select every badge — both the red "Sale" one and the blue "New" one — with a single selector.
<div class="card">
<span class="badge sale">Sale</span>
<div class="title">Wireless Mouse</div>
<div class="price">₹899</div>
<div class="meta">In stock</div>
</div>
<div class="card">
<span class="badge new">New</span>
...
</div>
<div class="card">
<div class="title">USB-C Hub</div>
... <!-- no badge on this one -->
</div>Task: select only the featured plan — the single plan marked with a unique id, not the other two.
<div class="plan"><h3>Starter</h3>...</div>
<div class="plan featured" id="featured-plan">
<h3>Professional</h3>
<div class="amt">₹2,499/mo</div>
</div>
<div class="plan"><h3>Enterprise</h3>...</div>Task: select only the card that is both on sale and currently in stock — not cards that are only one or the other.
<div class="card sale instock"><div class="title">Desk Lamp</div>...</div>
<div class="card sale"><div class="title">Bluetooth Speaker</div>...</div>
<div class="card instock"><div class="title">Table Fan</div>...</div>
<div class="card"><div class="title">Wall Clock</div>...</div>Filter on values that are not visible text — links, paths, flags.
Task A: in the nav bar, select only the links that actually go somewhere — ignore the two disabled, placeholder items that have no destination at all.
Task B: in the download list, select only the files whose link starts with a specific download path, ignoring the unrelated page links mixed in.
<a href="/pricing">Pricing</a>
<span class="disabled">Coming soon</span>
<a href="/docs">Docs</a>
<span class="disabled">Beta feature</span><a href="/download/invoice.pdf">Invoice.pdf</a>
<a href="/blog/gst-updates">GST updates — blog</a>
<a href="/download/report-q1.xlsx">Report Q1.xlsx</a>
<a href="/contact">Contact us</a>
<a href="/download/certificate.pdf">Certificate.pdf</a>How selectors relate to each other and the page structure.
Task: select every title AND every price across both cards, using a single selector (four elements total).
<div class="card">
<div class="title">Desk Lamp</div>
<div class="price">₹799</div>
</div>
<div class="card">
<div class="title">Table Fan</div>
<div class="price">₹1,499</div>
</div>This card has two elements with class .title — one is the real product title, a direct child of .card; the other is buried inside a .meta wrapper. Select only the direct-child title.
<div class="card">
<div class="title">Noise Cancelling Headphones</div>
<div class="price">₹6,999</div>
<div class="meta">
Related: <span class="title">Wired Earphones</span>
</div>
</div>Task: select only the direct answer that comes immediately after the question — not the follow-up note paragraph below it. Neither paragraph has a distinguishing class, so you will need to select by position relative to the heading.
Yes, we ship to over 40 countries worldwide.
Delivery times vary by region, typically 5–10 business days.
<div class="faq-item">
<h4>Do you ship internationally?</h4>
<p>Yes, we ship to over 40 countries worldwide.</p>
<p>Delivery times vary by region, typically 5–10 business days.</p>
</div>Select by position among siblings, and by exclusion.
Task A: select only the first step.
Task B: select only the last step. No class distinguishes any of these — only their position in the list.
<ol class="steps">
<li>Preheat the oven to 180°C.</li>
<li>Mix the dry ingredients together in a large bowl.</li>
<li>Fold in the wet ingredients gently, don't overmix.</li>
<li>Bake for 25 minutes, then cool before slicing.</li>
</ol>Task: select every data row except the header that falls on an odd position counting from the header row itself — visually, rows 3 and 5 in this table (the header is row 1).
| Plan | Setup Fee | Monthly |
|---|---|---|
| Starter | ₹0 | ₹999 |
| Professional | ₹500 | ₹2,499 |
| Business | ₹1,000 | ₹4,499 |
| Enterprise | ₹2,000 | ₹5,999 |
<table>
<thead><tr><th>Plan</th>...</tr></thead>
<tbody>
<tr><td>Starter</td>...</tr>
<tr><td>Professional</td>...</tr>
<tr><td>Business</td>...</tr>
<tr><td>Enterprise</td>...</tr>
</tbody>
</table>Task: select every plan except the featured one — without listing "Starter" and "Enterprise" individually.
<div class="plan"><h3>Starter</h3>...</div>
<div class="plan featured"><h3>Professional</h3>...</div>
<div class="plan"><h3>Enterprise</h3>...</div>Chain several techniques into one realistic extraction.
Task: select only the titles of in-stock products that are not already marked "Featured" elsewhere on the page (to avoid listing the same product twice in your extraction). You will need an attribute filter, a negation, and a descendant step — all in one selector.
<div class="card" data-instock="true">
<div class="title">Wireless Mouse</div>...
</div>
<div class="card" data-instock="false">
<div class="title">Mechanical Keyboard</div>...
</div>
<div class="card" data-instock="true" data-featured="true">
<div class="title">USB-C Hub</div>...
</div>
<div class="card" data-instock="true">
<div class="title">Laptop Stand</div>...
</div>Read each question, think through your answer, then expand to check. Where useful, try the selector in the matching challenge above first.
.price selects every element with class="price" (or that class among others).#featured-plan selects the one element with id="featured-plan".id is meant to be unique per page — if your selector matches more than one, something on the page is not following that rule.h2 as a selector match <h2> elements with any class, or only ones with no class at all?<span class="price sale">, will the selector .sale match it?.card.sale (no space) the same thing as .card .sale (with a space)?.card.sale means one single element must carry both classes. .card .sale means an element with class "sale" located anywhere inside an element with class "card" — two different elements, a parent and a descendant..card .title and .card > .title?.title at any depth inside .card — a child, grandchild, anywhere. The > version (child combinator) only matches a .title that is a direct child, one level down.span.price — chaining a tag name directly against a class (no space) narrows the match to that tag only.[href^="/download/"] mean, in plain words?href attribute starts with the text /download/. The ^= operator means "starts with".Compare with $= ("ends with") and *= ("contains anywhere").
src). "Text" pulls only the visible text content of an element — an <img> tag has no visible text, so Text returns nothing. The file path lives in the src attribute, which only "Attribute" can reach..title, .price?h4 + p and h4 ~ p?h4 + p (adjacent sibling) matches only the single paragraph immediately following the h4. h4 ~ p (general sibling) matches every paragraph that comes after the h4 at the same level, not just the very next one. The plus sign is the one used most often when you need exactly one specific neighbour.tr:nth-child(2) count the header row when deciding what "child #2" means?<thead> and <tbody> are separate parents, each row only counts its position among siblings within its own parent. A <tr> in tbody never counts the thead row as a sibling.*= — e.g. a[href*="sale"]. It matches the substring appearing anywhere in the attribute's value.Html.Table, how do you extract an attribute value (like src) instead of the element's visible text?{"imageUrl", ".card img", each [Attributes][src]}. Without the third part, Html.Table defaults to extracting visible text.div > div:nth-child(3) > span) tied to the page's exact current layout. A tiny redesign — one new wrapper div — breaks the whole chain. A selector built on a stable class or data-* attribute survives far more changes.data-instock="true" and class="card sale". Write a selector that requires both conditions..card.sale[data-instock="true"] — chaining two classes with no space (.card.sale) means "has both classes", and the attribute selector adds the third condition. All three chained directly means all must be true on the same element.li:first-child the same as li:nth-child(1)?:first-child is really just a convenient shorthand for the specific case of :nth-child(1); if the very first child were some other tag instead, neither would match the <li>, since both require that exact position, not merely "the first li"..plan elements except the one with class .featured, without listing the other classes individually..plan:not(.featured) — the negation pseudo-class excludes anything matching the condition inside its parentheses from an otherwise normal selector.table td:nth-child(2) — because within each <tr>, the <td> elements are the children being counted, and every row's 2nd cell shares that structural position..product-list > div:nth-child(2) > .price or .product-card .price?.product-card .price is far more resilient. It depends only on two class names staying consistent, regardless of how many wrapper elements sit between them or what order cards appear in. The nth-child version breaks the moment a card is added, removed, or reordered — it hard-codes a position rather than a meaning..card's full text occasionally includes the badge wording mixed into the description. Which n8n field fixes this, and how?.badge). This tells the node to ignore that child element's text when assembling the Text/HTML result for the parent, so its wording no longer leaks into the surrounding extraction..card[data-instock="true"]:not([data-featured="true"]) .title — the attribute selector filters to in-stock containers, :not() with an attribute condition inside it excludes the featured one, and the trailing space steps down into the title field once the right containers are isolated.