HTML Basics for Selenium & Playwright | Understanding Tags, Attributes, and Locators

When you start writing test automation with Selenium or Playwright, a common frustration is “I can’t find the element” or “I have no idea what locator to use.” In most cases, the root cause is simply not knowing how to read HTML. This guide covers the HTML fundamentals every QA engineer needs for test automation — with real code examples throughout.

💡 Term: What Is a “Locator”?

A locator is any method used to identify an element on a page in Selenium or Playwright. CSS selectors and XPath are both types of locators. You may also see the term “selector” used interchangeably — in this article, we’ll use “locator” as the standard term.

📌 Who This Article Is For

✅ Beginners with Selenium or Playwright who have little to no experience reading HTML
✅ Engineers unsure what to look for when writing locators
✅ Anyone who opens DevTools and sees HTML but doesn’t know where to start
✅ QA engineers with no web development background who want to get into test automation

✅ What You’ll Learn

  • How HTML tags and attributes work together
  • The difference between id, class, and data-testid — and when to use each
  • How common HTML elements map to locators in Selenium and Playwright
  • What to look for in DevTools when inspecting HTML for test code

👨‍💻 About the Author

Written by a QA engineer with 15+ years of hands-on experience in test automation using Selenium and Playwright. This guide is based on real-world experience — including the frustration of not being able to write a single locator because HTML felt like a foreign language.

📌 Key Takeaways

  • The HTML knowledge you need for test automation comes down to three things: tags, attributes, and form elements
  • In Selenium, prefer id → data-testid → name. In Playwright, choose get_by_role() / get_by_label() / get_by_test_id() based on the element type
  • Being able to read HTML makes DevTools dramatically easier to use

Every test automation script involves one thing above all: finding elements. In Selenium, that looks like driver.find_element(By.ID, "email"). In Playwright, it might be page.get_by_test_id("login-btn"). But where do the values "email" and "login-btn" actually come from?

The answer is the HTML. Test automation tools find elements using the attributes defined in HTML. That’s why being able to read HTML is one of the most direct shortcuts to becoming faster at test automation — and you don’t need a web development background to do it.

HTML Structure: What Are Tags and Attributes?

HTML is the language that defines the structure of a web page. When a test automation tool “clicks a button” or “types into an input field,” it finds those elements using the attributes written in HTML. For a guide on how to inspect those attributes in practice, see Chrome DevTools for QA Engineers.

The Basics of Tags

HTML is made up of tags (written as <tagname>). Most tags come in pairs — an opening tag and a closing tag.


<button>Log In</button>

<p>This is a paragraph of text.</p>


<input type="text">

What Are Attributes?

Tags can carry attributes — additional information written inside the opening tag in the format attribute-name="value".


<input type="email" id="user-email" name="email" placeholder="Email address">

These attribute values are exactly what you use as the basis for writing locators in your test code.

5 Attributes That Matter Most for Test Automation

HTML has dozens of attributes, but test automation relies on just a handful. Master these five and you can write locators for nearly any element.

AttributeRoleHow It’s Used in LocatorsStability
idUnique identifier on the pageBy.ID / #email⭐⭐⭐ Top priority
data-testidTest-only identifier[data-testid='xxx']⭐⭐⭐ Top priority
nameForm field key sent on submitBy.NAME / [name='email']⭐⭐ Relatively stable
classStyle group nameBy.CLASS_NAME / .btn-primary⭐ Breaks on design changes
typeInput field type[type='submit']⭐⭐ Useful when purpose is clear

① id: The Most Reliable Identifier

The id attribute must be unique within the page — only one element can have a given id at a time. That uniqueness makes it the most stable locator.


<input id="login-email" type="email">
<button id="login-btn">Log In</button>
# Selenium
driver.find_element(By.ID, "login-email")
driver.find_element(By.CSS_SELECTOR, "#login-btn")

# Playwright
page.locator("#login-email")
page.locator("#login-btn")

② data-testid: The Gold Standard for Test Locators

data-testid is an attribute that developers add specifically for testing purposes. Because it’s not tied to styling or business logic, it’s resilient to design changes and refactoring — making it the most stable locator option in test automation.


<button data-testid="submit-button" class="btn btn-primary btn-lg">
  Submit
</button>
# Selenium (using CSS selector)
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-button']")

# Playwright (dedicated method)
page.get_by_test_id("submit-button")

💡 What if data-testid isn’t there?

Ask the developers: “Could you add a data-testid to this element for testing?” It’s a common and accepted request — it improves test quality with minimal effort on their end.

In practice, it’s not uncommon for screens to have no id attributes at all. In those cases, requesting a data-testid is often the first and best option. Setting up a shared convention with developers early in the project makes everything smoother.

💡 HTML Tags Have Built-In Roles

A <button> tag is internally treated as having role="button". That’s why Playwright’s page.get_by_role("button", name="Log In") works — the tag name itself implies the role. Understanding this connection makes Playwright’s semantic locators much easier to reason about.

③ class: Convenient, but Fragile

class is a grouping attribute used to apply CSS styles. Multiple elements can share the same class name, which makes it an unreliable choice for locators.


<button class="btn btn-primary btn-lg disabled">Submit</button>
                         ↑ Changes with design updates or state changes


<button class="MuiButton-root css-1abcde">Submit</button>   <!-- Material UI -->
<button class="chakra-button">Submit</button>               <!-- Chakra UI -->
<button class="Button_btn__abc123">Submit</button>          <!-- CSS Modules -->
                                     ↑ May change with every build

⚠️ Treat class as a Last Resort

Classes like btn-primary, active, and selected change with design updates and state changes. Framework-generated class names like css-1abcde can change with every build. Only use class when id and data-testid aren’t available.

Form Elements You’ll Use in Almost Every Test

Form elements show up constantly in web app testing. Logins, registrations, searches — nearly every key user flow involves a form.

Form Element to Locator Reference Table

HTML TagWhat It Renders AsSelenium ExamplePlaywright Recommended
<input type="text">Text inputfind_element(By.ID, "...")
.send_keys("value")
get_by_label("Label")
<input type="email">Email inputfind_element(By.ID, "...")
.send_keys("value")
get_by_label("Email")
<input type="password">Password input (masked)find_element(By.ID, "...")
.send_keys("value")
get_by_label("Password")
<input type="checkbox">Checkboxfind_element(By.ID, "...")
.click()
get_by_label("Agree to terms")
<button>Buttonfind_element(By.ID, "...")
.click()
get_by_role("button", name="Log In")
<select>DropdownSelect(element).select_by_value("val")select_option("val")
<textarea>Multi-line text inputfind_element(By.ID, "...")
.send_keys("value")
get_by_label("Comment")
<a>Linkfind_element(By.LINK_TEXT, "text")get_by_role("link", name="text")

Practice: Reading a Login Form and Writing Locators

Let’s walk through a real HTML example and write locators from it.


<form id="login-form">
  <label for="email">Email address</label>
  <input type="email"
         id="email"
         name="email"
         data-testid="login-email"
         placeholder="e.g. user@example.com">

  <label for="password">Password</label>
  <input type="password"
         id="password"
         name="password"
         data-testid="login-password">

  <button type="submit"
          id="login-btn"
          data-testid="login-submit">
    Log In
  </button>
</form>

Here’s how you’d write the locators from this HTML:

# Selenium — id first
email_input    = driver.find_element(By.ID, "email")
password_input = driver.find_element(By.ID, "password")
login_button   = driver.find_element(By.ID, "login-btn")

email_input.send_keys("test@example.com")    # sample URL
password_input.send_keys("password123")
login_button.click()

# Playwright option ① — get_by_test_id (data-testid available)
page.get_by_test_id("login-email").fill("test@example.com")    # sample URL
page.get_by_test_id("login-password").fill("password123")
page.get_by_test_id("login-submit").click()

# Playwright option ② — semantic locators (get_by_label / get_by_role)
page.get_by_label("Email address").fill("test@example.com")    # sample URL
page.get_by_label("Password").fill("password123")
page.get_by_role("button", name="Log In").click()

Locator Priority: What to Look for First

Selenium: Prefer Stable Attribute-Based Locators

① id② data-testid③ name④ CSS⑤ XPath

More stable on the left → more fragile on the right

Playwright: Match the Locator to the Element Type

Playwright’s approach isn’t a fixed ranking — it’s about choosing the right locator for each element type. The right choice depends on context.

Element TypeRecommended LocatorExample
Buttons, links, interactive elementsget_by_role()get_by_role("button", name="Log In")
Input fields with a labelget_by_label()get_by_label("Email address")
Elements with data-testidget_by_test_id()get_by_test_id("login-submit")
When none of the above work welllocator()locator("#email")
💡 Playwright’s philosophy: Prefer locators that reflect how a user perceives the element. That said, get_by_test_id() can be more stable in many real-world situations — the right choice depends on what’s available in the HTML.

Common Pitfalls When Reading HTML

🚧 Watch Out for These

① The id changes on every page load

Some frameworks generate dynamic ids like id="input-3f9a" that are different every time. These can’t be used as locators. Ask developers to add a data-testid instead.

② Multiple classes — which one to use?

Classes like btn-primary, active, and selected can change with design or state updates. Treat class as a last resort — use id or data-testid whenever possible.

③ get_by_label() doesn’t work

If get_by_label("Email address") fails, check whether the for attribute on <label> matches the id on <input>. When they don’t match, inspect the HTML in DevTools to confirm the connection.

④ Multiple elements share the same class

If class="list-item" appears on ten elements, a CSS selector alone won’t identify the right one. Combine with a parent element’s id (e.g. #product-list .list-item:first-child) or use data-testid.

⑤ The “button” is actually a <div> or <span>

A visual button on screen might be <div class="custom-btn"> in HTML. It’s reachable with XPath or CSS, but always confirm the actual tag in DevTools rather than assuming it’s a <button>.

Frequently Asked Questions

Q. Where do I view HTML in practice?

Use Chrome DevTools (F12) → Elements tab. Right-click any element and select “Inspect” to highlight it in the HTML tree. For a full guide, see Chrome DevTools for QA Engineers.

Q. Do I need to formally study HTML?

Not for test automation purposes. Knowing tags, attributes, and form elements — combined with the ability to read HTML in DevTools — is more than enough. You don’t need to reach a front-end developer’s level of knowledge.

Q. What’s the difference between id and class?

id must be unique on the page (think of it like a name), while class can be shared by many elements (think of it like a team label). For locators, id is preferred because its uniqueness makes it reliably point to a single element.

Q. Who adds data-testid to the HTML?

Typically frontend developers add it. QA engineers request it: “Could you add a data-testid to this element?” It’s a low-effort change that significantly improves test stability, so most developers are happy to accommodate. Establishing the convention early in a project goes a long way.

Q. Is data-testid required?

No, but having it makes a noticeable difference in test stability. Use id or name when they’re available; when they’re not, requesting a data-testid is the recommended path.

Q. Do Selenium and Playwright write locators differently?

The underlying concept is the same, but the syntax differs. Selenium uses attribute-based methods like By.ID and By.CSS_SELECTOR. Playwright recommends choosing a locator by element type — get_by_role(), get_by_label(), or get_by_test_id() — depending on what’s available. Both tools ultimately rely on HTML attributes as their source of truth.

Q. Should I use CSS selectors or XPath?

In Selenium, CSS selectors are the common choice — concise and fast. In Playwright, semantic locators like get_by_role() and get_by_label() are recommended first. Both CSS and XPath serve as fallback options when those don’t apply. For a deep dive, see CSS Selectors & XPath Basics.

Summary

  • HTML is built from tags + attributes. Attribute values are what you use to write locators
  • In Selenium, look for id → data-testid → name in that order. In Playwright, choose get_by_role() / get_by_label() / get_by_test_id() based on the element type
  • class changes with design and state — treat it as a last resort
  • Form elements (input, button, select) are the most common targets in test automation
  • Building the habit of checking HTML in DevTools before writing test code makes locator selection dramatically faster

You don’t need to memorize all of HTML. Knowing “look for id and data-testid” and “recognize common form elements” is enough to clear the first big obstacle in test automation. Once you’re comfortable reading HTML, the next step is using Chrome DevTools to inspect real pages and validate your selectors on the spot.

🗺️ Your Learning Path: HTML to Test Automation

① Read HTML
← You are here
② Inspect with DevTools③ Write Locators④ Run Tests

📚 Related Articles

タイトルとURLをコピーしました