CSS Basics for Testers

CSS (Cascading Style Sheets) decides how HTML elements look and where they sit: colours, fonts, sizes, spacing, layout, and what changes on hover, on keyboard focus or on a narrow screen. The HTML says a checkout page has a "Pay now" button; the CSS makes it blue, full width on a phone and outlined when it has focus.

Testers rarely write CSS, but they need to read it. When a label is cut off, elements overlap or a page scrolls sideways on a phone, the cause is usually a CSS rule, and knowing the basics lets you find it and describe the defect precisely.

Why it matters

Visual defects are what users notice first. A price wrapped under its button or a field hidden behind a sticky header makes a product look broken even when every function works, and some are accessibility failures: removing the focus outline without a replacement fails WCAG 2.2 success criterion 2.4.7 Focus Visible.

CSS knowledge turns "the layout is broken on mobile" into a report a developer can act on: the element, the viewport width, the computed sizes and the rule involved. It also tells you what else to retest, because one rule can style every page that uses the same class.

Rules, selectors and the cascade

A stylesheet is a list of rules. Each rule has a selector, which chooses elements, and a declaration block in braces with property: value pairs. In #checkout .btn:hover { background-color: #0a58ca; } the selector picks elements with the class btn inside the element with the id checkout, while the pointer is over them. CSS comes from linked stylesheets, <style> elements and the style attribute, on top of the browser's own default styles.

When several rules set the same property on one element, the cascade picks one. Simplified: !important wins; then a style attribute beats stylesheet rules; then the more specific selector wins; at equal specificity, the later rule wins. Specificity counts ids, then classes, attributes and pseudo-classes, then element types and pseudo-elements, so #checkout .btn is 1,1,0 and one id outweighs any number of classes. Some properties, such as color and font-family, are also inherited by child elements.

  • button (type: every <button>), .btn-primary (class: every element that has btn-primary among the classes in its class attribute) and #email (id: the one element with id="email").
  • [type="email"] and [data-testid="pay-now"] (attribute); [href^="https"] matches values that start with https.
  • Combinators join selectors: a space means "anywhere inside" (form .error), and > means "a direct child of" (ul > li).
  • :hover, :focus-visible, :disabled, :checked and :invalid are pseudo-classes for states; each state has its own styles and needs its own check.
  • ::before and ::placeholder are pseudo-elements: they style parts that are not elements in the HTML.
/* Three rules set one property on the same button */
.btn           { background-color: #0b5ed7; }  /* 0,1,0                      */
#checkout .btn { background-color: #6c757d; }  /* 1,1,0: wins, it has an id  */
.btn           { background-color: #198754; }  /* 0,1,0: later, still loses  */

Try it: CSS Formatter Try it: Color Converter

The box model

Every element is drawn as a box with four layers, from the inside out: the content; the padding, which shows the background and still counts as the element when clicked; the border; and the margin, transparent space that keeps neighbours apart.

The box model from the outside in: margin, border, padding, content. With the default content-box, width: 300px sets only the content, so 16px padding and a 2px border on each side make the box 336px wide.
  1. 1MarginOutside the boxTransparent space that keeps neighbours apart

    Never part of width

    1. 2Border2pxThe line drawn around the padding

      Adds 2 × 2 = 4px with content-box

      1. 3Padding16pxShows the background and counts as the element when clicked

        Adds 2 × 16 = 32px with content-box

        1. 4Content300pxWhat width sets with the default content-box

          With border-box the box stays 300px and the content shrinks to 264px

Example values from this section: width: 300px, padding: 16px and a 2px border.

box-sizing decides what width measures. With the default content-box it sets the content only, so padding and border are added: width: 300px with padding: 16px and a 2px border gives a box 336 pixels wide. With border-box the box stays 300 pixels and the content shrinks to 264. Margins are never part of width. Many sites set border-box on every element; a component that misses it produces boxes slightly too wide.

Hiding an element is also a question of its box. Three declarations all make an element invisible, but they behave differently:

  • display: none removes the element: no space, no clicks, hidden from screen readers.
  • visibility: hidden keeps its space but hides it from sight, clicks and screen readers.
  • opacity: 0 only makes it transparent: it still takes clicks and is still announced, so an invisible layer over a button can block it.

Layout, media queries and the defects they cause

Block elements such as <div> stack vertically; inline elements such as <a> flow along a line. Flexbox (display: flex) arranges children in one row or column and controls how they grow, shrink, wrap and align; grid (display: grid) places them in rows and columns at once, so grid-template-columns: repeat(3, 1fr) makes three equal columns. Both follow the writing direction, so a flex row starts on the right in an Arabic page. position: absolute and fixed take elements out of the flow, sticky keeps one in view while the page scrolls, and z-index decides which overlapping element is on top.

A media query applies rules only when a condition is true. @media (max-width: 600px) { … } applies when the viewport, the area of the browser window that shows the page, is 600 CSS pixels wide or less, so 600 gets the phone layout and 601 does not. CSS pixels are not device pixels; a high-density phone uses several device pixels for each. On phones this works only when the page has the viewport <meta> element from the page skeleton in the HTML lesson; without it, the phone lays the page out as if on a wide screen and shrinks it. Each breakpoint is a boundary to test on both sides. Queries also react to prefers-color-scheme: dark, prefers-reduced-motion and printing.

Most visual defects trace back to these properties or to the box model:

  • Clipped or overflowing text: a fixed height or width, overflow: hidden, white-space: nowrap; long names, translations and enlarged text expose it.
  • Overlap: absolute or fixed positioning, negative margins, a wrong z-index. A sticky header that completely hides the focused field also fails WCAG 2.2 criterion 2.4.11 Focus Not Obscured (Minimum).
  • Sideways scrolling on phones: fixed widths, images without max-width: 100%, flex rows that never wrap (flex-wrap defaults to nowrap), box-sizing arithmetic.
  • Misalignment: similar elements with different margins, padding or alignment settings.
  • Unreadable text: a colour too close to the background, often only in dark mode or on hover.
  • No visible focus: outline: none with no replacement style.
  • Mirroring defects in right-to-left pages: physical properties such as margin-left do not flip, while flex, grid and logical properties such as margin-inline-start do.

Try it: Lorem Ipsum Generator Try it: Multilingual Test Data Generator

Worked example: a card that scrolls sideways

A teammate reports that the checkout page scrolls sideways on a phone. You open the browser's developer tools with F12 and switch on device mode (Ctrl+Shift+M in Chrome, Edge and Firefox), which sets the viewport to any width. At 360 pixels the promotion card sticks out past the edge of the screen; at desktop widths it looks fine. The CSS is below, and the card's parent is as wide as the viewport.

Right-click the card and choose Inspect. The Styles pane lists the rules that apply to it, with overridden declarations crossed out: the media query is active, so width: 100% applies. The box-model diagram in the Computed pane shows 16 pixels of padding and a 2-pixel border on each side. With the default content-box, 360 + 2 × 16 + 2 × 2 = 396 pixels: 36 pixels wider than the screen. The same 36 pixels are added at every width up to 480; from 481 the card is 320 + 36 = 356 pixels wide and fits.

The bug report states facts: page, element (.promo-card), browser, viewport (every width up to 480 pixels; 481 is fine), expected (the card fits the screen) and actual (a 396-pixel box and a horizontal scrollbar at 360), with a screenshot and the computed values. Mention box-sizing as the likely cause; the developer chooses the fix. Then retest at 480 and 481 pixels, in English and Arabic, and on the other pages that use .promo-card.

.promo-card {
  width: 320px;
  padding: 16px;
  border: 2px solid #d0d7de;
}

@media (max-width: 480px) {
  .promo-card { width: 100%; }
}

/* Viewport 360px, box-sizing: content-box (the default):
   360 + 2 × 16 + 2 × 2 = 396px wide, 36px wider than the screen */

Try it: Lorem Ipsum Generator Try it: Multilingual Test Data Generator

CSS selectors as locators: a preview

An automated test must find an element before it can click or check it, and a CSS selector is one way to describe it: Selenium's By.cssSelector, Cypress's cy.get and Playwright's page.locator all accept one. In the Console tab of the developer tools, document.querySelectorAll('[data-testid="pay-now"]').length counts the matches; a locator should match exactly one element.

A good styling selector is not always a good locator. Styling classes change with a redesign, long paths break when a wrapper is added, and generated names such as css-1q2w3e change between builds. Prefer short selectors on attributes meant for identification: a stable id, a field's name, or a test attribute such as data-testid. Playwright's documentation recommends user-facing locators (role, label, text) and test ids, and advises against CSS and XPath because they break when the page structure changes. In Selenium and Cypress, a test id is itself normally found with a CSS attribute selector.

  • Brittle: div.col-md-6 > div:nth-child(3) > button.btn-primary.
  • Better: [data-testid="pay-now"], #email or input[name="email"].

Try it: HTML Formatter

Common mistakes

  • Reporting "the layout is broken" without the element, viewport width, browser, language and theme; many visual defects appear in only one of them.
  • Testing one desktop and one phone width, and never the widths on each side of a breakpoint.
  • Checking only the default state: hover, focus, disabled, error and selected states have their own CSS and their own defects.
  • Treating an invisible element as gone: opacity: 0 hides nothing from clicks or screen readers.
  • Pasting the long selector from the browser's "Copy selector" menu into a test; it encodes the page structure and breaks with the next layout change.

The QA perspective

In refinement and design reviews, ask what happens at each breakpoint, with long or translated text, in right-to-left layouts and in dark mode, and whether hover, focus and error states are designed. While testing, keep a short visual checklist per screen and read computed values in the developer tools instead of judging by eye; colours appear there as rgb() values such as rgb(11, 94, 215), while designs usually give hex values such as #0b5ed7, the same blue, so convert one into the other before comparing.

In bug reports, give the element, viewport size, browser, language and theme, the wrong computed values and a screenshot with the problem marked; offer the rule you suspect only as a hint. When developers add stable ids or test attributes, agree on a naming convention early: it is cheaper than repairing locators after every redesign.

Practice exercise

Read a stylesheet like a tester

Your task

The styles and markup below belong to a checkout page. On a phone, the viewport and main are both 360 pixels wide. Answer the four steps without running the page, and give the reason for each answer.

Material
<style>
  .card { width: 100%; padding: 12px; border: 1px solid #d0d7de; }
  .btn { padding: 10px 16px; background-color: #0b5ed7; color: #ffffff; }
  #checkout .btn { background-color: #6c757d; }
  .btn:focus { outline: none; }
  @media (max-width: 480px) {
    .card { padding: 24px; }
  }
</style>
<main id="checkout">
  <div class="card">
    <button class="btn" data-testid="pay-now">Pay now</button>
  </div>
</main>
Steps
  1. Which background colour does the button show, and why?
  2. How wide is the card's box on the phone, and does it fit?
  3. Which declaration removes something users need, and which test reveals it?
  4. Which CSS selector would you use to locate the button in an automated test?
Show the model answer
Model answer

Grey, #6c757d: #checkout .btn (1,1,0) is more specific than .btn (0,1,0), and order only decides between equal specificities. If the design says blue, report it with the Styles view, where the losing declaration is shown crossed out.

The media query applies (360 is at most 480), so the padding is 24 pixels: 360 + 2 × 24 + 2 × 1 = 410 pixels, 50 more than main, so the page scrolls sideways. Test 480 and 481 pixels, where the padding changes; at wider sizes the card still overflows main by 26 pixels.

.btn:focus { outline: none; }. Press Tab until the button has focus: if you cannot see where focus is, the page fails WCAG 2.2 success criterion 2.4.7 Focus Visible.

[data-testid="pay-now"]: it exists for tests, matches one element and survives a redesign. .btn matches every button with that style, and main > div > button breaks when the structure changes.

Frequently asked questions

Do testers need to write CSS?

Not usually. You need to read it: recognise selectors and properties, find which rule applies in the developer tools, and do the box-model arithmetic. Editing a value live in the developer tools is a quick way to confirm a suspected cause, but the change disappears on reload and the fix belongs in the code.

What is the difference between a CSS selector and an XPath expression?

Both find elements. CSS selectors are shorter for ids, classes and attributes, and they are what stylesheets use. XPath can also match by text and move from a child to its parent; CSS cannot match text, and its :has() pseudo-class covers only some parent cases. Selenium and Playwright accept both; Cypress's cy.get takes CSS selectors.

Is a visual defect always low severity?

No. A slightly misaligned icon may be minor, but a clipped price, a payment button pushed off the screen or a missing focus indicator can stop a purchase or lock out keyboard users. Judge severity by the impact on the user, as for any defect.

References

  • CSS: Cascading Style Sheets — MDNMDN Web Docs (Mozilla)MDN's CSS reference and guides: selectors, the cascade, properties and the layout systems that decide how a page looks and adapts.
  • Responsive web design — MDNMDN Web Docs (Mozilla)Responsive design in practice: flexible layouts with flexbox and grid, media queries and the viewport meta tag, so one page works across screen sizes.
  • Web Content Accessibility Guidelines (WCAG) 2.2W3C · 2024The W3C Recommendation that defines the testable success criteria for accessible web content, organised under four principles and three conformance levels (A, AA, AAA).
  • Locators — PlaywrightMicrosoft (Playwright)How Playwright finds elements, why locators re-resolve and auto-wait, and why role-based locators are recommended first.

Practise with Mutqan tools

Beautify CSS with one declaration per line, indentation and sorted output options.

Formatting

Convert colors between HEX, RGB(A), HSL(A), HSB/HSV, CMYK and CSS named colors with a live preview.

Utilities

Beautify HTML with proper indentation of nested elements, preserving pre, script and style blocks.

Formatting

Generate placeholder text as paragraphs, sentences or words — Latin, Arabic or Hindi.

Utilities

Generate the same records in multiple languages (English, Arabic, Hindi, and more) to test i18n and RTL/LTR mixing.

Test Data

Go deeper

Your progress on this topic

Part of the QA Roadmap