shot them all: compare shots

This commit is contained in:
Lauréline Guérin 2021-06-17 15:33:39 +02:00
parent bb5e118f1b
commit 129c1ecd58
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 167 additions and 10 deletions

120
bin/compare_them_all.py Normal file
View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#
# Compare desktop/mobile shots of all themes from publik-base-theme.
#
# Usage:
# --input1-directory -- directory to find version 1 screenshots
# --input2-directory -- directory to find version 2 screenshots
# --output-directory DIRECTORY -- directory to store comparison results, defaults to results/
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
# --theme THEME -- theme to shot (substring match)
#
# Example:
# hobo-manage tenant_command runscript compare_them_all.py -d hobo.fred.local.0d.be
#
import argparse
import os
import sys
from hobo.theme.utils import get_themes
from django.template import Template, Context
from PIL import Image, ImageDraw
parser = argparse.ArgumentParser()
parser.add_argument('--input1-directory', dest='input1_directory', type=str, required=True)
parser.add_argument('--input2-directory', dest='input2_directory', type=str, required=True)
parser.add_argument('--output-directory', dest='output_directory', type=str, default='results')
parser.add_argument('--overlay', dest='overlay', type=str, default='none')
parser.add_argument('--theme', dest='theme', type=str)
args = parser.parse_args()
if not os.path.exists(args.output_directory):
os.mkdir(args.output_directory)
result = []
for theme in get_themes():
theme_id = theme['id']
if args.overlay == 'all':
pass
elif args.overlay == 'none' and not theme.get('overlay'):
pass
elif args.overlay == theme.get('overlay'):
pass
else:
continue
if args.theme and args.theme not in theme['id']:
continue
sys.stderr.write("%-25s" % theme_id)
shots = [
'desktop',
'mobile',
'mobile-horizontal',
]
def process_region(image, x, y, width, height):
region_total = 0
# This can be used as the sensitivity factor, the larger it is the less sensitive the comparison
factor = 100
for coordinate_y in range(y, y + height):
for coordinate_x in range(x, x + width):
try:
pixel = image.getpixel((coordinate_x, coordinate_y))
region_total += sum(pixel) / 4
except Exception:
return
return region_total / factor
theme_data = {
'label': theme['label'],
'shots': [],
}
for shot in shots:
filepath_1 = os.path.join(args.input1_directory, '%s-%s.png' % (theme_id, shot))
filepath_2 = os.path.join(args.input2_directory, '%s-%s.png' % (theme_id, shot))
filepath_output = os.path.join(args.output_directory, '%s-%s.png' % (theme_id, shot))
try:
screenshot_1 = Image.open(filepath_1)
screenshot_2 = Image.open(filepath_2)
except FileNotFoundError:
continue
width, height = screenshot_1.size
columns = 60
rows = 80
screen_width, screen_height = screenshot_1.size
block_width = ((screen_width - 1) // columns) + 1
block_height = ((screen_height - 1) // rows) + 1
for y in range(0, screen_height, block_height + 1):
for x in range(0, screen_width, block_width + 1):
region_1 = process_region(screenshot_1, x, y, block_width, block_height)
region_2 = process_region(screenshot_2, x, y, block_width, block_height)
if region_1 is not None and region_2 is not None and region_1 != region_2:
draw = ImageDraw.Draw(screenshot_1)
draw.rectangle((x, y, x + block_width, y + block_height), outline="red")
screenshot_1.save(filepath_output)
theme_data['shots'].append({
'label': shot,
'width': width,
'height': height,
'before': '../%s' % filepath_1,
'after': '../%s' % filepath_2,
'diff': '../%s' % filepath_output,
})
result.append(theme_data)
sys.stderr.write(u'\n')
with open('index_template.html', 'r') as f:
template = Template(f.read())
context = Context({'themes': result})
html = template.render(context)
with open(os.path.join(args.output_directory, 'index.html'), 'w') as f:
f.write(html)

26
bin/index_template.html Normal file
View File

@ -0,0 +1,26 @@
<html>
<meta charset="UTF-8">
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://www.jqueryscript.net/demo/before-after-image-viewer/beforeafter.jquery-1.0.0.min.js"></script>
</head>
<body>
{% for theme in themes %}
<h3>{{ theme.label }}</h3>
{% for shot in theme.shots %}
<h4>{{ shot.label }}</h4>
<div class="beforeAfter" style="width: {{ shot.width }}px; height: {{ shot.height }}px">
<img src="{{ shot.before }}"/>
<img src="{{ shot.after }}"/>
</div>
<h5>Diff</h5>
<img src="{{ shot.diff }}" />
{% endfor %}
{% endfor %}
<script>
$(function(){
$('.beforeAfter').beforeAfter();
});
</script>
</body>
</html>

View File

@ -3,14 +3,15 @@
# Take desktop/mobile shots of all themes from publik-base-theme.
#
# Usage:
# --directory DIRECTORY -- directory to store shots, defaults to shots/
# --no-headless -- run in an actual browser window
# --no-memcached -- do not restart memcached after theme changes
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
# --reshot -- retake existing shots
# --timeout TIMEOUT -- timeout between shots (time for hobo deploy)
# --theme THEME -- theme to shot (substring match)
# URL -- specific URL to shot
# --directory DIRECTORY -- directory to store shots, defaults to shots/
# --no-headless -- run in an actual browser window
# --no-memcached -- do not restart memcached after theme changes
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
# --reshot -- retake existing shots
# --timeout TIMEOUT -- timeout between shots (time for hobo deploy)
# --cell-timeout TIMEOUT -- timeout after url loading (time for ajax cells loading)
# --theme THEME -- theme to shot (substring match)
# URL -- specific URL to shot
#
# Example:
# hobo-manage tenant_command runscript shot_them_all.py -d hobo.fred.local.0d.be
@ -31,9 +32,10 @@ parser.add_argument('--no-memcached', dest='no_memcached', action='store_true')
parser.add_argument('--overlay', dest='overlay', type=str, default='none')
parser.add_argument('--reshot', dest='reshot', action='store_true')
parser.add_argument('--timeout', dest='timeout', type=int, default=20) # seconds
parser.add_argument('--cell-timeout', dest='cell_timeout', type=int, default=0) # seconds
parser.add_argument('--theme', dest='theme', type=str)
parser.add_argument('url', metavar='URL', type=str, nargs='?',
default='https://auquo.fred.local.0d.be/contactez-nous/inscription-sur-les-listes/')
default='https://auquo.fred.local.0d.be/contactez-nous/inscription-sur-les-listes/')
args = parser.parse_args()
if not os.path.exists(args.directory):
@ -54,7 +56,7 @@ for theme in get_themes():
pass
else:
continue
if args.theme and not args.theme in theme['id']:
if args.theme and args.theme not in theme['id']:
continue
sys.stderr.write("%-25s" % theme_id)
shots = [
@ -75,9 +77,18 @@ for theme in get_themes():
if not args.no_memcached:
os.system('sudo service memcached restart')
browser.get(args.url)
for i in range(args.cell_timeout):
sys.stderr.write('.')
time.sleep(1)
def size(value):
return browser.execute_script('return document.body.parentNode.scroll' + value)
for shot in shots:
browser.set_window_size(shot[0], shot[1])
browser.set_window_size(shot[0], size('Height'))
browser.save_screenshot(os.path.join(args.directory, '%s-%s.png' % (theme_id, shot[2])))
sys.stderr.write(u' 📸 \n')
browser.close()