Module ez_visual_regression.api

Expand source code
# Standard lib dependencies
import os                                                              # Path verification & modification
import time                                                            # Allows for pauses
import logging                                                         # Enables logging
import webbrowser
from typing import Union, List

# Third Party Dependencies
## Image comparison
from ez_img_diff.api import compare_images

## Browser automation
from selenium import webdriver                                         # Instantiates a browser
from selenium.webdriver.common.by import By                            # Specify find_element type
from selenium.webdriver.chrome.options import Options                  # Allows webdriver config
from selenium.webdriver.remote.webdriver import WebDriver              # Used for type hinting
from selenium.common.exceptions import NoSuchElementException          # Allows for error catching

### Used to manage driver installation
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager

### Services needed for instantiating browsers
from selenium.webdriver.chrome.service import Service as ChromeService # Helps instantiate browser
from selenium.webdriver.edge.service import Service as EdgeService     # Helps instantiate browser
from selenium.webdriver.firefox.service import Service as FirefoxService


def get_screenshot(driver:WebDriver, url:str, filename:str, locator:Union[str, None]=None, ignored_elements: List[str]= None):
    """Takes a screenshot of a page or element

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    filename : str, optional
        The file to export the screenshot to, by default None

    locator : Union[str, None], optional
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)
        
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore

    Notes
    -----
    - If locator is not specified a full page screenshot is used
    
    
    References
    ----------
    - How to use by if you've never seen it https://selenium-python.readthedocs.io/locating-elements.html
    
    Raises
    ------
    FileNotFoundError
        If the URL is a file path and it does not exist
        
    Examples
    --------
    ### Create a screenshot of full page
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    get_screenshot(driver, URL, filename=filename)
    ```
    
    ### Create a screenshot of an element
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    locator = "#myChart"

    get_screenshot(driver, URL, filename=filename, locator=locator)
    ```
    
    ### Create a screenshot of full page while ignoring elements with the card class and nav element(s)
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    ignored_elements = ["nav", ".card"]

    get_screenshot(driver, URL, filename=filename, ignored_elements=ignored_elements)
    ```
    """
    if not url.startswith("http"):
        if (url.endswith(".html") or url.endswith(".pdf")) and not url.startswith("file:///"): # Assume file path
            logging.debug("URL provided does not have protocol, defaulting to file")
            abs_fp = os.path.abspath(url).replace("\\","/")
            if not os.path.exists(abs_fp):
                raise FileNotFoundError(f"File path {abs_fp} does not exist")
            url = f"file:///{abs_fp}"
        else:
            url = "http://" + url
    if not os.path.exists(os.path.dirname(filename)):
        os.mkdir(os.path.dirname(filename))
    print(f"{filename=} {locator=}")
    driver.get(url)

    
    # Wait for page to load and run all animations
    time.sleep(3) # TODO: Be better
    if ignored_elements:
        for selector in ignored_elements:
            driver.execute_script(f'document.querySelectorAll("{selector}").forEach((el)=>{{el.style.opacity=0}})')
    if locator: # Screenshot element
        try:
            driver.find_elements(By.CSS_SELECTOR, locator)[0].screenshot(filename)
        except NoSuchElementException:
            logging.error(f"\033[0;m Element does not exist when looking for css selector: {locator} confirm spelling and capitalization\033[1;37m")
            exit(1)
    else: # Screenshot page
        driver.save_screenshot(filename)

def compare_multiple_elements(driver:WebDriver, url:str, folder:str, locator:str, ignored_elements: List[str]= None) -> List[float]:
    """Regression test multiple elements

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    folder : str
        The folder to export the screenshots to

    locator : str
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)
        
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore

    Notes
    -----
    - If locator is not specified a full page screenshot is used
    
    
    References
    ----------
    - How to use by if you've never seen it https://selenium-python.readthedocs.io/locating-elements.html
    
    Raises
    ------
    FileNotFoundError
        If the URL is a file path and it does not exist
    
    Returns
    -------
    List[float]:
        The differences of each found element
    
    Examples
    --------
    ### Compare multiple elements
    ```
    from ez_visual_regression.api import compare_multiple_elements,instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    
    compare_multiple_elements(driver, URL,folder, locator) # Returns (assuming 3 total elements): [0.1, 19.8, 0.4]
    ```
    
    ### Compare multiple elements while ignoring divs and the red class
    ```
    from ez_visual_regression.api import compare_multiple_elements,nstantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    ignored_elements= ["div", ".red"]

    compare_multiple_elements(driver, URL,folder, locator, ignored_elements) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
    ```
    """
    
    if not url.startswith("http"):
        if (url.endswith(".html") or url.endswith(".pdf")) and not url.startswith("file:///"): # Assume file path
            logging.debug("URL provided does not have protocol, defaulting to file")
            abs_fp = os.path.abspath(url).replace("\\","/")
            if not os.path.exists(abs_fp):
                raise FileNotFoundError(f"File path {abs_fp} does not exist")
            url = f"file:///{abs_fp}"
        else:
            url = "http://" + url
    if not os.path.isdir(folder):
        os.mkdir(folder)
    
    driver.get(url)
    
    # Wait for page to load and run all animations
    time.sleep(3) # TODO: Be better
    if ignored_elements:
        for selector in ignored_elements:
            driver.execute_script(f'document.querySelectorAll("{selector}").forEach((el)=>{{el.style.opacity=0}})')

    try:
        elements = driver.find_elements(By.CSS_SELECTOR, locator)
        baseline_images = [(os.path.join(folder,f"baseline-{index}.png"), element) for index, element in enumerate(elements)]
        current_images = [element for element in elements]

        diffs = []
        
        for path, element in baseline_images:
            if not os.path.exists(path):
                element.screenshot(path)
                
        for index,element in enumerate(current_images):
            element.screenshot(os.path.join(folder,f"current-{index}.png"))
            diffs.append(compare_images(os.path.join(folder,f"current-{index}.png"), os.path.join(folder,f"baseline-{index}.png"), os.path.join(folder,f"diff-{index}.png"),os.path.join(folder,f"thresh-{index}.png")))
            
    except NoSuchElementException:
        logging.error(f"\033[0;m Element does not exist when looking for css selector: {locator} confirm spelling and capitalization\033[1;37m")
        exit(1)
    return diffs

def assert_image_similarity_to_baseline(driver:WebDriver, url:str, folder:str, locator:Union[str, None]=None, warning_threshold:float=10, error_threshold:float=30, ignored_elements: List[str]= None, multielements:bool=False) -> Union[float, List[float]]:
    """Asserts the current screenshot of a page is similar to `<folder>/baseline.png` within: 0 < diff < error_threshold

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    folder : str
        The folder to save the baseline, threshold, current and difference images to

    locator : Union[str, None], optional
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)

    warning_threshold : float, optional
        The threshold at which there will be a logged warning, by default 10

    error_threshold : float, optional
        The threshold at which an explicit error will be thrown, by default 30
    
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore
        
    multielements: bool, option
        Whether to screenshot all occurances of a css selector (True), or just the firs occurance (False), default False

    Raises
    ------
    AssertionError
        If diff > error_threshold
        
    Returns
    -------
    Union[float, List[float]]:
        The difference between the two images as a whole number percent (i.e. 1.313 is 1.313% or 15.928 is 15.928%), or list of floats if multielement is True
    
    Examples
    --------
    ### Create baseline images for a webpage
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    locator = "#myChart" # The queryselector to find an element with

    # Creates baseline if one isn't available
    assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder)
    ```
    
    ### Test against baseline image
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    locator = "#myChart" # The queryselector to find an element with

    try:
        assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder )
    except AssertionError:
        print("Image too far from baseline!")
    ```
    
    ### Take screenshot of whole page while ignoring h2's and elements with id of myChart
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    
    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    ignored_elements = ["h2", "#myChart"]

    assert_image_similarity_to_baseline(driver,url, folder=folder, ignored_elements=ignored_elements)
    ```
    
    ### Take screenshot of all nav elements (not just 1) on a page
    ```
    from ez_visual_regression.api import compare_multiple_elements, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    ignored_elements= ["div", ".red"]

    assert_image_similarity_to_baseline(driver, URL, folder, locator, ignored_elements=ignored_elements, multielements=True) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
    ```
    """
    if not os.path.isdir(folder):
        print(f"No directory was found called {folder}, creating...")
        os.mkdir(folder)
    if not os.path.exists(os.path.join(folder, "baseline.png")) and not multielements:
        print(f"No baseline image(s) found in {os.path.join(folder, 'baseline.png')}, creating...")
        get_screenshot(driver, url, os.path.join(folder, "baseline.png"), locator, ignored_elements)

    if multielements:
        diffs = compare_multiple_elements(driver, url, folder, locator, ignored_elements)
        return diffs
    else:
        get_screenshot(driver, url, os.path.join(folder, "current.png"), locator, ignored_elements)

        diff = compare_images(os.path.join(folder, "baseline.png"), os.path.join(folder, "current.png"), os.path.join(folder, "diff.png"), os.path.join(folder, "thresh.png"))

        if diff > error_threshold:
            logging.error(f"Difference {diff} is over error threshold {error_threshold}")
            raise AssertionError(f"Difference {diff} is over error threshold {error_threshold}")
        if error_threshold > diff > warning_threshold:
            logging.warning(f"Difference {diff} is over warning threshold {error_threshold}")
            
        return diff
    
def instantiate_driver(driver:str) -> WebDriver:
    """Creates a webdriver based on a driver name

    Parameters
    ----------
    driver : str
        The name of the driver to use (can be "edge", "chrome" or "firefox")

    Returns
    -------
    WebDriver
        The webdriver for the specified browser

    Raises
    ------
    ValueError
        If the driver does not exist
    
    Examples
    --------
    ### Get any supported browser, and instantiate it
    ```
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome" # Either "chrome", "firefox","edge" or raises webbrowser.Error
    instantiate_driver(driver_name) # Returns a WebDriver of the correct browser type
    ```
    
    ### Check if person has chrome installed
    ```
    from ez_visual_regression.api import instantiate_driver

    get_installed_driver("chrome") # Either "chrome" or raises webbrowser.Error
    instantiate_driver(driver_name) # Returns a Chrome.WebDriver
    ```
    """
    if driver == "chrome":
        chrome_options = Options()
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--no-sandbox")
        return webdriver.Chrome(options=chrome_options, service=ChromeService(ChromeDriverManager().install()))
    elif driver == "edge":
        edge_options = Options()
        return webdriver.Edge(options=edge_options, service=EdgeService(EdgeChromiumDriverManager().install()))
    elif driver == "firefox":
        return webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install()))
    else:
        raise ValueError(f"Driver not supported {driver}")

Functions

def assert_image_similarity_to_baseline(driver: selenium.webdriver.remote.webdriver.WebDriver, url: str, folder: str, locator: Optional[str] = None, warning_threshold: float = 10, error_threshold: float = 30, ignored_elements: List[str] = None, multielements: bool = False) ‑> Union[float, List[float]]

Asserts the current screenshot of a page is similar to <folder>/baseline.png within: 0 < diff < error_threshold

Parameters

driver : WebDriver
The browser to use for capturing screenshots
url : str
The URl you want to get a screenshot from (or filepath)
folder : str
The folder to save the baseline, threshold, current and difference images to
locator : Union[str, None], optional
The CSS selector to search the element with (i.e. #myChart, .rows etc.)
warning_threshold : float, optional
The threshold at which there will be a logged warning, by default 10
error_threshold : float, optional
The threshold at which an explicit error will be thrown, by default 30
ignored_elements : List[str], option
Use a query selector to specify elements to ignore
multielements : bool, option
Whether to screenshot all occurances of a css selector (True), or just the firs occurance (False), default False

Raises

AssertionError
If diff > error_threshold

Returns

Union[float, List[float]]:
The difference between the two images as a whole number percent (i.e. 1.313 is 1.313% or 15.928 is 15.928%), or list of floats if multielement is True

Examples

Create baseline images for a webpage

# Setup driver
from ez_visual_regression.api import instantiate_driver

driver_name = "chrome"
driver = instantiate_driver(driver_name)

# import functions needed for testing
from ez_visual_regression.api import assert_image_similarity_to_baseline

url = "tests/example_sites/no_difference/index.html" # File in this case
folder = "tests/example_sites/no_difference" # Where to store output images
locator = "#myChart" # The queryselector to find an element with

# Creates baseline if one isn't available
assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder)

Test against baseline image

# Setup driver
from ez_visual_regression.api import instantiate_driver

driver_name = "chrome"
driver = instantiate_driver(driver_name)

# import functions needed for testing
from ez_visual_regression.api import assert_image_similarity_to_baseline

url = "tests/example_sites/no_difference/index.html" # File in this case
folder = "tests/example_sites/no_difference" # Where to store output images
locator = "#myChart" # The queryselector to find an element with

try:
    assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder )
except AssertionError:
    print("Image too far from baseline!")

Take screenshot of whole page while ignoring h2's and elements with id of myChart

# Setup driver
from ez_visual_regression.api import instantiate_driver

driver_name = "chrome"
driver = instantiate_driver(driver_name)

# import functions needed for testing
from ez_visual_regression.api import assert_image_similarity_to_baseline

url = "tests/example_sites/no_difference/index.html" # File in this case
folder = "tests/example_sites/no_difference" # Where to store output images
ignored_elements = ["h2", "#myChart"]

assert_image_similarity_to_baseline(driver,url, folder=folder, ignored_elements=ignored_elements)

Take screenshot of all nav elements (not just 1) on a page

from ez_visual_regression.api import compare_multiple_elements, instantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
folder = "canadiancoding"

driver = instantiate_driver("chrome")
locator = ".nav-item p"
ignored_elements= ["div", ".red"]

assert_image_similarity_to_baseline(driver, URL, folder, locator, ignored_elements=ignored_elements, multielements=True) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
Expand source code
def assert_image_similarity_to_baseline(driver:WebDriver, url:str, folder:str, locator:Union[str, None]=None, warning_threshold:float=10, error_threshold:float=30, ignored_elements: List[str]= None, multielements:bool=False) -> Union[float, List[float]]:
    """Asserts the current screenshot of a page is similar to `<folder>/baseline.png` within: 0 < diff < error_threshold

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    folder : str
        The folder to save the baseline, threshold, current and difference images to

    locator : Union[str, None], optional
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)

    warning_threshold : float, optional
        The threshold at which there will be a logged warning, by default 10

    error_threshold : float, optional
        The threshold at which an explicit error will be thrown, by default 30
    
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore
        
    multielements: bool, option
        Whether to screenshot all occurances of a css selector (True), or just the firs occurance (False), default False

    Raises
    ------
    AssertionError
        If diff > error_threshold
        
    Returns
    -------
    Union[float, List[float]]:
        The difference between the two images as a whole number percent (i.e. 1.313 is 1.313% or 15.928 is 15.928%), or list of floats if multielement is True
    
    Examples
    --------
    ### Create baseline images for a webpage
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    locator = "#myChart" # The queryselector to find an element with

    # Creates baseline if one isn't available
    assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder)
    ```
    
    ### Test against baseline image
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    locator = "#myChart" # The queryselector to find an element with

    try:
        assert_image_similarity_to_baseline(driver,url, locator=locator, folder=folder )
    except AssertionError:
        print("Image too far from baseline!")
    ```
    
    ### Take screenshot of whole page while ignoring h2's and elements with id of myChart
    ```
    # Setup driver
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    
    # import functions needed for testing
    from ez_visual_regression.api import assert_image_similarity_to_baseline

    url = "tests/example_sites/no_difference/index.html" # File in this case
    folder = "tests/example_sites/no_difference" # Where to store output images
    ignored_elements = ["h2", "#myChart"]

    assert_image_similarity_to_baseline(driver,url, folder=folder, ignored_elements=ignored_elements)
    ```
    
    ### Take screenshot of all nav elements (not just 1) on a page
    ```
    from ez_visual_regression.api import compare_multiple_elements, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    ignored_elements= ["div", ".red"]

    assert_image_similarity_to_baseline(driver, URL, folder, locator, ignored_elements=ignored_elements, multielements=True) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
    ```
    """
    if not os.path.isdir(folder):
        print(f"No directory was found called {folder}, creating...")
        os.mkdir(folder)
    if not os.path.exists(os.path.join(folder, "baseline.png")) and not multielements:
        print(f"No baseline image(s) found in {os.path.join(folder, 'baseline.png')}, creating...")
        get_screenshot(driver, url, os.path.join(folder, "baseline.png"), locator, ignored_elements)

    if multielements:
        diffs = compare_multiple_elements(driver, url, folder, locator, ignored_elements)
        return diffs
    else:
        get_screenshot(driver, url, os.path.join(folder, "current.png"), locator, ignored_elements)

        diff = compare_images(os.path.join(folder, "baseline.png"), os.path.join(folder, "current.png"), os.path.join(folder, "diff.png"), os.path.join(folder, "thresh.png"))

        if diff > error_threshold:
            logging.error(f"Difference {diff} is over error threshold {error_threshold}")
            raise AssertionError(f"Difference {diff} is over error threshold {error_threshold}")
        if error_threshold > diff > warning_threshold:
            logging.warning(f"Difference {diff} is over warning threshold {error_threshold}")
            
        return diff
def compare_multiple_elements(driver: selenium.webdriver.remote.webdriver.WebDriver, url: str, folder: str, locator: str, ignored_elements: List[str] = None) ‑> List[float]

Regression test multiple elements

Parameters

driver : WebDriver
The browser to use for capturing screenshots
url : str
The URl you want to get a screenshot from (or filepath)
folder : str
The folder to export the screenshots to
locator : str
The CSS selector to search the element with (i.e. #myChart, .rows etc.)
ignored_elements : List[str], option
Use a query selector to specify elements to ignore

Notes

  • If locator is not specified a full page screenshot is used

References

Raises

FileNotFoundError
If the URL is a file path and it does not exist

Returns

List[float]:
The differences of each found element

Examples

Compare multiple elements

from ez_visual_regression.api import compare_multiple_elements,instantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
folder = "canadiancoding"

driver = instantiate_driver("chrome")
locator = ".nav-item p"

compare_multiple_elements(driver, URL,folder, locator) # Returns (assuming 3 total elements): [0.1, 19.8, 0.4]

Compare multiple elements while ignoring divs and the red class

from ez_visual_regression.api import compare_multiple_elements,nstantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
folder = "canadiancoding"

driver = instantiate_driver("chrome")
locator = ".nav-item p"
ignored_elements= ["div", ".red"]

compare_multiple_elements(driver, URL,folder, locator, ignored_elements) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
Expand source code
def compare_multiple_elements(driver:WebDriver, url:str, folder:str, locator:str, ignored_elements: List[str]= None) -> List[float]:
    """Regression test multiple elements

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    folder : str
        The folder to export the screenshots to

    locator : str
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)
        
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore

    Notes
    -----
    - If locator is not specified a full page screenshot is used
    
    
    References
    ----------
    - How to use by if you've never seen it https://selenium-python.readthedocs.io/locating-elements.html
    
    Raises
    ------
    FileNotFoundError
        If the URL is a file path and it does not exist
    
    Returns
    -------
    List[float]:
        The differences of each found element
    
    Examples
    --------
    ### Compare multiple elements
    ```
    from ez_visual_regression.api import compare_multiple_elements,instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    
    compare_multiple_elements(driver, URL,folder, locator) # Returns (assuming 3 total elements): [0.1, 19.8, 0.4]
    ```
    
    ### Compare multiple elements while ignoring divs and the red class
    ```
    from ez_visual_regression.api import compare_multiple_elements,nstantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    folder = "canadiancoding"

    driver = instantiate_driver("chrome")
    locator = ".nav-item p"
    ignored_elements= ["div", ".red"]

    compare_multiple_elements(driver, URL,folder, locator, ignored_elements) # Returns (assuming 3 total elements): [0.1, 0.0, 0.4]
    ```
    """
    
    if not url.startswith("http"):
        if (url.endswith(".html") or url.endswith(".pdf")) and not url.startswith("file:///"): # Assume file path
            logging.debug("URL provided does not have protocol, defaulting to file")
            abs_fp = os.path.abspath(url).replace("\\","/")
            if not os.path.exists(abs_fp):
                raise FileNotFoundError(f"File path {abs_fp} does not exist")
            url = f"file:///{abs_fp}"
        else:
            url = "http://" + url
    if not os.path.isdir(folder):
        os.mkdir(folder)
    
    driver.get(url)
    
    # Wait for page to load and run all animations
    time.sleep(3) # TODO: Be better
    if ignored_elements:
        for selector in ignored_elements:
            driver.execute_script(f'document.querySelectorAll("{selector}").forEach((el)=>{{el.style.opacity=0}})')

    try:
        elements = driver.find_elements(By.CSS_SELECTOR, locator)
        baseline_images = [(os.path.join(folder,f"baseline-{index}.png"), element) for index, element in enumerate(elements)]
        current_images = [element for element in elements]

        diffs = []
        
        for path, element in baseline_images:
            if not os.path.exists(path):
                element.screenshot(path)
                
        for index,element in enumerate(current_images):
            element.screenshot(os.path.join(folder,f"current-{index}.png"))
            diffs.append(compare_images(os.path.join(folder,f"current-{index}.png"), os.path.join(folder,f"baseline-{index}.png"), os.path.join(folder,f"diff-{index}.png"),os.path.join(folder,f"thresh-{index}.png")))
            
    except NoSuchElementException:
        logging.error(f"\033[0;m Element does not exist when looking for css selector: {locator} confirm spelling and capitalization\033[1;37m")
        exit(1)
    return diffs
def get_screenshot(driver: selenium.webdriver.remote.webdriver.WebDriver, url: str, filename: str, locator: Optional[str] = None, ignored_elements: List[str] = None)

Takes a screenshot of a page or element

Parameters

driver : WebDriver
The browser to use for capturing screenshots
url : str
The URl you want to get a screenshot from (or filepath)
filename : str, optional
The file to export the screenshot to, by default None
locator : Union[str, None], optional
The CSS selector to search the element with (i.e. #myChart, .rows etc.)
ignored_elements : List[str], option
Use a query selector to specify elements to ignore

Notes

  • If locator is not specified a full page screenshot is used

References

Raises

FileNotFoundError
If the URL is a file path and it does not exist

Examples

Create a screenshot of full page

from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
filename = "screenshot.png"

driver_name = "chrome"
driver = instantiate_driver(driver_name)

get_screenshot(driver, URL, filename=filename)

Create a screenshot of an element

from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
filename = "screenshot.png"

driver_name = "chrome"
driver = instantiate_driver(driver_name)
locator = "#myChart"

get_screenshot(driver, URL, filename=filename, locator=locator)

Create a screenshot of full page while ignoring elements with the card class and nav element(s)

from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

# Configuration variables
URL = "https://canadiancoding.ca"
filename = "screenshot.png"

driver_name = "chrome"
driver = instantiate_driver(driver_name)
ignored_elements = ["nav", ".card"]

get_screenshot(driver, URL, filename=filename, ignored_elements=ignored_elements)
Expand source code
def get_screenshot(driver:WebDriver, url:str, filename:str, locator:Union[str, None]=None, ignored_elements: List[str]= None):
    """Takes a screenshot of a page or element

    Parameters
    ----------
    driver : WebDriver
        The browser to use for capturing screenshots

    url : str
        The URl you want to get a screenshot from (or filepath)

    filename : str, optional
        The file to export the screenshot to, by default None

    locator : Union[str, None], optional
        The CSS selector to search the element with (i.e. #myChart, .rows etc.)
        
    ignored_elements: List[str], option
        Use a query selector to specify elements to ignore

    Notes
    -----
    - If locator is not specified a full page screenshot is used
    
    
    References
    ----------
    - How to use by if you've never seen it https://selenium-python.readthedocs.io/locating-elements.html
    
    Raises
    ------
    FileNotFoundError
        If the URL is a file path and it does not exist
        
    Examples
    --------
    ### Create a screenshot of full page
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)

    get_screenshot(driver, URL, filename=filename)
    ```
    
    ### Create a screenshot of an element
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    locator = "#myChart"

    get_screenshot(driver, URL, filename=filename, locator=locator)
    ```
    
    ### Create a screenshot of full page while ignoring elements with the card class and nav element(s)
    ```
    from ez_visual_regression.api import get_screenshot, get_installed_driver, instantiate_driver

    # Configuration variables
    URL = "https://canadiancoding.ca"
    filename = "screenshot.png"

    driver_name = "chrome"
    driver = instantiate_driver(driver_name)
    ignored_elements = ["nav", ".card"]

    get_screenshot(driver, URL, filename=filename, ignored_elements=ignored_elements)
    ```
    """
    if not url.startswith("http"):
        if (url.endswith(".html") or url.endswith(".pdf")) and not url.startswith("file:///"): # Assume file path
            logging.debug("URL provided does not have protocol, defaulting to file")
            abs_fp = os.path.abspath(url).replace("\\","/")
            if not os.path.exists(abs_fp):
                raise FileNotFoundError(f"File path {abs_fp} does not exist")
            url = f"file:///{abs_fp}"
        else:
            url = "http://" + url
    if not os.path.exists(os.path.dirname(filename)):
        os.mkdir(os.path.dirname(filename))
    print(f"{filename=} {locator=}")
    driver.get(url)

    
    # Wait for page to load and run all animations
    time.sleep(3) # TODO: Be better
    if ignored_elements:
        for selector in ignored_elements:
            driver.execute_script(f'document.querySelectorAll("{selector}").forEach((el)=>{{el.style.opacity=0}})')
    if locator: # Screenshot element
        try:
            driver.find_elements(By.CSS_SELECTOR, locator)[0].screenshot(filename)
        except NoSuchElementException:
            logging.error(f"\033[0;m Element does not exist when looking for css selector: {locator} confirm spelling and capitalization\033[1;37m")
            exit(1)
    else: # Screenshot page
        driver.save_screenshot(filename)
def instantiate_driver(driver: str) ‑> selenium.webdriver.remote.webdriver.WebDriver

Creates a webdriver based on a driver name

Parameters

driver : str
The name of the driver to use (can be "edge", "chrome" or "firefox")

Returns

WebDriver
The webdriver for the specified browser

Raises

ValueError
If the driver does not exist

Examples

Get any supported browser, and instantiate it

from ez_visual_regression.api import instantiate_driver

driver_name = "chrome" # Either "chrome", "firefox","edge" or raises webbrowser.Error
instantiate_driver(driver_name) # Returns a WebDriver of the correct browser type

Check if person has chrome installed

from ez_visual_regression.api import instantiate_driver

get_installed_driver("chrome") # Either "chrome" or raises webbrowser.Error
instantiate_driver(driver_name) # Returns a Chrome.WebDriver
Expand source code
def instantiate_driver(driver:str) -> WebDriver:
    """Creates a webdriver based on a driver name

    Parameters
    ----------
    driver : str
        The name of the driver to use (can be "edge", "chrome" or "firefox")

    Returns
    -------
    WebDriver
        The webdriver for the specified browser

    Raises
    ------
    ValueError
        If the driver does not exist
    
    Examples
    --------
    ### Get any supported browser, and instantiate it
    ```
    from ez_visual_regression.api import instantiate_driver

    driver_name = "chrome" # Either "chrome", "firefox","edge" or raises webbrowser.Error
    instantiate_driver(driver_name) # Returns a WebDriver of the correct browser type
    ```
    
    ### Check if person has chrome installed
    ```
    from ez_visual_regression.api import instantiate_driver

    get_installed_driver("chrome") # Either "chrome" or raises webbrowser.Error
    instantiate_driver(driver_name) # Returns a Chrome.WebDriver
    ```
    """
    if driver == "chrome":
        chrome_options = Options()
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--no-sandbox")
        return webdriver.Chrome(options=chrome_options, service=ChromeService(ChromeDriverManager().install()))
    elif driver == "edge":
        edge_options = Options()
        return webdriver.Edge(options=edge_options, service=EdgeService(EdgeChromiumDriverManager().install()))
    elif driver == "firefox":
        return webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install()))
    else:
        raise ValueError(f"Driver not supported {driver}")