Skip to content

Framework

The botcity.web module contains specialized implementations aimed at Web automation such as WebBot which is described below.

You are expected to implement the action method of the WebBot class in your Bot class.

Here is a very brief example of a bot which opens the BotCity website using Google Chrome and the ChromeDriver WebDriver to remote control the browser.

from botcity.web import WebBot


class Bot(WebBot):
    def action(self, execution=None
        # Configure whether or not to run on headless mode
        self.headless = False

        # Opens the BotCity website.
        self.browse("https://botcity.dev/en")

        # Wait for 10 seconds before closing everything
        self.sleep(10000)

        # Stop the browser and clean up
        self.stop_browser()


if __name__ == '__main__':
    Bot.main()

botcity.web.bot.WebBot (BaseBot)

Source code in web/bot.py
class WebBot(BaseBot):
    KEYS = Keys
    DEFAULT_DIMENSIONS = (1600, 900)
    """
    Base class for Web Bots.
    Users must implement the `action` method in their classes.

    Attributes:
        state (State): The internal state of this bot.
        maestro (BotMaestroSDK): an instance to interact with the BotMaestro server.

    """

    def __init__(self, headless=False):
        self.state = State()
        self.maestro = BotMaestroSDK() if MAESTRO_AVAILABLE else None

        self._browser = Browser.CHROME
        self._options = None
        self._capabilities = None
        self._driver_path = None

        self._driver = None
        self._headless = headless
        self._page_load_strategy = PageLoadStrategy.NORMAL

        self._clipboard = ""

        # Stub mouse coordinates
        self._html_elem = None
        self._x = 0
        self._y = 0

        # State for Key modifiers
        self._shift_hold = False

        self._download_folder_path = os.getcwd()

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, traceback):
        self.stop_browser()

    @property
    def driver(self):
        """
        The WebDriver driver instance.

        Returns:
            driver (WebDriver): The WebDriver driver instance.

        """
        return self._driver

    @property
    def driver_path(self):
        return self._driver_path

    @driver_path.setter
    def driver_path(self, driver_path):
        """
        The webdriver executable path.

        Args:
            driver_path (str): The full path to the proper webdriver path used for the selected browser.
                If set to None, the code will look into the PATH for the proper file when starting the browser.
        """
        driver_path = os.path.abspath(os.path.expanduser(os.path.expandvars(driver_path)))
        if driver_path and not os.path.isfile(driver_path):
            raise ValueError("Invalid driver_path. The file does not exist.")
        self._driver_path = driver_path

    @property
    def browser(self):
        """
        The web browser to be used.

        Returns:
            browser (Browser): The web browser to be used.
        """
        return self._browser

    @browser.setter
    def browser(self, browser):
        """
        The web browser to be used.

        Args:
            browser (Browser): The name of web browser to be used from the Browser enum.
        """
        self._browser = browser

    @property
    def options(self):
        """
        The options to be passed down to the WebDriver when starting the browser.

        Returns:
            options (Options): The browser specific options to be used.
        """
        return self._options

    @options.setter
    def options(self, options):
        """
        The options to be passed down to the WebDriver when starting the browser.

        Args:
            options (Options): The browser specific options to be used.
        """
        self._options = options

    @property
    def capabilities(self):
        """
        The capabilities to be passed down to the WebDriver when starting the browser.

        Returns:
            capabilities (Dict): The browser specific capabilities to be used.
        """
        return self._capabilities

    @capabilities.setter
    def capabilities(self, capabilities):
        """
        The capabilities to be passed down to the WebDriver when starting the browser.

        Args:
            capabilities (Dict): The browser specific capabilities to be used.
        """
        self._capabilities = capabilities

    @property
    def download_folder_path(self):
        return self._download_folder_path

    @download_folder_path.setter
    def download_folder_path(self, folder_path):
        """
        The download folder path to be used. Set it up before starting the Browser or browsing a URL or restart the
        browser after changing it.

        Args:
            folder_path (str): The desired download folder path.
        """
        self._download_folder_path = folder_path

    @property
    def headless(self):
        """
        Controls whether or not the bot will run headless.

        Returns:
            headless (bool): Whether or not to run the browser on headless mode.
        """
        return self._headless

    @headless.setter
    def headless(self, headless):
        """
        Controls whether or not the bot will run headless.

        Args:
            headless (boolean): If set to True will make the bot run headless.
        """
        if self._driver:
            logger.warning("Browser is running. Invoke stop_browser and start browser for changes to take effect.")
        self._headless = headless

    @property
    def page_load_strategy(self) -> PageLoadStrategy:
        """
        The page load strategy to be used.

        Returns:
            page_load_strategy (PageLoadStrategy): The page load strategy to be used.
        """
        return self._page_load_strategy

    @page_load_strategy.setter
    def page_load_strategy(self, page_load_strategy: PageLoadStrategy):
        """
        The page load strategy to be used.

        Args:
            page_load_strategy (PageLoadStrategy): The page load strategy to be used.
        """
        if self._driver:
            logger.warning("Browser is running. Invoke stop_browser and start browser for changes to take effect.")
        self._page_load_strategy = page_load_strategy

    def start_browser(self):
        """
        Starts the selected browser.
        """
        def check_driver():
            # Look for driver
            driver_name = BROWSER_CONFIGS.get(self.browser).get("driver")
            location = shutil.which(driver_name)
            if not location:
                raise RuntimeError(
                    f"{driver_name} was not found. Please make sure to have it on your PATH or set driver_path")
            return location

        # Specific webdriver class for a given browser
        driver_class = BROWSER_CONFIGS.get(self.browser).get("class")
        # Specific default options method for a given browser
        func_def_options = BROWSER_CONFIGS.get(self.browser).get("options")
        # Specific capabilities method for a given browser
        func_def_capabilities = BROWSER_CONFIGS.get(self.browser).get("capabilities")

        opt = self.options or func_def_options(
            self.headless, self._download_folder_path, None, self.page_load_strategy
        )
        cap = self.capabilities or func_def_capabilities()
        self.options = opt
        self.capabilities = cap
        driver_path = self.driver_path or check_driver()
        self.driver_path = driver_path
        if compat.version_selenium_is_larger_than_four():
            service = BROWSER_CONFIGS.get(self.browser).get("service")
            service = service(executable_path=self.driver_path)
            service.desired_capabilities = cap

            self._driver = driver_class(options=opt, service=service)
        else:
            self._driver = driver_class(options=opt, desired_capabilities=cap, executable_path=driver_path)
        self.set_screen_resolution()

    def stop_browser(self):
        """
        Stops the Chrome browser and clean up the User Data Directory.

        Warning:
            After invoking this method, you will need to reassign your custom options and capabilities.
        """
        if not self._driver:
            return
        self._driver.close()
        self._driver.quit()
        self.options = None
        self.capabilities = None
        self._driver = None

    def set_screen_resolution(self, width=None, height=None):
        """
        Configures the browser dimensions.

        Args:
            width (int): The desired width.
            height (int): The desired height.
        """
        dimensions = (width or self.DEFAULT_DIMENSIONS[0], height or self.DEFAULT_DIMENSIONS[1])

        if self.headless:
            # When running headless the window size is the viewport size
            window_size = dimensions
        else:
            # When running non-headless we need to account for the borders and etc
            # So the size must be bigger to have the same viewport size as before
            window_size = self._driver.execute_script("""
                return [window.outerWidth - window.innerWidth + arguments[0],
                  window.outerHeight - window.innerHeight + arguments[1]];
                """, *dimensions)
        self._driver.set_window_size(*window_size)

    def _webdriver_command(self, command, params=None, req_type="POST"):
        """
        Execute a webdriver command.

        Args:
            command (str): The command URL after the session part
            params (dict): The payload to be serialized and sent to the webdriver. Defaults to None.
            req_type (str, optional): The type of request to be made. Defaults to "POST".

        Returns:
            str: The value of the response
        """
        if not params:
            params = {}

        resource = f"/session/{self.driver.session_id}/{command}"
        url = self.driver.command_executor._url + resource
        body = json.dumps(params)
        response = self.driver.command_executor._request(req_type, url, body)

        if not response:
            raise Exception(response.get('value'))

        return response.get('value')

    ##########
    # Display
    ##########

    def get_screen_image(self, region=None):
        """
        Capture and returns a screenshot from the browser.

        Args:
            region (tuple): A tuple containing the left, top, width and height
                to crop the screen image.

        Returns:
            image (Image): The screenshot Image object.
        """
        if not region:
            region = (0, 0, 0, 0)

        x = region[0]
        y = region[1]
        width = region[2] or self._get_page_size()[0]
        height = region[3] or self._get_page_size()[1]

        try:
            data = self._driver.get_screenshot_as_base64()
            image_data = base64.b64decode(data)
            img = Image.open(io.BytesIO(image_data))
        except:  # noqa: E722
            img = Image.new("RGB", (width, height))

        img = img.crop((x, y, x + width, y + height))
        return img

    def get_viewport_size(self):
        """
        Returns the browser current viewport size.

        Returns:
            width (int): The current viewport width.
            height (int): The current viewport height.
        """
        # Access each dimension individually
        width = self._driver.get_window_size().get("width")
        height = self._driver.get_window_size().get("height")
        return width, height

    def _get_page_size(self):
        """
        Returns the browser current page size.

        Returns:
            width (int): The current page width.
            height (int): The current page height.
        """
        if not self._driver:
            return self.DEFAULT_DIMENSIONS

        width = self.execute_javascript("return window.innerWidth")
        height = self.execute_javascript("return window.innerHeight")
        return width, height

    def add_image(self, label, path):
        """
        Add an image into the state image map.

        Args:
            label (str): The image identifier
            path (str): The path for the image on disk
        """
        self.state.map_images[label] = path

    def get_image_from_map(self, label):
        """
        Return an image from teh state image map.

        Args:
            label (str): The image identifier

        Returns:
            Image: The Image object
        """
        path = self.state.map_images.get(label)
        if not path:
            raise KeyError('Invalid label for image map.')
        img = Image.open(path)
        return img

    def find_multiple(self, labels, x=None, y=None, width=None, height=None, *,
                      threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
        """
        Find multiple elements defined by label on screen until a timeout happens.

        Args:
            labels (list): A list of image identifiers
            x (int, optional): Search region start position x. Defaults to 0.
            y (int, optional): Search region start position y. Defaults to 0.
            width (int, optional): Search region width. Defaults to screen width.
            height (int, optional): Search region height. Defaults to screen height.
            threshold (int, optional): The threshold to be applied when doing grayscale search.
                Defaults to None.
            matching (float, optional): The matching index ranging from 0 to 1.
                Defaults to 0.9.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            best (bool, optional): Whether or not to keep looking until the best matching is found.
                Defaults to True.
            grayscale (bool, optional): Whether or not to convert to grayscale before searching.
                Defaults to False.

        Returns:
            results (dict): A dictionary in which the key is the label and value are the element coordinates in a
               NamedTuple.
        """

        def _to_dict(lbs, elems):
            return {k: v for k, v in zip(lbs, elems)}

        screen_w, screen_h = self._get_page_size()
        x = x or 0
        y = y or 0
        w = width or screen_w
        h = height or screen_h

        region = (x, y, w, h)

        results = [None] * len(labels)
        paths = [self._search_image_file(la) for la in labels]
        paths = [self._image_path_as_image(la) for la in paths]

        if threshold:
            # TODO: Figure out how we should do threshold
            print('Threshold not yet supported')

        if not best:
            # TODO: Implement best=False.
            print('Warning: Ignoring best=False for now. It will be supported in the future.')

        start_time = time.time()

        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > waiting_time:
                return _to_dict(labels, results)

            haystack = self.screenshot()
            helper = functools.partial(self._find_multiple_helper, haystack, region, matching, grayscale)

            results = [helper(p) for p in paths]

            results = [r for r in results]
            if None in results:
                continue
            else:
                return _to_dict(labels, results)

    def _find_multiple_helper(self, haystack, region, confidence, grayscale, needle):
        ele = cv2find.locate_all_opencv(
            needle, haystack, region=region, confidence=confidence, grayscale=grayscale
        )
        try:
            ele = next(ele)
        except StopIteration:
            ele = None
        return ele

    def find(self, label, x=None, y=None, width=None, height=None, *,
             threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
        """
        Find an element defined by label on screen until a timeout happens.

        Args:
            label (str): The image identifier
            x (int, optional): Search region start position x. Defaults to 0.
            y (int, optional): Search region start position y. Defaults to 0.
            width (int, optional): Search region width. Defaults to screen width.
            height (int, optional): Search region height. Defaults to screen height.
            threshold (int, optional): The threshold to be applied when doing grayscale search.
                Defaults to None.
            matching (float, optional): The matching index ranging from 0 to 1.
                Defaults to 0.9.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            best (bool, optional): Whether or not to keep looking until the best matching is found.
                Defaults to True.
            grayscale (bool, optional): Whether or not to convert to grayscale before searching.
                Defaults to False.

        Returns:
            element (NamedTuple): The element coordinates. None if not found.
        """
        return self.find_until(label=label, x=x, y=y, width=width, height=height, threshold=threshold,
                               matching=matching, waiting_time=waiting_time, best=best, grayscale=grayscale)

    def find_until(self, label, x=None, y=None, width=None, height=None, *,
                   threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
        """
        Find an element defined by label on screen until a timeout happens.

        Args:
            label (str): The image identifier
            x (int, optional): Search region start position x. Defaults to 0.
            y (int, optional): Search region start position y. Defaults to 0.
            width (int, optional): Search region width. Defaults to screen width.
            height (int, optional): Search region height. Defaults to screen height.
            threshold (int, optional): The threshold to be applied when doing grayscale search.
                Defaults to None.
            matching (float, optional): The matching index ranging from 0 to 1.
                Defaults to 0.9.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            best (bool, optional): Whether or not to keep looking until the best matching is found.
                Defaults to True.
            grayscale (bool, optional): Whether or not to convert to grayscale before searching.
                Defaults to False.

        Returns:
            element (NamedTuple): The element coordinates. None if not found.
        """
        self.state.element = None
        screen_w, screen_h = self._get_page_size()
        x = x or 0
        y = y or 0
        w = width or screen_w
        h = height or screen_h

        region = (x, y, w, h)

        element_path = self._search_image_file(label)
        element_path = self._image_path_as_image(element_path)

        if threshold:
            # TODO: Figure out how we should do threshold
            print('Threshold not yet supported')

        if not best:
            # TODO: Implement best=False.
            print('Warning: Ignoring best=False for now. It will be supported in the future.')

        start_time = time.time()

        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > waiting_time:
                return None
            haystack = self.get_screen_image()
            it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                           region=region, confidence=matching, grayscale=grayscale)
            try:
                ele = next(it)
            except StopIteration:
                ele = None

            if ele is not None:
                self.state.element = ele
                return ele

    def set_current_element(self, element: cv2find.Box):
        """
        Changes the current screen element the bot will interact when using click(), move(), and similar methods.

        This method is equivalent to self.state.element = element.

        Args:
            element (Box): A screen element from self.state.element or the find_all(as_list=True) method.
        """
        self.state.element = element

    def find_all(self, label, x=None, y=None, width=None, height=None, *,
                 threshold=None, matching=0.9, waiting_time=10000, grayscale=False, as_list: bool = False):
        """
        Find all elements defined by label on screen until a timeout happens.

        Args:
            label (str): The image identifier
            x (int, optional): Search region start position x. Defaults to 0.
            y (int, optional): Search region start position y. Defaults to 0.
            width (int, optional): Search region width. Defaults to screen width.
            height (int, optional): Search region height. Defaults to screen height.
            threshold (int, optional): The threshold to be applied when doing grayscale search.
                Defaults to None.
            matching (float, optional): The matching index ranging from 0 to 1.
                Defaults to 0.9.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            grayscale (bool, optional): Whether or not to convert to grayscale before searching.
                Defaults to False.
            as_list (bool, Optional): If True, returns a list of element coordinates instead of a generator.
                Use set_active_element() to be able to interact with the found elements.
                This parameter must be True if you intend to run multiple find_all() concurrently.
                Defaults to False.

        Returns:
            elements (collections.Iterable[NamedTuple]): A generator with all element coordinates found.
                None if not found.
        """
        def deduplicate(elems):
            def find_same(item, items):
                x_start = item.left
                x_end = item.left + item.width
                y_start = item.top
                y_end = item.top + item.height
                similars = []
                for itm in items:
                    if itm == item:
                        continue
                    if (itm.left >= x_start and itm.left < x_end)\
                            and (itm.top >= y_start and itm.top < y_end):
                        similars.append(itm)
                        continue
                return similars

            index = 0
            while True:
                try:
                    dups = find_same(elems[index], elems[index:])
                    for d in dups:
                        elems.remove(d)
                    index += 1
                except IndexError:
                    break
            return elems

        self.state.element = None
        screen_w, screen_h = self._get_page_size()
        x = x or 0
        y = y or 0
        w = width or screen_w
        h = height or screen_h

        region = (x, y, w, h)

        element_path = self._search_image_file(label)
        element_path = self._image_path_as_image(element_path)

        if threshold:
            # TODO: Figure out how we should do threshold
            print('Threshold not yet supported')

        start_time = time.time()

        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > waiting_time:
                return None

            haystack = self.get_screen_image()
            it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                           region=region, confidence=matching, grayscale=grayscale)

            eles = [ele for ele in it]
            if not eles:
                continue
            eles = deduplicate(list(eles))

            # As List
            if as_list:
                return eles

            # As Generator
            for ele in eles:
                if ele is not None:
                    self.state.element = ele
                    yield ele
            break

    def find_text(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9,
                  waiting_time=10000, best=True):
        """
        Find an element defined by label on screen until a timeout happens.

        Args:
            label (str): The image identifier
            x (int, optional): Search region start position x. Defaults to 0.
            y (int, optional): Search region start position y. Defaults to 0.
            width (int, optional): Search region width. Defaults to screen width.
            height (int, optional): Search region height. Defaults to screen height.
            threshold (int, optional): The threshold to be applied when doing grayscale search.
                Defaults to None.
            matching (float, optional): The matching index ranging from 0 to 1.
                Defaults to 0.9.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            best (bool, optional): Whether or not to keep looking until the best matching is found.
                Defaults to True.

        Returns:
            element (NamedTuple): The element coordinates. None if not found.
        """
        return self.find_until(label, x, y, width, height, threshold=threshold, matching=matching,
                               waiting_time=waiting_time, best=best, grayscale=True)

    def get_last_element(self):
        """
        Return the last element found.

        Returns:
            element (NamedTuple): The element coordinates (left, top, width, height)
        """
        return self.state.element

    def display_size(self):
        """
        Returns the display size in pixels.

        Returns:
            size (Tuple): The screen dimension (width and height) in pixels.
        """
        return self._get_page_size()

    def screenshot(self, filepath=None, region=None):
        """
        Capture a screenshot.

        Args:
            filepath (str, optional): The filepath in which to save the screenshot. Defaults to None.
            region (tuple, optional): Bounding box containing left, top, width and height to crop screenshot.

        Returns:
            Image: The screenshot Image object
        """
        img = self.get_screen_image(region)
        if filepath:
            img.save(filepath)
        return img

    def get_screenshot(self, filepath=None, region=None):
        """
        Capture a screenshot.

        Args:
            filepath (str, optional): The filepath in which to save the screenshot. Defaults to None.
            region (tuple, optional): Bounding box containing left, top, width and height to crop screenshot.

        Returns:
            Image: The screenshot Image object
        """
        return self.screenshot(filepath, region)

    def screen_cut(self, x, y, width=None, height=None):
        """
        Capture a screenshot from a region of the screen.

        Args:
            x (int): region start position x
            y (int): region start position y
            width (int): region width
            height (int): region height

        Returns:
            Image: The screenshot Image object
        """
        screen_size = self._get_page_size()
        x = x or 0
        y = y or 0
        width = width or screen_size[0]
        height = height or screen_size[1]
        img = self.screenshot(region=(x, y, width, height))
        return img

    def save_screenshot(self, path):
        """
        Saves a screenshot in a given path.

        Args:
            path (str): The filepath in which to save the screenshot

        """
        self.screenshot(path)

    def get_element_coords(self, label, x=None, y=None, width=None, height=None, matching=0.9, best=True):
        """
        Find an element defined by label on screen and returns its coordinates.

        Args:
            label (str): The image identifier
            x (int, optional): X (Left) coordinate of the search area.
            y (int, optional): Y (Top) coordinate of the search area.
            width (int, optional): Width of the search area.
            height (int, optional): Height of the search area.
            matching (float, optional): Minimum score to consider a match in the element image recognition process.
                Defaults to 0.9.
            best (bool, optional): Whether or not to search for the best value. If False the method returns on
                the first find. Defaults to True.

        Returns:
            coords (Tuple): A tuple containing the x and y coordinates for the element.
        """
        self.state.element = None
        screen_size = self._get_page_size()
        x = x or 0
        y = y or 0
        width = width or screen_size[0]
        height = height or screen_size[1]
        region = (x, y, width, height)

        if not best:
            print('Warning: Ignoring best=False for now. It will be supported in the future.')

        element_path = self._search_image_file(label)
        element_path = self._image_path_as_image(element_path)
        haystack = self.get_screen_image()

        it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                       region=region, confidence=matching)
        try:
            ele = next(it)
        except StopIteration:
            ele = None
        self.state.element = ele

        if ele:
            return ele.left, ele.top
        else:
            return None, None

    def get_element_coords_centered(self, label, x=None, y=None, width=None, height=None,
                                    matching=0.9, best=True):
        """
        Find an element defined by label on screen and returns its centered coordinates.

        Args:
            label (str): The image identifier
            x (int, optional): X (Left) coordinate of the search area.
            y (int, optional): Y (Top) coordinate of the search area.
            width (int, optional): Width of the search area.
            height (int, optional): Height of the search area.
            matching (float, optional): Minimum score to consider a match in the element image recognition process.
                Defaults to 0.9.
            best (bool, optional): Whether or not to search for the best value. If False the method returns on
                the first find. Defaults to True.

        Returns:
            coords (Tuple): A tuple containing the x and y coordinates for the center of the element.
        """
        self.get_element_coords(label, x, y, width, height, matching, best)
        return self.state.center()

    #########
    # Browser
    #########
    def page_title(self):
        """
        Returns the active page title.

        Returns:
            title (str): The page title.
        """
        try:
            return self._driver.title
        except InvalidSessionIdException:
            return None

    def page_source(self):
        """
        Returns the active page source.

        Returns:
            soup (BeautifulSoup): BeautifulSoup object for the page source.
        """
        try:
            soup = BeautifulSoup(self._driver.page_source, 'html.parser')
            return soup
        except InvalidSessionIdException:
            return None

    def navigate_to(self, url, is_retry=False):
        """
        Opens the browser on the given URL.

        Args:
            url (str):  The URL to be visited.
            is_retry (bool): Whether or not this is a retry attempt.
        """
        self._x = 0
        self._y = 0
        if not self._driver:
            self.start_browser()
        try:
            self._driver.get(url)
        except InvalidSessionIdException:
            if not is_retry:
                self.stop_browser()
                self.navigate_to(url, is_retry=True)

    def browse(self, url):
        """
        Opens the browser on the given URL.

        Args:
            url (str):  The URL to be visited.
        """
        self.navigate_to(url)

    def back(self):
        """
        Goes one step backward in the browser history.
        """
        self._driver.back()

    def forward(self):
        """
        Goes one step forward in the browser history.
        """
        self._driver.forward()

    def refresh(self):
        """
        Refreshes the current page.
        """
        self._driver.refresh()

    @contextmanager
    def wait_for_new_page(self, waiting_time=10000, activate=True):
        """Context manager to wait for a new page to load and activate it.

        Args:
            waiting_time (int, optional): The maximum waiting time. Defaults to 10000.
            activate (bool, optional): Whether or not to activate the new page. Defaults to True.

        """
        tabs = self.get_tabs()
        yield
        start_time = time.time()
        while tabs == self.get_tabs():
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > waiting_time:
                return None
            time.sleep(0.1)
        if activate:
            self.activate_tab(self.get_tabs()[-1])

    def execute_javascript(self, code):
        """
        Execute the given javascript code.

        Args:
            code (str): The code to be executed.

        Returns:
            value (object): Returns the code output or None if not available or if an error happens.
        """
        return self._driver.execute_script(code)

    def handle_js_dialog(self, accept=True, prompt_text=None):
        """
        Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).
        This also cleans the dialog information in the local buffer.

        Args:
            accept (bool): Whether to accept or dismiss the dialog.
            prompt_text (str): The text to enter into the dialog prompt before accepting.
                Used only if this is a prompt dialog.
        """
        dialog = self.get_js_dialog()
        if not dialog:
            # TODO: Maybe we should raise an exception here if no alert available
            return
        if prompt_text is not None:
            dialog.send_keys(prompt_text)
        if accept:
            dialog.accept()
        else:
            dialog.dismiss()

    def get_js_dialog(self):
        """
        Return the last found dialog. Invoke first the `find_js_dialog` method to look up.

        Returns:
            dialog (dict): The dialog information or None if not available.
                See https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening
        """
        try:
            dialog = self._driver.switch_to.alert
            return dialog
        except Exception:
            return None

    def get_tabs(self):
        """Get a list of tab handlers

        Returns:
            list: List of tab handlers
        """
        try:
            return self._driver.window_handles
        except InvalidSessionIdException:
            return []

    def create_tab(self, url):
        """Create a new tab and navigate to the given URL.

        Args:
            url (str): The desired URL.
        """
        try:
            # Refactor this when Selenium 4 is released
            self.execute_javascript(f"window.open('{url}', '_blank');")
            self._driver.switch_to.window(self.get_tabs()[-1])
        except InvalidSessionIdException:
            self.navigate_to(url)

    def create_window(self, url):
        """Creates a new window with the given URL.

        Args:
            url (str): The desired URL.
        """
        try:
            # Refactor this when Selenium 4 is released
            self.execute_javascript(f"window.open('{url}', '_blank', 'location=0');")
            self._driver.switch_to.window(self.get_tabs()[-1])
        except InvalidSessionIdException:
            self.navigate_to(url)

    def close_page(self):
        """Close the current active page (tab or window).
        """
        try:
            self._driver.close()

            # If it was the last tab we can't switch
            tabs = self.get_tabs()
            if tabs:
                self._driver.switch_to.window(tabs[-1])
        except InvalidSessionIdException:
            pass

    def activate_tab(self, handle):
        """Activate a tab given by the handle.

        Args:
            handle (str): The tab or window handle.
        """
        self._driver.switch_to.window(handle)

    def print_pdf(self, path=None, print_options=None):
        """Print the current page as a PDF file.

        Args:
            path (str, optional): The path for the file to be saved. Defaults to None.
            print_options (dict, optional): Print options as defined at. Defaults to None.

        Returns:
            str: the saved file path
        """
        title = self.page_title() or "document"
        title = re.sub("[\\\\|/:–]", "", title)
        timeout = 60000
        default_path = os.path.expanduser(os.path.join(self.download_folder_path, f"{title}.pdf"))

        if self.browser in [Browser.CHROME, Browser.EDGE] and not self.headless:
            pdf_current_count = self.get_file_count(file_extension=".pdf")
            # Chrome still does not support headless webdriver print
            # but Firefox does.
            self.execute_javascript("window.print();")

            # We need to wait for the file to be available in this case.
            if self.page_title():
                self.wait_for_file(default_path, timeout=timeout)
            else:
                # Waiting when the file don't have the page title in path
                self.wait_for_new_file(file_extension=".pdf", current_count=pdf_current_count)

            # Move the downloaded pdf file if the path is not None
            if path:
                last_downloaded_pdf = self.get_last_created_file(self.download_folder_path, ".pdf")
                os.rename(last_downloaded_pdf, path)
                return path
            self.wait(2000)
            return default_path

        if print_options is None:
            print_options = {
                'landscape': False,
                'displayHeaderFooter': False,
                'printBackground': True,
                'preferCSSPageSize': True,
                'marginTop': 0,
                'marginBottom': 0
            }
        data = self._webdriver_command("print", print_options)
        bytes_file = base64.b64decode(data)
        if not path:
            path = default_path
        with open(path, "wb") as f:
            f.write(bytes_file)
        return path

    def wait_for_downloads(self, timeout: int = 120000):
        """
        Wait for all downloads to be finished.
        Beware that this method replaces the current page with the downloads window.

        Args:
            timeout (int, optional): Timeout in millis. Defaults to 120000.
        """
        if self.browser in [Browser.CHROME, Browser.EDGE] and self.headless:
            start_time = time.time()
            while True:
                elapsed_time = (time.time() - start_time) * 1000
                if elapsed_time > timeout:
                    return False
                downloads_count = self.get_file_count(self.download_folder_path, ".crdownload")
                if downloads_count == 0:
                    return True
                self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

        wait_method = BROWSER_CONFIGS.get(self.browser).get("wait_for_downloads")
        # waits for all the files to be completed
        WebDriverWait(self._driver, timeout/1000.0, 1).until(wait_method)

    def find_elements(self, selector: str, by: By = By.CSS_SELECTOR,
                      waiting_time=10000, ensure_visible: bool = True) -> List[WebElement]:
        """Find elements using the specified selector with selector type specified by `by`.

        Args:
            selector (str): The selector string to be used.
            by (str, optional): Selector type. Defaults to By.CSS_SELECTOR.
                [See more](https://selenium-python.readthedocs.io/api.html#selenium.webdriver.common.by.By)
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            ensure_visible (bool, optional): Whether to wait for the element to be visible. Defaults to True.

        Returns:
            List[WebElement]: List of elements found.

        **Example:**
        ```python
        from botcity.web import By
        ...
        # Find element by ID
        all_cells = self.find_elements("//td", By.XPATH)
        ...
        ```
        """
        if ensure_visible:
            condition = EC.visibility_of_all_elements_located
        else:
            condition = EC.presence_of_all_elements_located

        try:
            elements = WebDriverWait(
                self._driver, timeout=waiting_time / 1000.0
            ).until(
                condition((by, selector))
            )
            return elements
        except (TimeoutException, NoSuchElementException) as ex:
            print("Exception on find_elements", ex)
            return None

    def find_element(self, selector: str, by: str = By.CSS_SELECTOR, waiting_time=10000,
                     ensure_visible: bool = False, ensure_clickable: bool = False) -> WebElement:
        """Find an element using the specified selector with selector type specified by `by`.
        If more than one element is found, the first instance is returned.

        Args:
            selector (str): The selector string to be used.
            by (str, optional): Selector type. Defaults to By.CSS_SELECTOR.
                [See more](https://selenium-python.readthedocs.io/api.html#selenium.webdriver.common.by.By)
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
            ensure_visible (bool, optional): Whether to wait for the element to be visible. Defaults to False.
            ensure_clickable (bool, optional): Whether to wait for the element to be clickable. Defaults to False.
                If True, `ensure_clickable` takes precedence over `ensure_visible`.

        Returns:
            WebElement: The element found.

        **Example:**
        ```python
        from botcity.web import By
        ...
        # Find element by ID
        elem = self.find_element("my_elem", By.ID)
        # Find element by XPath
        elem = self.find_element("//input[@type='submit']", By.XPATH)
        ...
        ```
        """
        condition = EC.visibility_of_element_located if ensure_visible else EC.presence_of_element_located
        condition = EC.element_to_be_clickable if ensure_clickable else condition

        try:
            element = WebDriverWait(
                self._driver, timeout=waiting_time/1000.0
            ).until(
                condition((by, selector))
            )
            return element
        except (TimeoutException, NoSuchElementException):
            return None

    def scroll_element(self, element: WebElement, steps: int = 100, interval: float = 500,
                       start: int = 0, end: int = None):
        """Scrolls down an element by its scroll height or a given amount defined by `start` and `end`.

        This is useful for scrolling down a page to load more content or
        to scroll down a dynamically loaded element.

        Args:
            element (WebElement): The element to scroll.
            steps (int, optional): Number of steps in which to conclude the scroll. Defaults to 100.
            interval (float, optional): Time interval between each step. Defaults to 500ms.
            start (int, optional): Start position. Defaults to 0.
            end (int, optional): End position. Defaults to None.
        """
        ele_height = self.driver.execute_script(
            "return arguments[0].scrollHeight;", element
        )

        start = max(0, start)
        end = min(ele_height, end) if end is not None else ele_height

        for i in range(start, end, steps):
            self.driver.execute_script(
                "arguments[0].scrollTo(0, arguments[1])", element, i)
            self.sleep(interval/1000.0)

    def wait_for_stale_element(self, element: WebElement, timeout: int = 10000):
        """
        Wait until the WebElement element becomes stale (outdated).

        Args:
            element (WebElement): The element to monitor for staleness.
            timeout (int, optional): Timeout in millis. Defaults to 120000.
        """
        try:
            WebDriverWait(self._driver, timeout=timeout/1000.0).until(EC.staleness_of(element))
        except (TimeoutException, NoSuchElementException):
            pass

    def wait_for_element_visibility(self, element: WebElement, visible: bool = True, waiting_time=10000):
        """Wait for the element to be visible or hidden.

        Args:
            element (WebElement): The element to wait for.
            visible (bool, optional): Whether to wait for the element to be visible. Defaults to True.
            waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 10000ms (10s).
        """
        if visible:
            wait_method = EC.visibility_of
        else:
            wait_method = EC.invisibility_of_element

        WebDriverWait(self._driver, timeout=waiting_time/1000.0).until(wait_method(element))

    def set_file_input_element(self, element: WebElement, filepath: str):
        """Configure the filepath for upload in a file element.
        Note: This method does not submit the form.

        Args:
            element (WebElement): The file upload element.
            filepath (str): The path to the file to be uploaded.

        **Example:**
        ```python
        ...
        # Find element
        elem = self.find_element("body > form > input[type=file]")
        # Set the filepath
        self.set_file_input_element(elem, "./test.txt")
        ...
        ```
        """
        fpath = os.path.abspath(os.path.expanduser(os.path.expandvars(filepath)))
        element.send_keys(fpath)

    def enter_iframe(self, iframe: WebElement):
        """Switch the WebBot driver to the specified iframe.

        Args:
            iframe (WebElement): The desired iFrame.
        """
        self._driver.switch_to.frame(iframe)

    def leave_iframe(self):
        """Leave the iframe and switch the WebBot driver to the default content.
        """
        self._driver.switch_to.default_content()

    def install_firefox_extension(self, extension):
        """
        Install an extension in the Firefox browser.
        This will start the browser if it was not started yet.

        Args:
            extension (str): The path of the .xpi extension to be loaded.
        """
        if self.browser != Browser.FIREFOX:
            raise ValueError("install_firefox_extension only works with Firefox.")
        if not self._driver:
            self.start_browser()
        self._driver.install_addon(os.path.abspath(extension))

    #######
    # Mouse
    #######
    def click_on(self, label):
        """
        Click on the element.

        Args:
            label (str): The image identifier
        """
        x, y = self.get_element_coords_centered(label)
        if None in (x, y):
            raise ValueError(f'Element not available. Cannot find {label}.')
        self.click_at(x, y)

    def get_last_x(self):
        """
        Get the last X position for the mouse.

        Returns:
            x (int): The last x position for the mouse.
        """
        return self._x

    def get_last_y(self):
        """
        Get the last Y position for the mouse.

        Returns:
            y (int): The last y position for the mouse.
        """
        return self._y

    def mouse_move(self, x, y):
        """
        Mouse the move to the coordinate defined by x and y

        Args:
            x (int): The X coordinate
            y (int): The Y coordinate

        """
        if self.browser == Browser.FIREFOX:
            # Reset coordinates if the page has gone stale. Only required for Firefox
            if self._html_elem is None:
                self._html_elem = self._driver.find_element(By.TAG_NAME, 'body')
                self._x = 0
                self._y = 0
            else:
                try:
                    self._html_elem.is_enabled()
                except StaleElementReferenceException:
                    self._html_elem = self._driver.find_element(By.TAG_NAME, 'body')
                    self._x = 0
                    self._y = 0

        mx = x - self._x
        my = y - self._y
        self._x = x
        self._y = y
        ActionChains(self._driver).move_by_offset(mx, my).perform()

    def click_at(self, x, y, *, clicks=1, interval_between_clicks=0, button='left'):
        """
        Click at the coordinate defined by x and y

        Args:
            x (int): The X coordinate
            y (int): The Y coordinate
            clicks (int, optional): Number of times to click. Defaults to 1.
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            button (str, optional): One of 'left', 'right'. Defaults to 'left'
        """
        self.mouse_move(x, y)
        ac = ActionChains(self._driver)
        for i in range(clicks):
            if button == 'left':
                ac.click()
            elif button == 'right':
                ac.context_click()
            else:
                raise ValueError('Invalid value for button. Accepted values are left or right.')
            ac.pause(interval_between_clicks/1000.0)
        ac.perform()

    @only_if_element
    def click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
              clicks=1, interval_between_clicks=0, button='left'):
        """
        Click on the last found element.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
            clicks (int, optional): Number of times to click. Defaults to 1.
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            button (str, optional): One of 'left', 'right'. Defaults to 'left'
        """
        x, y = self.state.center()
        self.click_at(x, y, clicks=clicks, button=button, interval_between_clicks=interval_between_clicks)
        self.sleep(wait_after)

    @only_if_element
    def click_relative(self, x, y, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
                       clicks=1, interval_between_clicks=0, button='left'):
        """
        Click Relative on the last found element.

        Args:
            x (int): Horizontal offset
            y (int): Vertical offset
            wait_after (int, optional): Interval to wait after clicking on the element.
            clicks (int, optional): Number of times to click. Defaults to 1.
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            button (str, optional): One of 'left', 'right'. Defaults to 'left'
        """
        x = self.state.x() + x
        y = self.state.y() + y
        self.click_at(x, y, clicks=clicks, button=button, interval_between_clicks=interval_between_clicks)
        self.sleep(wait_after)

    @only_if_element
    def double_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
        """
        Double Click on the last found element.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
        """
        self.click(interval_between_clicks=wait_after, clicks=2)

    @only_if_element
    def double_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
        """
        Double Click Relative on the last found element.

        Args:
            x (int): Horizontal offset
            y (int): Vertical offset
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            wait_after (int, optional): Interval to wait after clicking on the element.
        """
        self.click_relative(x, y, wait_after=wait_after, clicks=2, interval_between_clicks=interval_between_clicks)

    @only_if_element
    def triple_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
        """
        Triple Click on the last found element.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
        """
        self.click(wait_after=wait_after, clicks=3)

    @only_if_element
    def triple_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
        """
        Triple Click Relative on the last found element.

        Args:
            x (int): Horizontal offset
            y (int): Vertical offset
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            wait_after (int, optional): Interval to wait after clicking on the element.
        """
        self.click_relative(x, y, wait_after=wait_after, clicks=3, interval_between_clicks=interval_between_clicks)

    def mouse_down(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *, button='left'):
        """
        Holds down the requested mouse button.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
            button (str, optional): One of 'left', 'right', 'middle'. Defaults to 'left'
        """
        ActionChains(self._driver).click_and_hold().perform()
        self.sleep(wait_after)

    def mouse_up(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *, button='left'):
        """
        Releases the requested mouse button.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
            button (str, optional): One of 'left', 'right', 'middle'. Defaults to 'left'
        """
        ActionChains(self._driver).release().perform()
        self.sleep(wait_after)

    def scroll_down(self, clicks):
        """
        Scroll Down n clicks

        Args:
            clicks (int): Number of times to scroll down.
        """
        for i in range(clicks):
            self._driver.execute_script("window.scrollTo(0, window.scrollY + 200)")
            self.sleep(200)

    def scroll_up(self, clicks):
        """
        Scroll Up n clicks

        Args:
            clicks (int): Number of times to scroll up.
        """
        for i in range(clicks):
            self._driver.execute_script("window.scrollTo(0, window.scrollY - 200)")
            self.sleep(200)

    def move_to(self, x, y):
        """
        Move the mouse relative to its current position.

        Args:
            x (int): The X coordinate
            y (int): The Y coordinate
        """
        self.mouse_move(x, y)

    @only_if_element
    def move(self):
        """
        Move to the center position of last found item.
        """
        x, y = self.state.center()
        self.move_to(x, y)

    def move_relative(self, x, y):
        """
        Move the mouse relative to its current position.

        Args:
            x (int): Horizontal offset
            y (int): Vertical offset

        """
        x = self.get_last_x() + x
        y = self.get_last_y() + y
        self.move_to(x, y)

    def move_random(self, range_x, range_y):
        """
        Move randomly along the given x, y range.

        Args:
            range_x (int): Horizontal range
            range_y (int): Vertical range

        """
        x = int(random.random() * range_x)
        y = int(random.random() * range_y)
        self.move_to(x, y)

    @only_if_element
    def right_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
                    clicks=1, interval_between_clicks=0):
        """
        Right click on the last found element.

        Args:
            wait_after (int, optional): Interval to wait after clicking on the element.
            clicks (int, optional): Number of times to click. Defaults to 1.
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        """
        self.click(clicks=clicks, button='right', interval_between_clicks=interval_between_clicks)
        self.sleep(wait_after)

    def right_click_at(self, x, y):
        """
        Right click at the coordinate defined by x and y

        Args:
            x (int): The X coordinate
            y (int): The Y coordinate
        """
        self.click_at(x, y, button='right')

    @only_if_element
    def right_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
        """
        Right Click Relative on the last found element.

        Args:
            x (int): Horizontal offset
            y (int): Vertical offset
            interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
            wait_after (int, optional): Interval to wait after clicking on the element.

        """
        self.click_relative(x, y, wait_after=wait_after, interval_between_clicks=interval_between_clicks,
                            button='right')

    ##########
    # Keyboard
    ##########
    def kb_type(self, text, interval=0):
        """
        Type a text char by char (individual key events).

        Args:
            text (str): text to be typed.
            interval (int, optional): interval (ms) between each key press. Defaults to 0
        """
        action = ActionChains(self._driver)

        for c in text:
            action.send_keys(c)
            action.pause(interval / 1000.0)

        action.perform()
        self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

    def paste(self, text=None, wait=0):
        """
        Paste content from the clipboard.

        Args:
            text (str, optional): The text to be pasted. Defaults to None
            wait (int, optional): Wait interval (ms) after task
        """
        text_to_paste = self._clipboard
        if text:
            text_to_paste = text
        self.kb_type(text_to_paste)

    def copy_to_clipboard(self, text, wait=0):
        """
        Copy content to the clipboard.

        Args:
            text (str): The text to be copied.
            wait (int, optional): Wait interval (ms) after task
        """
        self._clipboard = text
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def tab(self, wait=0):
        """
        Press key Tab

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.TAB)
        action.key_up(Keys.TAB)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def enter(self, wait=0):
        """
        Press key Enter

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ENTER)
        action.key_up(Keys.ENTER)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def key_right(self, wait=0):
        """
        Press key Right

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ARROW_RIGHT)
        action.key_up(Keys.ARROW_RIGHT)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def key_enter(self, wait=0):
        """
        Press key Enter

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        self.enter(wait)

    def key_home(self, wait=0):
        """
        Press key Home

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        # TODO: Investigate why with Firefox the key isn't working properly
        action = ActionChains(self._driver)
        action.key_down(Keys.HOME)
        action.key_up(Keys.HOME)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def key_end(self, wait=0):
        """
        Press key End

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.END)
        action.key_up(Keys.END)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def page_up(self, wait=0):
        """
        Press Page Up key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        # TODO: Investigate why with Firefox the key isn't working properly
        action = ActionChains(self._driver)
        action.key_down(Keys.PAGE_UP)
        action.key_up(Keys.PAGE_UP)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def page_down(self, wait=0):
        """
        Press Page Down key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        # TODO: Investigate why with Firefox the key isn't working properly
        action = ActionChains(self._driver)
        action.key_down(Keys.PAGE_DOWN)
        action.key_up(Keys.PAGE_DOWN)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def key_esc(self, wait=0):
        """
        Press key Esc

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ESCAPE)
        action.key_up(Keys.ESCAPE)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def _key_fx(self, idx, wait=0):
        """
        Press key Fidx where idx is a value from 1 to 12

        Args:
            idx (int): F key index from 1 to 12
            wait (int, optional): Wait interval (ms) after task

        """
        if idx < 1 or idx > 12:
            raise ValueError("Only F1 to F12 allowed.")
        action = ActionChains(self._driver)
        key = getattr(Keys, f"F{idx}")
        action.key_down(key)
        action.key_up(key)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def hold_shift(self, wait=0):
        """
        Hold key Shift

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.SHIFT)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def release_shift(self):
        """
        Release key Shift.
        This method needs to be invoked after holding Shift or similar.
        """
        action = ActionChains(self._driver)
        action.key_up(Keys.SHIFT)
        action.perform()

    def maximize_window(self):
        """
        Shortcut to maximize window on Windows OS.
        """
        # TODO: Understand the complications associated with maximizing the browser and the resolution
        self._driver.maximize_window()

    def type_keys_with_interval(self, interval, keys):
        """
        Press a sequence of keys. Hold the keys in the specific order and releases them.

        Args:
            interval (int): Interval (ms) in which to press and release keys
            keys (list): List of Keys to be pressed
        """
        action = ActionChains(self._driver)

        for k in keys:
            action.key_down(k)
            action.pause(interval / 1000.0)
        for k in reversed(keys):
            action.key_up(k)
            action.pause(interval / 1000.0)
        action.perform()

    def type_keys(self, keys):
        """
        Press a sequence of keys. Hold the keys in the specific order and releases them.

        Args:
            keys (list): List of keys to be pressed
        """
        self.type_keys_with_interval(100, keys)

    def control_c(self, wait=0):
        """
        Press keys CTRL+C

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        # Firefox can't do window.getSelection() and return a proper value when the selected text
        # is in an input of similar. While Firefox doesn't get its shit together we apply this
        # ugly alternative so control+c works for "all" browsers tested so far.
        cmd = """
            try {
                return document.activeElement.value.substring(
                    document.activeElement.selectionStart,
                    document.activeElement.selectionEnd
                );
            } catch(error) {
                return window.getSelection().toString();
            }
        """

        self._clipboard = self.execute_javascript(cmd)
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def control_v(self, wait=0):
        """
        Press keys CTRL+V

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        self.paste()

    def control_a(self, wait=0):
        """
        Press keys CTRL+A

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        key = Keys.CONTROL
        if platform.system() == 'Darwin':
            key = Keys.COMMAND

        action.key_down(key)
        action.send_keys('a')
        action.key_up(key)
        action.perform()

    def get_clipboard(self):
        """
        Get the current content in the clipboard.

        Returns:
            text (str): Current clipboard content
        """
        return self._clipboard

    def type_left(self, wait=0):
        """
        Press Left key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ARROW_LEFT)
        action.key_up(Keys.ARROW_LEFT)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def type_right(self, wait=0):
        """
        Press Right key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        self.key_right(wait=wait)

    def type_down(self, wait=0):
        """
        Press Down key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ARROW_DOWN)
        action.key_up(Keys.ARROW_DOWN)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def type_up(self, wait=0):
        """
        Press Up key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.ARROW_UP)
        action.key_up(Keys.ARROW_UP)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def space(self, wait=0):
        """
        Press Space key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.SPACE)
        action.key_up(Keys.SPACE)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def backspace(self, wait=0):
        """
        Press Backspace key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.BACK_SPACE)
        action.key_up(Keys.BACK_SPACE)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    def delete(self, wait=0):
        """
        Press Delete key

        Args:
            wait (int, optional): Wait interval (ms) after task

        """
        action = ActionChains(self._driver)
        action.key_down(Keys.DELETE)
        action.key_up(Keys.DELETE)
        action.perform()
        delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
        self.sleep(delay)

    ######
    # Misc
    ######

    def wait(self, interval):
        """
        Wait / Sleep for a given interval.

        Args:
            interval (int): Interval in milliseconds

        """
        time.sleep(interval / 1000.0)

    def sleep(self, interval):
        """
        Wait / Sleep for a given interval.

        Args:
            interval (int): Interval in milliseconds

        """
        self.wait(interval)

    def wait_for_file(self, path, timeout=60000):
        """
        Wait for a file to be available on disk.

        Args:
            path (str): The path for the file to be executed
            timeout (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 60000ms (60s).

        Returns:
            status (bool): Whether or not the file was available before the timeout

        """
        path = os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
        start_time = time.time()

        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > timeout:
                return False
            if os.path.isfile(path) and os.access(path, os.R_OK):
                if self.browser == Browser.FIREFOX and os.path.getsize(path) == 0:
                    # if file is empty, the download is not completed.
                    continue
                self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)
                return True
            self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

    def get_last_created_file(self, path=None, file_extension=""):
        """Returns the last created file in a specific folder path.

        Args:
            path (str, optional): The path of the folder where the file is expected. Defaults to None.
            file_extension (str, optional): The extension of the file to be searched for (e.g., .pdf, .txt).

        Returns:
            str: the path of the last created file
        """
        if not path:
            path = self.download_folder_path

        files_path = glob.glob(os.path.expanduser(os.path.join(path, f"*{file_extension}")))
        last_created_file = max(files_path, key=os.path.getctime)
        return last_created_file

    def get_file_count(self, path=None, file_extension=""):
        """Get the total number of files of the same type.

        Args:
            path (str, optional): The path of the folder where the files are saved.
            file_extension (str, optional): The extension of the files to be searched for (e.g., .pdf, .txt).

        Returns:
            int: the number of files of the given type
        """
        if not path:
            path = self.download_folder_path

        files_path = glob.glob(os.path.expanduser(os.path.join(path, f"*{file_extension}")))
        return len(files_path)

    def wait_for_new_file(self, path=None, file_extension="", current_count=0, timeout=60000):
        """
        Wait for a new file to be available on disk without the file path.

        Args:
            path (str, optional): The path of the folder where the file is expected. Defaults to None.
            file_extension (str, optional): The extension of the file to be searched for (e.g., .pdf, .txt).
            current_count (int): The current number of files in the folder of the given type. Defaults to 0 files
            timeout (int, optional): Maximum wait time (ms) to search for a hit.
                Defaults to 60000ms (60s).

        Returns:
            str: the path of the last created file of the given type
        """
        if not path:
            path = self.download_folder_path

        start_time = time.time()
        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > timeout:
                return None
            file_count = self.get_file_count(path, f"*{file_extension}")
            if file_count == current_count + 1:
                self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)
                return self.get_last_created_file(path, f"*{file_extension}")
            self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

browser property writable

The web browser to be used.

Returns:

Type Description
browser (Browser)

The web browser to be used.

capabilities property writable

The capabilities to be passed down to the WebDriver when starting the browser.

Returns:

Type Description
capabilities (Dict)

The browser specific capabilities to be used.

driver property readonly

The WebDriver driver instance.

Returns:

Type Description
driver (WebDriver)

The WebDriver driver instance.

headless property writable

Controls whether or not the bot will run headless.

Returns:

Type Description
headless (bool)

Whether or not to run the browser on headless mode.

options property writable

The options to be passed down to the WebDriver when starting the browser.

Returns:

Type Description
options (Options)

The browser specific options to be used.

page_load_strategy: PageLoadStrategy property writable

The page load strategy to be used.

Returns:

Type Description
page_load_strategy (PageLoadStrategy)

The page load strategy to be used.

activate_tab(self, handle)

Activate a tab given by the handle.

Parameters:

Name Type Description Default
handle str

The tab or window handle.

required
Source code in web/bot.py
def activate_tab(self, handle):
    """Activate a tab given by the handle.

    Args:
        handle (str): The tab or window handle.
    """
    self._driver.switch_to.window(handle)

add_image(self, label, path)

Add an image into the state image map.

Parameters:

Name Type Description Default
label str

The image identifier

required
path str

The path for the image on disk

required
Source code in web/bot.py
def add_image(self, label, path):
    """
    Add an image into the state image map.

    Args:
        label (str): The image identifier
        path (str): The path for the image on disk
    """
    self.state.map_images[label] = path

back(self)

Goes one step backward in the browser history.

Source code in web/bot.py
def back(self):
    """
    Goes one step backward in the browser history.
    """
    self._driver.back()

backspace(self, wait=0)

Press Backspace key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def backspace(self, wait=0):
    """
    Press Backspace key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.BACK_SPACE)
    action.key_up(Keys.BACK_SPACE)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

browse(self, url)

Opens the browser on the given URL.

Parameters:

Name Type Description Default
url str

The URL to be visited.

required
Source code in web/bot.py
def browse(self, url):
    """
    Opens the browser on the given URL.

    Args:
        url (str):  The URL to be visited.
    """
    self.navigate_to(url)

click(self, wait_after=300, *, clicks=1, interval_between_clicks=0, button='left')

Click on the last found element.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
clicks int

Number of times to click. Defaults to 1.

1
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
button str

One of 'left', 'right'. Defaults to 'left'

'left'
Source code in web/bot.py
@only_if_element
def click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
          clicks=1, interval_between_clicks=0, button='left'):
    """
    Click on the last found element.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
        clicks (int, optional): Number of times to click. Defaults to 1.
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        button (str, optional): One of 'left', 'right'. Defaults to 'left'
    """
    x, y = self.state.center()
    self.click_at(x, y, clicks=clicks, button=button, interval_between_clicks=interval_between_clicks)
    self.sleep(wait_after)

click_at(self, x, y, *, clicks=1, interval_between_clicks=0, button='left')

Click at the coordinate defined by x and y

Parameters:

Name Type Description Default
x int

The X coordinate

required
y int

The Y coordinate

required
clicks int

Number of times to click. Defaults to 1.

1
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
button str

One of 'left', 'right'. Defaults to 'left'

'left'
Source code in web/bot.py
def click_at(self, x, y, *, clicks=1, interval_between_clicks=0, button='left'):
    """
    Click at the coordinate defined by x and y

    Args:
        x (int): The X coordinate
        y (int): The Y coordinate
        clicks (int, optional): Number of times to click. Defaults to 1.
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        button (str, optional): One of 'left', 'right'. Defaults to 'left'
    """
    self.mouse_move(x, y)
    ac = ActionChains(self._driver)
    for i in range(clicks):
        if button == 'left':
            ac.click()
        elif button == 'right':
            ac.context_click()
        else:
            raise ValueError('Invalid value for button. Accepted values are left or right.')
        ac.pause(interval_between_clicks/1000.0)
    ac.perform()

click_on(self, label)

Click on the element.

Parameters:

Name Type Description Default
label str

The image identifier

required
Source code in web/bot.py
def click_on(self, label):
    """
    Click on the element.

    Args:
        label (str): The image identifier
    """
    x, y = self.get_element_coords_centered(label)
    if None in (x, y):
        raise ValueError(f'Element not available. Cannot find {label}.')
    self.click_at(x, y)

click_relative(self, x, y, wait_after=300, *, clicks=1, interval_between_clicks=0, button='left')

Click Relative on the last found element.

Parameters:

Name Type Description Default
x int

Horizontal offset

required
y int

Vertical offset

required
wait_after int

Interval to wait after clicking on the element.

300
clicks int

Number of times to click. Defaults to 1.

1
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
button str

One of 'left', 'right'. Defaults to 'left'

'left'
Source code in web/bot.py
@only_if_element
def click_relative(self, x, y, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
                   clicks=1, interval_between_clicks=0, button='left'):
    """
    Click Relative on the last found element.

    Args:
        x (int): Horizontal offset
        y (int): Vertical offset
        wait_after (int, optional): Interval to wait after clicking on the element.
        clicks (int, optional): Number of times to click. Defaults to 1.
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        button (str, optional): One of 'left', 'right'. Defaults to 'left'
    """
    x = self.state.x() + x
    y = self.state.y() + y
    self.click_at(x, y, clicks=clicks, button=button, interval_between_clicks=interval_between_clicks)
    self.sleep(wait_after)

close_page(self)

Close the current active page (tab or window).

Source code in web/bot.py
def close_page(self):
    """Close the current active page (tab or window).
    """
    try:
        self._driver.close()

        # If it was the last tab we can't switch
        tabs = self.get_tabs()
        if tabs:
            self._driver.switch_to.window(tabs[-1])
    except InvalidSessionIdException:
        pass

control_a(self, wait=0)

Press keys CTRL+A

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def control_a(self, wait=0):
    """
    Press keys CTRL+A

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    key = Keys.CONTROL
    if platform.system() == 'Darwin':
        key = Keys.COMMAND

    action.key_down(key)
    action.send_keys('a')
    action.key_up(key)
    action.perform()

control_c(self, wait=0)

Press keys CTRL+C

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def control_c(self, wait=0):
    """
    Press keys CTRL+C

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    # Firefox can't do window.getSelection() and return a proper value when the selected text
    # is in an input of similar. While Firefox doesn't get its shit together we apply this
    # ugly alternative so control+c works for "all" browsers tested so far.
    cmd = """
        try {
            return document.activeElement.value.substring(
                document.activeElement.selectionStart,
                document.activeElement.selectionEnd
            );
        } catch(error) {
            return window.getSelection().toString();
        }
    """

    self._clipboard = self.execute_javascript(cmd)
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

control_v(self, wait=0)

Press keys CTRL+V

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def control_v(self, wait=0):
    """
    Press keys CTRL+V

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    self.paste()

copy_to_clipboard(self, text, wait=0)

Copy content to the clipboard.

Parameters:

Name Type Description Default
text str

The text to be copied.

required
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def copy_to_clipboard(self, text, wait=0):
    """
    Copy content to the clipboard.

    Args:
        text (str): The text to be copied.
        wait (int, optional): Wait interval (ms) after task
    """
    self._clipboard = text
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

create_tab(self, url)

Create a new tab and navigate to the given URL.

Parameters:

Name Type Description Default
url str

The desired URL.

required
Source code in web/bot.py
def create_tab(self, url):
    """Create a new tab and navigate to the given URL.

    Args:
        url (str): The desired URL.
    """
    try:
        # Refactor this when Selenium 4 is released
        self.execute_javascript(f"window.open('{url}', '_blank');")
        self._driver.switch_to.window(self.get_tabs()[-1])
    except InvalidSessionIdException:
        self.navigate_to(url)

create_window(self, url)

Creates a new window with the given URL.

Parameters:

Name Type Description Default
url str

The desired URL.

required
Source code in web/bot.py
def create_window(self, url):
    """Creates a new window with the given URL.

    Args:
        url (str): The desired URL.
    """
    try:
        # Refactor this when Selenium 4 is released
        self.execute_javascript(f"window.open('{url}', '_blank', 'location=0');")
        self._driver.switch_to.window(self.get_tabs()[-1])
    except InvalidSessionIdException:
        self.navigate_to(url)

delete(self, wait=0)

Press Delete key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def delete(self, wait=0):
    """
    Press Delete key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.DELETE)
    action.key_up(Keys.DELETE)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

display_size(self)

Returns the display size in pixels.

Returns:

Type Description
size (Tuple)

The screen dimension (width and height) in pixels.

Source code in web/bot.py
def display_size(self):
    """
    Returns the display size in pixels.

    Returns:
        size (Tuple): The screen dimension (width and height) in pixels.
    """
    return self._get_page_size()

double_click(self, wait_after=300)

Double Click on the last found element.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
Source code in web/bot.py
@only_if_element
def double_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
    """
    Double Click on the last found element.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
    """
    self.click(interval_between_clicks=wait_after, clicks=2)

double_click_relative(self, x, y, interval_between_clicks=0, wait_after=300)

Double Click Relative on the last found element.

Parameters:

Name Type Description Default
x int

Horizontal offset

required
y int

Vertical offset

required
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
wait_after int

Interval to wait after clicking on the element.

300
Source code in web/bot.py
@only_if_element
def double_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
    """
    Double Click Relative on the last found element.

    Args:
        x (int): Horizontal offset
        y (int): Vertical offset
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        wait_after (int, optional): Interval to wait after clicking on the element.
    """
    self.click_relative(x, y, wait_after=wait_after, clicks=2, interval_between_clicks=interval_between_clicks)

enter(self, wait=0)

Press key Enter

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def enter(self, wait=0):
    """
    Press key Enter

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ENTER)
    action.key_up(Keys.ENTER)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

enter_iframe(self, iframe)

Switch the WebBot driver to the specified iframe.

Parameters:

Name Type Description Default
iframe WebElement

The desired iFrame.

required
Source code in web/bot.py
def enter_iframe(self, iframe: WebElement):
    """Switch the WebBot driver to the specified iframe.

    Args:
        iframe (WebElement): The desired iFrame.
    """
    self._driver.switch_to.frame(iframe)

execute_javascript(self, code)

Execute the given javascript code.

Parameters:

Name Type Description Default
code str

The code to be executed.

required

Returns:

Type Description
value (object)

Returns the code output or None if not available or if an error happens.

Source code in web/bot.py
def execute_javascript(self, code):
    """
    Execute the given javascript code.

    Args:
        code (str): The code to be executed.

    Returns:
        value (object): Returns the code output or None if not available or if an error happens.
    """
    return self._driver.execute_script(code)

find(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False)

Find an element defined by label on screen until a timeout happens.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

Search region start position x. Defaults to 0.

None
y int

Search region start position y. Defaults to 0.

None
width int

Search region width. Defaults to screen width.

None
height int

Search region height. Defaults to screen height.

None
threshold int

The threshold to be applied when doing grayscale search. Defaults to None.

None
matching float

The matching index ranging from 0 to 1. Defaults to 0.9.

0.9
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
best bool

Whether or not to keep looking until the best matching is found. Defaults to True.

True
grayscale bool

Whether or not to convert to grayscale before searching. Defaults to False.

False

Returns:

Type Description
element (NamedTuple)

The element coordinates. None if not found.

Source code in web/bot.py
def find(self, label, x=None, y=None, width=None, height=None, *,
         threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
    """
    Find an element defined by label on screen until a timeout happens.

    Args:
        label (str): The image identifier
        x (int, optional): Search region start position x. Defaults to 0.
        y (int, optional): Search region start position y. Defaults to 0.
        width (int, optional): Search region width. Defaults to screen width.
        height (int, optional): Search region height. Defaults to screen height.
        threshold (int, optional): The threshold to be applied when doing grayscale search.
            Defaults to None.
        matching (float, optional): The matching index ranging from 0 to 1.
            Defaults to 0.9.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        best (bool, optional): Whether or not to keep looking until the best matching is found.
            Defaults to True.
        grayscale (bool, optional): Whether or not to convert to grayscale before searching.
            Defaults to False.

    Returns:
        element (NamedTuple): The element coordinates. None if not found.
    """
    return self.find_until(label=label, x=x, y=y, width=width, height=height, threshold=threshold,
                           matching=matching, waiting_time=waiting_time, best=best, grayscale=grayscale)

find_all(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9, waiting_time=10000, grayscale=False, as_list=False)

Find all elements defined by label on screen until a timeout happens.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

Search region start position x. Defaults to 0.

None
y int

Search region start position y. Defaults to 0.

None
width int

Search region width. Defaults to screen width.

None
height int

Search region height. Defaults to screen height.

None
threshold int

The threshold to be applied when doing grayscale search. Defaults to None.

None
matching float

The matching index ranging from 0 to 1. Defaults to 0.9.

0.9
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
grayscale bool

Whether or not to convert to grayscale before searching. Defaults to False.

False
as_list bool, Optional

If True, returns a list of element coordinates instead of a generator. Use set_active_element() to be able to interact with the found elements. This parameter must be True if you intend to run multiple find_all() concurrently. Defaults to False.

False

Returns:

Type Description
elements (collections.Iterable[NamedTuple])

A generator with all element coordinates found. None if not found.

Source code in web/bot.py
def find_all(self, label, x=None, y=None, width=None, height=None, *,
             threshold=None, matching=0.9, waiting_time=10000, grayscale=False, as_list: bool = False):
    """
    Find all elements defined by label on screen until a timeout happens.

    Args:
        label (str): The image identifier
        x (int, optional): Search region start position x. Defaults to 0.
        y (int, optional): Search region start position y. Defaults to 0.
        width (int, optional): Search region width. Defaults to screen width.
        height (int, optional): Search region height. Defaults to screen height.
        threshold (int, optional): The threshold to be applied when doing grayscale search.
            Defaults to None.
        matching (float, optional): The matching index ranging from 0 to 1.
            Defaults to 0.9.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        grayscale (bool, optional): Whether or not to convert to grayscale before searching.
            Defaults to False.
        as_list (bool, Optional): If True, returns a list of element coordinates instead of a generator.
            Use set_active_element() to be able to interact with the found elements.
            This parameter must be True if you intend to run multiple find_all() concurrently.
            Defaults to False.

    Returns:
        elements (collections.Iterable[NamedTuple]): A generator with all element coordinates found.
            None if not found.
    """
    def deduplicate(elems):
        def find_same(item, items):
            x_start = item.left
            x_end = item.left + item.width
            y_start = item.top
            y_end = item.top + item.height
            similars = []
            for itm in items:
                if itm == item:
                    continue
                if (itm.left >= x_start and itm.left < x_end)\
                        and (itm.top >= y_start and itm.top < y_end):
                    similars.append(itm)
                    continue
            return similars

        index = 0
        while True:
            try:
                dups = find_same(elems[index], elems[index:])
                for d in dups:
                    elems.remove(d)
                index += 1
            except IndexError:
                break
        return elems

    self.state.element = None
    screen_w, screen_h = self._get_page_size()
    x = x or 0
    y = y or 0
    w = width or screen_w
    h = height or screen_h

    region = (x, y, w, h)

    element_path = self._search_image_file(label)
    element_path = self._image_path_as_image(element_path)

    if threshold:
        # TODO: Figure out how we should do threshold
        print('Threshold not yet supported')

    start_time = time.time()

    while True:
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > waiting_time:
            return None

        haystack = self.get_screen_image()
        it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                       region=region, confidence=matching, grayscale=grayscale)

        eles = [ele for ele in it]
        if not eles:
            continue
        eles = deduplicate(list(eles))

        # As List
        if as_list:
            return eles

        # As Generator
        for ele in eles:
            if ele is not None:
                self.state.element = ele
                yield ele
        break

find_element(self, selector, by='css selector', waiting_time=10000, ensure_visible=False, ensure_clickable=False)

Find an element using the specified selector with selector type specified by by. If more than one element is found, the first instance is returned.

Parameters:

Name Type Description Default
selector str

The selector string to be used.

required
by str

Selector type. Defaults to By.CSS_SELECTOR. See more

'css selector'
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
ensure_visible bool

Whether to wait for the element to be visible. Defaults to False.

False
ensure_clickable bool

Whether to wait for the element to be clickable. Defaults to False. If True, ensure_clickable takes precedence over ensure_visible.

False

Returns:

Type Description
WebElement

The element found.

Example:

from botcity.web import By
...
# Find element by ID
elem = self.find_element("my_elem", By.ID)
# Find element by XPath
elem = self.find_element("//input[@type='submit']", By.XPATH)
...

Source code in web/bot.py
def find_element(self, selector: str, by: str = By.CSS_SELECTOR, waiting_time=10000,
                 ensure_visible: bool = False, ensure_clickable: bool = False) -> WebElement:
    """Find an element using the specified selector with selector type specified by `by`.
    If more than one element is found, the first instance is returned.

    Args:
        selector (str): The selector string to be used.
        by (str, optional): Selector type. Defaults to By.CSS_SELECTOR.
            [See more](https://selenium-python.readthedocs.io/api.html#selenium.webdriver.common.by.By)
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        ensure_visible (bool, optional): Whether to wait for the element to be visible. Defaults to False.
        ensure_clickable (bool, optional): Whether to wait for the element to be clickable. Defaults to False.
            If True, `ensure_clickable` takes precedence over `ensure_visible`.

    Returns:
        WebElement: The element found.

    **Example:**
    ```python
    from botcity.web import By
    ...
    # Find element by ID
    elem = self.find_element("my_elem", By.ID)
    # Find element by XPath
    elem = self.find_element("//input[@type='submit']", By.XPATH)
    ...
    ```
    """
    condition = EC.visibility_of_element_located if ensure_visible else EC.presence_of_element_located
    condition = EC.element_to_be_clickable if ensure_clickable else condition

    try:
        element = WebDriverWait(
            self._driver, timeout=waiting_time/1000.0
        ).until(
            condition((by, selector))
        )
        return element
    except (TimeoutException, NoSuchElementException):
        return None

find_elements(self, selector, by='css selector', waiting_time=10000, ensure_visible=True)

Find elements using the specified selector with selector type specified by by.

Parameters:

Name Type Description Default
selector str

The selector string to be used.

required
by str

Selector type. Defaults to By.CSS_SELECTOR. See more

'css selector'
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
ensure_visible bool

Whether to wait for the element to be visible. Defaults to True.

True

Returns:

Type Description
List[WebElement]

List of elements found.

Example:

from botcity.web import By
...
# Find element by ID
all_cells = self.find_elements("//td", By.XPATH)
...

Source code in web/bot.py
def find_elements(self, selector: str, by: By = By.CSS_SELECTOR,
                  waiting_time=10000, ensure_visible: bool = True) -> List[WebElement]:
    """Find elements using the specified selector with selector type specified by `by`.

    Args:
        selector (str): The selector string to be used.
        by (str, optional): Selector type. Defaults to By.CSS_SELECTOR.
            [See more](https://selenium-python.readthedocs.io/api.html#selenium.webdriver.common.by.By)
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        ensure_visible (bool, optional): Whether to wait for the element to be visible. Defaults to True.

    Returns:
        List[WebElement]: List of elements found.

    **Example:**
    ```python
    from botcity.web import By
    ...
    # Find element by ID
    all_cells = self.find_elements("//td", By.XPATH)
    ...
    ```
    """
    if ensure_visible:
        condition = EC.visibility_of_all_elements_located
    else:
        condition = EC.presence_of_all_elements_located

    try:
        elements = WebDriverWait(
            self._driver, timeout=waiting_time / 1000.0
        ).until(
            condition((by, selector))
        )
        return elements
    except (TimeoutException, NoSuchElementException) as ex:
        print("Exception on find_elements", ex)
        return None

find_multiple(self, labels, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False)

Find multiple elements defined by label on screen until a timeout happens.

Parameters:

Name Type Description Default
labels list

A list of image identifiers

required
x int

Search region start position x. Defaults to 0.

None
y int

Search region start position y. Defaults to 0.

None
width int

Search region width. Defaults to screen width.

None
height int

Search region height. Defaults to screen height.

None
threshold int

The threshold to be applied when doing grayscale search. Defaults to None.

None
matching float

The matching index ranging from 0 to 1. Defaults to 0.9.

0.9
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
best bool

Whether or not to keep looking until the best matching is found. Defaults to True.

True
grayscale bool

Whether or not to convert to grayscale before searching. Defaults to False.

False

Returns:

Type Description
results (dict)

A dictionary in which the key is the label and value are the element coordinates in a NamedTuple.

Source code in web/bot.py
def find_multiple(self, labels, x=None, y=None, width=None, height=None, *,
                  threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
    """
    Find multiple elements defined by label on screen until a timeout happens.

    Args:
        labels (list): A list of image identifiers
        x (int, optional): Search region start position x. Defaults to 0.
        y (int, optional): Search region start position y. Defaults to 0.
        width (int, optional): Search region width. Defaults to screen width.
        height (int, optional): Search region height. Defaults to screen height.
        threshold (int, optional): The threshold to be applied when doing grayscale search.
            Defaults to None.
        matching (float, optional): The matching index ranging from 0 to 1.
            Defaults to 0.9.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        best (bool, optional): Whether or not to keep looking until the best matching is found.
            Defaults to True.
        grayscale (bool, optional): Whether or not to convert to grayscale before searching.
            Defaults to False.

    Returns:
        results (dict): A dictionary in which the key is the label and value are the element coordinates in a
           NamedTuple.
    """

    def _to_dict(lbs, elems):
        return {k: v for k, v in zip(lbs, elems)}

    screen_w, screen_h = self._get_page_size()
    x = x or 0
    y = y or 0
    w = width or screen_w
    h = height or screen_h

    region = (x, y, w, h)

    results = [None] * len(labels)
    paths = [self._search_image_file(la) for la in labels]
    paths = [self._image_path_as_image(la) for la in paths]

    if threshold:
        # TODO: Figure out how we should do threshold
        print('Threshold not yet supported')

    if not best:
        # TODO: Implement best=False.
        print('Warning: Ignoring best=False for now. It will be supported in the future.')

    start_time = time.time()

    while True:
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > waiting_time:
            return _to_dict(labels, results)

        haystack = self.screenshot()
        helper = functools.partial(self._find_multiple_helper, haystack, region, matching, grayscale)

        results = [helper(p) for p in paths]

        results = [r for r in results]
        if None in results:
            continue
        else:
            return _to_dict(labels, results)

find_text(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9, waiting_time=10000, best=True)

Find an element defined by label on screen until a timeout happens.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

Search region start position x. Defaults to 0.

None
y int

Search region start position y. Defaults to 0.

None
width int

Search region width. Defaults to screen width.

None
height int

Search region height. Defaults to screen height.

None
threshold int

The threshold to be applied when doing grayscale search. Defaults to None.

None
matching float

The matching index ranging from 0 to 1. Defaults to 0.9.

0.9
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
best bool

Whether or not to keep looking until the best matching is found. Defaults to True.

True

Returns:

Type Description
element (NamedTuple)

The element coordinates. None if not found.

Source code in web/bot.py
def find_text(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9,
              waiting_time=10000, best=True):
    """
    Find an element defined by label on screen until a timeout happens.

    Args:
        label (str): The image identifier
        x (int, optional): Search region start position x. Defaults to 0.
        y (int, optional): Search region start position y. Defaults to 0.
        width (int, optional): Search region width. Defaults to screen width.
        height (int, optional): Search region height. Defaults to screen height.
        threshold (int, optional): The threshold to be applied when doing grayscale search.
            Defaults to None.
        matching (float, optional): The matching index ranging from 0 to 1.
            Defaults to 0.9.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        best (bool, optional): Whether or not to keep looking until the best matching is found.
            Defaults to True.

    Returns:
        element (NamedTuple): The element coordinates. None if not found.
    """
    return self.find_until(label, x, y, width, height, threshold=threshold, matching=matching,
                           waiting_time=waiting_time, best=best, grayscale=True)

find_until(self, label, x=None, y=None, width=None, height=None, *, threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False)

Find an element defined by label on screen until a timeout happens.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

Search region start position x. Defaults to 0.

None
y int

Search region start position y. Defaults to 0.

None
width int

Search region width. Defaults to screen width.

None
height int

Search region height. Defaults to screen height.

None
threshold int

The threshold to be applied when doing grayscale search. Defaults to None.

None
matching float

The matching index ranging from 0 to 1. Defaults to 0.9.

0.9
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
best bool

Whether or not to keep looking until the best matching is found. Defaults to True.

True
grayscale bool

Whether or not to convert to grayscale before searching. Defaults to False.

False

Returns:

Type Description
element (NamedTuple)

The element coordinates. None if not found.

Source code in web/bot.py
def find_until(self, label, x=None, y=None, width=None, height=None, *,
               threshold=None, matching=0.9, waiting_time=10000, best=True, grayscale=False):
    """
    Find an element defined by label on screen until a timeout happens.

    Args:
        label (str): The image identifier
        x (int, optional): Search region start position x. Defaults to 0.
        y (int, optional): Search region start position y. Defaults to 0.
        width (int, optional): Search region width. Defaults to screen width.
        height (int, optional): Search region height. Defaults to screen height.
        threshold (int, optional): The threshold to be applied when doing grayscale search.
            Defaults to None.
        matching (float, optional): The matching index ranging from 0 to 1.
            Defaults to 0.9.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
        best (bool, optional): Whether or not to keep looking until the best matching is found.
            Defaults to True.
        grayscale (bool, optional): Whether or not to convert to grayscale before searching.
            Defaults to False.

    Returns:
        element (NamedTuple): The element coordinates. None if not found.
    """
    self.state.element = None
    screen_w, screen_h = self._get_page_size()
    x = x or 0
    y = y or 0
    w = width or screen_w
    h = height or screen_h

    region = (x, y, w, h)

    element_path = self._search_image_file(label)
    element_path = self._image_path_as_image(element_path)

    if threshold:
        # TODO: Figure out how we should do threshold
        print('Threshold not yet supported')

    if not best:
        # TODO: Implement best=False.
        print('Warning: Ignoring best=False for now. It will be supported in the future.')

    start_time = time.time()

    while True:
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > waiting_time:
            return None
        haystack = self.get_screen_image()
        it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                       region=region, confidence=matching, grayscale=grayscale)
        try:
            ele = next(it)
        except StopIteration:
            ele = None

        if ele is not None:
            self.state.element = ele
            return ele

forward(self)

Goes one step forward in the browser history.

Source code in web/bot.py
def forward(self):
    """
    Goes one step forward in the browser history.
    """
    self._driver.forward()

get_clipboard(self)

Get the current content in the clipboard.

Returns:

Type Description
text (str)

Current clipboard content

Source code in web/bot.py
def get_clipboard(self):
    """
    Get the current content in the clipboard.

    Returns:
        text (str): Current clipboard content
    """
    return self._clipboard

get_element_coords(self, label, x=None, y=None, width=None, height=None, matching=0.9, best=True)

Find an element defined by label on screen and returns its coordinates.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

X (Left) coordinate of the search area.

None
y int

Y (Top) coordinate of the search area.

None
width int

Width of the search area.

None
height int

Height of the search area.

None
matching float

Minimum score to consider a match in the element image recognition process. Defaults to 0.9.

0.9
best bool

Whether or not to search for the best value. If False the method returns on the first find. Defaults to True.

True

Returns:

Type Description
coords (Tuple)

A tuple containing the x and y coordinates for the element.

Source code in web/bot.py
def get_element_coords(self, label, x=None, y=None, width=None, height=None, matching=0.9, best=True):
    """
    Find an element defined by label on screen and returns its coordinates.

    Args:
        label (str): The image identifier
        x (int, optional): X (Left) coordinate of the search area.
        y (int, optional): Y (Top) coordinate of the search area.
        width (int, optional): Width of the search area.
        height (int, optional): Height of the search area.
        matching (float, optional): Minimum score to consider a match in the element image recognition process.
            Defaults to 0.9.
        best (bool, optional): Whether or not to search for the best value. If False the method returns on
            the first find. Defaults to True.

    Returns:
        coords (Tuple): A tuple containing the x and y coordinates for the element.
    """
    self.state.element = None
    screen_size = self._get_page_size()
    x = x or 0
    y = y or 0
    width = width or screen_size[0]
    height = height or screen_size[1]
    region = (x, y, width, height)

    if not best:
        print('Warning: Ignoring best=False for now. It will be supported in the future.')

    element_path = self._search_image_file(label)
    element_path = self._image_path_as_image(element_path)
    haystack = self.get_screen_image()

    it = cv2find.locate_all_opencv(element_path, haystack_image=haystack,
                                   region=region, confidence=matching)
    try:
        ele = next(it)
    except StopIteration:
        ele = None
    self.state.element = ele

    if ele:
        return ele.left, ele.top
    else:
        return None, None

get_element_coords_centered(self, label, x=None, y=None, width=None, height=None, matching=0.9, best=True)

Find an element defined by label on screen and returns its centered coordinates.

Parameters:

Name Type Description Default
label str

The image identifier

required
x int

X (Left) coordinate of the search area.

None
y int

Y (Top) coordinate of the search area.

None
width int

Width of the search area.

None
height int

Height of the search area.

None
matching float

Minimum score to consider a match in the element image recognition process. Defaults to 0.9.

0.9
best bool

Whether or not to search for the best value. If False the method returns on the first find. Defaults to True.

True

Returns:

Type Description
coords (Tuple)

A tuple containing the x and y coordinates for the center of the element.

Source code in web/bot.py
def get_element_coords_centered(self, label, x=None, y=None, width=None, height=None,
                                matching=0.9, best=True):
    """
    Find an element defined by label on screen and returns its centered coordinates.

    Args:
        label (str): The image identifier
        x (int, optional): X (Left) coordinate of the search area.
        y (int, optional): Y (Top) coordinate of the search area.
        width (int, optional): Width of the search area.
        height (int, optional): Height of the search area.
        matching (float, optional): Minimum score to consider a match in the element image recognition process.
            Defaults to 0.9.
        best (bool, optional): Whether or not to search for the best value. If False the method returns on
            the first find. Defaults to True.

    Returns:
        coords (Tuple): A tuple containing the x and y coordinates for the center of the element.
    """
    self.get_element_coords(label, x, y, width, height, matching, best)
    return self.state.center()

get_file_count(self, path=None, file_extension='')

Get the total number of files of the same type.

Parameters:

Name Type Description Default
path str

The path of the folder where the files are saved.

None
file_extension str

The extension of the files to be searched for (e.g., .pdf, .txt).

''

Returns:

Type Description
int

the number of files of the given type

Source code in web/bot.py
def get_file_count(self, path=None, file_extension=""):
    """Get the total number of files of the same type.

    Args:
        path (str, optional): The path of the folder where the files are saved.
        file_extension (str, optional): The extension of the files to be searched for (e.g., .pdf, .txt).

    Returns:
        int: the number of files of the given type
    """
    if not path:
        path = self.download_folder_path

    files_path = glob.glob(os.path.expanduser(os.path.join(path, f"*{file_extension}")))
    return len(files_path)

get_image_from_map(self, label)

Return an image from teh state image map.

Parameters:

Name Type Description Default
label str

The image identifier

required

Returns:

Type Description
Image

The Image object

Source code in web/bot.py
def get_image_from_map(self, label):
    """
    Return an image from teh state image map.

    Args:
        label (str): The image identifier

    Returns:
        Image: The Image object
    """
    path = self.state.map_images.get(label)
    if not path:
        raise KeyError('Invalid label for image map.')
    img = Image.open(path)
    return img

get_js_dialog(self)

Return the last found dialog. Invoke first the find_js_dialog method to look up.

Returns:

Type Description
dialog (dict)

The dialog information or None if not available. See https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening

Source code in web/bot.py
def get_js_dialog(self):
    """
    Return the last found dialog. Invoke first the `find_js_dialog` method to look up.

    Returns:
        dialog (dict): The dialog information or None if not available.
            See https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening
    """
    try:
        dialog = self._driver.switch_to.alert
        return dialog
    except Exception:
        return None

get_last_created_file(self, path=None, file_extension='')

Returns the last created file in a specific folder path.

Parameters:

Name Type Description Default
path str

The path of the folder where the file is expected. Defaults to None.

None
file_extension str

The extension of the file to be searched for (e.g., .pdf, .txt).

''

Returns:

Type Description
str

the path of the last created file

Source code in web/bot.py
def get_last_created_file(self, path=None, file_extension=""):
    """Returns the last created file in a specific folder path.

    Args:
        path (str, optional): The path of the folder where the file is expected. Defaults to None.
        file_extension (str, optional): The extension of the file to be searched for (e.g., .pdf, .txt).

    Returns:
        str: the path of the last created file
    """
    if not path:
        path = self.download_folder_path

    files_path = glob.glob(os.path.expanduser(os.path.join(path, f"*{file_extension}")))
    last_created_file = max(files_path, key=os.path.getctime)
    return last_created_file

get_last_element(self)

Return the last element found.

Returns:

Type Description
element (NamedTuple)

The element coordinates (left, top, width, height)

Source code in web/bot.py
def get_last_element(self):
    """
    Return the last element found.

    Returns:
        element (NamedTuple): The element coordinates (left, top, width, height)
    """
    return self.state.element

get_last_x(self)

Get the last X position for the mouse.

Returns:

Type Description
x (int)

The last x position for the mouse.

Source code in web/bot.py
def get_last_x(self):
    """
    Get the last X position for the mouse.

    Returns:
        x (int): The last x position for the mouse.
    """
    return self._x

get_last_y(self)

Get the last Y position for the mouse.

Returns:

Type Description
y (int)

The last y position for the mouse.

Source code in web/bot.py
def get_last_y(self):
    """
    Get the last Y position for the mouse.

    Returns:
        y (int): The last y position for the mouse.
    """
    return self._y

get_screen_image(self, region=None)

Capture and returns a screenshot from the browser.

Parameters:

Name Type Description Default
region tuple

A tuple containing the left, top, width and height to crop the screen image.

None

Returns:

Type Description
image (Image)

The screenshot Image object.

Source code in web/bot.py
def get_screen_image(self, region=None):
    """
    Capture and returns a screenshot from the browser.

    Args:
        region (tuple): A tuple containing the left, top, width and height
            to crop the screen image.

    Returns:
        image (Image): The screenshot Image object.
    """
    if not region:
        region = (0, 0, 0, 0)

    x = region[0]
    y = region[1]
    width = region[2] or self._get_page_size()[0]
    height = region[3] or self._get_page_size()[1]

    try:
        data = self._driver.get_screenshot_as_base64()
        image_data = base64.b64decode(data)
        img = Image.open(io.BytesIO(image_data))
    except:  # noqa: E722
        img = Image.new("RGB", (width, height))

    img = img.crop((x, y, x + width, y + height))
    return img

get_screenshot(self, filepath=None, region=None)

Capture a screenshot.

Parameters:

Name Type Description Default
filepath str

The filepath in which to save the screenshot. Defaults to None.

None
region tuple

Bounding box containing left, top, width and height to crop screenshot.

None

Returns:

Type Description
Image

The screenshot Image object

Source code in web/bot.py
def get_screenshot(self, filepath=None, region=None):
    """
    Capture a screenshot.

    Args:
        filepath (str, optional): The filepath in which to save the screenshot. Defaults to None.
        region (tuple, optional): Bounding box containing left, top, width and height to crop screenshot.

    Returns:
        Image: The screenshot Image object
    """
    return self.screenshot(filepath, region)

get_tabs(self)

Get a list of tab handlers

Returns:

Type Description
list

List of tab handlers

Source code in web/bot.py
def get_tabs(self):
    """Get a list of tab handlers

    Returns:
        list: List of tab handlers
    """
    try:
        return self._driver.window_handles
    except InvalidSessionIdException:
        return []

get_viewport_size(self)

Returns the browser current viewport size.

Returns:

Type Description
width (int)

The current viewport width. height (int): The current viewport height.

Source code in web/bot.py
def get_viewport_size(self):
    """
    Returns the browser current viewport size.

    Returns:
        width (int): The current viewport width.
        height (int): The current viewport height.
    """
    # Access each dimension individually
    width = self._driver.get_window_size().get("width")
    height = self._driver.get_window_size().get("height")
    return width, height

handle_js_dialog(self, accept=True, prompt_text=None)

Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload). This also cleans the dialog information in the local buffer.

Parameters:

Name Type Description Default
accept bool

Whether to accept or dismiss the dialog.

True
prompt_text str

The text to enter into the dialog prompt before accepting. Used only if this is a prompt dialog.

None
Source code in web/bot.py
def handle_js_dialog(self, accept=True, prompt_text=None):
    """
    Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).
    This also cleans the dialog information in the local buffer.

    Args:
        accept (bool): Whether to accept or dismiss the dialog.
        prompt_text (str): The text to enter into the dialog prompt before accepting.
            Used only if this is a prompt dialog.
    """
    dialog = self.get_js_dialog()
    if not dialog:
        # TODO: Maybe we should raise an exception here if no alert available
        return
    if prompt_text is not None:
        dialog.send_keys(prompt_text)
    if accept:
        dialog.accept()
    else:
        dialog.dismiss()

hold_shift(self, wait=0)

Hold key Shift

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def hold_shift(self, wait=0):
    """
    Hold key Shift

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.SHIFT)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

install_firefox_extension(self, extension)

Install an extension in the Firefox browser. This will start the browser if it was not started yet.

Parameters:

Name Type Description Default
extension str

The path of the .xpi extension to be loaded.

required
Source code in web/bot.py
def install_firefox_extension(self, extension):
    """
    Install an extension in the Firefox browser.
    This will start the browser if it was not started yet.

    Args:
        extension (str): The path of the .xpi extension to be loaded.
    """
    if self.browser != Browser.FIREFOX:
        raise ValueError("install_firefox_extension only works with Firefox.")
    if not self._driver:
        self.start_browser()
    self._driver.install_addon(os.path.abspath(extension))

kb_type(self, text, interval=0)

Type a text char by char (individual key events).

Parameters:

Name Type Description Default
text str

text to be typed.

required
interval int

interval (ms) between each key press. Defaults to 0

0
Source code in web/bot.py
def kb_type(self, text, interval=0):
    """
    Type a text char by char (individual key events).

    Args:
        text (str): text to be typed.
        interval (int, optional): interval (ms) between each key press. Defaults to 0
    """
    action = ActionChains(self._driver)

    for c in text:
        action.send_keys(c)
        action.pause(interval / 1000.0)

    action.perform()
    self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

key_end(self, wait=0)

Press key End

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def key_end(self, wait=0):
    """
    Press key End

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.END)
    action.key_up(Keys.END)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

key_enter(self, wait=0)

Press key Enter

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def key_enter(self, wait=0):
    """
    Press key Enter

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    self.enter(wait)

key_esc(self, wait=0)

Press key Esc

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def key_esc(self, wait=0):
    """
    Press key Esc

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ESCAPE)
    action.key_up(Keys.ESCAPE)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

key_home(self, wait=0)

Press key Home

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def key_home(self, wait=0):
    """
    Press key Home

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    # TODO: Investigate why with Firefox the key isn't working properly
    action = ActionChains(self._driver)
    action.key_down(Keys.HOME)
    action.key_up(Keys.HOME)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

key_right(self, wait=0)

Press key Right

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def key_right(self, wait=0):
    """
    Press key Right

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ARROW_RIGHT)
    action.key_up(Keys.ARROW_RIGHT)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

leave_iframe(self)

Leave the iframe and switch the WebBot driver to the default content.

Source code in web/bot.py
def leave_iframe(self):
    """Leave the iframe and switch the WebBot driver to the default content.
    """
    self._driver.switch_to.default_content()

maximize_window(self)

Shortcut to maximize window on Windows OS.

Source code in web/bot.py
def maximize_window(self):
    """
    Shortcut to maximize window on Windows OS.
    """
    # TODO: Understand the complications associated with maximizing the browser and the resolution
    self._driver.maximize_window()

mouse_down(self, wait_after=300, *, button='left')

Holds down the requested mouse button.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
button str

One of 'left', 'right', 'middle'. Defaults to 'left'

'left'
Source code in web/bot.py
def mouse_down(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *, button='left'):
    """
    Holds down the requested mouse button.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
        button (str, optional): One of 'left', 'right', 'middle'. Defaults to 'left'
    """
    ActionChains(self._driver).click_and_hold().perform()
    self.sleep(wait_after)

mouse_move(self, x, y)

Mouse the move to the coordinate defined by x and y

Parameters:

Name Type Description Default
x int

The X coordinate

required
y int

The Y coordinate

required
Source code in web/bot.py
def mouse_move(self, x, y):
    """
    Mouse the move to the coordinate defined by x and y

    Args:
        x (int): The X coordinate
        y (int): The Y coordinate

    """
    if self.browser == Browser.FIREFOX:
        # Reset coordinates if the page has gone stale. Only required for Firefox
        if self._html_elem is None:
            self._html_elem = self._driver.find_element(By.TAG_NAME, 'body')
            self._x = 0
            self._y = 0
        else:
            try:
                self._html_elem.is_enabled()
            except StaleElementReferenceException:
                self._html_elem = self._driver.find_element(By.TAG_NAME, 'body')
                self._x = 0
                self._y = 0

    mx = x - self._x
    my = y - self._y
    self._x = x
    self._y = y
    ActionChains(self._driver).move_by_offset(mx, my).perform()

mouse_up(self, wait_after=300, *, button='left')

Releases the requested mouse button.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
button str

One of 'left', 'right', 'middle'. Defaults to 'left'

'left'
Source code in web/bot.py
def mouse_up(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *, button='left'):
    """
    Releases the requested mouse button.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
        button (str, optional): One of 'left', 'right', 'middle'. Defaults to 'left'
    """
    ActionChains(self._driver).release().perform()
    self.sleep(wait_after)

move(self)

Move to the center position of last found item.

Source code in web/bot.py
@only_if_element
def move(self):
    """
    Move to the center position of last found item.
    """
    x, y = self.state.center()
    self.move_to(x, y)

move_random(self, range_x, range_y)

Move randomly along the given x, y range.

Parameters:

Name Type Description Default
range_x int

Horizontal range

required
range_y int

Vertical range

required
Source code in web/bot.py
def move_random(self, range_x, range_y):
    """
    Move randomly along the given x, y range.

    Args:
        range_x (int): Horizontal range
        range_y (int): Vertical range

    """
    x = int(random.random() * range_x)
    y = int(random.random() * range_y)
    self.move_to(x, y)

move_relative(self, x, y)

Move the mouse relative to its current position.

Parameters:

Name Type Description Default
x int

Horizontal offset

required
y int

Vertical offset

required
Source code in web/bot.py
def move_relative(self, x, y):
    """
    Move the mouse relative to its current position.

    Args:
        x (int): Horizontal offset
        y (int): Vertical offset

    """
    x = self.get_last_x() + x
    y = self.get_last_y() + y
    self.move_to(x, y)

move_to(self, x, y)

Move the mouse relative to its current position.

Parameters:

Name Type Description Default
x int

The X coordinate

required
y int

The Y coordinate

required
Source code in web/bot.py
def move_to(self, x, y):
    """
    Move the mouse relative to its current position.

    Args:
        x (int): The X coordinate
        y (int): The Y coordinate
    """
    self.mouse_move(x, y)

navigate_to(self, url, is_retry=False)

Opens the browser on the given URL.

Parameters:

Name Type Description Default
url str

The URL to be visited.

required
is_retry bool

Whether or not this is a retry attempt.

False
Source code in web/bot.py
def navigate_to(self, url, is_retry=False):
    """
    Opens the browser on the given URL.

    Args:
        url (str):  The URL to be visited.
        is_retry (bool): Whether or not this is a retry attempt.
    """
    self._x = 0
    self._y = 0
    if not self._driver:
        self.start_browser()
    try:
        self._driver.get(url)
    except InvalidSessionIdException:
        if not is_retry:
            self.stop_browser()
            self.navigate_to(url, is_retry=True)

page_down(self, wait=0)

Press Page Down key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def page_down(self, wait=0):
    """
    Press Page Down key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    # TODO: Investigate why with Firefox the key isn't working properly
    action = ActionChains(self._driver)
    action.key_down(Keys.PAGE_DOWN)
    action.key_up(Keys.PAGE_DOWN)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

page_source(self)

Returns the active page source.

Returns:

Type Description
soup (BeautifulSoup)

BeautifulSoup object for the page source.

Source code in web/bot.py
def page_source(self):
    """
    Returns the active page source.

    Returns:
        soup (BeautifulSoup): BeautifulSoup object for the page source.
    """
    try:
        soup = BeautifulSoup(self._driver.page_source, 'html.parser')
        return soup
    except InvalidSessionIdException:
        return None

page_title(self)

Returns the active page title.

Returns:

Type Description
title (str)

The page title.

Source code in web/bot.py
def page_title(self):
    """
    Returns the active page title.

    Returns:
        title (str): The page title.
    """
    try:
        return self._driver.title
    except InvalidSessionIdException:
        return None

page_up(self, wait=0)

Press Page Up key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def page_up(self, wait=0):
    """
    Press Page Up key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    # TODO: Investigate why with Firefox the key isn't working properly
    action = ActionChains(self._driver)
    action.key_down(Keys.PAGE_UP)
    action.key_up(Keys.PAGE_UP)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

paste(self, text=None, wait=0)

Paste content from the clipboard.

Parameters:

Name Type Description Default
text str

The text to be pasted. Defaults to None

None
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def paste(self, text=None, wait=0):
    """
    Paste content from the clipboard.

    Args:
        text (str, optional): The text to be pasted. Defaults to None
        wait (int, optional): Wait interval (ms) after task
    """
    text_to_paste = self._clipboard
    if text:
        text_to_paste = text
    self.kb_type(text_to_paste)

print_pdf(self, path=None, print_options=None)

Print the current page as a PDF file.

Parameters:

Name Type Description Default
path str

The path for the file to be saved. Defaults to None.

None
print_options dict

Print options as defined at. Defaults to None.

None

Returns:

Type Description
str

the saved file path

Source code in web/bot.py
def print_pdf(self, path=None, print_options=None):
    """Print the current page as a PDF file.

    Args:
        path (str, optional): The path for the file to be saved. Defaults to None.
        print_options (dict, optional): Print options as defined at. Defaults to None.

    Returns:
        str: the saved file path
    """
    title = self.page_title() or "document"
    title = re.sub("[\\\\|/:–]", "", title)
    timeout = 60000
    default_path = os.path.expanduser(os.path.join(self.download_folder_path, f"{title}.pdf"))

    if self.browser in [Browser.CHROME, Browser.EDGE] and not self.headless:
        pdf_current_count = self.get_file_count(file_extension=".pdf")
        # Chrome still does not support headless webdriver print
        # but Firefox does.
        self.execute_javascript("window.print();")

        # We need to wait for the file to be available in this case.
        if self.page_title():
            self.wait_for_file(default_path, timeout=timeout)
        else:
            # Waiting when the file don't have the page title in path
            self.wait_for_new_file(file_extension=".pdf", current_count=pdf_current_count)

        # Move the downloaded pdf file if the path is not None
        if path:
            last_downloaded_pdf = self.get_last_created_file(self.download_folder_path, ".pdf")
            os.rename(last_downloaded_pdf, path)
            return path
        self.wait(2000)
        return default_path

    if print_options is None:
        print_options = {
            'landscape': False,
            'displayHeaderFooter': False,
            'printBackground': True,
            'preferCSSPageSize': True,
            'marginTop': 0,
            'marginBottom': 0
        }
    data = self._webdriver_command("print", print_options)
    bytes_file = base64.b64decode(data)
    if not path:
        path = default_path
    with open(path, "wb") as f:
        f.write(bytes_file)
    return path

refresh(self)

Refreshes the current page.

Source code in web/bot.py
def refresh(self):
    """
    Refreshes the current page.
    """
    self._driver.refresh()

release_shift(self)

Release key Shift. This method needs to be invoked after holding Shift or similar.

Source code in web/bot.py
def release_shift(self):
    """
    Release key Shift.
    This method needs to be invoked after holding Shift or similar.
    """
    action = ActionChains(self._driver)
    action.key_up(Keys.SHIFT)
    action.perform()

right_click(self, wait_after=300, *, clicks=1, interval_between_clicks=0)

Right click on the last found element.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
clicks int

Number of times to click. Defaults to 1.

1
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
Source code in web/bot.py
@only_if_element
def right_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION, *,
                clicks=1, interval_between_clicks=0):
    """
    Right click on the last found element.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
        clicks (int, optional): Number of times to click. Defaults to 1.
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
    """
    self.click(clicks=clicks, button='right', interval_between_clicks=interval_between_clicks)
    self.sleep(wait_after)

right_click_at(self, x, y)

Right click at the coordinate defined by x and y

Parameters:

Name Type Description Default
x int

The X coordinate

required
y int

The Y coordinate

required
Source code in web/bot.py
def right_click_at(self, x, y):
    """
    Right click at the coordinate defined by x and y

    Args:
        x (int): The X coordinate
        y (int): The Y coordinate
    """
    self.click_at(x, y, button='right')

right_click_relative(self, x, y, interval_between_clicks=0, wait_after=300)

Right Click Relative on the last found element.

Parameters:

Name Type Description Default
x int

Horizontal offset

required
y int

Vertical offset

required
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
wait_after int

Interval to wait after clicking on the element.

300
Source code in web/bot.py
@only_if_element
def right_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
    """
    Right Click Relative on the last found element.

    Args:
        x (int): Horizontal offset
        y (int): Vertical offset
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        wait_after (int, optional): Interval to wait after clicking on the element.

    """
    self.click_relative(x, y, wait_after=wait_after, interval_between_clicks=interval_between_clicks,
                        button='right')

save_screenshot(self, path)

Saves a screenshot in a given path.

Parameters:

Name Type Description Default
path str

The filepath in which to save the screenshot

required
Source code in web/bot.py
def save_screenshot(self, path):
    """
    Saves a screenshot in a given path.

    Args:
        path (str): The filepath in which to save the screenshot

    """
    self.screenshot(path)

screen_cut(self, x, y, width=None, height=None)

Capture a screenshot from a region of the screen.

Parameters:

Name Type Description Default
x int

region start position x

required
y int

region start position y

required
width int

region width

None
height int

region height

None

Returns:

Type Description
Image

The screenshot Image object

Source code in web/bot.py
def screen_cut(self, x, y, width=None, height=None):
    """
    Capture a screenshot from a region of the screen.

    Args:
        x (int): region start position x
        y (int): region start position y
        width (int): region width
        height (int): region height

    Returns:
        Image: The screenshot Image object
    """
    screen_size = self._get_page_size()
    x = x or 0
    y = y or 0
    width = width or screen_size[0]
    height = height or screen_size[1]
    img = self.screenshot(region=(x, y, width, height))
    return img

screenshot(self, filepath=None, region=None)

Capture a screenshot.

Parameters:

Name Type Description Default
filepath str

The filepath in which to save the screenshot. Defaults to None.

None
region tuple

Bounding box containing left, top, width and height to crop screenshot.

None

Returns:

Type Description
Image

The screenshot Image object

Source code in web/bot.py
def screenshot(self, filepath=None, region=None):
    """
    Capture a screenshot.

    Args:
        filepath (str, optional): The filepath in which to save the screenshot. Defaults to None.
        region (tuple, optional): Bounding box containing left, top, width and height to crop screenshot.

    Returns:
        Image: The screenshot Image object
    """
    img = self.get_screen_image(region)
    if filepath:
        img.save(filepath)
    return img

scroll_down(self, clicks)

Scroll Down n clicks

Parameters:

Name Type Description Default
clicks int

Number of times to scroll down.

required
Source code in web/bot.py
def scroll_down(self, clicks):
    """
    Scroll Down n clicks

    Args:
        clicks (int): Number of times to scroll down.
    """
    for i in range(clicks):
        self._driver.execute_script("window.scrollTo(0, window.scrollY + 200)")
        self.sleep(200)

scroll_element(self, element, steps=100, interval=500, start=0, end=None)

Scrolls down an element by its scroll height or a given amount defined by start and end.

This is useful for scrolling down a page to load more content or to scroll down a dynamically loaded element.

Parameters:

Name Type Description Default
element WebElement

The element to scroll.

required
steps int

Number of steps in which to conclude the scroll. Defaults to 100.

100
interval float

Time interval between each step. Defaults to 500ms.

500
start int

Start position. Defaults to 0.

0
end int

End position. Defaults to None.

None
Source code in web/bot.py
def scroll_element(self, element: WebElement, steps: int = 100, interval: float = 500,
                   start: int = 0, end: int = None):
    """Scrolls down an element by its scroll height or a given amount defined by `start` and `end`.

    This is useful for scrolling down a page to load more content or
    to scroll down a dynamically loaded element.

    Args:
        element (WebElement): The element to scroll.
        steps (int, optional): Number of steps in which to conclude the scroll. Defaults to 100.
        interval (float, optional): Time interval between each step. Defaults to 500ms.
        start (int, optional): Start position. Defaults to 0.
        end (int, optional): End position. Defaults to None.
    """
    ele_height = self.driver.execute_script(
        "return arguments[0].scrollHeight;", element
    )

    start = max(0, start)
    end = min(ele_height, end) if end is not None else ele_height

    for i in range(start, end, steps):
        self.driver.execute_script(
            "arguments[0].scrollTo(0, arguments[1])", element, i)
        self.sleep(interval/1000.0)

scroll_up(self, clicks)

Scroll Up n clicks

Parameters:

Name Type Description Default
clicks int

Number of times to scroll up.

required
Source code in web/bot.py
def scroll_up(self, clicks):
    """
    Scroll Up n clicks

    Args:
        clicks (int): Number of times to scroll up.
    """
    for i in range(clicks):
        self._driver.execute_script("window.scrollTo(0, window.scrollY - 200)")
        self.sleep(200)

set_current_element(self, element)

Changes the current screen element the bot will interact when using click(), move(), and similar methods.

This method is equivalent to self.state.element = element.

Parameters:

Name Type Description Default
element Box

A screen element from self.state.element or the find_all(as_list=True) method.

required
Source code in web/bot.py
def set_current_element(self, element: cv2find.Box):
    """
    Changes the current screen element the bot will interact when using click(), move(), and similar methods.

    This method is equivalent to self.state.element = element.

    Args:
        element (Box): A screen element from self.state.element or the find_all(as_list=True) method.
    """
    self.state.element = element

set_file_input_element(self, element, filepath)

Configure the filepath for upload in a file element. Note: This method does not submit the form.

Parameters:

Name Type Description Default
element WebElement

The file upload element.

required
filepath str

The path to the file to be uploaded.

required

Example:

...
# Find element
elem = self.find_element("body > form > input[type=file]")
# Set the filepath
self.set_file_input_element(elem, "./test.txt")
...

Source code in web/bot.py
def set_file_input_element(self, element: WebElement, filepath: str):
    """Configure the filepath for upload in a file element.
    Note: This method does not submit the form.

    Args:
        element (WebElement): The file upload element.
        filepath (str): The path to the file to be uploaded.

    **Example:**
    ```python
    ...
    # Find element
    elem = self.find_element("body > form > input[type=file]")
    # Set the filepath
    self.set_file_input_element(elem, "./test.txt")
    ...
    ```
    """
    fpath = os.path.abspath(os.path.expanduser(os.path.expandvars(filepath)))
    element.send_keys(fpath)

set_screen_resolution(self, width=None, height=None)

Configures the browser dimensions.

Parameters:

Name Type Description Default
width int

The desired width.

None
height int

The desired height.

None
Source code in web/bot.py
def set_screen_resolution(self, width=None, height=None):
    """
    Configures the browser dimensions.

    Args:
        width (int): The desired width.
        height (int): The desired height.
    """
    dimensions = (width or self.DEFAULT_DIMENSIONS[0], height or self.DEFAULT_DIMENSIONS[1])

    if self.headless:
        # When running headless the window size is the viewport size
        window_size = dimensions
    else:
        # When running non-headless we need to account for the borders and etc
        # So the size must be bigger to have the same viewport size as before
        window_size = self._driver.execute_script("""
            return [window.outerWidth - window.innerWidth + arguments[0],
              window.outerHeight - window.innerHeight + arguments[1]];
            """, *dimensions)
    self._driver.set_window_size(*window_size)

sleep(self, interval)

Wait / Sleep for a given interval.

Parameters:

Name Type Description Default
interval int

Interval in milliseconds

required
Source code in web/bot.py
def sleep(self, interval):
    """
    Wait / Sleep for a given interval.

    Args:
        interval (int): Interval in milliseconds

    """
    self.wait(interval)

space(self, wait=0)

Press Space key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def space(self, wait=0):
    """
    Press Space key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.SPACE)
    action.key_up(Keys.SPACE)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

start_browser(self)

Starts the selected browser.

Source code in web/bot.py
def start_browser(self):
    """
    Starts the selected browser.
    """
    def check_driver():
        # Look for driver
        driver_name = BROWSER_CONFIGS.get(self.browser).get("driver")
        location = shutil.which(driver_name)
        if not location:
            raise RuntimeError(
                f"{driver_name} was not found. Please make sure to have it on your PATH or set driver_path")
        return location

    # Specific webdriver class for a given browser
    driver_class = BROWSER_CONFIGS.get(self.browser).get("class")
    # Specific default options method for a given browser
    func_def_options = BROWSER_CONFIGS.get(self.browser).get("options")
    # Specific capabilities method for a given browser
    func_def_capabilities = BROWSER_CONFIGS.get(self.browser).get("capabilities")

    opt = self.options or func_def_options(
        self.headless, self._download_folder_path, None, self.page_load_strategy
    )
    cap = self.capabilities or func_def_capabilities()
    self.options = opt
    self.capabilities = cap
    driver_path = self.driver_path or check_driver()
    self.driver_path = driver_path
    if compat.version_selenium_is_larger_than_four():
        service = BROWSER_CONFIGS.get(self.browser).get("service")
        service = service(executable_path=self.driver_path)
        service.desired_capabilities = cap

        self._driver = driver_class(options=opt, service=service)
    else:
        self._driver = driver_class(options=opt, desired_capabilities=cap, executable_path=driver_path)
    self.set_screen_resolution()

stop_browser(self)

Stops the Chrome browser and clean up the User Data Directory.

Warning

After invoking this method, you will need to reassign your custom options and capabilities.

Source code in web/bot.py
def stop_browser(self):
    """
    Stops the Chrome browser and clean up the User Data Directory.

    Warning:
        After invoking this method, you will need to reassign your custom options and capabilities.
    """
    if not self._driver:
        return
    self._driver.close()
    self._driver.quit()
    self.options = None
    self.capabilities = None
    self._driver = None

tab(self, wait=0)

Press key Tab

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def tab(self, wait=0):
    """
    Press key Tab

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.TAB)
    action.key_up(Keys.TAB)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

triple_click(self, wait_after=300)

Triple Click on the last found element.

Parameters:

Name Type Description Default
wait_after int

Interval to wait after clicking on the element.

300
Source code in web/bot.py
@only_if_element
def triple_click(self, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
    """
    Triple Click on the last found element.

    Args:
        wait_after (int, optional): Interval to wait after clicking on the element.
    """
    self.click(wait_after=wait_after, clicks=3)

triple_click_relative(self, x, y, interval_between_clicks=0, wait_after=300)

Triple Click Relative on the last found element.

Parameters:

Name Type Description Default
x int

Horizontal offset

required
y int

Vertical offset

required
interval_between_clicks int

The interval between clicks in ms. Defaults to 0.

0
wait_after int

Interval to wait after clicking on the element.

300
Source code in web/bot.py
@only_if_element
def triple_click_relative(self, x, y, interval_between_clicks=0, wait_after=config.DEFAULT_SLEEP_AFTER_ACTION):
    """
    Triple Click Relative on the last found element.

    Args:
        x (int): Horizontal offset
        y (int): Vertical offset
        interval_between_clicks (int, optional): The interval between clicks in ms. Defaults to 0.
        wait_after (int, optional): Interval to wait after clicking on the element.
    """
    self.click_relative(x, y, wait_after=wait_after, clicks=3, interval_between_clicks=interval_between_clicks)

type_down(self, wait=0)

Press Down key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def type_down(self, wait=0):
    """
    Press Down key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ARROW_DOWN)
    action.key_up(Keys.ARROW_DOWN)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

type_keys(self, keys)

Press a sequence of keys. Hold the keys in the specific order and releases them.

Parameters:

Name Type Description Default
keys list

List of keys to be pressed

required
Source code in web/bot.py
def type_keys(self, keys):
    """
    Press a sequence of keys. Hold the keys in the specific order and releases them.

    Args:
        keys (list): List of keys to be pressed
    """
    self.type_keys_with_interval(100, keys)

type_keys_with_interval(self, interval, keys)

Press a sequence of keys. Hold the keys in the specific order and releases them.

Parameters:

Name Type Description Default
interval int

Interval (ms) in which to press and release keys

required
keys list

List of Keys to be pressed

required
Source code in web/bot.py
def type_keys_with_interval(self, interval, keys):
    """
    Press a sequence of keys. Hold the keys in the specific order and releases them.

    Args:
        interval (int): Interval (ms) in which to press and release keys
        keys (list): List of Keys to be pressed
    """
    action = ActionChains(self._driver)

    for k in keys:
        action.key_down(k)
        action.pause(interval / 1000.0)
    for k in reversed(keys):
        action.key_up(k)
        action.pause(interval / 1000.0)
    action.perform()

type_left(self, wait=0)

Press Left key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def type_left(self, wait=0):
    """
    Press Left key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ARROW_LEFT)
    action.key_up(Keys.ARROW_LEFT)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

type_right(self, wait=0)

Press Right key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def type_right(self, wait=0):
    """
    Press Right key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    self.key_right(wait=wait)

type_up(self, wait=0)

Press Up key

Parameters:

Name Type Description Default
wait int

Wait interval (ms) after task

0
Source code in web/bot.py
def type_up(self, wait=0):
    """
    Press Up key

    Args:
        wait (int, optional): Wait interval (ms) after task

    """
    action = ActionChains(self._driver)
    action.key_down(Keys.ARROW_UP)
    action.key_up(Keys.ARROW_UP)
    action.perform()
    delay = max(0, wait or config.DEFAULT_SLEEP_AFTER_ACTION)
    self.sleep(delay)

wait(self, interval)

Wait / Sleep for a given interval.

Parameters:

Name Type Description Default
interval int

Interval in milliseconds

required
Source code in web/bot.py
def wait(self, interval):
    """
    Wait / Sleep for a given interval.

    Args:
        interval (int): Interval in milliseconds

    """
    time.sleep(interval / 1000.0)

wait_for_downloads(self, timeout=120000)

Wait for all downloads to be finished. Beware that this method replaces the current page with the downloads window.

Parameters:

Name Type Description Default
timeout int

Timeout in millis. Defaults to 120000.

120000
Source code in web/bot.py
def wait_for_downloads(self, timeout: int = 120000):
    """
    Wait for all downloads to be finished.
    Beware that this method replaces the current page with the downloads window.

    Args:
        timeout (int, optional): Timeout in millis. Defaults to 120000.
    """
    if self.browser in [Browser.CHROME, Browser.EDGE] and self.headless:
        start_time = time.time()
        while True:
            elapsed_time = (time.time() - start_time) * 1000
            if elapsed_time > timeout:
                return False
            downloads_count = self.get_file_count(self.download_folder_path, ".crdownload")
            if downloads_count == 0:
                return True
            self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

    wait_method = BROWSER_CONFIGS.get(self.browser).get("wait_for_downloads")
    # waits for all the files to be completed
    WebDriverWait(self._driver, timeout/1000.0, 1).until(wait_method)

wait_for_element_visibility(self, element, visible=True, waiting_time=10000)

Wait for the element to be visible or hidden.

Parameters:

Name Type Description Default
element WebElement

The element to wait for.

required
visible bool

Whether to wait for the element to be visible. Defaults to True.

True
waiting_time int

Maximum wait time (ms) to search for a hit. Defaults to 10000ms (10s).

10000
Source code in web/bot.py
def wait_for_element_visibility(self, element: WebElement, visible: bool = True, waiting_time=10000):
    """Wait for the element to be visible or hidden.

    Args:
        element (WebElement): The element to wait for.
        visible (bool, optional): Whether to wait for the element to be visible. Defaults to True.
        waiting_time (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 10000ms (10s).
    """
    if visible:
        wait_method = EC.visibility_of
    else:
        wait_method = EC.invisibility_of_element

    WebDriverWait(self._driver, timeout=waiting_time/1000.0).until(wait_method(element))

wait_for_file(self, path, timeout=60000)

Wait for a file to be available on disk.

Parameters:

Name Type Description Default
path str

The path for the file to be executed

required
timeout int

Maximum wait time (ms) to search for a hit. Defaults to 60000ms (60s).

60000

Returns:

Type Description
status (bool)

Whether or not the file was available before the timeout

Source code in web/bot.py
def wait_for_file(self, path, timeout=60000):
    """
    Wait for a file to be available on disk.

    Args:
        path (str): The path for the file to be executed
        timeout (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 60000ms (60s).

    Returns:
        status (bool): Whether or not the file was available before the timeout

    """
    path = os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
    start_time = time.time()

    while True:
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > timeout:
            return False
        if os.path.isfile(path) and os.access(path, os.R_OK):
            if self.browser == Browser.FIREFOX and os.path.getsize(path) == 0:
                # if file is empty, the download is not completed.
                continue
            self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)
            return True
        self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

wait_for_new_file(self, path=None, file_extension='', current_count=0, timeout=60000)

Wait for a new file to be available on disk without the file path.

Parameters:

Name Type Description Default
path str

The path of the folder where the file is expected. Defaults to None.

None
file_extension str

The extension of the file to be searched for (e.g., .pdf, .txt).

''
current_count int

The current number of files in the folder of the given type. Defaults to 0 files

0
timeout int

Maximum wait time (ms) to search for a hit. Defaults to 60000ms (60s).

60000

Returns:

Type Description
str

the path of the last created file of the given type

Source code in web/bot.py
def wait_for_new_file(self, path=None, file_extension="", current_count=0, timeout=60000):
    """
    Wait for a new file to be available on disk without the file path.

    Args:
        path (str, optional): The path of the folder where the file is expected. Defaults to None.
        file_extension (str, optional): The extension of the file to be searched for (e.g., .pdf, .txt).
        current_count (int): The current number of files in the folder of the given type. Defaults to 0 files
        timeout (int, optional): Maximum wait time (ms) to search for a hit.
            Defaults to 60000ms (60s).

    Returns:
        str: the path of the last created file of the given type
    """
    if not path:
        path = self.download_folder_path

    start_time = time.time()
    while True:
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > timeout:
            return None
        file_count = self.get_file_count(path, f"*{file_extension}")
        if file_count == current_count + 1:
            self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)
            return self.get_last_created_file(path, f"*{file_extension}")
        self.sleep(config.DEFAULT_SLEEP_AFTER_ACTION)

wait_for_new_page(self, waiting_time=10000, activate=True)

Context manager to wait for a new page to load and activate it.

Parameters:

Name Type Description Default
waiting_time int

The maximum waiting time. Defaults to 10000.

10000
activate bool

Whether or not to activate the new page. Defaults to True.

True
Source code in web/bot.py
@contextmanager
def wait_for_new_page(self, waiting_time=10000, activate=True):
    """Context manager to wait for a new page to load and activate it.

    Args:
        waiting_time (int, optional): The maximum waiting time. Defaults to 10000.
        activate (bool, optional): Whether or not to activate the new page. Defaults to True.

    """
    tabs = self.get_tabs()
    yield
    start_time = time.time()
    while tabs == self.get_tabs():
        elapsed_time = (time.time() - start_time) * 1000
        if elapsed_time > waiting_time:
            return None
        time.sleep(0.1)
    if activate:
        self.activate_tab(self.get_tabs()[-1])

wait_for_stale_element(self, element, timeout=10000)

Wait until the WebElement element becomes stale (outdated).

Parameters:

Name Type Description Default
element WebElement

The element to monitor for staleness.

required
timeout int

Timeout in millis. Defaults to 120000.

10000
Source code in web/bot.py
def wait_for_stale_element(self, element: WebElement, timeout: int = 10000):
    """
    Wait until the WebElement element becomes stale (outdated).

    Args:
        element (WebElement): The element to monitor for staleness.
        timeout (int, optional): Timeout in millis. Defaults to 120000.
    """
    try:
        WebDriverWait(self._driver, timeout=timeout/1000.0).until(EC.staleness_of(element))
    except (TimeoutException, NoSuchElementException):
        pass