Skip to content

Add a settings file parser for overriding default SeleniumBase settings #355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
language: python
sudo: false
dist: trusty
matrix:
include:
- python: 2.7
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/sb_media_logo_4.png" title="SeleniumBase" height="170">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)

[<img src="https://img.shields.io/github/release/seleniumbase/SeleniumBase.svg" />](https://github.com/seleniumbase/SeleniumBase/releases) [<img src="https://dev.azure.com/seleniumbase/seleniumbase/_apis/build/status/seleniumbase.SeleniumBase?branchName=master" />](https://dev.azure.com/seleniumbase/seleniumbase/_build/latest?definitionId=1&branchName=master) [<img src="https://travis-ci.org/seleniumbase/SeleniumBase.svg?branch=master" alt="Build Status" />](https://travis-ci.org/seleniumbase/SeleniumBase) [<img src="https://badges.gitter.im/seleniumbase/SeleniumBase.svg" alt="Join the SeleniumBase Gitter chat" />](https://gitter.im/seleniumbase/SeleniumBase) [<img src="https://img.shields.io/badge/license-MIT-22BBCC.svg" alt="MIT License" />](https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE) [<img src="https://img.shields.io/github/stars/seleniumbase/seleniumbase.svg" alt="Stars" />](https://github.com/seleniumbase/SeleniumBase/stargazers)<br />
[<img src="https://img.shields.io/github/release/seleniumbase/SeleniumBase.svg" />](https://github.com/seleniumbase/SeleniumBase/releases) [<img src="https://dev.azure.com/seleniumbase/seleniumbase/_apis/build/status/seleniumbase.SeleniumBase?branchName=master" />](https://dev.azure.com/seleniumbase/seleniumbase/_build/latest?definitionId=1&branchName=master) [<img src="https://travis-ci.org/seleniumbase/SeleniumBase.svg?branch=master" alt="Build Status" />](https://travis-ci.org/seleniumbase/SeleniumBase) [<img src="https://badges.gitter.im/seleniumbase/SeleniumBase.svg" alt="Join the SeleniumBase Gitter chat" />](https://gitter.im/seleniumbase/SeleniumBase) [<img src="https://img.shields.io/badge/license-MIT-22BBCC.svg" alt="MIT License" />](https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE) [<img src="https://img.shields.io/github/stars/seleniumbase/seleniumbase.svg" alt="Stars" />](https://github.com/seleniumbase/SeleniumBase/stargazers) [<img src="https://img.shields.io/github/repo-size/seleniumbase/seleniumbase.svg" alt="Size" />](https://github.com/seleniumbase/SeleniumBase/releases)<br />

Automate & test more in less time with [Selenium-WebDriver](https://www.seleniumhq.org/) and [Pytest](https://docs.pytest.org/en/latest/).
✅ Everything you need to automate Web/UI testing.

<img src="https://cdn2.hubspot.net/hubfs/100006/sb_demo_mode.gif" title="SeleniumBase" height="236"><br />
(<i>Above: [my_first_test.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/my_first_test.py) from [examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples) running in demo mode, which adds JavaScript for highlighting page actions.</i>)<br />
```
pytest my_first_test.py --demo_mode
```

SeleniumBase is an all-in-one test automation framework that uses WebDriver APIs for spinning up web browsers while using pytest and nosetests for running tests.

## <img src="https://cdn2.hubspot.net/hubfs/100006/images/super_square_logo_3a.png" title="SeleniumBase" height="32"> Quick Start:

You'll need **[Python](https://www.python.org/downloads/)** [<img src="https://img.shields.io/badge/python-2.7,_3.5,_3.6,_3.7-22AADD.svg" alt="Python versions" />](https://www.python.org/downloads/)
Expand Down Expand Up @@ -71,13 +73,13 @@ pytest my_first_test.py --browser=chrome
SeleniumBase automatically handles common WebDriver actions such as spinning up web browsers and saving screenshots during test failures. (<i>[Read more about customizing test runs](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md).</i>)

#### **Simplified code:**<br />
Instead of using messy WebDriver commands such as:
SeleniumBase uses simple syntax for commands, such as:
```python
self.driver.find_element_by_css_selector("textarea").send_keys("text")
self.update_text("textarea", "text")
```
...you can do the following with SeleniumBase:
The same command with regular WebDriver is very messy:
```python
self.update_text("textarea", "text")
self.driver.find_element_by_css_selector("textarea").send_keys("text")
```
(<i>You can still use ``self.driver`` in your code.</i>)

Expand Down
83 changes: 83 additions & 0 deletions examples/custom_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
To override default settings stored in seleniumbase/config/settings.py,
change the values here and add "--settings=custom_settings.py" when running.
"""

# Default timeout values for waiting for page elements to appear.
MINI_TIMEOUT = 2
SMALL_TIMEOUT = 6
LARGE_TIMEOUT = 10
EXTREME_TIMEOUT = 30

# If False, only logs from the most recent test run will be saved.
ARCHIVE_EXISTING_LOGS = False
ARCHIVE_EXISTING_DOWNLOADS = False

# Waiting for Document.readyState to be "Complete" after browser actions.
WAIT_FOR_RSC_ON_PAGE_LOADS = True
WAIT_FOR_RSC_ON_CLICKS = True
WAIT_FOR_ANGULARJS = True

# Changing the default behavior of Demo Mode. Activate with: --demo_mode
DEFAULT_DEMO_MODE_TIMEOUT = 0.5
HIGHLIGHTS = 4
DEFAULT_MESSAGE_DURATION = 2.55

# Disabling the Content Security Policy of the browser by default.
DISABLE_CSP_ON_FIREFOX = True
DISABLE_CSP_ON_CHROME = False

# If True and --proxy=IP_ADDRESS:PORT is invalid, then error immediately.
RAISE_INVALID_PROXY_STRING_EXCEPTION = True

# Changing the default behavior of MasterQA Mode.
MASTERQA_DEFAULT_VALIDATION_MESSAGE = "Does the page look good?"
MASTERQA_WAIT_TIME_BEFORE_VERIFY = 0.5
MASTERQA_START_IN_FULL_SCREEN_MODE = False
MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT = 600

# Google Authenticator
# (For 2-factor authentication using a time-based one-time password algorithm)
# (See https://github.com/pyotp/pyotp and https://pypi.org/project/pyotp/ )
# (Also works with Authy and other compatible apps.)
TOTP_KEY = "base32secretABCD"

# MySQL DB Credentials
# (For saving data from tests to a MySQL DB)
# Add "--with-db_reporting" to save test data to a MySQL DB during test runs
DB_HOST = "127.0.0.1"
DB_USERNAME = "root"
DB_PASSWORD = "test"
DB_SCHEMA = "test_db"

# Amazon S3 Bucket Credentials
# (For saving screenshots and other log files from tests)
# (Bucket names are unique across all existing bucket names in Amazon S3)
# Add "--with-s3_logging" to save test results to S3
S3_LOG_BUCKET = "[S3 BUCKET NAME]"
S3_BUCKET_URL = "https://s3.amazonaws.com/[S3 BUCKET NAME]/"
S3_SELENIUM_ACCESS_KEY = "[S3 ACCESS KEY]"
S3_SELENIUM_SECRET_KEY = "[S3 SECRET KEY]"

# Encryption Settings
# (Used for string/password obfuscation)
# (You should reset the Encryption Key for every clone of SeleniumBase)
ENCRYPTION_KEY = "Pg^.l!8UdJ+Y7dMIe&fl*%!p9@ej]/#tL~3E4%6?"
# These tokens are added to the beginning and end of obfuscated passwords.
# Helps identify which strings/passwords have been obfuscated.
OBFUSCATION_START_TOKEN = "$^*ENCRYPT="
OBFUSCATION_END_TOKEN = "?&#$"

# Default Email Credentials
# (If tests send out emails, you can scan and verify them by using IMAP)
# Here's a list of imap strings for known email providers:
# - Gmail: imap.gmail.com
# - Outlook/Live: imap-mail.outlook.com
# - Yahoo Mail: imap.mail.yahoo.com
# - AT&T: imap.mail.att.net
# - Comcast: imap.comcast.net
# - Verizon: incoming.verizon.net
EMAIL_USERNAME = "[TEST ACCOUNT GMAIL USERNAME]@gmail.com"
EMAIL_PASSWORD = "[TEST ACCOUNT GMAIL PASSWORD]"
EMAIL_IMAP_STRING = "imap.gmail.com"
EMAIL_IMAP_PORT = 993
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pytest>=4.6.5;python_version<"3"
pytest>=5.0.1;python_version>="3"
pytest-cov>=2.7.1
pytest-forked>=1.0.2
pytest-html==1.20.0
pytest-html==1.22.0
pytest-metadata>=1.8.0
pytest-ordering>=0.6
pytest-rerunfailures>=7.0
Expand Down
157 changes: 157 additions & 0 deletions seleniumbase/core/settings_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import re
from seleniumbase.config import settings


def set_settings(settings_file):
if not settings_file.endswith('.py'):
raise Exception("\n\n`%s` is not a Python file!\n\n" % settings_file)

f = open(settings_file, 'r')
all_code = f.read()
f.close()

override_settings = {}
num_settings = 0

code_lines = all_code.split('\n')
for line in code_lines:

# KEY = "VALUE"
data = re.match(r'^\s*([\S]+)\s*=\s*"([\S\s]+)"\s*$', line)
if data:
key = data.group(1)
value = data.group(2)
override_settings[key] = value
num_settings += 1
continue

# KEY = 'VALUE'
data = re.match(r"^\s*([\S]+)\s*=\s*'([\S\s]+)'\s*$", line)
if data:
key = data.group(1)
value = data.group(2)
override_settings[key] = value
num_settings += 1
continue

# KEY = VALUE
data = re.match(r'^\s*([\S]+)\s*=\s*([\S]+)\s*$', line)
if data:
key = data.group(1)
value = data.group(2)
override_settings[key] = value
num_settings += 1
continue

for key in override_settings.keys():
value = override_settings[key]
if value.replace('.', '1').isdigit():
if value.count('.') == 1:
override_settings[key] = float(value)
elif value.count('.') == 0:
override_settings[key] = int(value)
else:
continue
elif value == "True":
override_settings[key] = True
elif value == "False":
override_settings[key] = False
else:
override_settings[key] = value

if key == "MINI_TIMEOUT":
settings.MINI_TIMEOUT = override_settings[key]
elif key == "SMALL_TIMEOUT":
settings.SMALL_TIMEOUT = override_settings[key]
elif key == "LARGE_TIMEOUT":
settings.LARGE_TIMEOUT = override_settings[key]
elif key == "EXTREME_TIMEOUT":
settings.EXTREME_TIMEOUT = override_settings[key]
elif key == "ARCHIVE_EXISTING_LOGS":
settings.ARCHIVE_EXISTING_LOGS = override_settings[key]
elif key == "ARCHIVE_EXISTING_DOWNLOADS":
settings.ARCHIVE_EXISTING_DOWNLOADS = override_settings[key]
elif key == "SCREENSHOT_NAME":
settings.SCREENSHOT_NAME = override_settings[key]
elif key == "BASIC_INFO_NAME":
settings.BASIC_INFO_NAME = override_settings[key]
elif key == "PAGE_SOURCE_NAME":
settings.PAGE_SOURCE_NAME = override_settings[key]
elif key == "LATEST_REPORT_DIR":
settings.LATEST_REPORT_DIR = override_settings[key]
elif key == "REPORT_ARCHIVE_DIR":
settings.REPORT_ARCHIVE_DIR = override_settings[key]
elif key == "HTML_REPORT":
settings.HTML_REPORT = override_settings[key]
elif key == "RESULTS_TABLE":
settings.RESULTS_TABLE = override_settings[key]
elif key == "WAIT_FOR_RSC_ON_PAGE_LOADS":
settings.WAIT_FOR_RSC_ON_PAGE_LOADS = override_settings[key]
elif key == "WAIT_FOR_RSC_ON_CLICKS":
settings.WAIT_FOR_RSC_ON_CLICKS = override_settings[key]
elif key == "WAIT_FOR_ANGULARJS":
settings.WAIT_FOR_ANGULARJS = override_settings[key]
elif key == "DEFAULT_DEMO_MODE_TIMEOUT":
settings.DEFAULT_DEMO_MODE_TIMEOUT = override_settings[key]
elif key == "HIGHLIGHTS":
settings.HIGHLIGHTS = override_settings[key]
elif key == "DEFAULT_MESSAGE_DURATION":
settings.DEFAULT_MESSAGE_DURATION = override_settings[key]
elif key == "DISABLE_CSP_ON_FIREFOX":
settings.DISABLE_CSP_ON_FIREFOX = override_settings[key]
elif key == "DISABLE_CSP_ON_CHROME":
settings.DISABLE_CSP_ON_CHROME = override_settings[key]
elif key == "RAISE_INVALID_PROXY_STRING_EXCEPTION":
settings.RAISE_INVALID_PROXY_STRING_EXCEPTION = (
override_settings[key])
elif key == "MASTERQA_DEFAULT_VALIDATION_MESSAGE":
settings.MASTERQA_DEFAULT_VALIDATION_MESSAGE = (
override_settings[key])
elif key == "MASTERQA_WAIT_TIME_BEFORE_VERIFY":
settings.MASTERQA_WAIT_TIME_BEFORE_VERIFY = (
override_settings[key])
elif key == "MASTERQA_START_IN_FULL_SCREEN_MODE":
settings.MASTERQA_START_IN_FULL_SCREEN_MODE = (
override_settings[key])
elif key == "MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT":
settings.MASTERQA_MAX_IDLE_TIME_BEFORE_QUIT = (
override_settings[key])
elif key == "TOTP_KEY":
settings.TOTP_KEY = override_settings[key]
elif key == "DB_HOST":
settings.DB_HOST = override_settings[key]
elif key == "DB_USERNAME":
settings.DB_USERNAME = override_settings[key]
elif key == "DB_PASSWORD":
settings.DB_PASSWORD = override_settings[key]
elif key == "DB_SCHEMA":
settings.DB_SCHEMA = override_settings[key]
elif key == "S3_LOG_BUCKET":
settings.S3_LOG_BUCKET = override_settings[key]
elif key == "S3_BUCKET_URL":
settings.S3_BUCKET_URL = override_settings[key]
elif key == "S3_SELENIUM_ACCESS_KEY":
settings.S3_SELENIUM_ACCESS_KEY = override_settings[key]
elif key == "S3_SELENIUM_SECRET_KEY":
settings.S3_SELENIUM_SECRET_KEY = override_settings[key]
elif key == "ENCRYPTION_KEY":
settings.ENCRYPTION_KEY = override_settings[key]
elif key == "OBFUSCATION_START_TOKEN":
settings.OBFUSCATION_START_TOKEN = override_settings[key]
elif key == "OBFUSCATION_END_TOKEN":
settings.OBFUSCATION_END_TOKEN = override_settings[key]
elif key == "EMAIL_USERNAME":
settings.EMAIL_USERNAME = override_settings[key]
elif key == "EMAIL_PASSWORD":
settings.EMAIL_PASSWORD = override_settings[key]
elif key == "EMAIL_IMAP_STRING":
settings.EMAIL_IMAP_STRING = override_settings[key]
elif key == "EMAIL_IMAP_PORT":
settings.EMAIL_IMAP_PORT = override_settings[key]
else:
continue

if num_settings == 0:
raise Exception("Unable to parse the settings file!")

return override_settings
11 changes: 9 additions & 2 deletions seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def test_anything(self):
from seleniumbase.core.testcase_manager import TestcaseManager
from seleniumbase.core import download_helper
from seleniumbase.core import log_helper
from seleniumbase.core import settings_parser
from seleniumbase.core import tour_helper
from seleniumbase.core import visual_helper
from seleniumbase.fixtures import constants
Expand Down Expand Up @@ -3195,6 +3196,7 @@ def setUp(self, masterqa_mode=False):
self.proxy_string = sb_config.proxy_string
self.user_agent = sb_config.user_agent
self.cap_file = sb_config.cap_file
self.settings_file = sb_config.settings_file
self.database_env = sb_config.database_env
self.message_duration = sb_config.message_duration
self.js_checking_on = sb_config.js_checking_on
Expand Down Expand Up @@ -3266,11 +3268,16 @@ def setUp(self, masterqa_mode=False):
# Chrome and Firefox now have built-in headless displays
pass

# Launch WebDriver for both Pytest and Nosetests
# Verify that SeleniumBase is installed successfully
if not hasattr(self, "browser"):
raise Exception("""SeleniumBase plugins did not load! """
"""Please reinstall using:\n"""
""" >>> "python setup.py install" <<< """)
""" >>> "pip install -r requirements.txt" <<<\n"""
""" >>> "python setup.py develop" <<< """)
if self.settings_file:
settings_parser.set_settings(self.settings_file)

# Launch WebDriver for both Pytest and Nosetests
self.driver = self.get_new_driver(browser=self.browser,
headless=self.headless,
servername=self.servername,
Expand Down
11 changes: 10 additions & 1 deletion seleniumbase/plugins/base_plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
""" This is the Nose plugin for saving logs and setting a test environment. """
""" This is the Nose plugin for setting a test environment and saving logs. """

import os
import sys
Expand All @@ -16,6 +16,7 @@ class Base(Plugin):
This parser plugin includes the following command-line options for Nose:
--env=ENV (Set a test environment. Use "self.env" to use this in tests.)
--data=DATA (Extra data to pass to tests. Use "self.data" in tests.)
--settings_file=FILE (Overrides SeleniumBase settings.py values.)
--log_path=LOG_PATH (The directory where log files get saved to.)
--archive_logs (Archive old log files instead of deleting them.)
--report (The option to create a fancy report after tests complete.)
Expand Down Expand Up @@ -45,6 +46,13 @@ def options(self, parser, env):
'--data', dest='data',
default=None,
help='Extra data to pass from the command line.')
parser.add_option(
'--settings_file', '--settings-file', '--settings',
action='store',
dest='settings_file',
default=None,
help="""The file that stores key/value pairs for overriding
values in the SeleniumBase settings.py file.""")
parser.add_option(
'--log_path', dest='log_path',
default='latest_logs/',
Expand Down Expand Up @@ -100,6 +108,7 @@ def beforeTest(self, test):
test.test.environment = self.options.environment
test.test.env = self.options.environment # Add a shortened version
test.test.data = self.options.data
test.test.settings_file = self.options.settings_file
test.test.log_path = self.options.log_path
test.test.args = self.options
test.test.report_on = self.report_on
Expand Down
8 changes: 8 additions & 0 deletions seleniumbase/plugins/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def pytest_addoption(parser):
This parser plugin includes the following command-line options for pytest:
--browser=BROWSER (The web browser to use.)
--cap_file=FILE (The web browser's desired capabilities to use.)
--settings_file=FILE (Overrides SeleniumBase settings.py values.)
--env=ENV (Set a test environment. Use "self.env" to use this in tests.)
--data=DATA (Extra data to pass to tests. Use "self.data" in tests.)
--user_data_dir=DIR (Set the Chrome user data directory to use.)
Expand Down Expand Up @@ -82,6 +83,12 @@ def pytest_addoption(parser):
default=None,
help="""The file that stores browser desired capabilities
for BrowserStack or Sauce Labs web drivers.""")
parser.addoption('--settings_file', '--settings-file', '--settings',
action='store',
dest='settings_file',
default=None,
help="""The file that stores key/value pairs for overriding
values in the SeleniumBase settings.py file.""")
parser.addoption('--user_data_dir', '--user-data-dir',
dest='user_data_dir',
default=None,
Expand Down Expand Up @@ -320,6 +327,7 @@ def pytest_configure(config):
sb_config.port = config.getoption('port')
sb_config.proxy_string = config.getoption('proxy_string')
sb_config.cap_file = config.getoption('cap_file')
sb_config.settings_file = config.getoption('settings_file')
sb_config.user_data_dir = config.getoption('user_data_dir')
sb_config.database_env = config.getoption('database_env')
sb_config.log_path = config.getoption('log_path')
Expand Down
Loading