20

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.

Purplejacket
  • 1,808
  • 2
  • 25
  • 43
  • 4
    You can take a screenshot of both and then overlay the screenshots and change the opacity of the top one to make sure they line up – APAD1 Oct 28 '19 at 16:47
  • 2
    @APAD1 — That's still eyeballing them. Use an image diff. – Quentin Oct 28 '19 at 17:16
  • Photoshop? Print screen the two and then overlay them. Or use a tool like https://www.diffchecker.com/image-diff – j08691 Oct 28 '19 at 17:17
  • If you don't shy away from external software: Beyond Compare does image comparison as well. – Constantin Groß Oct 28 '19 at 17:17
  • Would I then need a plugin to print to pgn? – Purplejacket Oct 28 '19 at 17:21
  • @Quentin it's not eyeballing at all, if you turn the opacity down to 50% on the top image you would easily be able to see if things weren't lining up. – APAD1 Oct 28 '19 at 18:36
  • 1
    Basically a dupe of the closed-as-too-broad [Compare two HTML sources and display visual differences](https://stackoverflow.com/q/18955417/215552). Also, doing an internet search of "visual diff of two html pages" brings up a long list of products and libraries... – Heretic Monkey Nov 15 '19 at 22:34
  • Coming back to this question - I think my answer works and answers the "method" part but I see you're looking for a more programmatic and dynamic way, or a tool. That's a big scope for an SO question IMO as @HereticMonkey pointed out. However you can take my answer and easily expand this yourself - it would be fairly trivial to traverse the entire DOM of two documents, and comparing the structure/offsets of the nodes. Definitely do some searching in NPM, etc. first though - I'd bet you'd be reinventing the wheel if you roll your own... – MrRobboto Nov 20 '19 at 18:28
  • 1
    I believe Smart Bear's TestComplete can do this [documentation](https://support.smartbear.com/testcomplete/docs/testing-with/checkpoints/regions/how-image-comparison-works.html), however it may be cost prohibitive. – Graham Nov 21 '19 at 21:43
  • Some RPA solutions, like Automation Anywhere have image comparison functionality built into them. Google "Automation Anywhere Image Recognition functionality". Has capability to compare images of differing resolutions, RGB pixel comparison, Grey scaling, convert to monochrome and compare. Not sure of what libraries it uses internally. You may also find this link useful too. https://stackoverflow.com/questions/843972/image-comparison-fast-algorithm – JGFMK Nov 22 '19 at 12:16

9 Answers9

10

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>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
5

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>
MrRobboto
  • 722
  • 3
  • 10
  • 1
    "I just replaced all the text nodes with divs" No, you replaced only the **parts** you were *seeing*. All the ` ` characters are also part of the TextNodes. This is important, because it means your solution can't work programmatically. – Kaiido Nov 22 '19 at 03:07
3

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/

Guilherme
  • 456
  • 4
  • 12
  • 2
    How do you compare the pixels? Is there an automated way to do this? Also, what if the HTML had been a lot longer than the example I gave, and didn't fit on the screen? – Purplejacket Oct 28 '19 at 17:23
2

First we have to capture images

Capture Method 1

Use the print screen button on your keyboard (or other screenshot tool).

For pages that are to long or wide to fit onscreen, take multiple screenshots of each page and piece them together in your photo editor. You can take overlapping screenshots of a large page, put them on separate layers, then use the overlapping portions to position the pieces precisely before merging layers to create a composite of the entire page.

Capture Method 2

You can use a browser extension such as fireshot to capture the entire page at once.


Second we compare the images

Method 1

  1. Open photoshop or go to photopea.

  2. Paste screenshot into a layer.

  3. Repeat for second page, pasting into a different layer.

  4. Toggle layer visibility and any changes will be obvious. Or adjust the opacity of the top layer (and/or set different blend modes) to compare.

Method 2

Use a browser extension such as pixel-perfect-2 which allows overlaying semi-transparent images within your browser. This is a useful extension for checking alignment and spacing as well, because you can overlay pages with a grid image within your browser.

Method 3

Use an image diff tool. A quick google search will turn up too many to list here. There are image diff plugins for some code editors, command line tools, Python scripts, and online services such as diffchecker.

Community
  • 1
  • 1
Veneseme Tyras
  • 596
  • 3
  • 9
2

You can place one above another in the same document with position:absolute; And maybe zoom in for closer look.

#a, #b {
  position:absolute;
  left:0;
  top:0;
  margin:0;
  padding:0;
  color:green;
  opacity:0.5;
}
#a {
  color:red;
}
#b {
  color:green;
}
<pre id="a">
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 style="font-family:monospace" id="b">
      <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>
V.Volkov
  • 731
  • 3
  • 12
  • That's very nice! I tried this idea but because of weird aspects of the `
    ` tag with respect to the `` margins I couldn't get them to align.  Your styles for `#a, #b { ... }` seem to make it work properly.
    – Purplejacket Nov 21 '19 at 23:32
2

Overlay the two pages on top of each other using iframes. This should allow you to see any differences between the two pages.

main.html:

<iframe src="doc1.html"></iframe>
<iframe src="doc2.html" style="opacity:0.5"></iframe>
<style>

html, body {
    margin:0;
    height:100%;
}
iframe {
    height:100%;
    width:100%;
    position:absolute;
    top:0;
    left:0;
}

</style>

doc1.html:

<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>

doc2.html

<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>

(I suspect there is a way to do this using Stack Snippets. Feel free to enlighten me)

ecc521
  • 576
  • 6
  • 14
1

There are some really complex and clever code methods in here, so here is a simple one,

Take a screenshot of one, open it in an image editor that supports transparency Like photoshop,

Past it in, then take screen show off your CSS way and paste it in set the second pasted image to an opacity of 50%, and see if they line up.

Barkermn01
  • 6,781
  • 33
  • 83
  • What if the page is too long to fit on the screen? – Purplejacket Nov 21 '19 at 17:56
  • If you have photoshop, or any other editor that supports it, set the blend mode on the topmost layer to difference. If the images are identical, you will see a solid black image. – steveax Nov 22 '19 at 02:45
  • Yes. HTML is one string. Simply use cURL or another method to print the string and then compare it. These are some over engineered examples runnin round here. – Ususipse Nov 22 '19 at 11:13
  • @Purplejacket, erm Screenshot the App as a whole, Ctrl+Shift+Print Screen, – Barkermn01 Nov 22 '19 at 12:01
  • 1
    @Ususipse most modern sites are much more than just static HTML strings. Even if it was just HTML, OP's question shows two different HTML structures that appear identical but are two ways of achieving essentially the same result, so printing the string isn't going to cut it. An image diffing algorithm or tool is a necessary approach for this use case, not overengineered. Similarly, this sort of manual "paste into photoshop" doesn't help if you need it to be automated, the common case. But yeah, if you're only doing it one time it's fine. – ggorlen Apr 27 '22 at 19:47
1

As I've seen so far, few people are mentioning actual testing tools.

There are plenty tools for your usecase (often part of bigger frameworks):

To name a few taken from this list. Maybe look for yourself and choose one fitting your environment.

If you want to build a sustainable automation don't rely on hacks. It may 'only' be QA but can seriously save your a** in future releases.

Nils K
  • 44
  • 6
1

Temani's answer is fantastic. I suggest reading it if you haven't already since it shows pixelmatch usage and the overall workflow. But I have a couple of issues with it, hence this supplementary answer.

The main issue is that I wasn't able to get dom-to-image to work consistently for me. It was chopping off the last child node in a <div> even on a simple example. Other users ran into issues as well, and a short visit to GitHub showed long threads such as this one with multiple users suggesting switching to html2canvas. As advertised, html2canvas turned out to be more reliable for me.

The secondary issue is that I'd prefer to use promises rather than setTimeout with an arbitrary delay, even for examples. Luckily, switching to html2canvas simplifies the workflow since it returns a canvas rather than a base64 string, ready to pass to pixelmatch.

Here's a minimal example (not a snippet due to cross-origin issues):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
  <script src="https://bundle.run/pixelmatch@5.2.1"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
  <style>
  p, canvas {
    border: 1px solid red;
  }
  </style>
</head>
<body>
  <p>Foobar</p>
  <p>Foobaz</p>

<script>
(async () => {
  const [aEl, bEl] = document.querySelectorAll("p");
  const aCanvas = await html2canvas(aEl);
  const bCanvas = await html2canvas(bEl);
  const aCtx = aCanvas.getContext("2d");
  const bCtx = bCanvas.getContext("2d");
  const {width: w, height: h} = aCanvas;
  const diffCanvas = document.createElement("canvas");
  document.body.appendChild(diffCanvas);
  diffCanvas.width = w;
  diffCanvas.height = h;
  const diffCtx = diffCanvas.getContext("2d");
  const diffImageData = diffCtx.createImageData(w, h);
  const mismatchedPixels = pixelmatch(
    aCtx.getImageData(0, 0, w, h).data,
    bCtx.getImageData(0, 0, bCanvas.width, bCanvas.height).data,
    diffImageData.data,
    w, h, {threshold: 0.1}
  );
  diffCtx.putImageData(diffImageData, 0, 0);
  console.log(mismatchedPixels);
})();
</script>
</body>
</html>
ggorlen
  • 44,755
  • 7
  • 76
  • 106