py-selenium-auto-core

Selenium core for Python


Keywords
testing, selenium, driver, test, automation
License
Apache-2.0
Install
pip install py-selenium-auto-core==0.5.5

Documentation

Selenium CORE for Python

Latest Version License Supported Python versions Supported Python implementations Tests codecov

Introduction

It's a library with core functions simplifying work with Selenium-controlled applications.

This package is based on Aquality Selenium CORE for .NET and provides a set of methods related to the most common actions performed with elements. So you shouldn't have a lot of difficulties with this solution if you interacted with Aquality Selenium CORE.

To simplify overriding of implementations this solution uses Dependency Injection.

Supported Python Versions

  • Python 3.7-3.12

Installation

If you have pip on your system, you can simply install or upgrade the Python bindings:

pip install py-selenium-auto-core

Alternately, you can download the source distribution from PyPI, unarchive it, and run:

python setup.py install

Quick start

  1. Setup Dependency Injection container using Startup

The solution offers a ServiceProvider implementation using Dependency Injection container as a single point for interacting with various services and their dependencies:

class ServiceProvider(containers.DeclarativeContainer):
    """Container that allows to resolve dependencies for all services in the library"""
    
    settings_file: Singleton[JsonSettingsFile] = Singleton(JsonSettingsFile({}))
    application: Factory[Application] = Factory(Application)
    logger: Singleton[Logger] = Singleton(Logger)
    logger_configuration: Singleton[LoggerConfiguration] = Singleton(LoggerConfiguration, settings_file)
    timeout_configuration: Singleton[TimeoutConfiguration] = Singleton(TimeoutConfiguration, settings_file)
    localization_manager: Singleton[LocalizationManager] = Singleton(LocalizationManager, logger_configuration, logger)
    ...

This allows you to control dependencies between packages and cause the creation of the objects themselves at the time of accessing them. Example of working with ServiceProvider:

ServiceProvider.logger().info("Message")  # message logging
ServiceProvider.timeout_configuration().interval  # Getting the Timeout.interval value

For ease of use, the solution contains a Startup object that makes it easy to get a ServiceProvider object with redefined dependencies for settings_file and application:

class Startup:

    @staticmethod
    def configure_services(application_provider: Callable, settings: Optional[JsonSettingsFile] = None, service_provider: Optional[T] = None,
    ) -> T | ServiceProvider:
        service_provider: T = service_provider or ServiceProvider()
        settings = settings or Startup.get_settings()

        service_provider.settings_file.override(Singleton(lambda: settings))
        service_provider.application.override(Factory(application_provider))

        return service_provider

    @staticmethod
    def get_settings() -> JsonSettingsFile:
        profile_name = EnvironmentConfiguration.get_variable("profile")
        settings_profile = "settings.json" if not profile_name else f"settings.{profile_name}.json"
        if FileReader.is_resource_file_exist(settings_profile, root_path=RootPathHelper.calling_root_path()):
            return JsonSettingsFile(setting_name=settings_profile, root_path=RootPathHelper.calling_root_path())
        return JsonSettingsFile(setting_name=settings_profile, root_path=RootPathHelper.executing_root_path())

service_provider = Startup.configure_services(application_provider=lambda: YourApplication())
service_provider.application()
service_provider.logger().info("Message")

To use a different implementation between dependencies, you need to execute provider.override:

service_provider = ServiceProvider()
service_provider.timeout_configuration.override(Singleton(CustomTimeoutConfiguration, service_provider.settings_file))

If you want to have your own CustomServiceProvider implementation that complements the base container, you can use inheritance, as in the example below:

class CustomServiceProvider(ServiceProvider):
    timeout: Sigleton[CustomTimeoutConfiguration] = Singleton(CustomTimeoutConfiguration, ServiceProvider.settings_file)

Keep in mind that the internal dependencies of the ServiceProvider container will interact with ServiceProvider.timeout, and not with CustomServiceProvider.timeout. The solution is quite simple:

ServiceProvider.override(CustomServiceProvider)
service_provider = Startup.configure_services(application_provider=lambda: YourApplication(), service_provider=CustomServiceProvider())
ServiceProvider.reset_override()  # necessary because override overrides the base container

or add your implementation of Startup:

class CustomSPStartup(Startup):

    @staticmethod
    def configure_services(
            application_provider: Callable, settings: Optional[JsonSettingsFile] = None, service_provider: Optional[ServiceProvider] = None,
    ) -> CustomServiceProvider:
        ServiceProvider.override(CustomServiceProvider)

        settings = JsonSettingsFile("settings.special.json", RootPathHelper.calling_root_path())
        service_provider = Startup.configure_services(application_provider, settings, CustomServiceProvider())

        ServiceProvider.reset_override()
        return service_provider
  1. Setup BrowserService using CoreServices

The solution also contains a CoreService, which helps to register your container and application, with which you can interact in the future:

The simplest way is to create your own Services class extended from abstract CoreServices with the following simple signature:

class BrowserServices(CoreServices):

    @classmethod
    def is_application_started(cls) -> bool:
        return cls._is_application_started()

    @classmethod
    def application(cls) -> YourApplication:
        return cls._get_application(lambda service: cls._start_application(service))

    @classmethod
    def service_provider(cls) -> ServiceProvider:
        return cls._get_service_provider(lambda service: cls.application())

    @classmethod
    def _start_application(cls, service_provider: ServiceProvider):
        ...  # your implementation

If you need to register your own services / rewrite the implementation, you need override Startup and implement BrowserServices like in example below:

class TestStartup(Startup):

    @staticmethod
    def configure_services(
            application_provider: Callable,
            settings: Optional[JsonSettingsFile] = None,
            service_provider: Optional[ServiceProvider] = None,
    ) -> ServiceProvider:
        settings = JsonSettingsFile("settings.special.json", RootPathHelper.calling_root_path())
        service_provider = Startup.configure_services(application_provider, settings)
        service_provider.timeout_configuration.override(
            Singleton(TestTimeoutConfiguration, service_provider.settings_file)
        )
        return service_provider

class BrowserServices:

    class _BrowserService(CoreServices):

        startup: TestStartup = TestStartup()

        def __init__(self):
            Logger.info("Create")

        @property
        def application(self) -> Application:
            return self._get_application(
                self._start_function,
                lambda: self.startup.configure_services(lambda service: self.application),
            )

        @property
        def service_provider(self) -> ServiceProvider:
            return self._get_service_provider(
                lambda service: self.application,
                lambda: self.startup.configure_services(lambda service: self.application),
            )

        def set_startup(self, startup: Startup):
            if startup is not None:
                self.startup = startup

        @property
        def _start_function(self):
            return lambda serive: Application()

    Instance: _BrowserService = _BrowserService()


class CustomStartup(Startup):

    @staticmethod
    def configure_services(application_provider: Callable, settings: JsonSettingsFile = None) -> ServiceProvider:
        service_provider = Startup.configure_services(application_provider, settings)
        # your implementation service_provider.timeout_configuration.override(Singleton(TimeoutConfiguration, service_provider.settings_file))
        return service_provider
  1. Work with Application via the implemented BrowserServices or via element services

All the services could be resolved from the Dependency Injection container via ServiceProvider

BrowserServices.application().driver.find_element(ELEMENT).click()
BrowserServices.service_provider().conditional_wait().wait_for_driver(
    lambda driver: len(driver.find_elements(Locator(By.XPATH, "//*"))) > 0
)

or with Instance:

BrowserServices.Instance.application.driver.find_element(ELEMENT).click()
BrowserServices.Instance.service_provider.conditional_wait().wait_for_driver(
    lambda driver: len(driver.find_elements(Locator(By.XPATH, "//*"))) > 0
)

License

Library's source code is made available under the Apache 2.0 license.