Computing Foundations › How the Internet Works › Day 20
Hands-on lab — Day 20: How Browsers Render: HTML, CSS, and JavaScript
- ← Back to the Day 20 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and/
Commands
Setup
cd labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and Run
bash examples/inspect_page.sh examples/page/index.html
bash starter/inspect_page.sh examples/page/index.html Test
bash tests/run_tests.sh File tree
examples/inspect_page.sh examples/page/index.html expected-output/FIELDS.md expected-output/inspect_page.txt expected-output/test-run.txt metadata.yml README.md requirements/README.md security.md starter/inspect_page.sh starter/render-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 020 lab — Build and Inspect a Tiny Web Page
Lesson
- Lesson title: How Browsers Render: HTML, CSS, and JavaScript
- Day number: 20 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-020-how-browsers-render-html-css-and
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-020-how-browsers-render-html-css-andwhen the site is running.
Purpose
Day 20's lesson explains what the browser does after it receives a page: parse HTML into the DOM, parse CSS into the CSSOM, build the render tree, lay it out, and paint it. This lab makes that concrete. You open a tiny, self-contained web page that shows all three web languages at once — structure (HTML), style (CSS), and behavior (JavaScript) — inspect its live DOM in your browser, and run a static inspector that reads the same file from the command line. Seeing the page from both sides — the built DOM and the raw source — is the core skill of debugging any web interface.
Learning objectives
- Open a local HTML file in a browser and inspect its live DOM with developer tools.
- Watch JavaScript edit the DOM in real time by clicking a button.
- Read a page's structure statically with
grep/wc— no browser needed. - Extract a page's
<title>, count its elements, and list its tags from the shell. - Explain why the static tag count and the live DOM count describe the same page from two directions.
Prerequisites
- The Day 20 lesson (read it first — it explains the DOM, CSSOM, render tree, layout, and paint).
- A terminal: Terminal.app (macOS), any terminal (Linux), or WSL/Git Bash (Windows).
- Any modern web browser with developer tools (all of them have these, free).
- No programming experience required; every command is given and explained.
Supported operating systems
- macOS — fully supported (tested on macOS with Apple Silicon).
- Linux — fully supported (any distribution with
bash,grep,sed,sort,uniq,wc). - Windows — run the shell scripts under WSL or Git Bash; open the HTML file in any installed browser.
Hardware requirements
Any computer made in roughly the last 15 years. The lab only reads a small text file and opens a page in a browser; it needs no special RAM, disk, or GPU.
Required software
- A web browser (Chrome, Edge, Firefox, or Safari) — for viewing and inspecting the page.
bash(3.2 or newer — preinstalled on macOS and Linux).- Standard text utilities:
grep,sed,sort,uniq,wc— all preinstalled.
Free and open-source options
Everything in this lab is free. Every browser ships professional developer tools (Elements, Console, Network, Performance, and Lighthouse) at no cost, and every shell tool used is open-source or part of your OS. No account, API key, or purchase is needed.
Installation
None. Clone the repository (or copy this directory) and you are ready:
cd labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and
File structure
day-020-how-browsers-render-html-css-and/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── inspect_page.sh ← YOUR working file (4 exercises)
│ └── render-worksheet.md ← worksheet for the practice assignment
├── examples/
│ ├── page/
│ │ └── index.html ← the tiny page you open and inspect
│ └── inspect_page.sh ← completed reference inspector
├── tests/
│ └── run_tests.sh ← automated checks
├── expected-output/
│ ├── inspect_page.txt ← real captured inspector run
│ ├── test-run.txt ← real captured test run
│ └── FIELDS.md ← required fields on every platform
├── requirements/
│ └── README.md ← dependency statement (browser + shell)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. Open the page in your browser (macOS shown; see notes for Linux/Windows)
open examples/page/index.html # Linux: xdg-open examples/page/index.html
## 2. Inspect the same file statically from the command line
bash examples/inspect_page.sh examples/page/index.html
## 3. Your task: complete the four exercises in the starter, then run it
bash starter/inspect_page.sh examples/page/index.html
## 4. Check your work
bash tests/run_tests.sh
In the browser, press F12 (or right-click → Inspect) to open developer tools.
Look at the Elements panel (the live DOM), switch to the Console and
run document.querySelectorAll('*').length, then click the page's button and
watch the paragraph's text change in the Elements panel.
What the commands do
open examples/page/index.html— opens the committed page in your default browser so you can view and inspect it. On Linux usexdg-open; on Windows, drag the file onto a browser window.bash examples/inspect_page.sh examples/page/index.html— the reference static inspector: it usessedto pull the<title>,grep -oEto list every opening tag,sort | uniq -cto tally element types,wc -lto count them, andgrep -qto confirm the<style>and<script>blocks. No browser, no network.bash starter/inspect_page.sh examples/page/index.html— the same skeleton with four values set tounknown; each exercise comment names the exact command to use. Edit the file and replace eachunknown.bash tests/run_tests.sh— checks that the committed page has the required structural tags and that the inspector extracts the right title and counts exactly 10 elements; exits 0 on success, non-zero on any failure.
Expected output
See expected-output/inspect_page.txt — a
real captured run:
=== Static page inspection: examples/page/index.html ===
Title: Tiny Web Page: Structure, Style, and Behavior
HTML elements (opening and void tags): 10
Distinct element types used:
1 body
1 button
1 h1
1 head
1 html
1 meta
1 p
1 script
1 style
1 title
Has <style> block (CSS / presentation): yes
Has <script> block (JavaScript / behavior): yes
=== End of inspection ===
The count of 10 matches what the browser Console reports for
document.querySelectorAll('*').length — each element has exactly one opening
tag. expected-output/FIELDS.md lists the fields
that must appear on every platform.
Validation steps
- Open
examples/page/index.htmlin a browser and confirm you see a heading, a navy paragraph, and a button. - Click the button and confirm the paragraph text changes to "Changed by JavaScript!".
- Run
bash examples/inspect_page.sh examples/page/index.htmland confirm the title and the count of 10. - Complete
starter/inspect_page.sh(replace everyunknown) and confirm it prints the same values. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 21 checks, 0 failure(s). The command exits 0 on success
and non-zero on any failure, so it can run in CI. It uses no browser and no
network. A captured run is in
expected-output/test-run.txt.
Cleanup
Nothing to clean up: the scripts only read a local file and print to the
console, and the page writes nothing. To reset any edits, restore the files
from git: git checkout -- examples/page/index.html starter/inspect_page.sh.
Troubleshooting
See troubleshooting.md for the full list (button not reacting, wrong directory, DevTools not opening, count differences, Windows notes).
Security notes
See security.md. Short version: the page is static and self-contained, its script only edits its own DOM and makes no network calls, the inspector only reads text, and nothing needs elevated privileges — but the habit to keep is to open local files you can read, not untrusted HTML.
Extension exercises
- Add a second CSS rule to the
<style>block (for example, style the<h1>) and re-run the inspector — note that the element count is unchanged because CSS never adds DOM nodes. - Add a second paragraph in the HTML and confirm the element count rises by one in both the inspector and the browser Console.
- Use the browser's Lighthouse panel to audit the page (served locally or any public page) and connect one flagged issue back to a stage of the critical rendering path.
- In the Performance panel, record a page load and find the layout and paint events in the timeline.
Navigation
- Previous day: Day 19 — HTTPS and TLS: Encryption on the Wire (
labs/sections/computing-foundations/day-019-https-and-tls-encryption-on-the/, to be written). - Next day: Day 21 — Inspecting Traffic with curl and Developer Tools (
labs/sections/computing-foundations/day-021-inspecting-traffic-with-curl-and-developer/, to be written).
Expected output
FIELDS.md
# Required inspection fields (all platforms)
A correct run of `inspect_page.sh` on the shipped `examples/page/index.html`
prints, in order:
1. `=== Static page inspection: <path> ===`
2. `Title: Tiny Web Page: Structure, Style, and Behavior`
3. `HTML elements (opening and void tags): 10`
4. `Distinct element types used:` followed by ten ` <n> <name>` lines, sorted
alphabetically: `body`, `button`, `h1`, `head`, `html`, `meta`, `p`,
`script`, `style`, `title` — each with a count of 1
5. `Has <style> block (CSS / presentation): yes`
6. `Has <script> block (JavaScript / behavior): yes`
7. `=== End of inspection ===`
`inspect_page.txt` in this directory is a real captured run (macOS, bash,
2026-07-12). `test-run.txt` is the captured output of `bash tests/run_tests.sh`.
Platform notes: the tools used (`grep`, `sed`, `sort`, `uniq`, `wc`) are POSIX
and behave identically on macOS and Linux for this page, so the output is the
same on both. The element count (10) equals the number the browser's Console
reports for `document.querySelectorAll('*').length`, because each element has
exactly one opening (or void) tag. If you edit the page, every number moves
with your edits — that is the intended lesson.
inspect_page.txt
=== Static page inspection: examples/page/index.html ===
Title: Tiny Web Page: Structure, Style, and Behavior
HTML elements (opening and void tags): 10
Distinct element types used:
1 body
1 button
1 h1
1 head
1 html
1 meta
1 p
1 script
1 style
1 title
Has <style> block (CSS / presentation): yes
Has <script> block (JavaScript / behavior): yes
=== End of inspection ===
test-run.txt
Checking the committed page: <repo>/labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and/examples/page/index.html
ok: page file exists
ok: page contains <html>
ok: page contains <head>
ok: page contains <title>
ok: page contains <style>
ok: page contains <script>
ok: page contains <body>
ok: page contains <button>
Running the inspector: <repo>/labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and/examples/inspect_page.sh
ok: inspector exits successfully
ok: inspector extracts the correct title
ok: inspector counts 10 elements
ok: inspector detects the <style> block
ok: inspector detects the <script> block
ok: distinct types list includes html
ok: distinct types list includes head
ok: distinct types list includes title
ok: distinct types list includes style
ok: distinct types list includes body
ok: distinct types list includes h1
ok: distinct types list includes button
ok: distinct types list includes script
21 checks, 0 failure(s).
Source files
examples/inspect_page.sh (2112 bytes)
#!/usr/bin/env bash
# Day 020 lab — static inspection of a web page's structure.
#
# Reads an HTML file with plain command-line tools (grep, sed, sort, wc) and
# reports its structure: the <title>, how many elements it contains, which
# element types appear, and whether it has CSS (<style>) and JS (<script>).
# No browser and no network are involved — this is a static view of the
# source, the complement to the live DOM you inspect in the browser.
#
# Usage: bash examples/inspect_page.sh [path-to-html]
# Default path is examples/page/index.html relative to the lab directory.
set -euo pipefail
# Resolve the default file relative to this script's lab directory so the
# command works from anywhere.
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
file="${1:-${lab_dir}/examples/page/index.html}"
if [ ! -f "${file}" ]; then
echo "Error: file not found: ${file}" >&2
exit 1
fi
echo "=== Static page inspection: ${file} ==="
# Title: the text between <title> and </title> (single line in this page).
title="$(sed -n 's/.*<title>\(.*\)<\/title>.*/\1/p' "${file}" | head -n 1)"
echo "Title: ${title}"
# Element (opening/void) tags: <name ...> but not closing tags (</name>) and
# not the <!doctype> declaration. Each element has exactly one opening tag,
# so this count equals the number of elements — comparable to the browser's
# document.querySelectorAll('*').length.
tag_names="$(grep -oE '<[a-zA-Z][a-zA-Z0-9]*' "${file}" | sed 's/<//')"
element_count="$(printf '%s\n' "${tag_names}" | grep -c . || true)"
echo "HTML elements (opening and void tags): ${element_count}"
echo "Distinct element types used:"
printf '%s\n' "${tag_names}" | sort | uniq -c | sed 's/^/ /'
# CSS and JavaScript blocks: the presentation and behavior layers.
if grep -q '<style' "${file}"; then
echo "Has <style> block (CSS / presentation): yes"
else
echo "Has <style> block (CSS / presentation): no"
fi
if grep -q '<script' "${file}"; then
echo "Has <script> block (JavaScript / behavior): yes"
else
echo "Has <script> block (JavaScript / behavior): no"
fi
echo "=== End of inspection ==="
examples/page/index.html (570 bytes)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tiny Web Page: Structure, Style, and Behavior</title>
<style>
p { color: navy; font-size: 18px; }
</style>
</head>
<body>
<h1>Hello from HTML</h1>
<p id="msg">This paragraph is navy and 18px because of the CSS.</p>
<button onclick="document.getElementById('msg').textContent = 'Changed by JavaScript!'">Change the text</button>
<script>
console.log('The behavior layer is running. Click the button to edit the DOM.');
</script>
</body>
</html>
metadata.yml (736 bytes)
lesson_id: D020
day: 20
kind: web-inspection
languages: [bash, html]
setup_commands:
- cd labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and
run_commands:
- bash examples/inspect_page.sh examples/page/index.html
- bash starter/inspect_page.sh examples/page/index.html
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- examples/page/index.html # optional: restore the shipped page if you edited it'
- 'git checkout -- starter/inspect_page.sh # optional: reset your starter work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 21 checks, 0 failure(s)'
requirements/README.md (892 bytes)
# Dependencies — Day 020 lab
**None beyond a shell and a web browser.** This lab has zero installable
dependencies:
- **To view the page:** any modern web browser (Chrome, Edge, Firefox, or
Safari) — all include the developer tools the exercise uses, for free.
- **To run the inspector and tests:** `bash` ≥ 3.2 (preinstalled on macOS and
every mainstream Linux distribution) plus the standard POSIX text tools
`grep`, `sed`, `sort`, `uniq`, and `wc` — all part of the base system.
The inspector needs only `grep` and `wc` at minimum; it performs a static read
of the committed HTML file and makes **no network connections**. There is
deliberately no `requirements.txt`/`package.json` here — the page is a single
self-contained file you open directly from disk.
Windows: run the shell parts under WSL (Ubuntu) or Git Bash, and open the HTML
file in any installed browser.
starter/inspect_page.sh (1607 bytes)
#!/usr/bin/env bash
# Day 020 lab — YOUR static page inspector (starter).
#
# This skeleton already resolves the file path and prints the report frame.
# Your job is the four exercises below: replace each `unknown` assignment with
# the single command shown in the comment. The completed reference version is
# examples/inspect_page.sh — try to build yours before peeking.
#
# Usage: bash starter/inspect_page.sh [path-to-html]
set -euo pipefail
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
file="${1:-${lab_dir}/examples/page/index.html}"
if [ ! -f "${file}" ]; then
echo "Error: file not found: ${file}" >&2
exit 1
fi
echo "=== Static page inspection: ${file} ==="
# Exercise 1 — extract the title.
# Command: sed -n 's/.*<title>\(.*\)<\/title>.*/\1/p' "${file}" | head -n 1
title="unknown"
echo "Title: ${title}"
# Exercise 2 — count the elements (opening/void tags: <name, but not </name).
# Command: grep -oE '<[a-zA-Z][a-zA-Z0-9]*' "${file}" | wc -l | tr -d ' '
element_count="unknown"
echo "HTML elements (opening and void tags): ${element_count}"
# Exercise 3 — list each distinct element type and how many times it appears.
# Command: grep -oE '<[a-zA-Z][a-zA-Z0-9]*' "${file}" | sed 's/<//' | sort | uniq -c
echo "Distinct element types used:"
echo " unknown — replace this line with the command in Exercise 3"
# Exercise 4 — find the JavaScript block (the behavior layer).
# Command: grep -q '<script' "${file}" && echo yes || echo no
has_script="unknown"
echo "Has <script> block (JavaScript / behavior): ${has_script}"
echo "=== End of inspection ==="
starter/render-worksheet.md (1630 bytes)
# Render worksheet — Day 020
Open `examples/page/index.html` in your own web browser, and use the static
inspector (`bash examples/inspect_page.sh examples/page/index.html`) alongside
the browser's developer tools. Fill in every blank in your own words.
## 1. What is in the DOM (structure — HTML)
- Page title (from the `<title>` element): __________
- Number of elements the inspector reports: __________
- Number the browser Console reports for `document.querySelectorAll('*').length`: __________
- Do those two numbers match? Why or why not? __________
- List the element types the page contains: __________
## 2. What the CSS changes (presentation — CSS)
- The `<style>` block defines exactly one rule. Write it out: __________
- Which element does it target, and what two things does it change? __________
- If you deleted the `<style>` block, what would still work, and what would change? __________
## 3. What the button does (behavior — JavaScript)
- Read the button's `onclick` code in the source. In one sentence, what will it do? __________
- **Predict before clicking:** after you click the button, the paragraph's text will read: __________
- Now click it. Was your prediction correct? __________
- Did clicking add or remove any DOM elements, or only change existing text? __________
## 4. Trace the pipeline for this page
In one short paragraph, walk this specific page through the five steps and name
what each produces:
1. Parse HTML → __________
2. Parse CSS → __________
3. Build the render tree → __________
4. Layout → __________
5. Paint → __________
Your paragraph: __________
tests/run_tests.sh (2867 bytes)
#!/usr/bin/env bash
# Tests for the Day 020 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that the committed HTML page is well-formed enough to render and
# that the static inspector reads it correctly (title and element count).
# No browser and no network are used — everything is local text processing.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
page="${lab_dir}/examples/page/index.html"
inspector="${lab_dir}/examples/inspect_page.sh"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
echo "Checking the committed page: ${page}"
if [ -f "${page}" ]; then
check "page file exists" "yes"
else
check "page file exists" "no"
echo
echo "${checks} checks, ${failures} failure(s)."
exit 1
fi
# The page must contain the structural elements the lesson describes.
for needle in "<html" "<head" "<title" "<style" "<script" "<body" "<button"; do
if grep -q "${needle}" "${page}"; then
check "page contains ${needle}> " "yes"
else
check "page contains ${needle}> " "no"
fi
done
echo "Running the inspector: ${inspector}"
if output="$(bash "${inspector}" "${page}" 2>&1)"; then
check "inspector exits successfully" "yes"
else
check "inspector exits successfully" "no"
echo "${output}" | sed 's/^/ /'
fi
# Title extraction must match the page's actual <title>.
expected_title="Tiny Web Page: Structure, Style, and Behavior"
if echo "${output}" | grep -qF "Title: ${expected_title}"; then
check "inspector extracts the correct title" "yes"
else
check "inspector extracts the correct title" "no"
fi
# Element count must be exactly 10 (the page has one of each element type).
count_line="$(echo "${output}" | sed -n 's/^HTML elements (opening and void tags): //p')"
if [ "${count_line}" = "10" ]; then
check "inspector counts 10 elements" "yes"
else
check "inspector counts 10 elements (got '${count_line}')" "no"
fi
# The inspector must detect both the CSS and the JS blocks.
echo "${output}" | grep -q "Has <style> block (CSS / presentation): yes" \
&& check "inspector detects the <style> block" "yes" \
|| check "inspector detects the <style> block" "no"
echo "${output}" | grep -q "Has <script> block (JavaScript / behavior): yes" \
&& check "inspector detects the <script> block" "yes" \
|| check "inspector detects the <script> block" "no"
# The distinct-types list must include the key elements.
for el in html head title style body h1 button script; do
echo "${output}" | grep -qE "[0-9]+ ${el}$" \
&& check "distinct types list includes ${el}" "yes" \
|| check "distinct types list includes ${el}" "no"
done
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 020 lab
The button does nothing when I click it
You almost certainly opened the file in a text editor, not a browser. The
button's behavior lives in the page's <script>, which only runs inside a
browser. Open examples/page/index.html in Chrome, Edge, Firefox, or Safari —
the address bar should show a file:// path ending in index.html.
bash: examples/inspect_page.sh: No such file or directory
You are not in the lab directory. Change into it first, then rerun:
cd labs/sections/computing-foundations/day-020-how-browsers-render-html-css-and
bash examples/inspect_page.sh examples/page/index.html
Permission denied when running a script
You don't need to make it executable — run it through bash explicitly:
bash examples/inspect_page.sh. If you prefer ./examples/inspect_page.sh,
first run chmod +x examples/inspect_page.sh.
The element count is not 10
If you edited examples/page/index.html, the count reflects your edits — that
is expected and correct. To restore the shipped page, run
git checkout -- examples/page/index.html. If you did not edit the page and
still see a different number, confirm you passed the right file path.
DevTools won't open in my browser
- Chrome/Edge/Firefox: press F12, or right-click an element and choose Inspect.
- Safari: enable the Develop menu first (Settings → Advanced → "Show features for web developers"), then use Develop → Show Web Inspector.
The browser Console prints a different number than the script
On this simple page they should both be 10. On real-world pages the live DOM count (Console) often differs from the static tag count, because scripts add and remove nodes after load — that difference is exactly the source-versus-DOM distinction the lesson teaches, not an error.
Windows: bash is not recognized
Use WSL (wsl --install, then open Ubuntu) or Git Bash to run the shell
scripts, and open the HTML file in any installed browser.
Security notes
Security notes — Day 020 lab
- The HTML page is static and safe.
examples/page/index.htmlis a single self-contained file with no external references — no remote scripts, images, fonts, or trackers. Its<script>only edits its own DOM (it changes one paragraph's text on a button click) and itsconsole.log; it makes no network requests and reads nothing about your machine. - The inspector reads, it does not execute.
inspect_page.shand the starter perform a static text scan of the HTML withgrep/sed/wc. They never run the page's JavaScript, open a network connection, or write files outside their own console output. - Privileges: everything runs as your normal user. Nothing here needs
sudo. If any tutorial ever tells you tosudoa script you haven't read, that is your cue to stop and read it first. - Open local files, not untrusted ones. Opening this page is safe because you can read every line of it. Treat HTML from unknown sources with care: a web page is a live, scriptable program, and opening a malicious one runs its JavaScript in your browser. The habit this lab builds — inspect before you trust — is the same one that keeps you safe on the wider web.