Writing tests interactively with a browser¶
Since your website is going to be used interactively, it can be helpful to write your tests in a similar way to how a user views the website, rather than doing it “blind”. This page outlines a method for doing that.
You can also view the content of this page as a video:
A complete example project for the setup can be found in the examples folder.
We are going to use pytest and pytest-django, and use all the Pytest integration
tips, including the --show-browser
option.
The essential technique is:
ensure the browser window is visible.
Using the example from the project, we’ve got a custom user class, and we’re going to test that the admin interface has been registered and still works.
We start with a test that looks like this:
from django_functest import FuncBaseMixin, ShortcutLoginMixin
from .base import SeleniumTestBase, WebTestBase
from .factories import create_user
class UserAdminBase(FuncBaseMixin, ShortcutLoginMixin):
def test_change_details(self):
pass
class UserAdminWT(UserAdminBase, WebTestBase):
pass
class UserAdminSL(UserAdminBase, SeleniumTestBase):
pass
The code here is just following the pattern from the guide, plus two things:
Adding the
ShortcutLoginMixin
Importing a little custom utility function for creating users.
We now add our IPython prompt code into the base class method, so it looks like this:
def test_change_details(self):
import IPython; IPython.embed()
Next, we are going to run the Selenium version of this test, with the browser
window visible. Since I’ve only got one test so far, we can select it like this,
using the selenium
marker:
pytest accounts/tests/test_admin.py -m selenium --show-browser -s
Notice the use of -s
to ensure that pytest doesn’t capture our input, which
would break IPython.
This will run the test, and we’ll find ourselves at an IPython prompt with a browser window open.
We can then interactively write the test into IPython a line at a time, and watch our commands being executed. We’ll probably start something like this:
user = create_user(is_superuser=True)
self.shortcut_login(user)
self.get_url("admin:accounts_user_changelist")
We can then use “Inspect” and web browser dev tools to work out what elements to refer to and interact with. We might continue like this:
self.follow_link("#result_list tbody a")
self.fill({"#id_first_name": "Joe", "#id_last_name": "Bloggs"})
self.submit('input[value="Save"]')
We can also do our asserts:
user.refresh_from_db()
assert user.first_name == "Joe"
If we found that some of our setup code was actually wrong, or we missed something, there is no need to restart — just fix it by executing commands interactively and carry on.
Finally, we can copy back code from the terminal into our editor. You can use
the history functionality in IPython (PageUp and PageDown) to look back through
everything you typed. Use Ctrl-D
to exit from IPython and allow pytest to
finish.
In our editor, we’ll probably want to clean it up, and once done, we can run the tests again. This time, we’ll want to run both the WebTest and Selenium versions:
pytest accounts/tests/test_admin.py -v
============================= test session starts ============================== platform linux -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0 -- /home/luke/.virtualenvs/django-functest-example/bin/python cachedir: .pytest_cache django: settings: example_project.settings (from ini) rootdir: /home/luke/devel/django-functest/examples/example_project, configfile: pytest.ini plugins: django-4.5.2, django-webtest-1.9.10 collecting ... collected 2 items accounts/tests/test_admin.py::UserAdminWT::test_change_own_details PASSED [ 50%] accounts/tests/test_admin.py::UserAdminSL::test_change_own_details PASSED [100%] =============================== 2 passed in 1.87s ===============================
If we get any failures, we can also use the IPython prompt technique to debug easily, by insert the “embed” line just before the failing line of code.
Hopefully you’ll enjoy this method of writing tests! If you’ve got any more tips for improving this method, do let us know.