This enhances selenium's webdriver to simplify the interface and capture screenshots along the way. When run it will output html with screenshots of different scenarios.
Python versions 3.6+ are supported.
If you rely on an older python3 version, you may want to pin your
version of webdriver-recorder to <4.0.0
# Using pip
pip install 'uw-webdriver-recorder'
# Using poetry
poetry add 'uw-webdriver-recorder'
The following table illustrates the compatible versions between this (webdriver-recorder), python, and selenium:
webdriver-recorder version | python version(s) | selenium version(s) |
---|---|---|
<4.0 | 3+ | <=3.141.59 |
4.0 | 3.6+ | <=3.141.59 |
5.0+ | 3.7+ | >=4.1 |
If running without docker, chromedriver
must be
discoverable on your test environment PATH.
See Google's documentation.
For convenience, you can use ./bootstrap_chromedriver.sh
.
The provided docker-compose.yml should work out of the box to run simple tests.
The following code should work as-is (note: the result should be a failure!):
test_dir=$(pwd)/examples docker-compose up --build --exit-code-from test-runner
After, you can view the results by opening ./webdriver-report/index.html
in your
browser of choice.
Note: if you are doing this for the first time, the initial build may take a couple of minutes.
If using the provided docker-compose.yml to run
tests, you can change the number of nodes of your selenium grid by editing the
SE_NODE_MAX_SESSIONS
environment variable. This handle is provided by
the Selenium maintainers.
Also as environment variable: REPORT_DIR
(Optional). If provided, will override the default (./webdriver-report
).
This is the directory where worker locks and report artifacts will be stored.
Your report will be saved here as index.html
and report.json
.
(Optional). If provided, will override the default included with this package. This must be the absolute path to your report template. For more information on creating or updating templates, see docs/templating.
(Optional). The title for your report. You may also provide this as a test fixture. See report_title
(Optional). Defaults to the REMOTE_SELENIUM
environment variable value,
which may be blank. If provided, a Remote
instance
will be created instead that will connect to the server provided.
This plugin makes several fixtures available to you:
A session-scoped browser instance. By default, this is always invoked,
which may pose runtime errors (like stuck tests) if you have a constrained
selenium grid. You can disable this default behavior by
setting disable_session_browser=1
in your environment.
Note that you may still invoke the session_browser fixture with this option, but it will not automatically be used.
def test_a_thing(session_browser):
session_browser.get('https://www.example.com')
def test_another_thing(session_browser):
# The page remains loaded from the previous test.
assert session_browser.wait_for_tag('h1', 'welcome to example.com')
See also browser.
A class-scoped browser instance that preserves the state for
the entire test class. The class_browser
will always be open
to a new, clean tab, which will be closed when all tests
in the class have run.
If you run with disable_session_browser
, the class_browser will be a fresh
instance of the browser for each class where it is used.
@pytest.mark.usefixtures('class_browser')
class TestCollection:
@pytest.fixture(autouse=True)
def initialize_collection(class_browser):
self.browser = class_browser
def test_a_thing(self):
self.browser.get('https://www.example.com')
def test_another_thing(self):
assert self.browser.wait_for_tag('h1', 'welcome to example.com')
A function-scoped browser tab that automatically cleans up after itself before the tab is closed by deleting browser cookies from its last visited domain.
If running with disable_session_browser
, a new instance will be created for each
browser instead. This has significant performance impacts*, but also guarantees the
"cleanest" browser experience.
* see Performance
If you do not want to use one of the above scopes, you
can use the browser_context
fixture directly, which
creates and cleans up a tab for the browser instance you provide.
When the scope exits, the tab's cookies are deleted, and the tab is closed.
You can optionally supply a list of additional urls to visit and clear cookies using
the cookie_urls
parameter. (The default browser behavior is to only delete
the cookies of the current domain.)
def test_a_thing(browser_context, chrome_options):
# Let's create a custom instance of Chrome
options.add_argument('--hide-scrollbars')
browser = Chrome(options=chrome_options)
with browser_context(
browser,
cookie_urls=['https://www.example.com/']
) as browser:
browser.get('https://www.example.com/')
browser.add_cookie({'name': 'foo', 'value': 'bar'})
browser.get('https://www.uw.edu/')
assert browser.current_url == 'https://www.uw.edu/'
assert browser.get_cookie('foo')['value'] == 'bar'
# Outside of the context, the context tab has closed, so the
assert not browser.current_url == 'https://www.uw.edu/'
browser.get('https://www.example.com/')
assert not browser.get_all_cookies()
You can fine-tune certain configuration by overriding fixtures.
While you may find you want to override the session_browser
or any
of the above scopes for your tests, the defaults are meant to be
self-cleaning and work out of the box.
Before you override the core fixtures, first see if a setting or argument can help you make any adjustments you need:
Use this to change which options your recorder uses when setting up the Chrome instance:
@pytest.fixture(scope='session')
def chrome_options(chrome_options):
chrome_options.add_argument('--debug')
return chrome_options
Use this to change the title of your report. This is a better option than the pytest argument (above) for cases where you want to programmatically assemble the title during test setup.
@pytest.fixture(scope='session')
def report_title():
return "Testing all the things"
- Install poetry (if not already done)
poetry env use /path/to/python3.7+
poetry install
- Run
./bootstrap_chromedriver.sh
-- doing this after poetry setup will automatically install to your poetry environment.
It is highly recommended that you use a pyenv version, e.g.:
poetry env use ~/.pyenv/versions/3.8.8/bin/python
- When? If you see a message that chromedriver is out of date
- What do I do?
./bootstrap_chromedriver.sh
- When? Whenever you need the latest release of something
- What do I do?
poetry update && poetry lock && poetry run tox
To release a change out in the wild, you should use the Github Actions UI.
- Visit the release workflow UI
- Click on
Run Workflow
- Select the branch you want to release.
- Leave the
dry-run
option set totrue
. - Click
Run
Wait for the dry run to complete in the #iam-bots
slack channel.
If the dry run succeeded, validate the generated version number is what you expected
and, if so, repeat steps 1–3 above, but change the dry-run
option to false
.
This means you can create prereleases for any branch you're working on to test it
with another package, before merging into main
!
If you want to release something without the use of Github Actions, you can follow these steps:
Release changes using poetry:
poetry update
poetry lock
poetry version [patch|minor|major|prerelease]
poetry run tox
poetry publish --build
- username:
uw-it-iam
- password: Ask @tomthorogood! (goodtom@uw.edu)
- username:
poetry run tox
(or simply tox
if you are already in the poetry shell
)
- Run validations before submitting using
poetry run tox
; this will prevent unnecessary churn in your pull request.
Creating browser instances is very inefficient. It is recommended that you
use the default behavior that configures a single browser instance
to use for all tests, that comes with an auto-managed context
for the browser
fixture.
In our own tox
tests, you can observe the performance impact
directly. The disable_session_browser
tox environment typically
takes more than double the amount of time to run than the same tests
using the default behavior.