Skip to content

Commit 0950aef

Browse files
committed
Add check_window() for making visual comparison validations
1 parent ded0dad commit 0950aef

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

seleniumbase/fixtures/base_case.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def test_anything(self):
2121
Code becomes greatly simplified and easier to maintain.
2222
"""
2323

24+
import codecs
25+
import json
2426
import logging
2527
import math
2628
import os
@@ -38,6 +40,7 @@ def test_anything(self):
3840
from seleniumbase.core import download_helper
3941
from seleniumbase.core import log_helper
4042
from seleniumbase.core import tour_helper
43+
from seleniumbase.core import visual_helper
4144
from seleniumbase.fixtures import constants
4245
from seleniumbase.fixtures import js_utils
4346
from seleniumbase.fixtures import page_actions
@@ -2436,6 +2439,198 @@ def switch_to_window(self, window, timeout=settings.SMALL_TIMEOUT):
24362439
def switch_to_default_window(self):
24372440
self.switch_to_window(0)
24382441

2442+
def check_window(self, name="default", level=0, baseline=False):
2443+
""" *** Automated Visual Testing with SeleniumBase ***
2444+
2445+
The first time a test calls self.check_window() for a unique "name"
2446+
parameter provided, it will set a visual baseline, meaning that it
2447+
creates a folder, saves the URL to a file, saves the current window
2448+
screenshot to a file, and creates the following three files
2449+
with the listed data saved:
2450+
tags_level1.txt -> HTML tags from the window
2451+
tags_level2.txt -> HTML tags + attributes from the window
2452+
tags_level3.txt -> HTML tags + attributes/values from the window
2453+
2454+
Baseline folders are named based on the test name and the name
2455+
parameter passed to self.check_window(). The same test can store
2456+
multiple baseline folders.
2457+
2458+
If the baseline is being set/reset, the "level" doesn't matter.
2459+
2460+
After the first run of self.check_window(), it will compare the
2461+
HTML tags of the latest window to the one from the initial run.
2462+
Here's how the level system works:
2463+
* level=0 ->
2464+
DRY RUN ONLY - Will perform a comparison to the baseline, and
2465+
print out any differences that are found, but
2466+
won't fail the test even if differences exist.
2467+
* level=1 ->
2468+
HTML tags are compared to tags_level1.txt
2469+
* level=2 ->
2470+
HTML tags are compared to tags_level1.txt and
2471+
HTML tags/attributes are compared to tags_level2.txt
2472+
* level=3 ->
2473+
HTML tags are compared to tags_level1.txt and
2474+
HTML tags + attributes are compared to tags_level2.txt and
2475+
HTML tags + attributes/values are compared to tags_level3.txt
2476+
As shown, Level-3 is the most strict, Level-1 is the least strict.
2477+
If the comparisons from the latest window to the existing baseline
2478+
don't match, the current test will fail, except for Level-0 tests.
2479+
2480+
You can reset the visual baseline on the command line by using:
2481+
--visual_baseline
2482+
As long as "--visual_baseline" is used on the command line while
2483+
running tests, the self.check_window() method cannot fail because
2484+
it will rebuild the visual baseline rather than comparing the html
2485+
tags of the latest run to the existing baseline. If there are any
2486+
expected layout changes to a website that you're testing, you'll
2487+
need to reset the baseline to prevent unnecessary failures.
2488+
2489+
self.check_window() will fail with "Page Domain Mismatch Failure"
2490+
if the page domain doesn't match the domain of the baseline.
2491+
2492+
If you want to use self.check_window() to compare a web page to
2493+
a later version of itself from within the same test run, you can
2494+
add the parameter "baseline=True" to the first time you call
2495+
self.check_window() in a test to use that as the baseline. This
2496+
only makes sense if you're calling self.check_window() more than
2497+
once with the same name parameter in the same test.
2498+
2499+
Automated Visual Testing with self.check_window() is not very
2500+
effective for websites that have dynamic content that changes
2501+
the layout and structure of web pages. For those, you're much
2502+
better off using regular SeleniumBase functional testing.
2503+
2504+
Example usage:
2505+
self.check_window(name="testing", level=0)
2506+
self.check_window(name="xkcd_home", level=1)
2507+
self.check_window(name="github_page", level=2)
2508+
self.check_window(name="wikipedia_page", level=3)
2509+
"""
2510+
if level == "0":
2511+
level = 0
2512+
if level == "1":
2513+
level = 1
2514+
if level == "2":
2515+
level = 2
2516+
if level == "3":
2517+
level = 3
2518+
if level != 0 and level != 1 and level != 2 and level != 3:
2519+
raise Exception('Parameter "level" must be set to 0, 1, 2, or 3!')
2520+
2521+
module = self.__class__.__module__
2522+
if '.' in module and len(module.split('.')[-1]) > 1:
2523+
module = module.split('.')[-1]
2524+
test_id = "%s.%s" % (module, self._testMethodName)
2525+
if not name or len(name) < 1:
2526+
name = "default"
2527+
name = str(name)
2528+
visual_helper.visual_baseline_folder_setup()
2529+
baseline_dir = constants.VisualBaseline.STORAGE_FOLDER
2530+
visual_baseline_path = baseline_dir + "/" + test_id + "/" + name
2531+
page_url_file = visual_baseline_path + "/page_url.txt"
2532+
screenshot_file = visual_baseline_path + "/screenshot.png"
2533+
level_1_file = visual_baseline_path + "/tags_level_1.txt"
2534+
level_2_file = visual_baseline_path + "/tags_level_2.txt"
2535+
level_3_file = visual_baseline_path + "/tags_level_3.txt"
2536+
2537+
set_baseline = False
2538+
if baseline or self.visual_baseline:
2539+
set_baseline = True
2540+
if not os.path.exists(visual_baseline_path):
2541+
set_baseline = True
2542+
try:
2543+
os.makedirs(visual_baseline_path)
2544+
except Exception:
2545+
pass # Only reachable during multi-threaded test runs
2546+
if not os.path.exists(page_url_file):
2547+
set_baseline = True
2548+
if not os.path.exists(screenshot_file):
2549+
set_baseline = True
2550+
if not os.path.exists(level_1_file):
2551+
set_baseline = True
2552+
if not os.path.exists(level_2_file):
2553+
set_baseline = True
2554+
if not os.path.exists(level_3_file):
2555+
set_baseline = True
2556+
2557+
page_url = self.get_current_url()
2558+
soup = self.get_beautiful_soup()
2559+
html_tags = soup.body.find_all()
2560+
level_1 = [[tag.name] for tag in html_tags]
2561+
level_1 = json.loads(json.dumps(level_1)) # Tuples become lists
2562+
level_2 = [[tag.name, sorted(tag.attrs.keys())] for tag in html_tags]
2563+
level_2 = json.loads(json.dumps(level_2)) # Tuples become lists
2564+
level_3 = [[tag.name, sorted(tag.attrs.items())] for tag in html_tags]
2565+
level_3 = json.loads(json.dumps(level_3)) # Tuples become lists
2566+
2567+
if set_baseline:
2568+
self.save_screenshot("screenshot.png", visual_baseline_path)
2569+
out_file = codecs.open(page_url_file, "w+")
2570+
out_file.writelines(page_url)
2571+
out_file.close()
2572+
out_file = codecs.open(level_1_file, "w+")
2573+
out_file.writelines(json.dumps(level_1))
2574+
out_file.close()
2575+
out_file = codecs.open(level_2_file, "w+")
2576+
out_file.writelines(json.dumps(level_2))
2577+
out_file.close()
2578+
out_file = codecs.open(level_3_file, "w+")
2579+
out_file.writelines(json.dumps(level_3))
2580+
out_file.close()
2581+
2582+
if not set_baseline:
2583+
f = open(page_url_file, 'r')
2584+
page_url_data = f.read().strip()
2585+
f.close()
2586+
f = open(level_1_file, 'r')
2587+
level_1_data = json.loads(f.read())
2588+
f.close()
2589+
f = open(level_2_file, 'r')
2590+
level_2_data = json.loads(f.read())
2591+
f.close()
2592+
f = open(level_3_file, 'r')
2593+
level_3_data = json.loads(f.read())
2594+
f.close()
2595+
2596+
domain_fail = (
2597+
"Page Domain Mismatch Failure: "
2598+
"Current Page Domain doesn't match the Page Domain of the "
2599+
"Baseline! Can't compare two completely different sites! "
2600+
"Run with --visual_baseline to reset the baseline!")
2601+
level_1_failure = (
2602+
"\n\n*** Exception: <Level 1> Visual Diff Failure:\n"
2603+
"* HTML tags don't match the baseline!")
2604+
level_2_failure = (
2605+
"\n\n*** Exception: <Level 2> Visual Diff Failure:\n"
2606+
"* HTML tag attributes don't match the baseline!")
2607+
level_3_failure = (
2608+
"\n\n*** Exception: <Level 3> Visual Diff Failure:\n"
2609+
"* HTML tag attribute values don't match the baseline!")
2610+
2611+
page_domain = self.get_domain_url(page_url)
2612+
page_data_domain = self.get_domain_url(page_url_data)
2613+
unittest.TestCase.maxDiff = 1000
2614+
if level == 1 or level == 2 or level == 3:
2615+
self.assert_equal(page_domain, page_data_domain, domain_fail)
2616+
self.assert_equal(level_1, level_1_data, level_1_failure)
2617+
unittest.TestCase.maxDiff = None
2618+
if level == 2 or level == 3:
2619+
self.assert_equal(level_2, level_2_data, level_2_failure)
2620+
if level == 3:
2621+
self.assert_equal(level_3, level_3_data, level_3_failure)
2622+
if level == 0:
2623+
try:
2624+
unittest.TestCase.maxDiff = 1000
2625+
self.assert_equal(
2626+
page_domain, page_data_domain, domain_fail)
2627+
self.assert_equal(level_1, level_1_data, level_1_failure)
2628+
unittest.TestCase.maxDiff = None
2629+
self.assert_equal(level_2, level_2_data, level_2_failure)
2630+
self.assert_equal(level_3, level_3_data, level_3_failure)
2631+
except Exception as e:
2632+
print(e) # Level-0 Dry Run (Only print the differences)
2633+
24392634
def save_screenshot(self, name, folder=None):
24402635
""" The screenshot will be in PNG format. """
24412636
return page_actions.save_screenshot(self.driver, name, folder)
@@ -2888,6 +3083,7 @@ def setUp(self, masterqa_mode=False):
28883083
self.verify_delay = sb_config.verify_delay
28893084
self.disable_csp = sb_config.disable_csp
28903085
self.save_screenshot_after_test = sb_config.save_screenshot
3086+
self.visual_baseline = sb_config.visual_baseline
28913087
self.timeout_multiplier = sb_config.timeout_multiplier
28923088
self.use_grid = False
28933089
if self.servername != "localhost":

0 commit comments

Comments
 (0)