Robot Framework is an open-source, keyword-driven test automation framework that you point at a running Django app to verify whole user journeys in plain, readable language. It sits on top of Python and lets QA and developers describe acceptance (end-to-end) tests as tables of keywords like New Page, Fill Text, and Click instead of imperative code — so business stakeholders can read the suite, and you can drive a real browser against http://127.0.0.1:8000.
The biggest change since older Robot Framework tutorials: for browser-based Django testing you should reach for the Browser Library (built on Microsoft Playwright) first. It is faster, auto-waits for elements, and ships modern Chromium/Firefox/WebKit support — and it is the library the Robot Framework community now recommends over the older SeleniumLibrary for new projects.
Key takeaways
- Robot Framework = acceptance / E2E layer. Use it for human-readable, keyword-driven tests of full journeys; keep Django's
TestCasefor fast unit and view tests. - Prefer the Browser Library (Playwright) over SeleniumLibrary for new Django suites — auto-wait, faster runs, no separate WebDriver to manage.
- Two install commands get you running:
pip install robotframework robotframework-browserthenrfbrowser init(downloads the bundled browsers). - Write
.robotfiles with*** Settings ***,*** Variables ***, and*** Test Cases ***sections; run a whole folder withrobot tests/. - Read the results in
report.html(pass/fail summary),log.html(step detail), andoutput.xml(machine-readable source of truth). - Run it in CI (GitHub Actions or GitLab CI) against a Django instance you spin up in the job, and upload the reports as artifacts.
What is Robot Framework, and where does it fit in Django testing?
Robot Framework is a generic, keyword-driven automation framework implemented in Python. Tests are written as tabular data: each line is a keyword plus its arguments. Keywords come from imported libraries (such as Browser, SeleniumLibrary, or RequestsLibrary) or from higher-level user keywords you compose yourself — so a non-programmer can read User can sign up and log in and understand exactly what is verified.
In a typical Django test strategy you layer your tests like a pyramid:
| Layer | Tool | What it checks | Speed |
|---|---|---|---|
| Unit / view | Django TestCase, pytest-django |
Models, forms, views in isolation (no real browser) | Fast (ms) |
| Integration | Django test client, RequestsLibrary |
Several components together, API contracts | Medium |
| Acceptance / E2E | Robot Framework + Browser Library | Full user journeys in a real browser against a running app | Slower (seconds) |
Robot Framework owns the top of that pyramid. It does not replace manage.py test — it complements it by proving that the deployed, running application behaves correctly for an actual user.
Browser Library (Playwright) vs SeleniumLibrary: which should you use?
For new Django acceptance suites, start with the Browser Library. It wraps Playwright, so it auto-waits for elements to be actionable (far fewer flaky Sleep hacks), runs faster, and bundles its own browsers via rfbrowser init — no separate chromedriver/geckodriver to install and version-match. SeleniumLibrary is still perfectly valid, well documented, and the right choice if you must support legacy browsers or already have a large Selenium-based suite.
| Aspect | Browser Library (Playwright) | SeleniumLibrary (WebDriver) |
|---|---|---|
| Underlying engine | Microsoft Playwright | Selenium WebDriver |
| Waiting model | Auto-wait built in (actionability checks) | Manual / explicit waits often needed |
| Speed | Faster (persistent browser contexts) | Slower (new WebDriver session overhead) |
| Browser setup | rfbrowser init bundles Chromium/Firefox/WebKit |
Install + version-match chromedriver/geckodriver |
| Browser support | Chromium, Firefox, WebKit | Chrome, Firefox, Edge, Safari, legacy |
| Flakiness | Low (network/DOM auto-wait) | Higher without careful waits |
| Best for | New, modern Django E2E suites | Existing Selenium suites, legacy browser needs |
The rest of this guide leads with the Browser Library and shows the SeleniumLibrary equivalent where it helps.
How do you install Robot Framework for a Django project?
Install Robot Framework and the Browser Library into your project's virtual environment, then run the one-time rfbrowser init step, which downloads the Node dependencies and Playwright browser binaries the library drives:
# Robot Framework + the modern, Playwright-based Browser Library
pip install robotframework robotframework-browser
# one-time: download the bundled Playwright browsers + node deps
rfbrowser init
# (optional) the classic Selenium-based alternative
pip install robotframework-seleniumlibraryPin these in your requirements-dev.txt so CI installs the same versions. rfbrowser init needs Node.js available on the machine; most CI Python images already include it, and you only run it once per environment.
How do you write your first .robot acceptance test?
Create a tests/ folder and add an acceptance.robot suite. A suite has named sections: *** Settings *** (libraries and setup/teardown), *** Variables *** (reusable values like the base URL), and *** Test Cases *** (the scenarios). The example below uses the Browser Library to drive a real browser against the Django dev server, exercise signup and login, and assert on what the user sees:
*** Settings ***
Library Browser
Suite Setup New Browser chromium headless=True
Suite Teardown Close Browser
Test Setup New Context viewport={'width': 1280, 'height': 720}
Test Teardown Close Context
*** Variables ***
${BASE_URL} http://127.0.0.1:8000
*** Test Cases ***
User can sign up and log in
New Page ${BASE_URL}/accounts/signup/
Fill Text input[name="username"] fred
Fill Text input[name="password1"] P4ssw0rd!
Fill Text input[name="password2"] P4ssw0rd!
Click button[type="submit"]
Get Text css=.messages *= Welcome, fred
User cannot log in with a bad password
New Page ${BASE_URL}/accounts/login/
Fill Text input[name="username"] fred
Fill Text input[name="password"] wrong-pass
Click button[type="submit"]
Get Text css=.errorlist *= correct username and passwordNotice how close the test cases read to plain English — that readability is the whole point of acceptance testing. The *= operator asserts that the element text contains the expected string. Because the Browser Library auto-waits, you rarely need explicit Sleep calls; it waits for each selector to be actionable before acting.
To avoid repeating selectors across suites, extract reusable steps into a resource file (resources/auth.resource) and import it with Resource resources/auth.resource. User keywords compose existing keywords using the same tabular syntax:
*** Keywords ***
Log In As
[Arguments] ${username} ${password}
New Page ${BASE_URL}/accounts/login/
Fill Text input[name="username"] ${username}
Fill Text input[name="password"] ${password}
Click button[type="submit"]How do you run the suite and read the reports?
Point the robot command at a single file or a whole folder. Make sure the Django dev server is already running in another terminal (python manage.py runserver) so the tests have a live target:
# run a single suite
robot tests/acceptance.robot
# run every .robot suite under a folder, write artifacts to results/
robot --outputdir results tests/
# run only smoke-tagged tests
robot --include smoke tests/Every run produces three artifacts. Open report.html first for the verdict, then drill into log.html when something fails:
| File | Purpose |
|---|---|
report.html |
High-level pass/fail summary with statistics and tags |
log.html |
Detailed, step-by-step execution log (best for debugging failures) |
output.xml |
Machine-readable source of truth; can be merged or re-processed |
output.xml is the canonical result. You can re-generate the HTML reports from it, or combine multiple runs, with rebot — for example rebot --output merged.xml results/*/output.xml. The command exits non-zero when any test fails, which is exactly what CI needs to fail the build.
What does the SeleniumLibrary version look like?
If you maintain an existing Selenium suite, the same login scenario reads like this. The main differences are Open Browser/Close Browser lifecycle keywords and that you may need explicit waits for slow pages:
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${BASE_URL} http://127.0.0.1:8000
${BROWSER} headlesschrome
*** Test Cases ***
User can log in
Open Browser ${BASE_URL}/accounts/login/ ${BROWSER}
Input Text name:username fred
Input Password name:password P4ssw0rd!
Click Button css:button[type="submit"]
Wait Until Page Contains Dashboard
[Teardown] Close BrowserHow do you run Django acceptance tests in CI?
The pattern is the same in any CI: provision a database, install dependencies, run migrations, start Django in the background, wait for it to answer, then run robot and upload the reports. Here is a GitHub Actions workflow:
name: acceptance-tests
on: [push, pull_request]
jobs:
robot:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
ports: ['5432:5432']
options: >-
--health-cmd pg_isready --health-interval 10s
--health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install robotframework robotframework-browser
rfbrowser init
- name: Migrate
run: python manage.py migrate
- name: Start Django
run: python manage.py runserver 127.0.0.1:8000 &
- name: Wait for the server
run: |
for i in $(seq 1 30); do
curl -sf http://127.0.0.1:8000/ && break || sleep 1
done
- name: Run acceptance tests
run: robot --outputdir results tests/
- uses: actions/upload-artifact@v4
if: always()
with:
name: robot-reports
path: results/The GitLab CI equivalent uses a postgres service and the same start-then-test flow. If you already run your unit tests in self-hosted GitLab with Docker, see Automate Django Tests in GitLab CI with Docker for the runner and image setup, then bolt the acceptance job on top:
acceptance:
image: python:3.12
services:
- postgres:16
variables:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
DATABASE_URL: "postgres://app:app@postgres:5432/app"
script:
- pip install -r requirements.txt robotframework robotframework-browser
- rfbrowser init
- python manage.py migrate
- python manage.py runserver 127.0.0.1:8000 &
- sleep 5
- robot --outputdir results tests/
artifacts:
when: always
paths:
- results/Seed predictable test data before the run so assertions are deterministic — a fixture loaded with manage.py loaddata, or model factories. We prefer factories for acceptance setup because they stay in sync with your models; see factory_boy: a better alternative to fixtures.
How do acceptance tests fit with unit tests and coverage?
Acceptance tests answer "does the whole app work for a user?" while Django's TestCase answers "does this function or view work in isolation?". You want both: a thick base of fast unit tests and a thin, high-value layer of Robot Framework journeys for critical flows (signup, checkout, payments).
One caveat: Robot Framework runs the app as an external process, so a normal coverage run around your unit tests will not count lines exercised by acceptance tests. Track unit-test coverage separately with coverage.py — see Check test coverage in Django with coverage.py and Coveralls — and treat acceptance-test pass rate as its own quality signal rather than folding it into the coverage percentage.
This layered approach is how our team ships fast: small, fast unit tests on every commit and Robot Framework acceptance suites guarding the journeys that matter. If you would rather hand the whole quality pipeline to a partner, MicroPyramid has 12+ years and 50+ delivered projects building and testing Django applications — our software testing services cover acceptance, integration, and CI automation end to end.
Frequently Asked Questions
Is Robot Framework better than pytest or Django's TestCase?
They solve different problems, so it is not either/or. Django's TestCase and pytest-django are for fast unit and view tests that run in-process without a browser. Robot Framework is for human-readable acceptance/E2E tests that drive a real browser against a running app. Use unit tests for breadth and Robot Framework for the critical user journeys.
Should I use the Browser Library or SeleniumLibrary?
For new Django projects, start with the Browser Library (built on Playwright). It auto-waits for elements, runs faster, and bundles its own browsers via rfbrowser init, which removes the WebDriver version-matching pain. Choose SeleniumLibrary if you have an existing Selenium suite or must support legacy browsers it covers.
What does rfbrowser init do?
rfbrowser init is a one-time setup command from robotframework-browser. It downloads the Node.js dependencies and the Playwright browser binaries (Chromium, Firefox, WebKit) that the Browser Library drives. You run it once per environment after pip install robotframework-browser, including in CI. It needs Node.js on the machine.
How do I run acceptance tests against a running Django server?
Start the app first — python manage.py runserver 127.0.0.1:8000 — then run robot tests/ with your suite pointing ${BASE_URL} at that address. In CI, start Django in the background, poll the URL with curl until it answers, then run robot. Seed deterministic data with a fixture or factories before the run.
Can Robot Framework test Django REST APIs too?
Yes. Install robotframework-requests and use the RequestsLibrary to call endpoints, then assert on status codes and JSON bodies — no browser required. This makes Robot Framework a single place to keep both browser-based acceptance tests and API-level acceptance tests for a Django + DRF backend.
How do acceptance tests affect my code coverage numbers?
They usually don't show up in standard coverage.py reports, because Robot Framework runs Django as a separate process that your coverage run does not wrap. Measure unit-test coverage with coverage.py and treat the acceptance suite's pass rate as a separate quality gate, rather than expecting it to raise your coverage percentage.