@@ -21,6 +21,8 @@ def test_anything(self):
2121Code becomes greatly simplified and easier to maintain.
2222"""
2323
24+ import codecs
25+ import json
2426import logging
2527import math
2628import os
@@ -38,6 +40,7 @@ def test_anything(self):
3840from seleniumbase .core import download_helper
3941from seleniumbase .core import log_helper
4042from seleniumbase .core import tour_helper
43+ from seleniumbase .core import visual_helper
4144from seleniumbase .fixtures import constants
4245from seleniumbase .fixtures import js_utils
4346from 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