HTML Basics for Testers
HTML (HyperText Markup Language) describes the content and structure of a web page: as you saw in the lesson on how browsers work, the server sends an HTML document and the browser turns it into the page you see. The document is made of elements: <button type="submit">Pay now</button> is a button whose text is "Pay now" and whose type attribute says that it submits a form.
Testers rarely write HTML, but they read it often. The markup shows what a control really is, which field names reach the server and which rules the browser enforces — facts a screenshot does not show.
Why it matters
Two pages can look identical and behave differently. A "button" built from a <div> may work with a mouse but not with the keyboard or a screen reader, the software that reads a page aloud to blind and low-vision users; a label that is not tied to its field leaves the field without a name for screen reader users; a link may point to the staging server. These defects are invisible on screen and obvious in the markup.
HTML is also the shared language of testers, developers and automation. "The label has for="mail" but the input’s id is email" gets fixed faster than "clicking the label does nothing", and UI automation later finds elements by their roles, labels and attributes.
Elements, tags and attributes
Most elements have a start tag, content and an end tag: in <a href="/pricing">See pricing</a>, "See pricing" is the content between the start tag and the end tag </a>. Elements nest like boxes, such as <li> items inside a <ul> list. Void elements such as <input>, <img> and <br> have no content and no end tag.
Attributes sit in the start tag as name="value" pairs. Three details matter on every page:
idnames one element and must be unique in the page; labels,#fragmentlinks and many locators (the queries automated tests use to find an element) point to it.- Boolean attributes such as
requiredanddisabledare on when present and off only when absent:disabled="false"still disables the field. - Characters with a meaning in HTML are written as character references, usually called entities:
<displays<and&displays&. Seeing&on screen means the text was encoded twice.
disabled is on just by being present, the content is the button’s name, and a void <input> has no end tag.An element with content
<
A void element
Part 8:<input type="email" name="email" required>
A character reference in text
<p>Tom
- 1–5Start tag: the element name and its attributes, between
<and> - 1–7The element: start tag, content and end tag
- 1Element nameSays what the element is: a real
buttoncomes with keyboard and screen reader behaviour that adivlacks - 2Attribute nameAttributes are written only in the start tag
- 3Attribute valueIn quotes after
=;submitmakes the button send its form - 4The
idattributeUnique in the page; labels,#paylinks and locators point to it - 5Boolean attributeOn when present, off only when removed:
disabled="false"still disables the button - 6ContentThe text the user sees; for a button it is also the name a screen reader announces
- 7End tagCloses the element; the content sits between the start tag and the end tag
- 8Void elementThe start tag is the whole element: no content and no end tag, as with
<img>and<br>.requiredis another boolean attribute - 9Character reference (entity)Displays
&, as<displays<. If&itself shows on screen, the text was encoded twice. The page shows:Tom & Jerry
The skeleton of every page
Every HTML document has the same skeleton. <!DOCTYPE html> asks for standards mode; without it, browsers fall back to an old "quirks" mode that changes the layout. <html> wraps everything, and its lang attribute states the language; Arabic pages add dir="rtl". <head> describes the page and is not displayed; <body> holds what the user sees.
<title>: shown in the tab and in search results, and announced first by screen readers; every page needs its own descriptive title (WCAG 2.2 criterion 2.4.2).lang: screen readers use it to choose the pronunciation (criterion 3.1.1).<meta charset="utf-8">: a missing or wrong encoding is a classic cause of garbled Arabic and Hindi text.<meta name="robots" content="noindex">: keeps the page out of search engines; right on staging, a serious defect on a public production page.<meta name="viewport">tells phones to lay the page out at their own screen width; without it, they show a shrunken desktop layout.<link rel="stylesheet">and<script src>load the CSS and JavaScript, so a wrong path there breaks the styling or the behaviour.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Track your order | Example Store</title>
<meta name="description" content="Check where your order is.">
<link rel="stylesheet" href="/styles.css">
<script src="/app.js" defer></script>
</head>
<body>
<header>...logo and menu...</header>
<main>...the content of this page...</main>
<footer>...contact and legal links...</footer>
</body>
</html>Semantic elements: meaning, not looks
A semantic element says what its content is: <header>, <nav>, <main> and <footer> mark regions of the page, <h1> to <h6> mark headings, <ul> marks a list, <button> an action and <a href> a link. <div> and <span> mean nothing; they are boxes for layout and styling.
CSS can make a <div> look exactly like a button, but a real <button> comes with behaviour: it is reached with Tab, activated with Enter or Space and announced as a button by screen readers. A clickable <div> gets none of this unless a developer adds it by hand. Likewise, big bold text in a <div> looks like a heading but is missing from a screen reader’s list of headings.
- Anything that acts on click should be a
<button>, or an<a href>if it goes to another page. - Images need
alt: a short description if they carry information,alt=""if they are decorative.
Forms and links in the markup
A <form> sends its fields to the address in action with the HTTP method in method: post puts the data in the request body, while get, the default, puts it in the URL, where it ends up in browser history and server logs. Each field is sent as its name plus its value; a field without a name is not sent at all.
A link is an <a> with an href: a full URL, a path such as /help, a fragment such as #faq that goes to the element with id="faq", or a scheme such as mailto:. The attributes of fields and links give you test ideas straight away:
type(email,number,date…) sets the browser’s built-in checks and the keyboard or picker shown on phones.required,minlength,maxlength,min,maxandpatternare rules to test, and their limits give you boundary values. Anyone can remove them, so the server must check the same rules. Note thatminlengthalone does not reject an empty field.- A
<label>gives a field its accessible name, the name a screen reader announces for it, when itsforequals the field’sidor when it wraps the field; then clicking the label also focuses the field. - A
<button>without atypeinside a form is a submit button. <input type="hidden">is invisible on screen but editable by the user, so the server must not trust its value.
Worked example: reading the source of a sign-up form
To see the HTML the server sent, press Ctrl+U in Chrome, Edge or Firefox on Windows and Linux, or type view-source: before the address. The source is the document before JavaScript changed it; the live page is in DevTools, covered in later lessons. Minified source, squeezed onto a few long lines, becomes readable in an HTML formatter. Many single-page applications (SPAs), which build the page with JavaScript, send an almost empty <body>, so their source shows little.
This sign-up form looks fine on screen. Its source does not:
method="get": the email and password would travel in the URL. Expected:post.- The Email
<label>has noforand does not wrap the input, so the field has no accessible name. for="pass"does not matchid="password": the same defect.type="text"for an email: no email keyboard on phones and no browser format check.minlength="12"withoutrequired: an empty password passes the browser check. Test 0, 11 and 12 characters and ask for the maximum.- The hidden
planisfree: on a test environment you are allowed to test, change it in DevTools and submit. The server, not the page, must decide the plan. <button>Cancel</button>has notype, so it submits the form. Expected:type="button".- "Create account" is a
<div>: it cannot be reached with Tab and is not announced as a button.
<form action="/signup" method="get">
<label>Email</label>
<input type="text" id="email" name="email" required>
<label for="pass">Password</label>
<input type="password" id="password" name="password" minlength="12">
<input type="hidden" name="plan" value="free">
<button>Cancel</button>
<div class="btn-primary" onclick="createAccount()">Create account</div>
</form>Common mistakes
- Judging an element by its looks: a
<div>styled as a button is not a button for the keyboard or a screen reader. - Treating
requiredormaxlengthas proof of validation instead of testing that the server enforces the same rules. - Reading only the page source of a single-page application, whose content appears only in the live DOM.
- Writing "the label is broken" in a bug report instead of quoting the element, the attribute and its value.
- Ignoring the
<head>: a wrong<title>,langornoindexnever shows on the page but harms screen reader users and search results.
The QA perspective
During exploratory testing, keep the source or DevTools open next to the page; when a control behaves oddly, check what element it really is. In reviews of new screens, ask for real <button>, <label> and heading elements, and for stable attributes such as id or data-testid that automation can rely on.
In a bug report, quote the smallest snippet that shows the defect, the expected markup and the impact on users ("keyboard users cannot create an account"), with the URL and environment, because staging and production can serve different HTML. Test only systems you are allowed to test, and pass findings such as exposed comments to your team’s security process.
Practice exercise
Review the source of a contact page
Below is the source of a contact page that passed a visual review. List every defect or open question you find in the markup, why it matters to a user, and the expected markup or the question you would ask.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled</title>
<meta name="robots" content="noindex">
</head>
<body>
<div class="top-bar">
<img src="/logo.png">
<a href="http://staging.example.com/pricing">Pricing</a>
<a href="#faq">FAQ</a>
<span class="link" onclick="openHelp()">Help</span>
</div>
<div class="big-title">Contact us</div>
<!-- demo login: demo@example.com / Demo-2026 (remove before release) -->
<form action="/contact" method="post">
<label for="msg">Message</label>
<textarea id="message" name="message" maxlength="500"></textarea>
<button type="submit">Send</button>
</form>
<section id="questions">...</section>
</body>
</html>- Check the skeleton:
lang,<title>and the<meta>elements. - Check every
hrefand whether its target exists. - Check headings, images and anything clickable.
- Check the form, then the comments.
Show the model answer
Skeleton: <html> has no lang (criterion 3.1.1); <title>Untitled</title> does not describe the page (criterion 2.4.2); there is no <meta name="viewport">, so phones show a shrunken desktop layout; noindex keeps a public page out of search results, so ask whether it is left over from staging.
Links: Pricing points to http://staging.example.com, the wrong environment and not HTTPS. #faq matches no id (the section has id="questions"), so the link does nothing. Help is a <span>: not reachable with Tab, not announced as a link and impossible to open in a new tab.
Semantics: the logo <img> has no alt; "Contact us" is a styled <div> instead of an <h1>; the top bar is a <div>, not a <header> with a <nav>.
Form and comment: for="msg" does not match id="message", so the text area has no accessible name. The message has no required: ask whether an empty message may be sent. maxlength="500" gives boundary tests at 500 and 501 characters; the browser stops typing at 500, so 501 tests the server: does it enforce the limit too? The comment shows a login to anyone who opens the source: report it as a security defect.
Frequently asked questions
Do I need to learn to write HTML to be a tester?
No. You need to read it: recognise elements and attributes, find the field, link or heading you are testing, and quote it in a bug report. Writing small pages helps you learn, but reading is the skill you use every day.
What is the difference between the page source and the Elements panel in DevTools?
The page source is the HTML document the server sent. The Elements panel shows the live DOM: the page after the browser parsed it and JavaScript changed it. They match on simple pages and differ a lot in single-page applications; the later lessons on the DOM and DevTools cover the live view.
Is invalid HTML always a defect?
Not always. Browsers repair many errors, such as a missing end tag, so the page may still work. Report markup problems by their effect: a duplicate id that breaks a label, an unclosed element that swallows content, a missing alt. An HTML validator helps you find them, but the impact on users sets the severity.
References
- HTML Living StandardThe HTML language, including named character references (entities) and the parsing rules browsers follow.
- Web Content Accessibility Guidelines (WCAG) 2.2The W3C Recommendation that defines the testable success criteria for accessible web content, organised under four principles and three conformance levels (A, AA, AAA).
- HTML: HyperText Markup Language — MDNMDN's reference and guides for HTML elements and attributes — the semantic structure a page is built from, which tests and assistive technology rely on.
- Client-side form validation — MDNBuilt-in and scripted form validation in the browser, and why it helps users but never replaces validation on the server.
Practise with Mutqan tools
Beautify HTML with proper indentation of nested elements, preserving pre, script and style blocks.
Decode HTML entities (&, ©, …) back to plain characters.
Break a URL into protocol, host, port, path, query parameters, fragment and credentials.
Generate validation test cases for form fields: required, format, length, range, pattern and cross-field rules.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.