Skip to main content

htmlcompare

A Python library to check whether two HTML documents are "the same". It ignores differences which do not change what the document means, so a test does not fail just because a template engine emitted the attributes in a different order or wrote margin: 0px instead of margin: 0.

Usage

import htmlcompare

result = htmlcompare.compare_html(expected_html, actual_html)
if not result:
    print(result)

compare_html() returns a ComparisonResult which is true when both documents are the same. result.differences contains one Difference per mismatch, including the path to the node which caused it.

For tests the library provides two assertions:

from htmlcompare import assert_different_html, assert_same_html

assert_same_html('<div />', '<div></div>')
assert_different_html('<br>', '<p>')

Two files can also be compared on the command line:

htmlcompare expected.html actual.html

What is ignored

  • collapsible HTML whitespace which does not render with white-space: normal: indentation around block elements, spaces at the start or end of an inline line and spaces next to <br>. Longer whitespace runs collapse to one space.
  • <div /> written as <div></div>
  • the order of HTML attributes, and the order of the CSS classes inside a class attribute
  • an empty style or class attribute, which is treated like an absent one
  • HTML comments
  • the formatting of CSS. style attributes and <style> tags are parsed with a real CSS parser, so trailing semicolons, the order of declarations, the case of property, function and at-rule names, equivalent numeric spellings (.5px, 0.5px), the case of dimension units (1PX, 1px), the spelling of a URL (url(a.png), url("a.png")), a unit on a zero length and any whitespace CSS has no use for do not matter.

What is a difference

Something is only ignored when HTML and CSS guarantee that both spellings mean the same thing. Where the meaning can change, the documents differ:

  • whitespace which CSS gives a meaning to: .a .b is the descendant combinator and not .a.b, font-family: Arial Black is not font-family: ArialBlack
  • whitespace between rendered inline content: <b>foo</b> <b>bar</b> renders with a word separator, unlike <b>foo</b><b>bar</b>
  • the order of declarations which can override each other, such as background and background-color
  • custom CSS syntax beginning with --, whose names are case-sensitive, and custom-property values, which var() substitutes literally: --x: 0px is not the same as --x: 0
  • conditional comments in both their forms, because dropping one changes which clients display the content

Malformed CSS never raises an exception. Whatever the CSS parser can not represent is compared as source text instead, which may report a difference which is not one but never hides one.

Options

compare_html() and both assertions take a CompareOptions instance:

option default effect
ignore_comments True ignore HTML comments
ignore_conditional_comments False ignore conditional comments as well
compare_document_prefix False compare text before the <!DOCTYPE> (e.g. {# … #} metadata emitted by a template engine). Such a prefix is always preserved, it is just not compared by default.

Limitations / Plans

HTML whitespace and layout. Whitespace comparison assumes white-space: normal and the standard block/inline roles represented by htmlcompare's built-in element lists. htmlcompare does not resolve the CSS cascade, so author styles which change display or white-space are outside this model. Whitespace-preserving contexts such as pre, pre-wrap and break-spaces are not interpreted as browser layout.

No validation of conditional comments. Their condition is compared but not checked for validity. Not sure which library I can use here but at some point I'll likely need this as well.

JavaScript - for obvious reasons it will be impossible to implement perfect JS comparison but it might be possible to run some kind of "beautifier" to take care of insignificant stylistic changes. However I don't need this feature so this is unlikely to get implemented (unless contributed by someone else).

Custom hooks could help adapting the comparison to your specific needs. However I don't know which API would be best so this will wait until there are real-world use cases.

Better API: The current API is very minimal and implements just what I needed right now. I hope to improve the API once I use this project in more complex scenarios.

Other projects

xmldiff is a well established project to compare two XML documents. However it seems as if the code does not contain knowledge about specific HTML semantics (e.g. CSS, empty attributes, insignificant attribute order).

Misc

The code is licensed under the MIT license. It requires Python 3.9+.

Metadata

Release files for HTMLCompare 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for HTMLCompare 0.5.1
File Size Uploaded
htmlcompare-0.5.1.tar.gz 61.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for HTMLCompare 0.5.1
File Interpreter ABI Platform
htmlcompare-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 104.9 kB

Release files / htmlcompare-0.5.1.tar.gz

Download URL htmlcompare-0.5.1.tar.gz
Size 61.1 kB
Tags Source
SHA-256 checksum
How to use checksums
cbad1045bd68317743752a31a17cfb4079a4f90499fae03e94f240e1c5f08ffb
BLAKE2b-256 checksum
How to use checksums
ffdd222a4cee013d75c7680abcf3e381ada66fed8ad97d0e249292efa0893d04
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / htmlcompare-0.5.1-py3-none-any.whl

Download URL htmlcompare-0.5.1-py3-none-any.whl
Size 43.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9286787eef34661b73acc1094f48f4bef03b3bc9f588b59b361d78098c6dfe99
BLAKE2b-256 checksum
How to use checksums
e028180dd1d34c04beabc32fcccee5e734d1b4e71ac73225a46ff52fd801feae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page