@@ -21,6 +21,8 @@ def test_anything(self):
21
21
Code becomes greatly simplified and easier to maintain.
22
22
"""
23
23
24
+ import codecs
25
+ import json
24
26
import logging
25
27
import math
26
28
import os
@@ -38,6 +40,7 @@ def test_anything(self):
38
40
from seleniumbase .core import download_helper
39
41
from seleniumbase .core import log_helper
40
42
from seleniumbase .core import tour_helper
43
+ from seleniumbase .core import visual_helper
41
44
from seleniumbase .fixtures import constants
42
45
from seleniumbase .fixtures import js_utils
43
46
from seleniumbase .fixtures import page_actions
@@ -2436,6 +2439,198 @@ def switch_to_window(self, window, timeout=settings.SMALL_TIMEOUT):
2436
2439
def switch_to_default_window (self ):
2437
2440
self .switch_to_window (0 )
2438
2441
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
+
2439
2634
def save_screenshot (self , name , folder = None ):
2440
2635
""" The screenshot will be in PNG format. """
2441
2636
return page_actions .save_screenshot (self .driver , name , folder )
@@ -2888,6 +3083,7 @@ def setUp(self, masterqa_mode=False):
2888
3083
self .verify_delay = sb_config .verify_delay
2889
3084
self .disable_csp = sb_config .disable_csp
2890
3085
self .save_screenshot_after_test = sb_config .save_screenshot
3086
+ self .visual_baseline = sb_config .visual_baseline
2891
3087
self .timeout_multiplier = sb_config .timeout_multiplier
2892
3088
self .use_grid = False
2893
3089
if self .servername != "localhost" :
0 commit comments