Time Commitment: This recipe should take about 20 minutes to run through if you have an existing Bitbucket account and have Git, Python, and Firefox already installed.
Below is a recipe for building a basic Django Project that can be tested with Selenium in a BitBucket pipeline.
We create a bare project that includes just the admin
urls and app to keep this as barebones as possible.
Once the scaffolding for testing is working locally and in the bitbucket pipeline, starting and testing a new app with custom models, urls, and views will be straightforward.
Written by Wesley Emeneker 2022-12-17. Email dev@physs.co.
venv
module available. See References for the Python 3.11 I used.This recipe has 3 major gates. Each gate must be completed in order.
selenium_test
.
cd ~ # Use your home directory as the starting point
git clone git@bitbucket.org:<YOUR_PROJECT>/selenium_test.git
cd # Use your home directory as the starting point
git clone git@bitbucket.org:<YOUR_PROJECT>/selenium_test.git
If you clone with HTTPS instead of SSH, you may have to setup an App password to clone the repository
py -m venv selenium_test_env
selenium_test_env/Scripts/activate
pip install django pytest pytest-django selenium
python3 -m venv ~/selenium_test_env
source ~/selenium_test_env/bin/activate
pip3 install django pytest pytest-django selenium
This recipe uses python with venv
and pip
because it is the lowest common denominator. Conda, poetry, pipenv, virtualenv, etc. will all work well if you prefer them.
Use django_admin
for the username of the superuser. Choose a good password and save it. We need it later when adding the DJANGO_ADMIN_PASSWORD
variable to BitBucket.
django-admin startproject selenium_project selenium_test
cd selenium_test
.\manage.py makemigrations
.\manage.py migrate
.\manage.py createsuperuser --username django_admin
django-admin startproject selenium_project ~/selenium_test
cd ~/selenium_test
./manage.py makemigrations
./manage.py migrate
./manage.py createsuperuser --username django_admin
Later in the recipe, the password will be stored in an environment variable and we will store it as a secure variable in BitBucket.
.\manage.py runserver
./manage.py runserver
http://127.0.0.1:8000/admin
django_admin
account created earlierrunserver
. For Linux/Mac, press Ctrl+c. For Windows, either Ctrl+c or Ctrl+BREAKcd ~
cd selenium_test
New-Item -Path .\tests\tests_selenium\fixtures -Type Directory
.\manage.py dumpdata auth.User -o .\tests\tests_selenium\fixtures\users.json
cd ~
cd selenium_test
mkdir -p tests/tests_selenium/fixtures
./manage.py dumpdata auth.User -o tests/tests_selenium/fixtures/users.json
Storing the user model data in a file of JSON makes it easy to pre-load data for the database we will use for local Selenium tests. Storing the data this way is not a security risk. The passwords are not plaintext. That said, the password used for the django_admin account should not be reused anywhere else, and it definitely shouldn't be your production password.
tests/tests_selenium/test_selenium_testapp_login.py
import os
from pathlib import Path
from django.test.selenium import SeleniumTestCase
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
fixtures_directory = Path(__file__).parent / "fixtures"
"""
We MUST use a live server AND a transactional database for 2 reasons
1. If live_server is missing, the selenium webdriver won't be able to
actually receive any rendered page
2. If transactional_db is missing, all the app data will be kept in
memory in the test runner process, so the selenium process won't
be able to access any of the data.
We must have a live database for this to work.
"""
class SeleniumTest(SeleniumTestCase, StaticLiveServerTestCase):
databases = {"default"}
fixtures = [
fixtures_directory / "users.json",
]
# browser and implicit_wait are needed by the SeleniumTestCase class
# You can use 'chrome' or 'edge' here if you don't want to install firefox.
browser = 'firefox'
# Increasing the implicit_wait from the default of 3 makes the pipeline reliable
implicit_wait = 10
# The SELENIUM_HOST variable may be defined by our bitbucket
# repository settings for using the selenium docker image.
selenium_hub = None if not os.environ.get(
"SELENIUM_HOST", None
) else f'http://{os.environ["SELENIUM_HOST"]}:{os.environ.get("SELENIUM_PORT", 4444)}'
external_host = os.environ.get("SELENIUM_HOST", None)
def tearDown(self):
self.selenium.close()
def test_login(self):
login_button_xpath='//input[@value="Log in"]'
self.selenium.get(f"{self.live_server_url}/admin")
username_input = self.selenium.find_element(By.NAME, "username")
username_input.send_keys('django_admin')
password_input = self.selenium.find_element(By.NAME, "password")
password_input.send_keys(os.environ["DJANGO_ADMIN_PASSWORD"])
self.selenium.find_element(By.XPATH, login_button_xpath).click()
selenium_test
top-level directorycd $HOME\selenium_test
$env:DJANGO_SETTINGS_MODULE='selenium_project.settings'
$env:DJANGO_ADMIN_PASSWORD='<THE PASSWORD>'
pytest
cd ~/selenium_test
export DJANGO_SETTINGS_MODULE="selenium_project.settings"
export DJANGO_ADMIN_PASSWORD="<THE PASSWORD>"
pytest
The first time this is run, the browser may take a long time to start (20-30 seconds).
git add manage.py
git add selenium_project/wsgi.py selenium_project/urls.py \
selenium_project/asgi.py selenium_project/__init__.py
git add tests/tests_selenium/fixtures/users.json \
tests/tests_selenium/test_selenium_testapp_login.py
git commit -m "Ran the first successful Selenium test of selenium_testapp with Firefox"
git push
~/selenium_test
├── db.sqlite3 # './manage migrate' created this sqlite3 db
├── geckodriver.log # This log file was created by selenium
├── manage.py
├── selenium_project
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── tests
└── tests_selenium
├── fixtures
│ └── users.json
└── test_selenium_testapp_login.py
All of the above steps must be successfully completed before moving on.
Copy-Item .\selenium_project\settings.py .\selenium_project\settings.bitbucket-pipelines.py
cp selenium_project/settings.py selenium_project/settings.bitbucket-pipelines.py
selenium_project/settings.bitbucket-pipelines.py
so that the database will use postgres and the username and password will match. The DATABASES variable in selenium_project/settings.bitbucket-pipelines.py
should be identical to this:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'django',
"USER": 'django',
"PASSWORD": '<RECIPE_PASSWORD2>',
'HOST': '127.0.0.1',
"TEST": {
"NAME": 'django_test',
}
}
}
The original settings.py
uses a SQLite database definition that doesn't require a username and password. SQLite is a great choice for starting development. This recipe uses PostgreSQL in the pipeline because Bitbucket makes it easy to add and use services from docker images. This way, we get quick local development and quick pipeline work.
Put the following code into bitbucket-pipelines.yml
.
Make certain that the POSTGRES DB, USER, and PASSWORD variables match the NAME, USER, and PASSWORD from selenium_project/settings.bitbucket-pipelines.py
.
pipelines:
default:
- step:
name: "Python 3.10 tests"
image: python:3.10
services:
- postgresql14
- selenium-firefox
script:
- pip install django pytest pytest-django selenium psycopg2
- cp selenium_project/settings.bitbucket-pipelines.py selenium_project/settings.py
- DJANGO_SETTINGS_MODULE="selenium_project.settings" pytest
definitions:
services:
postgresql14:
image: postgres:14
variables:
POSTGRES_DB: 'django'
POSTGRES_USER: 'django'
POSTGRES_PASSWORD: '<RECIPE_PASSWORD2>'
POSTGRES_DB_TEST: 'django_test'
selenium-firefox:
image: selenium/standalone-firefox:latest
ports:
- "4444:4444"
cd
cd selenium_test
git add bitbucket-pipelines.yml selenium_project/settings.bitbucket-pipelines.py
git commit -m "Added bitbucket pipeline definition and Django project settings to match the pipeline configuration"
git push
The repository should now have these files:
~/selenium_test
├── bitbucket-pipelines.yml
├── db.sqlite3
├── geckodriver.log
├── manage.py
├── selenium_project
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.bitbucket-pipelines.py
│ ├── settings.py # This file should NOT be in the git repository
│ ├── urls.py
│ └── wsgi.py
└── tests
└── tests_selenium
├── fixtures
│ └── users.json
└── test_selenium_testapp_login.py
selenium_test
repository,
Repository settings
at the bottom of the left navigation bar,Settings
in the Pipelines section near the bottom of the left navigation barEnable Pipelines
Repository settings
.Repository variables
.django_admin
superuser password chosen in Gate 1; selenium.common.exceptions.SessionNotCreatedException: Message: Expected browser binary location, but unable to find binary in default location, no 'moz:firefoxOptions.binary' capability provided ...
selenium_test
at the top of the left navigation bar. Then click Pipelines
in the left navigation bar and finally click Run initial pipeline
.
Start from the master branch.
Run the default pipeline.
browser
variable in the unit test, and will require modifications to the bitbucket-pipelines.yml
file to use a Chrome or Edge service
test_selenium_testapp_login.py
sudo apt install python3 python3-venv