XML Formatting for Developers: Readability, Namespaces, and Safe Edits
How to format and inspect XML without breaking namespaces, attributes, or mixed content — plus when to beautify vs minify and how XPath fits the workflow.
When to use XPath vs CSS selectors, how to write stable queries, and a browser-side workflow for testing against real HTML without shipping brittle scrapers.
By codefunc
Selectors fail in the quietest ways: a test passes on a tidy fixture, then production HTML wraps the target in an extra <div> and your scraper returns null. XPath and CSS selectors are both ways to point at nodes — they are not interchangeable, and neither is a substitute for stable data-testid hooks you control. This guide compares the two, shows patterns that survive markup churn, and outlines a test-before-commit workflow.
Bottom line: Prefer CSS selectors for simple HTML queries; use XPath when you need axes, text predicates, or XML namespaces. Beautify markup first, then validate expressions in an XPath / CSS Selector Tester. Reach for a Regex Tester only for text — not for parsing HTML structure.
| Capability | CSS selectors | XPath |
|---|---|---|
| Element by id/class | Excellent (#id, .class) |
Verbose |
| Attribute equals / contains | Good ([data-id="x"], *=) |
Excellent |
| Parent / ancestor | Limited (:has() where supported) |
First-class (.., ancestor::) |
| Text content match | Weak / nonstandard | Strong (contains(text(),…)) |
| Namespaces | Awkward | Designed for it |
Browser querySelector |
Native | Needs document.evaluate |
| Readability for simple UI | High | Medium |
Default for frontend tests and DOM scripting: CSS selectors.
Reach for XPath when: you need "the td after this label," XML namespaces, or text-based predicates in mixed documents.
Garbage in, mysterious empty NodeLists out. Indent the document before you invent a clever query:
data-* attribute, ARIA role, or unique structure.<main>
<article data-testid="invoice">
<h1>Invoice INV-1042</h1>
<table>
<tr>
<th scope="row">Total</th>
<td data-field="total">$42.00</td>
</tr>
</table>
</article>
</main>
[data-testid="invoice"] [data-field="total"]
//*[@data-testid='invoice']//*[@data-field='total']
Both beat .css-1a2b3c from a CSS-in-JS hash or /html/body/div[3]/div[2]/span.
| Pattern | Example | Notes |
|---|---|---|
| Test id | [data-testid="login"] |
Best for app code you control |
| Role + name (testing libs) | Prefer Testing Library queries in unit tests | Closer to user behavior |
| Attribute prefix | [data-field^="addr-"] |
Flexible; do not overuse |
| Child combinator | nav > ul > li |
Stricter than descendant |
:not() |
li:not([hidden]) |
Avoids hidden duplicates |
| Fragile | Why it breaks |
|---|---|
div:nth-child(4) |
Inserted widgets shift indices |
| Deep chains of layout divs | Refactors rename structure |
| Classes that encode style only | .text-sm.font-bold is not identity |
Positional XPath /div[3]/span[1] |
Same problem, worse readability |
| Axis | Meaning | Example |
|---|---|---|
child:: (default) |
Direct children | ./td |
descendant:: / // |
Any depth | //table//td |
parent:: / .. |
Parent | ../th |
following-sibling:: |
Later siblings | following-sibling::td[1] |
ancestor:: |
Up the tree | ancestor::article |
Label → value is the classic XPath win:
//th[normalize-space()='Total']/following-sibling::td[1]
CSS struggles here without :has():
/* Modern browsers — still verify support in your target */
tr:has(th) td[data-field="total"]
//h1[contains(normalize-space(.), 'Invoice')]
normalize-space collapses whitespace — useful when beautifiers insert newlines inside text nodes.
HTML in browsers often puts elements in the HTML namespace while your XPath assumes no namespace. For real XML with prefixes, bind namespaces in the evaluator or use local-name() carefully:
//*[local-name()='price']
local-name() is convenient and easy to overuse — it can match the wrong vocabulary. Prefer proper namespace resolution in production code.
Save the HTML your code will see: authenticated views, empty states, and error banners. Redact secrets. Beautify with HTML Beautifier.
In XPath / CSS Selector Tester:
// Playwright
await expect(page.locator('[data-testid="invoice"] [data-field="total"]'))
.toHaveText('$42.00');
// DOM
const total = document.querySelector(
'[data-testid="invoice"] [data-field="total"]'
);
Regex cannot reliably parse nested tags. Use regex for flat strings (order IDs, currency tokens) after you have extracted text content — validate those patterns in a Regex Tester. Structure belongs to selectors or a real parser.
# OK: on plain text "INV-1042"
INV-\d+
# Not OK: extracting nested table cells with regex
| Context | Stability strategy |
|---|---|
| Your app's E2E tests | Add data-testid / roles; fail CI if missing |
| Partner HTML you do not control | Prefer structural + text XPath; version fixtures; alert on zero matches |
| One-off data rescue | Brittle selectors OK if you archive the HTML snapshot |
Always keep the HTML fixture next to the selector. When the partner changes markup, re-run expressions in the tester against the new snapshot before updating production jobs.
document.querySelector / querySelectorAll are fast for typical pages.//* on huge documents can be slower — narrow from a known root.article.locator(...)) reduce ambiguity and cost.| Mistake | Fix |
|---|---|
| Selector matches hidden template + visible node | Add :not([hidden]) or visible checks in the test runner |
| Assumed unique, got a NodeList | Tighten with data-* or ancestor scope |
| Copied absolute XPath from DevTools | Rewrite relative to a stable root |
| Mixed CSS mental model with XPath syntax | Pick one language per expression |
| Parsing HTML with regex | Extract then regex; or use selectors only |
Write the shortest unique selector against beautified, realistic HTML. Use CSS for straightforward DOM queries; use XPath when you need sibling axes, text predicates, or XML namespaces. Validate match counts in the XPath / CSS Selector Tester, prepare samples with the HTML Beautifier, and keep Regex Tester for post-extraction text — never as an HTML parser. Stable data-* hooks beat clever absolute paths every time.
How to format and inspect XML without breaking namespaces, attributes, or mixed content — plus when to beautify vs minify and how XPath fits the workflow.
Learn end-to-end testing with Playwright — writing reliable tests, handling authentication, testing across browsers, and integrating with CI/CD pipelines.
A practical guide to the testing pyramid — what unit, integration, and end-to-end tests actually do, when to write each, and how to automate them in CI without slowing your team down.
Find a developer tool