# -*- coding: utf-8 -*- u""" Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. This file is part of Toolium. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import os import re import shutil import mock import pytest from PIL import Image from needle.engines.imagemagick_engine import Engine as MagickEngine from needle.engines.perceptualdiff_engine import Engine as PerceptualEngine from needle.engines.pil_engine import Engine as PilEngine from toolium.config_files import ConfigFiles from toolium.driver_wrapper import DriverWrapper from toolium.driver_wrapper import DriverWrappersPool from toolium.test.utils.test_driver_utils import get_mock_element from toolium.visual_test import VisualTest # Get file paths root_path = os.path.dirname(os.path.realpath(__file__)) file_v1 = os.path.join(root_path, 'resources', 'register.png') file_v2 = os.path.join(root_path, 'resources', 'register_v2.png') file_small = os.path.join(root_path, 'resources', 'register_small.png') file_scroll = os.path.join(root_path, 'resources', 'register_chrome_scroll.png') file_ios = os.path.join(root_path, 'resources', 'ios.png') @pytest.yield_fixture def driver_wrapper(): # Remove previous visual path root_path = os.path.dirname(os.path.realpath(__file__)) visual_path = os.path.join(root_path, 'output', 'visualtests') if os.path.exists(visual_path): shutil.rmtree(visual_path) # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None # Create a new wrapper driver_wrapper = DriverWrappersPool.get_default_wrapper() driver_wrapper.driver = mock.MagicMock() # Configure properties root_path = os.path.dirname(os.path.realpath(__file__)) config_files = ConfigFiles() config_files.set_config_directory(os.path.join(root_path, 'conf')) config_files.set_config_properties_filenames('properties.cfg') config_files.set_output_directory(os.path.join(root_path, 'output')) driver_wrapper.configure(config_files) driver_wrapper.config.set('VisualTests', 'enabled', 'true') yield driver_wrapper # Remove visual path visual_path = os.path.join(root_path, 'output', 'visualtests') if os.path.exists(visual_path): shutil.rmtree(visual_path) # Reset wrappers pool values DriverWrappersPool._empty_pool() DriverWrapper.config_properties_filenames = None def test_no_enabled(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'enabled', 'false') visual = VisualTest(driver_wrapper) visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') driver_wrapper.driver.save_screenshot.assert_not_called() def test_engine_pil(driver_wrapper): visual = VisualTest(driver_wrapper) assert isinstance(visual.engine, PilEngine) def test_engine_perceptual(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'engine', 'perceptualdiff') visual = VisualTest(driver_wrapper) assert isinstance(visual.engine, PerceptualEngine) def test_engine_magick(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'engine', 'imagemagick') visual = VisualTest(driver_wrapper) assert isinstance(visual.engine, MagickEngine) def test_engine_empty(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'engine', '') visual = VisualTest(driver_wrapper) assert isinstance(visual.engine, PilEngine) def test_engine_unknown(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'engine', 'unknown') visual = VisualTest(driver_wrapper) assert isinstance(visual.engine, PilEngine) def test_compare_files_equal(driver_wrapper): visual = VisualTest(driver_wrapper) message = visual.compare_files('report_name', file_v1, file_v1, 0) assert message is None def test_compare_files_diff(driver_wrapper): visual = VisualTest(driver_wrapper) message = visual.compare_files('report_name', file_v1, file_v2, 0) assert 'Distance of 0.00080181' in message def test_compare_files_diff_fail(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'fail', 'true') visual = VisualTest(driver_wrapper) with pytest.raises(AssertionError) as exc: visual.compare_files('report_name', file_v1, file_v2, 0) assert str(exc.value) == "The new screenshot '%s' did not match the baseline '%s' " \ "(by a distance of 522.65)" % (file_v1, file_v2) def test_compare_files_size(driver_wrapper): visual = VisualTest(driver_wrapper) message = visual.compare_files('report_name', file_v1, file_small, 0) # PIL returns an empty error, but PyTest modifies AssertionError assert 'assert (1680, 388) == (1246, 388)' in message def test_compare_files_size_fail(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'fail', 'true') visual = VisualTest(driver_wrapper) with pytest.raises(AssertionError) as exc: visual.compare_files('report_name', file_v1, file_small, 0) assert str(exc.value).startswith('assert (1680, 388) == (1246, 388)') def test_get_img_element(driver_wrapper): expected_img = '<img src=".*register_v2.png" title="Baseline image"/>' visual = VisualTest(driver_wrapper) img = visual._get_img_element('register_v2.png', 'Baseline image') assert re.compile(expected_img).match(img) is not None def test_get_html_row(driver_wrapper): expected_row = '<tr class=diff><td>report_name</td><td><img src=".*register_v2.png" title="Baseline image"/>' \ '</td><td><img src=".*register.png" title="Screenshot image"/></td><td></td></tr>' visual = VisualTest(driver_wrapper) row = visual._get_html_row('diff', 'report_name', file_v1, file_v2) assert re.compile(expected_row).match(row) is not None def test_diff_message_equal(): message = None expected_message = '' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_size_pil(): message = '' expected_message = 'Image dimensions do not match' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_size_perceptualdiff(): message = """The new screenshot 'C:\\...\\01_login_form__test_login.png' did not match the baseline 'C:\\...\\login_form.png': FAIL: Image dimensions do not match""" expected_message = 'Image dimensions do not match' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_size_imagemagick(): message = 'Image dimensions do not match' expected_message = 'Image dimensions do not match' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_diff_pil(): message = ("The new screenshot 'C:\\...\\01_login_form__test_login.png' did not match the baseline " "'C:\\...\\login_form.png' (by a distance of 14794.11)") expected_message = 'Distance of 0.01479411' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_diff_perceptualdiff(): message = "The new screenshot 'C:\\...\\01_login_form__test_login.png' did not match the baseline " message += """'C:\\...\\login_form.png' (See C:\\...\\01_login_form__test_login.diff.png): FAIL: Images are visibly different 15040 pixels are different """ expected_message = 'Distance of 0.01504000' assert expected_message == VisualTest._get_diff_message(message, 1000000) def test_diff_message_diff_imagemagick(): message = ("The new screenshot 'C:\\...\\01_login_form__test_login.png' did not match the baseline " "'C:\\...\\login_form.png' (See C:\\...\\01_login_form__test_login.diff.png):\n" "5443.77 (0.0830666) @ 0,0") expected_message = 'Distance of 0.08306660' assert expected_message == VisualTest._get_diff_message(message, 1000000) def assert_image(visual, img, img_name, expected_image_filename): """Save img in an image file and compare with the expected image :param img: image object :param img_name: temporary filename :param expected_image_filename: filename of the expected image """ # Save result image in output folder result_file = os.path.join(visual.output_directory, img_name + '.png') img.save(result_file) # Output image and expected image must be equal expected_image = os.path.join(root_path, 'resources', expected_image_filename + '.png') compare_image_files(result_file, expected_image) def compare_image_files(image, expected_image, threshold=0.1): """Compare two images :param image: file path image to compare :param expected_image: expected image :param threshold: allowed threshold """ # Pil needs a pixel number threshold instead of a percentage threshold img = Image.open(expected_image) width, height = img.size threshold = int(width * height * threshold) PilEngine().assertSameFiles(image, expected_image, threshold) def test_crop_element(driver_wrapper): # Create element mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 web_element = get_mock_element(x=250, y=40, height=40, width=300) visual = VisualTest(driver_wrapper) # Resize image img = Image.open(file_v1) img = visual.crop_element(img, web_element) # Assert output image assert_image(visual, img, 'report_name', 'register_cropped_element') def test_crop_element_oversize(driver_wrapper): # Create element mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 web_element = get_mock_element(x=250, y=200, height=400, width=300) # y + width > img.size[1] --> 600 > 388 visual = VisualTest(driver_wrapper) # Resize image img = Image.open(file_v1) img = visual.crop_element(img, web_element) # Assert output image assert_image(visual, img, 'report_name', 'register_cropped_element_oversize') def test_get_scrolls_size(driver_wrapper): # Update conf and create a new VisualTest instance # Mock scrollHeight, scrollWidth, innerHeight, innerWidth driver_wrapper.driver.execute_script.side_effect = [600, 1200, 400, 900] driver_wrapper.config.set('Driver', 'type', 'chrome') visual = VisualTest(driver_wrapper) # Check chrome scrolls assert visual.get_scrolls_size() == {'x': 17, 'y': 17} def test_get_scrolls_size_y(driver_wrapper): # Update conf and create a new VisualTest instance # Mock scrollHeight, scrollWidth, innerHeight, innerWidth driver_wrapper.driver.execute_script.side_effect = [600, 1200, 400, 1200] driver_wrapper.config.set('Driver', 'type', 'chrome') visual = VisualTest(driver_wrapper) # Check chrome scrolls assert visual.get_scrolls_size() == {'x': 0, 'y': 17} def test_get_scrolls_size_without_scrolls(driver_wrapper): # Update conf and create a new VisualTest instance # Mock scrollHeight, scrollWidth, innerHeight, innerWidth driver_wrapper.driver.execute_script.side_effect = [600, 1200, 600, 1200] driver_wrapper.config.set('Driver', 'type', 'chrome') visual = VisualTest(driver_wrapper) # Check chrome scrolls assert visual.get_scrolls_size() == {'x': 0, 'y': 0} def test_get_scrolls_size_iexplore(driver_wrapper): # Update conf and create a new VisualTest instance # Mock scrollHeight, scrollWidth, innerHeight, innerWidth driver_wrapper.driver.execute_script.side_effect = [600, 1200, 400, 900] driver_wrapper.config.set('Driver', 'type', 'iexplore') visual = VisualTest(driver_wrapper) # Check chrome scrolls assert visual.get_scrolls_size() == {'x': 21, 'y': 21} def test_get_scrolls_size_firefox(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.config.set('Driver', 'type', 'firefox') visual = VisualTest(driver_wrapper) # Check chrome scrolls assert visual.get_scrolls_size() == {'x': 0, 'y': 0} def test_remove_scrolls(driver_wrapper): # Create a new VisualTest instance visual = VisualTest(driver_wrapper) visual.get_scrolls_size = lambda: {'x': 0, 'y': 17} # Remove scroll img = Image.open(file_scroll) img = visual.remove_scrolls(img) # Assert output image assert_image(visual, img, 'report_name', 'register_chrome_scroll_removed') def test_remove_scrolls_without_scroll(driver_wrapper): # Create a new VisualTest instance visual = VisualTest(driver_wrapper) visual.get_scrolls_size = lambda: {'x': 0, 'y': 0} # Remove scroll img = Image.open(file_scroll) img = visual.remove_scrolls(img) # Assert output image assert_image(visual, img, 'report_name', 'register_chrome_scroll') def test_mobile_resize(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.driver.get_window_size.return_value = {'width': 375, 'height': 667} driver_wrapper.config.set('Driver', 'type', 'ios') visual = VisualTest(driver_wrapper) # Resize image img = Image.open(file_ios) img = visual.mobile_resize(img) # Assert output image assert_image(visual, img, 'report_name', 'ios_resized') def test_mobile_no_resize(driver_wrapper): # Update conf and create a new VisualTest instance driver_wrapper.driver.get_window_size.return_value = {'width': 750, 'height': 1334} driver_wrapper.config.set('Driver', 'type', 'ios') visual = VisualTest(driver_wrapper) # Resize image orig_img = Image.open(file_ios) img = visual.mobile_resize(orig_img) # Assert that image object has not been modified assert orig_img == img def test_exclude_elements(driver_wrapper): # Create elements mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 visual = VisualTest(driver_wrapper) web_elements = [get_mock_element(x=250, y=40, height=40, width=300), get_mock_element(x=250, y=90, height=20, width=100)] img = Image.open(file_v1) # Exclude elements img = visual.exclude_elements(img, web_elements) # Assert output image assert_image(visual, img, 'report_name', 'register_exclude') def test_exclude_element_outofimage(driver_wrapper): # Create elements mock visual = VisualTest(driver_wrapper) driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 web_elements = [get_mock_element(x=250, y=40, height=40, width=1500)] img = Image.open(file_v1) # Exclude elements img = visual.exclude_elements(img, web_elements) # Assert output image assert_image(visual, img, 'report_name', 'register_exclude_outofimage') def test_exclude_no_elements(driver_wrapper): # Exclude no elements visual = VisualTest(driver_wrapper) img = Image.open(file_v1) img = visual.exclude_elements(img, []) # Assert output image assert_image(visual, img, 'report_name', 'register') def test_assert_screenshot_no_enabled_force(driver_wrapper): # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'enabled', 'false') visual = VisualTest(driver_wrapper, force=True) # Add v1 baseline image baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_full.png') shutil.copyfile(file_v1, baseline_file) # Assert screenshot visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() def test_assert_screenshot_no_enabled_force_fail(driver_wrapper): # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data # Update conf and create a new VisualTest instance driver_wrapper.config.set('VisualTests', 'fail', 'false') driver_wrapper.config.set('VisualTests', 'enabled', 'false') visual = VisualTest(driver_wrapper, force=True) # Add v2 baseline image baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_full.png') shutil.copyfile(file_v2, baseline_file) # Assert screenshot with pytest.raises(AssertionError) as exc: visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() assert str(exc.value).endswith("did not match the baseline '%s' (by a distance of 522.65)" % baseline_file) def test_assert_screenshot_full_and_save_baseline(driver_wrapper): # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data driver_wrapper.config.set('VisualTests', 'save', 'true') visual = VisualTest(driver_wrapper) # Assert screenshot visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') output_file = os.path.join(visual.output_directory, '01_screenshot_full__screenshot_suffix.png') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() # Output image and new baseline image must be equal baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_full.png') compare_image_files(output_file, baseline_file) def test_assert_screenshot_element_and_save_baseline(driver_wrapper): # Create element mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 web_element = get_mock_element(x=250, y=40, height=40, width=300) # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data driver_wrapper.config.set('VisualTests', 'save', 'true') visual = VisualTest(driver_wrapper) # Assert screenshot visual.assert_screenshot(web_element, filename='screenshot_elem', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() # Check cropped image expected_image = os.path.join(root_path, 'resources', 'register_cropped_element.png') output_file = os.path.join(visual.output_directory, '01_screenshot_elem__screenshot_suffix.png') compare_image_files(output_file, expected_image) # Output image and new baseline image must be equal baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_elem.png') compare_image_files(output_file, baseline_file) def test_assert_screenshot_full_and_compare(driver_wrapper): # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data visual = VisualTest(driver_wrapper) # Add baseline image baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_full.png') shutil.copyfile(file_v1, baseline_file) # Assert screenshot visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() def test_assert_screenshot_element_and_compare(driver_wrapper): # Add baseline image driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 visual = VisualTest(driver_wrapper) expected_image = os.path.join(root_path, 'resources', 'register_cropped_element.png') baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_elem.png') shutil.copyfile(expected_image, baseline_file) # Create element mock web_element = get_mock_element(x=250, y=40, height=40, width=300) # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data # Assert screenshot visual.assert_screenshot(web_element, filename='screenshot_elem', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() def test_assert_screenshot_full_without_baseline(driver_wrapper): # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data driver_wrapper.config.set('VisualTests', 'fail', 'true') visual = VisualTest(driver_wrapper) # Assert screenshot with pytest.raises(AssertionError) as exc: visual.assert_screenshot(None, filename='screenshot_full', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_full.png') assert str(exc.value) == 'Baseline file not found: %s' % baseline_file def test_assert_screenshot_element_without_baseline(driver_wrapper): # Add baseline image driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 driver_wrapper.config.set('VisualTests', 'fail', 'true') visual = VisualTest(driver_wrapper) # Create element mock web_element = get_mock_element(x=250, y=40, height=40, width=300) # Configure driver mock with open(file_v1, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data # Assert screenshot with pytest.raises(AssertionError) as exc: visual.assert_screenshot(web_element, filename='screenshot_elem', file_suffix='screenshot_suffix') driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_elem.png') assert str(exc.value) == 'Baseline file not found: %s' % baseline_file def test_assert_screenshot_mobile_resize_and_exclude(driver_wrapper): # Create elements mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 exclude_elements = [get_mock_element(x=0, y=0, height=24, width=375)] # Configure driver mock with open(file_ios, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data driver_wrapper.driver.get_window_size.return_value = {'width': 375, 'height': 667} # Update conf and create a new VisualTest instance driver_wrapper.config.set('Driver', 'type', 'ios') driver_wrapper.config.set('VisualTests', 'save', 'true') visual = VisualTest(driver_wrapper) # Assert screenshot visual.assert_screenshot(None, filename='screenshot_ios', file_suffix='screenshot_suffix', exclude_elements=exclude_elements) driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() # Check cropped image expected_image = os.path.join(root_path, 'resources', 'ios_excluded.png') output_file = os.path.join(visual.output_directory, '01_screenshot_ios__screenshot_suffix.png') compare_image_files(output_file, expected_image) # Output image and new baseline image must be equal baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_ios.png') compare_image_files(output_file, baseline_file) def test_assert_screenshot_mobile_web_resize_and_exclude(driver_wrapper): # Create elements mock driver_wrapper.driver.execute_script.return_value = 0 # scrollX=0 and scrollY=0 form_element = get_mock_element(x=0, y=0, height=559, width=375) exclude_elements = [get_mock_element(x=15, y=296.515625, height=32, width=345)] # Configure driver mock file_ios_web = os.path.join(root_path, 'resources', 'ios_web.png') with open(file_ios_web, "rb") as f: image_data = f.read() driver_wrapper.driver.get_screenshot_as_png.return_value = image_data driver_wrapper.driver.get_window_size.return_value = {'width': 375, 'height': 667} # Update conf and create a new VisualTest instance driver_wrapper.config.set('Driver', 'type', 'ios') driver_wrapper.config.set('AppiumCapabilities', 'browserName', 'safari') driver_wrapper.config.set('VisualTests', 'save', 'true') visual = VisualTest(driver_wrapper) # Assert screenshot visual.assert_screenshot(form_element, filename='screenshot_ios_web', file_suffix='screenshot_suffix', exclude_elements=exclude_elements) driver_wrapper.driver.get_screenshot_as_png.assert_called_once_with() # Check cropped image expected_image = os.path.join(root_path, 'resources', 'ios_web_exclude.png') output_file = os.path.join(visual.output_directory, '01_screenshot_ios_web__screenshot_suffix.png') compare_image_files(output_file, expected_image) # Output image and new baseline image must be equal baseline_file = os.path.join(root_path, 'output', 'visualtests', 'baseline', 'firefox', 'screenshot_ios_web.png') compare_image_files(output_file, baseline_file) def test_assert_screenshot_str_threshold(driver_wrapper): visual = VisualTest(driver_wrapper) with pytest.raises(TypeError) as exc: visual.assert_screenshot(None, 'screenshot_full', threshold='name') assert str(exc.value) == 'Threshold must be a number between 0 and 1: name' def test_assert_screenshot_greater_threshold(driver_wrapper): visual = VisualTest(driver_wrapper) with pytest.raises(TypeError) as exc: visual.assert_screenshot(None, 'screenshot_full', threshold=2) assert str(exc.value) == 'Threshold must be a number between 0 and 1: 2'