Skip to content

Commit

Permalink
fix: fix the providers
Browse files Browse the repository at this point in the history
Speedtest and speedofme changed and add some more banner.
I had to adapt and add new detection.

I also improved the code to make is more robust.

Signed-off-by: Lionel Hubaut <lionel.hubaut@tessares.net>
  • Loading branch information
lion24 committed Apr 4, 2024
1 parent 7abebe3 commit 328e717
Show file tree
Hide file tree
Showing 12 changed files with 911 additions and 516 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python.analysis.extraPaths": [
"lib/"
]
}
16 changes: 6 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PLATFORM := linux64
VERSION=$(shell curl http://chromedriver.storage.googleapis.com/LATEST_RELEASE)
VERSION=$(shell curl https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE)
PROJECT_NAME := "automated-speedtest"
BOLD := \033[1m
RESET := \033[0m
Expand All @@ -17,14 +17,10 @@ env: requirements.txt
&& pip install -Ur $<

webdriver:
ifeq (, $(shell which bsdtar))
$(error "bsdtar is not install, consider doing apt-get install bsdtar")
endif
curl http://chromedriver.storage.googleapis.com/$(VERSION)/chromedriver_$(PLATFORM).zip \
| bsdtar -xvf - -C env/bin/
@chmod a+x env/bin/chromedriver

check:
curl -o /tmp/driver.zip https://storage.googleapis.com/chrome-for-testing-public/$(VERSION)/$(PLATFORM)/chromedriver-$(PLATFORM).zip \
&& unzip -oj /tmp/driver.zip -d env/bin && rm -rf /tmp/driver.zip

check:
@flake8

lint:
Expand All @@ -39,7 +35,7 @@ clean:
find . -name '__pycache__' -exec rm -fr {} +

run: clean
@PYTHONPATH=$(PYTHONPATH):lib/ python3 speedtest.py
python main.py

test: clean
pytest -q tests
Expand Down
40 changes: 40 additions & 0 deletions lib/browser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
browser is the module handling the different browser logic and their
implementation in order to be used with selenium.
"""

import abc

from selenium.webdriver.remote.webdriver import WebDriver


class BrowserInterface(metaclass=abc.ABCMeta):
"""
BrowserInterface is a generic interface each browser subclass will need to
implement in order to correctly configure the selenium webdriver. This interface
ensures that all subclasses provide a specific method to load and configure
a Selenium WebDriver instance appropriate for the browser they represent.
"""
@classmethod
def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'load_driver') and
callable(subclass.load_driver) or
NotImplemented)

@classmethod
@abc.abstractmethod
def load_driver(cls) -> WebDriver:
"""
Loads and returns a configured instance of Selenium WebDriver specific to the browser.
This method must be implemented by subclasses to provide a ready-to-use WebDriver
instance that is appropriately configured for the browser the subclass represents.
The configuration may include setting browser options, capabilities, and webdriver paths.
Returns:
WebDriver: An instance of a Selenium WebDriver ready for browser automation tasks.
Raises:
NotImplementedError: If the subclass does not implement this method.
"""
raise NotImplementedError
66 changes: 66 additions & 0 deletions lib/browser/chromium.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
This module defines the ChromiumBrower class, which is an implementation of the BrowserInterface
for creating and configuring a Selenium WebDriver specific to Chromium-based browsers. Currently,
the implementation focuses on Google Chrome, with the intention to extend support to other
Chromium-based browsers in the future.
Key Components:
- BrowserInterface: An abstract base class that defines a generic interface for browser subclasses.
- ChromiumBrower: A concrete class that implements the BrowserInterface for the Chrome browser,
providing a method to load and configure a Selenium WebDriver with Chromium-specific options.
"""

from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.chromium.service import ChromiumService
from selenium.webdriver.chromium.options import ChromiumOptions

from . import BrowserInterface

@BrowserInterface.register
class ChromiumBrower:
"""
ChromiumBrower implements the BrowserInterface to provide a method for loading
and configuring a WebDriver instance specifically for Chromium browsers.
This class currently supports Chrome browser, with plans to extend support
to other Chromium-based browsers. It demonstrates how to set up a Selenium
WebDriver with specific options tailored for a Chromium browser instance,
including setting the binary location, window size, and disabling GPU acceleration.
Methods:
load_driver(): Creates and returns a configured Selenium WebDriver instance
for the Chromium browser.
"""

def load_driver(self) -> WebDriver:
"""
Initializes and returns a Selenium WebDriver instance configured for the Chrome browser.
This method sets up a ChromiumService and configures ChromiumOptions to specify
the binary location of the Chrome browser, set the window size, disable GPU acceleration,
and set the browser language. These options ensure that the WebDriver instance is
ready for web automation tasks with Chrome.
Note: While this implementation currently supports Chromium, there is a plan to expand
support to other browsers.
Returns:
WebDriver: A configured instance of Selenium WebDriver for the Chromium browser.
Example:
>>> chromium_browser = ChromiumBrower()
>>> driver = chromium_browser.load_driver()
# Now `driver` can be used to automate web interactions using Chromium.
"""
service = ChromiumService()
options = ChromiumOptions()
options.binary_location = '/snap/chromium/2805/usr/lib/chromium-browser/chrome'
#options.add_argument('--headless')
options.add_argument('--window-size=1400x900')
options.add_argument('--disable-gpu')
options.add_argument('--lang=en_US')
driver = webdriver.Chrome(service=service, options=options)
return driver
# As using selenium api > 2.x, this call should block until
# readyState is hit.
122 changes: 71 additions & 51 deletions lib/provider/__init__.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,103 @@
# coding: utf-8
# Disable broad-except for now, will refine later.
# pylint: disable=broad-except
"""
Generic Provider class which provides an abstraction for the different drivers
we would like to use.
"""

import time
import sys
import traceback
from abc import ABC, abstractmethod
from selenium import webdriver
from browser import BrowserInterface
from selenium.common.exceptions import (
StaleElementReferenceException,
NoSuchElementException,
ElementNotInteractableException,
TimeoutException
)
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from selenium.webdriver.remote.webelement import WebElement

class Provider(ABC):
"""
Each driver will be derive from this Abstract provider class.
This class also contains generic methods which needs to be implemented
in the concrete provider classes.
"""
def __init__(self, target):
self.target = target
self.driver = self.driver_init()
self.driver.get(target)
def __init__(self, browser: BrowserInterface):
try:
self.driver = browser.load_driver()
except Exception as e:
print("browser.load_driver() exception: ", e)

@staticmethod
def driver_init():
def wait_to_be_visible(self, element: WebElement, timeout=90):
"""
Init a driver. Right now, only chrome is support, plan is to add support for
more drivers.
Method that wait until an element is present and clickable in the DOM.
"""
options = webdriver.ChromeOptions()
options.binary_location = '/usr/bin/google-chrome'
options.add_argument('--headless')
options.add_argument('--window-size=1400x900')
options.add_argument('--disable-gpu')
options.add_argument('--lang=en_US')
return webdriver.Chrome(chrome_options=options)
# As using selenium api > 2.x, this call should block until
# readyState is hit.
print("wait_to_be_visible...")
errors = [NoSuchElementException, ElementNotInteractableException]
try:
wait = WebDriverWait(
self.driver,
timeout=timeout,
poll_frequency=.2,
ignored_exceptions=errors)

def wait_for_clickable(self, element, timeout=90):
res = wait.until(lambda d: element.is_displayed() or False)
print(f"res: {res}")
return True
except TimeoutException as e:
print("wait_for_clickable timed out waiting: ", e)
return False
except Exception as e:
print("wait_for_clickable expection occurred: ", e)
return False

def wait_for_element(self, locator, timeout=120) -> WebElement:
"""
Method that wait until an element is present and clickable in the DOM.
Wait for an element to be visible and returns it
If not found, NoSuchElementException is raised.
:param locator: A tuple of (By, locator) to find the element.
"""
time.sleep(2) # Hack, when element is clicked, it remains active for a
# small period on the DOM before beeing staled.
try:
WebDriverWait(self.driver,
timeout,
poll_frequency=2,
ignored_exceptions=(StaleElementReferenceException)
).until(
EC.element_to_be_clickable(
(By.CSS_SELECTOR, element)))
except TimeoutException as ex:
print("Timeout in finding element {} from DOM, reason: {}"
.format(element, str(ex))
)
raise
except Exception:
print("Unexpected error occured:", sys.exc_info()[0])
raise
element = WebDriverWait(self.driver, timeout).until(
EC.visibility_of_element_located(locator)
)
return element
except TimeoutException as e:
raise NoSuchElementException(
f"Element with locator {locator} was not visible after {timeout} second") from e

def cleanup(self, errno=0, exp=None):
def wait_for_button_clickable(self, locator, timeout=120) -> WebElement:
"""
If any error occured, we will cleanup reserved resources.
Wait for a button to be visible and clickable
If not found, NoSuchElementException is raised.
:param locator: A tuple of (By, locator) to find the element.
Returns:
WebElement: The button identified by locator
"""
if exp:
print("An error occured: " + exp)
traceback.print_exc()
self.driver.close()
self.driver.quit()
self.driver = None
try:
element = WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable(locator)
)
return element
except TimeoutException as e:
raise NoSuchElementException(
f"Element with locator {locator} was not clickable after {timeout} second") from e

def cleanup(self, errno=0):
"""
Cleanup every reserved resources.
"""
if self.driver:
self.driver.close()
self.driver.quit()
self.driver = None

if errno:
sys.exit(errno)

Expand All @@ -86,11 +106,11 @@ def run(self, filename):
"""
Actual method that would trigger the test for the given provider.
"""
raise "Should be implemented in daughter class"
raise NotImplementedError("Should be implemented in daughter class")

@abstractmethod
def parse_results(self):
"""
Method that would gather results from the speedtest for the given provider
"""
raise "Should be implemented in daughter class"
raise NotImplementedError("Should be implemented in daughter class")
Loading

0 comments on commit 328e717

Please sign in to comment.