end-to-end (E2E) test
An end-to-end (E2E) test exercises a complete business process against a running instance of the whole system, from the user interface down through every service and database behind it. Nothing is stubbed out, so a passing E2E test is evidence about the assembled product rather than its individual parts.
E2E tests sit at the top of the test pyramid, where the tests are broad and few. Each one follows a journey a real customer takes, such as signing up, buying a book, or exporting a report.
Because the test talks to a deployed system through its real interfaces, it catches failures no isolated test can see, like a broken redirect between two healthy pages. That reach is also what makes E2E tests the slowest to run and the most flaky, breaking for reasons unrelated to the code you changed.
In Python, you drive browser journeys with Playwright or Selenium and API journeys with an HTTP client. Django bakes the pattern into LiveServerTestCase, which starts your app on a free port for a browser driver to work against.
Example
Say a shop charges a customer correctly but its receipt page comes back empty. A test of the pricing function alone can’t catch that, so the test below serves the real app with wsgiref, the standard library’s built-in WSGI server, and walks the purchase over HTTP:
test_checkout_e2e.py
import json
import threading
import unittest
import urllib.request
from wsgiref.simple_server import make_server
from shop import app
def get_json(url, payload=None):
data = json.dumps(payload).encode() if payload else None
with urllib.request.urlopen(url, data) as response:
return json.load(response)
class TestCheckout(unittest.TestCase):
def setUp(self):
self.server = make_server("localhost", 0, app)
threading.Thread(target=self.server.serve_forever, daemon=True).start()
self.addCleanup(self.server.server_close)
self.addCleanup(self.server.shutdown)
self.url = f"http://localhost:{self.server.server_port}"
def test_customer_can_buy_two_books(self):
order = get_json(f"{self.url}/checkout", {"PY-101": 2})
receipt = get_json(f"{self.url}/orders/{order['id']}")
self.assertEqual(receipt["status"], "paid")
self.assertEqual(receipt["total"], 59.98)
if __name__ == "__main__":
unittest.main()
Running the file logs the two requests that crossed the wire before the verdict:
$ python test_checkout_e2e.py
127.0.0.1 - - [07/Aug/2026 02:06:00] "POST /checkout HTTP/1.1" 200 9
127.0.0.1 - - [07/Aug/2026 02:06:00] "GET /orders/1 HTTP/1.1" 200 34
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK
The test never calls the checkout code directly. It posts a cart to /checkout, then fetches the resulting order, so the assertion on the receipt total passes only if routing, pricing, and storage work together in the running app.
Plant a fault anywhere in the shop below, then walk the same purchase through it:
Only the broken price shows up in a test of price_cart() on its own. The unmarked order, the missing route, and the unset environment variable never touch that function, so they surface only once a test drives the running app.
Related Resources
Tutorial
Modern Web Automation With Python and Selenium
Learn advanced Python web automation techniques with Selenium, such as headless browsing, interacting with web elements, and implementing the Page Object Model pattern.
For additional information on related topics, take a look at the following resources:
- pytest Tutorial: Effective Python Testing (Tutorial)
- Python's unittest: Writing Unit Tests for Your Code (Tutorial)
- Testing Your Code With pytest (Course)
- Testing Your Code With Python's unittest (Course)
- Test-Driven Development With pytest (Course)
- How to Provide Test Fixtures for Django Models in pytest (Tutorial)
- Web Automation With Python and Selenium (Quiz)
- Effective Testing with Pytest (Quiz)
- Python's unittest: Writing Unit Tests for Your Code (Quiz)
Have a question about this? Mentor AI can show you examples, compare related terms, and point you to tutorials.
By Martin Breuss • Updated Sept. 24, 2026