7.8 — How the browser renders
You've mutated the tree (7.3) and heard the word "repaint" more than once already. Today, gently, the whole pipeline behind it — and the one gap in it that causes more flaky tests than almost anything else: existing in the DOM is not the same as being visible on screen.
Four stages, in order, every single page load: Parse → Render Tree → Layout → Paint. Learn the shape once, and "why is my test clicking something that isn't there yet" stops being a mystery.
<!-- the HTML the server sent -->
<body>
<h1>My List</h1>
<p class="note">loading…</p>
</body>
<!-- the CSS the server sent -->
<style>
.note { display: none; }
</style>
// in JS: a mutation, later
document.querySelector("h1").textContent = "Shopping";Parsing produces TWO trees at once: the DOM (from the HTML text, 7.1 — you already own this) and a new one, the CSSOM (from the CSS text) — a tree of style rules, same shape idea, different source.
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
"Gently" because the real pipeline has more nuance the job rarely needs day to day: layout can happen more than once per frame if JS reads a geometry property (like offsetHeight) right after writing one (forcing a "layout thrash").
And modern engines composite separate layers so some animations (transform, opacity) skip layout and paint almost entirely — which is exactly why those two properties are the fast ones to animate.
Precision on the CSSOM: it isn't optional or skippable — a page with zero CSS still builds one (with browser-default styles), because the render tree always needs computed styles paired with content, never content alone.
Job note: this whole lesson is the "why" behind auto-waiting, the single feature that makes Playwright tests dramatically less flaky than hand-rolled ones. Phase 11 teaches the API; today you own the reason it has to exist at all.
⌨️ model the render-tree exclusion
The sandbox has no real rendering — so model the ONE rule that matters most: which nodes make it into the render tree, and which don’t.
requirements:
- An array
nodesof objects:{ tag: "h1", display: "block" },{ tag: "p", display: "none" },{ tag: "ul", display: "block" }. - Write
renderTree(nodes): return only the nodes whosedisplayis NOT"none"(4.9’s filter). - Print how many nodes are in the full
nodesarray, then how many survive intorenderTree(nodes).
when you press RUN, the console must show exactly:
✏️ Quick check 1
An element has display: none. Is it part of the render tree? Type yes or no.
✏️ Quick check 2
Put these four stages in order (comma-separated): Paint, Parse, Layout, Render Tree
✏️ Quick check 3
Can an element exist in the DOM (queryable with querySelector) before it has been painted on screen? Type yes or no.
🗣️ Now teach it back
Explain to a friend the four stages of the render pipeline, in order — and why "it exists in the DOM" is not the same claim as "it is visible on screen," with an example of a real testing consequence.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.