Skip to content

Commit 8baea99

Browse files
author
dmy.berezovskyi
committed
Add encryption for sensitive data
1 parent 2e411ca commit 8baea99

File tree

21 files changed

+114
-49
lines changed

21 files changed

+114
-49
lines changed

.idea/dbnavigator.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/simple-python-selenium-framework.iml

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ A simple UI automation framework built with:
1818
- Integrated with **GitHub Actions** CI workflow for Darwin (Mac) and Linux.
1919
- Supports multiple environments: **dev**, **stage**.
2020
- Generates **pytest reports** and **custom logs**.
21+
- Secure Secrets Handling: Sensitive data like passwords are encrypted and stored securely.
22+
- Test Data Management: Integrated with YAML files for test data storage and access.
2123

2224
## Getting Started
2325

2426
### Prerequisites
2527

2628
- Python 3.8 - 3.12
2729
- If you're not using macOS with ARM64 architecture or a Selenium version below 4.24.0, please upload the appropriate driver corresponding to your OS to the `resources` directory.
30+
- secure-test-automation for encrypting sensitive data
2831

2932
### Local Usage
3033

@@ -68,7 +71,7 @@ A simple UI automation framework built with:
6871
| 1. Drivers factory: local, remote, [Chrome, Firefox] | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
6972
| 2. `pytest.ini` config: addopts, errors, markers | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
7073
| 3. Environments: dev, stag, prod | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
71-
| 4. Secrets | ![Status](https://img.shields.io/badge/TODO-yellow) |
74+
| 4. Secrets | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
7275
| 5. Utilities: YAML reader, logger | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
7376
| 6. BasePage: wait strategy, base actions | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
7477
| 7. Properties: make properties helper | ![Status](https://img.shields.io/badge/DONE-brightgreen) |
@@ -107,4 +110,20 @@ The linting configuration defines rules that dictate the checks performed. Custo
107110
```plaintext
108111
$FilePathRelativeToProjectRoot$ --config .ruff.toml
109112
```
113+
### Secrets
114+
To secure our passwords or sensitive data, we store them in an encrypted form. For this, we use the [secure-test-automation](https://pypi.org/project/secure-test-automation/) library.
115+
implementation on framework side: utils/crypto.py
116+
The encryption key is stored in `/config/key.properties` (this file should be added to `.gitignore`) for local testing.
117+
For executing tests in remote environments (e.g., BrowserStack, Jenkins, etc.), we have integrated the Vault HashiCorp library.
118+
119+
```python
120+
@pytest.fixture
121+
def get_password():
122+
secure = Secure()
123+
read = YAMLReader.read("data.yaml", to_simple_namespace=True)
124+
password = read.users.john.details.password
125+
return secure.decrypt_password(password)
126+
```
127+
128+
110129
### Demo tool https://demoqa.com/text-box

config/data.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
users:
2-
username1:
2+
john:
33
username: "john_doe@gmail.com"
44
details:
55
first_name: "John"
66
last_name: "Doe"
7-
password: ""
7+
password: gAAAAABngSXGxzoV1WZASXEj7VbEH37pOJ4_vC9gcdUnSpUI46fPOvTxhQ4-p_JNdCpRqmDlqX4nE0c9ISBB3-0-lWs2WcvdAQ==
88
username2:
99
username: "username"
1010
names:

config/key.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rd-xPFY051UmVWPmmNoMXkq3qbuCJ7Ajc0BY7MtXm6g=

conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from dotenv import load_dotenv
55
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver
66

7-
from core.event_listener import EventListener
8-
from core.driver_factory import WebDriverFactory
7+
from core_driver.event_listener import EventListener
8+
from core_driver.driver_factory import WebDriverFactory
99
from utils.logger import Logger, LogLevel
1010

1111
log = Logger(log_lvl=LogLevel.INFO).get_instance()

core/driver.py renamed to core_driver/driver.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from selenium.webdriver.chrome.service import Service as ChromeService
55
from selenium.webdriver.remote.remote_connection import RemoteConnection
66
from webdriver_manager.chrome import ChromeDriverManager
7-
from core.driver_options import _init_driver_options
7+
from core_driver.driver_options import _init_driver_options
88
from utils.error_handler import ErrorHandler, ErrorType
99
from utils.logger import Logger, LogLevel
1010
from properties import Properties
@@ -57,16 +57,13 @@ def create_driver(self, environment=None, dr_type="local"):
5757
driver_path = ChromeDriverManager().install()
5858
options = _init_driver_options(dr_type=dr_type)
5959
driver = webdriver.Chrome(
60-
service=ChromeService(executable_path=driver_path),
61-
options=options
60+
service=ChromeService(executable_path=driver_path), options=options
6261
)
6362
log.info(
6463
f"Created local Chrome driver with session: {driver.session_id}"
6564
)
6665
except Exception as e:
67-
log.error(
68-
f"Failed to create Chrome driver {e}"
69-
)
66+
log.error(f"Failed to create Chrome driver {e}")
7067
driver = webdriver.Chrome(
7168
service=ChromeService(_get_driver_path(dr_type)),
7269
options=_init_driver_options(dr_type=dr_type),
@@ -82,18 +79,14 @@ def create_driver(self, environment=None, dr_type=None):
8279
command_executor=RemoteConnection("your remote URL"),
8380
desired_capabilities={"LT:Options": caps}, # noqa
8481
)
85-
log.info(
86-
f"Remote Chrome driver created with session: {driver.session_id}"
87-
)
82+
log.info(f"Remote Chrome driver created with session: {driver.session_id}")
8883
return driver
8984

9085

9186
class FirefoxDriver(Driver):
9287
def create_driver(self, environment=None, dr_type=None):
9388
try:
94-
driver = webdriver.Firefox(
95-
options=_init_driver_options(dr_type=dr_type)
96-
)
89+
driver = webdriver.Firefox(options=_init_driver_options(dr_type=dr_type))
9790
log.info(f"Created Firefox driver with session: {driver.session_id}")
9891
except Exception as e:
9992
driver = webdriver.Chrome(

core/driver_factory.py renamed to core_driver/driver_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from core.driver import ChromeRemoteDriver, FirefoxDriver, LocalDriver
1+
from core_driver.driver import ChromeRemoteDriver, FirefoxDriver, LocalDriver
22
from utils.error_handler import ErrorHandler, ErrorType
33
from utils.logger import Logger, LogLevel
44

core/driver_options.py renamed to core_driver/driver_options.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def _shared_driver_options(options):
1010
options.add_argument("--start-maximized")
1111
options.add_argument("--disable-dev-shm-usage")
12-
options.page_load_strategy = 'none' # disable waiting for fully load page
12+
options.page_load_strategy = "none" # disable waiting for fully load page
1313
if platform.system() == "Linux":
1414
options.add_argument("--no-sandbox")
1515
log.info(f"Driver options {options.arguments}")
@@ -25,9 +25,7 @@ def _init_driver_options(dr_type=None):
2525
options = driver_option_mapping.get(dr_type)
2626

2727
if options is None:
28-
raise ErrorHandler.raise_error(
29-
ErrorType.UNSUPPORTED_DRIVER_TYPE, dr_type
30-
)
28+
raise ErrorHandler.raise_error(ErrorType.UNSUPPORTED_DRIVER_TYPE, dr_type)
3129

3230
_shared_driver_options(options)
3331
log.info(f"Driver options {options.arguments}")

0 commit comments

Comments
 (0)