End-to-end testing with the separated frontend

Nowadays since we are living in the era of SPA’s with separated frontend and backend apps, creating working end-to-end test setup becomes more and more complex. One of the most common problems is how to set up test data for the frontend to interact. From my experience using libraries like protractor doesn’t end too well. Why don’t we take advantage of having tests on the server side which is already connected to the database and use a similar test suite as the one that Django recommends. In this article, I am going to show you how to create such a setup on the example of Django, Vue, and Docker using selenium and pytest. However, note that this approach is applicable to any other stack.

Selenium hub

We are going to use selenium hub image, which allows chrome and firefox workers images to connect. Then selenium driver can connect remotely to the hub. After connecting driver is ready to send requests which are passed to the browser driver. Below is shown part of the docker-compose file which overrides the setting for our base compose file.

Node chrome debug image opens VNC session on port 5900 which allows you to watch how tests are performed in remote desktop using any VNC client. Personally, I was using Vinagre client but feel free to use any you want. More about this in later sections.

Common problem

The biggest problem with e2e testing in this kind of apps is how to use a test server connection while performing tests. What we have to do is to create a backend test server accessible from other containers and inform the frontend part where should he access the backend.

Test server

Unfortunately, it is impossible to use Django's LiveServerTestCase, because it creates a new test server session for each test and by default binds it to port 0. What we want is to share the test server session and selenium driver between our testcases using the same port for all of them. Without this, this setup would be impossible to achieve. Additionally, it significantly improves the time of performing all testcases (sharing webdriver removes the need for opening a new browser every time).

First make sure you have pytest, pytest-django and selenium packages installed. Then, you are going to need two of my pytest fixtures (put them into conftest.py):

The first fixture creates a test server in session scope similar to one in LiveServerTestCase. Unfortunately, the fixture contained in pytest-django package doesn’t allow it to bind to the given port for newer Django versions. Because of this reason I made my own LiveServer class which combines the one contained in pytest-django and the original LiveServerTestCase server additionally given some functionalities. This class is shown below.

Lastly, you can add a binding test server to localhost if you would like to check how the backend looks like while testing locally.

The server-side is now ready.

Frontend set up

Given you are using reverse proxy for reaching the backend all we have to do is to substitute the original backend port. It can be done using an environment variable passed directly to the server process. You should do this in the file where you configure the dev server (e.g. webpack.conf.js, dev-server.js). In the example project to serve frontend express server is used.

The default port is set 8080. If the BACKEND_PORT environment variable is set this value will be used. Backend is reached through container name using docker DNS (the service name is translated to container IP).

Lastly, modify the run server command for the frontend to inject the environment variable.

Writing tests

Let's go to the clue of this article now. How do we write tests? Here is shown example test suite in page object pattern.

Most important are pytest decorators and function arguments:

The first one enables fixtures to use, the second one enables transaction for every test method. Method arguments provide live server and selenium driver fixture.

Running tests

Now when we have our setup how do we run these tests? At the moment you should have two compose files where e2e conf overrides the basic one. Basic configuration may look like this:

Now, you have the complete e2e configuration file:

Here is an example script to run this setup:

Script will run vinagre session by default to inspect how tests are running through VNC session. The backend container is restarted to instantly release the test server port. You can skip vinagre section if you want to use another client or do not use any.

And voilà! Now you have the complete end-to-end setup.