7.2 — Selecting elements
If this curriculum could teach you only one syntax for your future job, it would be this one. Automation testing is, hour by hour, the craft of pointing at elements — "click that button, assert that message" — and the pointing language is CSS selectors, spoken through querySelector.
Five forms cover the working day: tag, .class, #id, [attribute], and combinations with descent.
Playwright's locators accept this exact language — lesson 11.3's SelectorLab v2 is this lesson wearing a work badge. Learn it here where it's calm.
document.querySelector("li")
// first <li> in the tree
document.querySelectorAll("li.todo")
// ALL <li class="todo"> — a NodeList
document.querySelector("#items")
// the id — unique per page
document.querySelector("ul#items li.done")
// descent: a .done li INSIDE #items
document.querySelector("[data-test=submit]")
// by attribute — the tester's favoriteForm 1 — the bare tag: querySelector("li") walks the tree and returns the FIRST matching element (a live reference, 7.1).
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
Why does JavaScript speak CSS here? Because stylesheets needed a "which elements does this rule hit" language first, and it was too good not to reuse — one grammar for styling, querying, and (in your future) locating.
Engines match selectors right to left: for "ul li.done" they find li.done candidates first, then verify a ul ancestor — exactly how you should read them too.
More grammar when you need it: ul > li (child only, no grandchildren), li:first-child / :nth-child(3) (position), [href^="https"] (attribute prefix).
Don't memorize the long tail — recognize it, and keep MDN's selector page bookmarked like every professional does.
And the career footnote: DevTools Console understands $('sel') and $$('sel') as shorthand for querySelector/All — the fastest way to TEST a selector against a live page before it goes in a script. You'll do this daily; start today, F12 on any site.
⌨️ build a tiny selector engine
Nothing demystifies selectors like implementing one. Given nodes as plain objects, write match(node, selector) for the three core forms — the same logic every browser runs a million times a day.
requirements:
- Nodes look like
{ tag: "li", id: "top", classes: ["todo", "done"] }. Build an arraynodeswith: anh1(id"title", no classes), aliwith classes["todo"], and aliwith classes["todo", "done"]. - Write
match(node, sel): ifselstarts with#compare the id; if it starts with.checkclasses.includes; otherwise compare the tag. - Print how many nodes match
".todo", then how many match"li", then the id of the first"#title"match (use 4.10's find).
when you press RUN, the console must show exactly:
✏️ Quick check 1
Write the selector that finds elements carrying BOTH classes "card" and "active" on the same element:
✏️ Quick check 2
Write the selector for a <button> with attribute data-test="save":
✏️ Quick check 3
In "nav a.external" — what does the SPACE mean? Type one word: inside or sibling.
🗣️ Now teach it back
Teach a friend the five selector forms with one example each — and explain why data-test attributes are the gold standard for automation, and what the space in a selector means.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.