Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prove that two HTML pages look identical?

Tags:

html

css

acid3

E.g., I have this:

<pre>
sun<br/>
&nbsp;&nbsp;&nbsp;&nbsp;mercury <br/>
&nbsp;&nbsp;&nbsp;&nbsp;venus <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;earth <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mars <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jupiter <br/>
&nbsp;&nbsp;&nbsp;&nbsp;saturn <br/>
</pre>

and this:

<div style="font-family:monospace">
  <div style="text-indent: 0">sun</div> <br/>
  <div style="text-indent: 4ch">mercury</div> <br/>
  <div style="text-indent: 4ch">venus</div> <br/>
  <div style="text-indent: 8ch">earth</div> <br/>
  <div style="text-indent: 8ch">mars</div> <br/>
  <div style="text-indent: 12ch">jupiter</div> <br/>
  <div style="text-indent: 4ch">saturn</div> <br/>
</div>

And I want the second one to look exactly like the first.

I believe those look identical, but my only proof was to use the old "switch back and forth between windows real quick and eyeball it" technique. (Astronomers call this a "blink comparator" -- https://en.wikipedia.org/wiki/Blink_comparator ). I made sure the windows were the same size and in the same position. But if the rendered HTML didn't fit on the screen this might have been too difficult.

Is there a more definitive tool or method to do this comparison?

I looked at these in both Chrome 77.0.3865.120 and Firefox 69.0.3.

I know for instance that with the browser Acid tests that were originally part of the Web Standards Project -- https://www.acidtests.org/ -- pixel perfect rendering was the benchmark.

(Extra Credit: The HTML for the second code snippet is probably adequate for my needs; if you care to suggest improvements those would be welcome.)

EDIT: My question compares two small HTML samples, which can be rendered to fit on the visible portion of the browser. But in general I would like to know the answer for HTML that could be quite long.

like image 261
Purplejacket Avatar asked Oct 28 '19 02:10

Purplejacket


People also ask

How do I compare HTML pages?

How to compare HTML files. Upload the two HTML files to be compared separately. Press the "COMPARE" button. View the highlighting differences between two HTML files.

How do I switch between two pages in HTML?

Linking to other Web Pages. Linking in HTML code is done with the anchor tag, the <A> tag. The letter "A" in the tag is then followed by an attribute. For a link to another web page, the "A" is followed by "HREF".


3 Answers

I have done something similar when developing a CSS related webiste. I had to compare an output produced by a HTML/CSS with an image that was previously generated with a HTML/CSS.

I used dom-to-image, which converts code to a base64-encoded image. I place this image inside a canvas and then use pixelmatch to compare between both images.

Here is an example to illustrate:

var node1 = document.querySelector("pre");
var node2 = document.querySelector(".div");
var canvas1 = document.querySelector(".first");
var canvas2 = document.querySelector(".second");
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
/* Change both code to Image and put inside Canvas */
domtoimage.toPng(node1)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx1.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

domtoimage.toPng(node2)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx2.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

/* Run the pixel matching*/
setTimeout(function() {
  var im_r = ctx1.getImageData(0, 0, 300, 300).data;
  var im_o = ctx2.getImageData(0, 0, 300, 300).data;
  var pixDiff = pixelmatch(im_r, im_o, false, 280, 280, {
    threshold: 0.1
  });
  console.log(pixDiff);
}, 3000);
canvas {
  border: 1px solid;
}

pre,
.div {
  border: 2px solid red;
  width: 300px;
  height: 300px;
  box-sizing: border-box;
  margin: 0;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/pixel.js"></script>
<pre>
sun<br/>
&nbsp;&nbsp;&nbsp;&nbsp;mercury <br/>
&nbsp;&nbsp;&nbsp;&nbsp;venus <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;earth <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mars <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jupiter <br/>
&nbsp;&nbsp;&nbsp;&nbsp;saturn <br/>
</pre>


<div class="div" style="font-family:monospace">
  <div style="text-indent: 0">sun</div> <br/>
  <div style="text-indent: 4ch">mercury</div> <br/>
  <div style="text-indent: 4ch">venus</div> <br/>
  <div style="text-indent: 8ch">earth</div> <br/>
  <div style="text-indent: 8ch">mars</div> <br/>
  <div style="text-indent: 12ch">jupiter</div> <br/>
  <div style="text-indent: 4ch">saturn</div> <br/>
</div>

<canvas width="300" height="300" class="first"></canvas>
<canvas width="300" height="300" class="second"></canvas>

In the above code, we have our 2 HTML blocks and 2 canvases where we will paint our blocks. As you can see, the JS is quite simple. The code at the end runs pixel matching and shows how many different pixels both canvases have. I added a delay to make sure both images are loaded (you can optimize later with some events)

You can also consider a third canvas to highlight the difference between both images and obtain your visual difference:

var node1 = document.querySelector("pre");
var node2 = document.querySelector(".div");
var canvas1 = document.querySelector(".first");
var canvas2 = document.querySelector(".second");
var canvas3 = document.querySelector(".result");
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var ctx3 = canvas3.getContext("2d");
/* Change both code to Image and put inside Canvas */
domtoimage.toPng(node1)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx1.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

domtoimage.toPng(node2)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx2.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

/* Run the pixel matching*/
setTimeout(function() {
  var im_r = ctx1.getImageData(0, 0, 300, 300).data;
  var im_o = ctx2.getImageData(0, 0, 300, 300).data;
  var diff = ctx3.createImageData(300, 300);
  var pixDiff = pixelmatch(im_r, im_o, diff.data, 300, 300, {
    threshold: 0.1
  });
  ctx3.putImageData(diff, 0, 0);
  console.log(pixDiff);
}, 3000);
canvas {
  border: 1px solid;
}

pre,
.div {
  border: 2px solid red;
  width: 300px;
  height: 300px;
  box-sizing: border-box;
  margin: 0;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/pixel.js"></script>
<pre>
sun<br/>
&nbsp;&nbsp;&nbsp;&nbsp;mercury <br/>
&nbsp;&nbsp;&nbsp;&nbsp;venus <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;earth <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mars <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jupiter <br/>
&nbsp;&nbsp;&nbsp;&nbsp;saturn <br/>
</pre>


<div class="div" style="font-family:monospace">
  <div style="text-indent: 0">sun</div> <br/>
  <div style="text-indent: 4ch">mercury</div> <br/>
  <div style="text-indent: 4ch">venus</div> <br/>
  <div style="text-indent: 8ch">earth</div> <br/>
  <div style="text-indent: 8ch">mars</div> <br/>
  <div style="text-indent: 12ch">jupiter</div> <br/>
  <div style="text-indent: 4ch">saturn</div> <br/>
</div>

<canvas width="300" height="300" class="first"></canvas>
<canvas width="300" height="300" class="second"></canvas>
<canvas width="300" height="300" class="result"></canvas>

Let's change the content to see some difference:

var node1 = document.querySelector("pre");
var node2 = document.querySelector(".div");
var canvas1 = document.querySelector(".first");
var canvas2 = document.querySelector(".second");
var canvas3 = document.querySelector(".result");
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var ctx3 = canvas3.getContext("2d");
/* Change both code to Image and put inside Canvas */
domtoimage.toPng(node1)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx1.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

domtoimage.toPng(node2)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx2.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

/* Run the pixel matching*/
setTimeout(function() {
  var im_r = ctx1.getImageData(0, 0, 300, 300).data;
  var im_o = ctx2.getImageData(0, 0, 300, 300).data;
  var diff = ctx3.createImageData(300, 300);
  var pixDiff = pixelmatch(im_r, im_o, diff.data, 300, 300, {
    threshold: 0.1
  });
  ctx3.putImageData(diff, 0, 0);
  console.log(pixDiff);
}, 3000);
canvas {
  border: 1px solid;
}

pre,
.div {
  border: 2px solid red;
  width: 300px;
  height: 300px;
  box-sizing: border-box;
  margin: 0;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/pixel.js"></script>
<pre>
sun<br/>
&nbsp;&nbsp;&nbsp;&nbsp;mercury <br/>
&nbsp;&nbsp;&nbsp;&nbsp;venus <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;earth <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mars <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;jupiter <br/>
&nbsp;&nbsp;&nbsp;&nbsp;saturn <br/>
</pre>


<div class="div" style="font-family:monospace">
  <div style="text-indent: 0">sun</div> <br/>
  <div style="text-indent: 4ch">mercury</div> <br/>
  <div style="text-indent: 4ch">venus</div> <br/>
  <div style="text-indent: 8ch">earth</div> <br/>
  <div style="text-indent: 8ch">april</div> <br/>
  <div style="text-indent: 12ch">jupiter</div> <br/>
  <div style="text-indent: 4ch">saturn</div> <br/>
</div>

<canvas width="300" height="300" class="first"></canvas>
<canvas width="300" height="300" class="second"></canvas>
<canvas width="300" height="300" class="result"></canvas>

You can read more about how the two plugins I used work and find more interesting options.

I am fixing the sizes to 300x300 to make the demonstration easy inside the snippet, but you can consider bigger height and width.


Update

Here is a more realistic example to compare between two layouts that produce the same result. The width/height of the canvas will be dynamic and based on the content. I will show only the last canvas with the difference.

var node1 = document.querySelector(".flex");
var node2 = document.querySelector(".grid");
var canvas1 = document.querySelector(".first");
var canvas2 = document.querySelector(".second");
var canvas3 = document.querySelector(".result");
canvas1.height= node1.offsetHeight;
canvas2.height= node2.offsetHeight;
canvas3.height= node1.offsetHeight;
canvas1.width= node1.offsetWidth;
canvas2.width= node2.offsetWidth;
canvas3.width= node1.offsetWidth;
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var ctx3 = canvas3.getContext("2d");

domtoimage.toPng(node1)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx1.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

domtoimage.toPng(node2)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx2.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })


setTimeout(function() {
  var im_r = ctx1.getImageData(0, 0, canvas1.width, canvas1.height).data;
  var im_o = ctx2.getImageData(0, 0, canvas1.width, canvas1.height).data;
  var diff = ctx3.createImageData(canvas1.width, canvas1.height);
  var pixDiff = pixelmatch(im_r, im_o, diff.data, canvas1.width, canvas1.height, {
    threshold: 0.2
  });
  ctx3.putImageData(diff, 0, 0);
  console.log(pixDiff);
}, 3000);
.grid {
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  border:2px solid red;
}
h1 {
  text-align:center;
  grid-column:1/-1;
  flex-basis:100%;
}

.flex {
  display:flex;
  flex-wrap:wrap;
  border:2px solid red;
}
.flex > div {
  flex-grow:1;
  flex-basis:0;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/pixel.js"></script>
<div class="grid">
<h1>A title here</h1>
<div>
<img src="https://picsum.photos/id/10/200/200" >
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus aliquam condimentum mollis. Phasellus faucibus diam quis lorem efficitur, id egestas neque malesuada</div>
<div> Maecenas sollicitudin lacinia finibus. Integer vel varius eros. Morbi et ante eget est mollis sollicitudin.</div>
</div>
<div class="flex">
<h1>A title here</h1>
<div>
<img src="https://picsum.photos/id/10/200/200" >
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus aliquam condimentum mollis. Phasellus faucibus diam quis lorem efficitur, id egestas neque malesuada</div>
<div> Maecenas sollicitudin lacinia finibus. Integer vel varius eros. Morbi et ante eget est mollis sollicitudin.</div>
</div>

<canvas width="300" height="300" class="first" style="display:none;"></canvas>
<canvas width="300" height="300" class="second" style="display:none;"></canvas>
<canvas width="300" height="300" class="result"></canvas>

Let's use a different image:

var node1 = document.querySelector(".flex");
var node2 = document.querySelector(".grid");
var canvas1 = document.querySelector(".first");
var canvas2 = document.querySelector(".second");
var canvas3 = document.querySelector(".result");
canvas1.height= node1.offsetHeight;
canvas2.height= node2.offsetHeight;
canvas3.height= node1.offsetHeight;
canvas1.width= node1.offsetWidth;
canvas2.width= node2.offsetWidth;
canvas3.width= node1.offsetWidth;
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var ctx3 = canvas3.getContext("2d");

domtoimage.toPng(node1)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx1.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })

domtoimage.toPng(node2)
  .then(function(dataUrl) {
    var image = new Image();
    image.onload = function() {
      ctx2.drawImage(this, 0, 0);
    };
    image.src = dataUrl;
  })


setTimeout(function() {
  var im_r = ctx1.getImageData(0, 0, canvas1.width, canvas1.height).data;
  var im_o = ctx2.getImageData(0, 0, canvas1.width, canvas1.height).data;
  var diff = ctx3.createImageData(canvas1.width, canvas1.height);
  var pixDiff = pixelmatch(im_r, im_o, diff.data, canvas1.width, canvas1.height, {
    threshold: 0.2
  });
  ctx3.putImageData(diff, 0, 0);
  console.log(pixDiff);
}, 3000);
.grid {
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  border:2px solid red;
}
h1 {
  text-align:center;
  grid-column:1/-1;
  flex-basis:100%;
}

.flex {
  display:flex;
  flex-wrap:wrap;
  border:2px solid red;
}
.flex > div {
  flex-grow:1;
  flex-basis:0;
}
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/dom-to-image.js"></script>
<script type="text/javascript" src="https://css-challenges.com/wp-content/themes/ronneby_child/js/pixel.js"></script>
<div class="grid">
<h1>A title here</h1>
<div>
<img src="https://picsum.photos/id/10/200/200" >
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus aliquam condimentum mollis. Phasellus faucibus diam quis lorem efficitur, id egestas neque malesuada</div>
<div> Maecenas sollicitudin lacinia finibus. Integer vel varius eros. Morbi et ante eget est mollis sollicitudin.</div>
</div>
<div class="flex">
<h1>A title here</h1>
<div>
<img src="https://picsum.photos/id/12/200/200" >
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus aliquam condimentum mollis. Phasellus faucibus diam quis lorem efficitur, id egestas neque malesuada</div>
<div> Maecenas sollicitudin lacinia finibus. Integer vel varius eros. Morbi et ante eget est mollis sollicitudin.</div>
</div>

<canvas width="300" height="300" class="first" style="display:none;"></canvas>
<canvas width="300" height="300" class="second" style="display:none;"></canvas>
<canvas width="300" height="300" class="result"></canvas>
like image 192
Temani Afif Avatar answered Oct 16 '22 07:10

Temani Afif


Here's an idea to actually do some measuring with the DOM - I just replaced the text in question with divs that have a class that can be queried. Then you can print the offset of all the nodes.

From what I measure the character indents are indeed the same as the &nbsp;.

var nodes = document.getElementsByClassName('measure');

for (var n of nodes) {
  console.log(n.offsetLeft);
}
.measure {
  display: inline-block;
  background: red;
  width: 50px;
  height: 5px;
}
<pre>
<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<div class="measure"></div><br/>
</pre>

var nodes = document.getElementsByClassName('measure');

for (var n of nodes) {
  console.log(n.offsetLeft);
}
.measure {
  display: inline-block;
  background: red;
  width: 50px;
  height: 5px;
}
<div style="font-family:monospace">
  <div style="text-indent: 0"><div class="measure"></div></div> <br/>
  <div style="text-indent: 4ch"><div class="measure"></div></div> <br/>
  <div style="text-indent: 4ch"><div class="measure"></div></div> <br/>
  <div style="text-indent: 8ch"><div class="measure"></div></div> <br/>
  <div style="text-indent: 8ch"><div class="measure"></div></div> <br/>
  <div style="text-indent: 12ch"><div class="measure"></div></div> <br/>
  <div style="text-indent: 4ch"><div class="measure"></div></div> <br/>
</div>
like image 22
MrRobboto Avatar answered Oct 16 '22 07:10

MrRobboto


Print the screens of the two pages and compare the pixels.
You can use a webdriver like selenium, render the two screens in a image file (png, jpg, etc) - selenium has a method to do this- and write a software to read the pixels of the two to compare for similarity.
The webdriver is a browser you can controll with code. And you can find software that compare images in the web.

You can find more info in this link: https://selenium.dev/

like image 3
Guilherme Avatar answered Oct 16 '22 09:10

Guilherme