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
classattribute - an empty
styleorclassattribute, which is treated like an absent one - HTML comments
- the formatting of CSS.
styleattributes 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 .bis the descendant combinator and not.a.b,font-family: Arial Blackis notfont-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
backgroundandbackground-color - custom CSS syntax beginning with
--, whose names are case-sensitive, and custom-property values, whichvar()substitutes literally:--x: 0pxis 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)
| File | Size | Uploaded | |
|---|---|---|---|
| htmlcompare-0.5.1.tar.gz | 61.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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