Computing FoundationsHow the Internet Works › Day 20

Hands-on lab — Day 20: How Browsers Render: HTML, CSS, and JavaScript

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

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 use xdg-open; on Windows, drag the file onto a browser window.
  • bash examples/inspect_page.sh examples/page/index.html — the reference static inspector: it uses sed to pull the <title>, grep -oE to list every opening tag, sort | uniq -c to tally element types, wc -l to count them, and grep -q to 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 to unknown; each exercise comment names the exact command to use. Edit the file and replace each unknown.
  • 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

  1. Open examples/page/index.html in a browser and confirm you see a heading, a navy paragraph, and a button.
  2. Click the button and confirm the paragraph text changes to "Changed by JavaScript!".
  3. Run bash examples/inspect_page.sh examples/page/index.html and confirm the title and the count of 10.
  4. Complete starter/inspect_page.sh (replace every unknown) and confirm it prints the same values.
  5. 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

  1. 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.
  2. Add a second paragraph in the HTML and confirm the element count rises by one in both the inspector and the browser Console.
  3. 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.
  4. In the Performance panel, record a page load and find the layout and paint events in the timeline.
  • 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.html is 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 its console.log; it makes no network requests and reads nothing about your machine.
  • The inspector reads, it does not execute. inspect_page.sh and the starter perform a static text scan of the HTML with grep/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 to sudo a 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.