Skip to main content

Selenium core for Python

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_selenium_auto_core-0.5.5.tar.gz (28.7 kB view hashes)

Uploaded Source

Built Distribution

py_selenium_auto_core-0.5.5-py3-none-any.whl (39.1 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page