# Selenium CORE for Python
[![Latest Version](https://img.shields.io/pypi/v/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)
[![License](https://img.shields.io/pypi/l/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)
[![Supported Python implementations](https://img.shields.io/pypi/implementation/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)
[![Tests](https://github.com/Polmik/py-selenium-auto-core/actions/workflows/tests.yml/badge.svg)](https://github.com/Polmik/py-selenium-auto-core/actions/workflows/tests.yml)
[![codecov](https://codecov.io/gh/Polmik/py-selenium-auto-core/branch/main/graph/badge.svg)](https://codecov.io/gh/Polmik/py-selenium-auto-core)
### 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](https://pip.pypa.io/en/stable/) on your system, you can simply install or upgrade the Python bindings:
```bash
pip install py-selenium-auto-core
```
Alternately, you can download the source distribution from [PyPI](https://pypi.org/project/py-selenium-auto-core/#files), unarchive it, and run:
```bash
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:
```python
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:
```python
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:
```python
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:
```python
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:
```python
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:
```python
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:
```python
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
```
2. 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:
```python
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:
```python
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
```
3. Work with Application via the implemented BrowserServices or via element services
All the services could be resolved from the Dependency Injection container via ServiceProvider
```python
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:
```python
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](https://github.com/Polmik/py-selenium-auto-core/blob/main/LICENSE).
Raw data
{
"_id": null,
"home_page": "https://github.com/Polmik/py-selenium-auto-core",
"name": "py-selenium-auto-core",
"maintainer": "Egor Ryaboshapko",
"docs_url": null,
"requires_python": null,
"maintainer_email": "mrpolmik@hotmail.com",
"keywords": "testing, selenium, driver, test automation",
"author": "Egor Ryaboshapko",
"author_email": "mrpolmik@hotmail.com",
"download_url": "https://files.pythonhosted.org/packages/9b/d9/3fd51e1920461efcbcd2e43461e0f596b18934e762166c32c5fe72b44066/py_selenium_auto_core-0.5.5.tar.gz",
"platform": "any",
"description": "# Selenium CORE for Python\n\n[![Latest Version](https://img.shields.io/pypi/v/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)\n[![License](https://img.shields.io/pypi/l/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)\n[![Supported Python versions](https://img.shields.io/pypi/pyversions/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)\n[![Supported Python implementations](https://img.shields.io/pypi/implementation/py_selenium_auto_core.svg)](https://pypi.org/project/py-selenium-auto-core/)\n[![Tests](https://github.com/Polmik/py-selenium-auto-core/actions/workflows/tests.yml/badge.svg)](https://github.com/Polmik/py-selenium-auto-core/actions/workflows/tests.yml)\n[![codecov](https://codecov.io/gh/Polmik/py-selenium-auto-core/branch/main/graph/badge.svg)](https://codecov.io/gh/Polmik/py-selenium-auto-core)\n\n### Introduction\n\nIt's a library with core functions simplifying work with Selenium-controlled applications.\n\nThis 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**.\n\nTo simplify overriding of implementations this solution uses Dependency Injection.\n\n### Supported Python Versions\n\n* Python 3.7-3.12\n\n### Installation \n\nIf you have [pip](https://pip.pypa.io/en/stable/) on your system, you can simply install or upgrade the Python bindings:\n\n```bash\npip install py-selenium-auto-core\n```\n\nAlternately, you can download the source distribution from [PyPI](https://pypi.org/project/py-selenium-auto-core/#files), unarchive it, and run:\n\n```bash\npython setup.py install\n```\n\n### Quick start\n\n1. Setup Dependency Injection container using Startup\n\nThe solution offers a ServiceProvider implementation using Dependency Injection container as a single point for interacting with various services and their dependencies:\n\n```python\nclass ServiceProvider(containers.DeclarativeContainer):\n \"\"\"Container that allows to resolve dependencies for all services in the library\"\"\"\n \n settings_file: Singleton[JsonSettingsFile] = Singleton(JsonSettingsFile({}))\n application: Factory[Application] = Factory(Application)\n logger: Singleton[Logger] = Singleton(Logger)\n logger_configuration: Singleton[LoggerConfiguration] = Singleton(LoggerConfiguration, settings_file)\n timeout_configuration: Singleton[TimeoutConfiguration] = Singleton(TimeoutConfiguration, settings_file)\n localization_manager: Singleton[LocalizationManager] = Singleton(LocalizationManager, logger_configuration, logger)\n ...\n```\n\nThis allows you to control dependencies between packages and cause the creation of the objects themselves at the time of accessing them.\nExample of working with ServiceProvider:\n```python\nServiceProvider.logger().info(\"Message\") # message logging\nServiceProvider.timeout_configuration().interval # Getting the Timeout.interval value\n```\n\nFor 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:\n```python\nclass Startup:\n\n @staticmethod\n def configure_services(application_provider: Callable, settings: Optional[JsonSettingsFile] = None, service_provider: Optional[T] = None,\n ) -> T | ServiceProvider:\n service_provider: T = service_provider or ServiceProvider()\n settings = settings or Startup.get_settings()\n\n service_provider.settings_file.override(Singleton(lambda: settings))\n service_provider.application.override(Factory(application_provider))\n\n return service_provider\n\n @staticmethod\n def get_settings() -> JsonSettingsFile:\n profile_name = EnvironmentConfiguration.get_variable(\"profile\")\n settings_profile = \"settings.json\" if not profile_name else f\"settings.{profile_name}.json\"\n if FileReader.is_resource_file_exist(settings_profile, root_path=RootPathHelper.calling_root_path()):\n return JsonSettingsFile(setting_name=settings_profile, root_path=RootPathHelper.calling_root_path())\n return JsonSettingsFile(setting_name=settings_profile, root_path=RootPathHelper.executing_root_path())\n\nservice_provider = Startup.configure_services(application_provider=lambda: YourApplication())\nservice_provider.application()\nservice_provider.logger().info(\"Message\")\n```\n\nTo use a different implementation between dependencies, you need to execute provider.override:\n```python\nservice_provider = ServiceProvider()\nservice_provider.timeout_configuration.override(Singleton(CustomTimeoutConfiguration, service_provider.settings_file))\n```\n\nIf you want to have your own CustomServiceProvider implementation that complements the base container, you can use inheritance, as in the example below:\n```python\nclass CustomServiceProvider(ServiceProvider):\n timeout: Sigleton[CustomTimeoutConfiguration] = Singleton(CustomTimeoutConfiguration, ServiceProvider.settings_file)\n```\n\nKeep 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:\n```python\nServiceProvider.override(CustomServiceProvider)\nservice_provider = Startup.configure_services(application_provider=lambda: YourApplication(), service_provider=CustomServiceProvider())\nServiceProvider.reset_override() # necessary because override overrides the base container\n```\n\nor add your implementation of Startup:\n```python\nclass CustomSPStartup(Startup):\n\n @staticmethod\n def configure_services(\n application_provider: Callable, settings: Optional[JsonSettingsFile] = None, service_provider: Optional[ServiceProvider] = None,\n ) -> CustomServiceProvider:\n ServiceProvider.override(CustomServiceProvider)\n\n settings = JsonSettingsFile(\"settings.special.json\", RootPathHelper.calling_root_path())\n service_provider = Startup.configure_services(application_provider, settings, CustomServiceProvider())\n\n ServiceProvider.reset_override()\n return service_provider\n```\n\n2. Setup BrowserService using CoreServices\n\nThe solution also contains a CoreService, which helps to register your container and application, with which you can interact in the future:\n\nThe simplest way is to create your own Services class extended from abstract CoreServices with the following simple signature:\n\n```python\nclass BrowserServices(CoreServices):\n\n @classmethod\n def is_application_started(cls) -> bool:\n return cls._is_application_started()\n\n @classmethod\n def application(cls) -> YourApplication:\n return cls._get_application(lambda service: cls._start_application(service))\n\n @classmethod\n def service_provider(cls) -> ServiceProvider:\n return cls._get_service_provider(lambda service: cls.application())\n\n @classmethod\n def _start_application(cls, service_provider: ServiceProvider):\n ... # your implementation\n```\n\nIf you need to register your own services / rewrite the implementation, you need override Startup and implement BrowserServices like in example below:\n\n```python\nclass TestStartup(Startup):\n\n @staticmethod\n def configure_services(\n application_provider: Callable,\n settings: Optional[JsonSettingsFile] = None,\n service_provider: Optional[ServiceProvider] = None,\n ) -> ServiceProvider:\n settings = JsonSettingsFile(\"settings.special.json\", RootPathHelper.calling_root_path())\n service_provider = Startup.configure_services(application_provider, settings)\n service_provider.timeout_configuration.override(\n Singleton(TestTimeoutConfiguration, service_provider.settings_file)\n )\n return service_provider\n\nclass BrowserServices:\n\n class _BrowserService(CoreServices):\n\n startup: TestStartup = TestStartup()\n\n def __init__(self):\n Logger.info(\"Create\")\n\n @property\n def application(self) -> Application:\n return self._get_application(\n self._start_function,\n lambda: self.startup.configure_services(lambda service: self.application),\n )\n\n @property\n def service_provider(self) -> ServiceProvider:\n return self._get_service_provider(\n lambda service: self.application,\n lambda: self.startup.configure_services(lambda service: self.application),\n )\n\n def set_startup(self, startup: Startup):\n if startup is not None:\n self.startup = startup\n\n @property\n def _start_function(self):\n return lambda serive: Application()\n\n Instance: _BrowserService = _BrowserService()\n\n\nclass CustomStartup(Startup):\n\n @staticmethod\n def configure_services(application_provider: Callable, settings: JsonSettingsFile = None) -> ServiceProvider:\n service_provider = Startup.configure_services(application_provider, settings)\n # your implementation service_provider.timeout_configuration.override(Singleton(TimeoutConfiguration, service_provider.settings_file))\n return service_provider\n```\n\n3. Work with Application via the implemented BrowserServices or via element services\n\nAll the services could be resolved from the Dependency Injection container via ServiceProvider\n```python\nBrowserServices.application().driver.find_element(ELEMENT).click()\nBrowserServices.service_provider().conditional_wait().wait_for_driver(\n lambda driver: len(driver.find_elements(Locator(By.XPATH, \"//*\"))) > 0\n)\n```\n\nor with Instance:\n```python\nBrowserServices.Instance.application.driver.find_element(ELEMENT).click()\nBrowserServices.Instance.service_provider.conditional_wait().wait_for_driver(\n lambda driver: len(driver.find_elements(Locator(By.XPATH, \"//*\"))) > 0\n)\n```\n\n\n### License\nLibrary's source code is made available under the [Apache 2.0 license](https://github.com/Polmik/py-selenium-auto-core/blob/main/LICENSE).\n",
"bugtrack_url": null,
"license": "Apache",
"summary": "Selenium core for Python",
"version": "0.5.5",
"project_urls": {
"Homepage": "https://github.com/Polmik/py-selenium-auto-core"
},
"split_keywords": [
"testing",
" selenium",
" driver",
" test automation"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "6b2bc97bc9fa74f2a5a093e272d75d6ef6f6de54194178d7b0ccf72e77358c0f",
"md5": "3657bb5dbff8c79d7e43de74d1fdfdab",
"sha256": "1a595bb9567f2b06359ee805196b7b87f002c8435f40285becba244350c0d4d8"
},
"downloads": -1,
"filename": "py_selenium_auto_core-0.5.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3657bb5dbff8c79d7e43de74d1fdfdab",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 39078,
"upload_time": "2024-06-24T06:46:25",
"upload_time_iso_8601": "2024-06-24T06:46:25.894579Z",
"url": "https://files.pythonhosted.org/packages/6b/2b/c97bc9fa74f2a5a093e272d75d6ef6f6de54194178d7b0ccf72e77358c0f/py_selenium_auto_core-0.5.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "9bd93fd51e1920461efcbcd2e43461e0f596b18934e762166c32c5fe72b44066",
"md5": "74d0351fa2fc8e07805b4e356023e9b1",
"sha256": "07f4a3b53b0e5a704f118b519d4293ddf66e233c1e7cac10dca4cde33194b2f4"
},
"downloads": -1,
"filename": "py_selenium_auto_core-0.5.5.tar.gz",
"has_sig": false,
"md5_digest": "74d0351fa2fc8e07805b4e356023e9b1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 28748,
"upload_time": "2024-06-24T06:46:27",
"upload_time_iso_8601": "2024-06-24T06:46:27.657643Z",
"url": "https://files.pythonhosted.org/packages/9b/d9/3fd51e1920461efcbcd2e43461e0f596b18934e762166c32c5fe72b44066/py_selenium_auto_core-0.5.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-24 06:46:27",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Polmik",
"github_project": "py-selenium-auto-core",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "PyHamcrest",
"specs": [
[
"==",
"2.1.0"
]
]
},
{
"name": "pytest",
"specs": [
[
"==",
"7.4.4"
]
]
},
{
"name": "allure-pytest",
"specs": [
[
"==",
"2.13.5"
]
]
},
{
"name": "selenium",
"specs": [
[
"==",
"4.11.2"
]
]
},
{
"name": "webdriver-manager",
"specs": [
[
"==",
"4.0.1"
]
]
},
{
"name": "dependency-injector-fork",
"specs": [
[
"==",
"4.42.1"
]
]
},
{
"name": "pytest-xdist",
"specs": [
[
"==",
"3.5.0"
]
]
}
],
"lcname": "py-selenium-auto-core"
}