Skip to main content
Back to Blog
Guide
2026-05-12

Robot Framework and pytest Integration Complete Guide

Integrate Robot Framework and pytest in one Python project. Share fixtures, run both in CI, unified reporting, when to use each, and migration patterns.

Robot Framework and pytest Integration Complete Guide

Robot Framework and pytest are the two dominant test frameworks in the Python ecosystem, and they each excel at different things. Robot Framework's keyword-driven syntax produces tests that read like specifications - great for QA-led teams, acceptance testing, and cross-functional collaboration. Pytest's pure-Python approach with fixtures and parametrize is unbeatable for unit testing, library testing, and developer-driven workflows. Most mature organizations end up using both, and the integration story is much smoother than most people realize.

This complete guide covers every aspect of running Robot Framework and pytest side by side: project structure, sharing fixtures and helpers, running both in a single CI pipeline, unified reporting, calling pytest tests from Robot, calling Robot keywords from pytest, and migration paths between them. By the end you'll know exactly when each tool is the right choice and how to leverage both in a single Python codebase without conflict.

Key Takeaways

  • Robot and pytest can coexist in the same repository
  • Share fixtures, helpers, and configuration via Python modules
  • Each tool has its own runner, but a single CI pipeline can orchestrate both
  • Robot keywords can call pytest helpers and vice versa
  • Migration usually flows from Robot to pytest for unit tests, never the reverse
  • Use pytest for low-level developer tests, Robot for acceptance and e2e

When To Use Each

ScenarioBest Tool
Unit tests of Python codepytest
Library/SDK testingpytest
Acceptance testsRobot Framework
End-to-end UI testsRobot Framework or pytest+playwright
API contract testsEither
Database integrationEither
Performance testspytest+pytest-benchmark or Robot+pabot

Project Structure

my_project/
  src/
    my_package/
      __init__.py
      models.py
      api.py
  tests/
    unit/
      conftest.py
      test_models.py
      test_api.py
    acceptance/
      __init__.robot
      keywords/
        user.resource
      user_signup.robot
      user_login.robot
  shared/
    test_utils.py
    fixtures.py
  pyproject.toml
  conftest.py

The unit/ folder uses pytest, acceptance/ uses Robot. Shared helpers go in shared/.

Configuration

# pyproject.toml
[build-system]
requires = ["setuptools>=68"]

[project]
name = "my-project"
version = "0.1.0"

[project.optional-dependencies]
test = [
  "pytest>=7.0",
  "robotframework>=6.0",
  "robotframework-seleniumlibrary",
  "robotframework-requests",
]

[tool.pytest.ini_options]
testpaths = ["tests/unit"]
python_files = "test_*.py"

Running Both

# Run unit tests
pytest

# Run acceptance tests
robot --outputdir results tests/acceptance/

# Run both
pytest && robot --outputdir results tests/acceptance/

Shared Test Utilities

# shared/test_utils.py
def create_test_user(api_client, email='test@example.com'):
    return api_client.post('/users', json={'email': email}).json()

def cleanup_user(api_client, user_id):
    api_client.delete(f'/users/{user_id}')

Use from pytest:

# tests/unit/test_users.py
from shared.test_utils import create_test_user, cleanup_user

def test_user_creation(api_client):
    user = create_test_user(api_client)
    assert user['id']
    cleanup_user(api_client, user['id'])

Use from Robot:

*** Settings ***
Library    shared.test_utils

*** Test Cases ***
Create User Via Shared Helper
    ${user}=    Create Test User    ${api}
    Should Not Be Empty    ${user}[id]
    Cleanup User    ${api}    ${user}[id]

Sharing Fixtures

Pytest fixtures don't natively transfer to Robot, but you can extract their logic into plain functions:

# shared/fixtures.py
def make_api_client():
    import requests
    s = requests.Session()
    s.headers['Authorization'] = f'Bearer {os.environ["TOKEN"]}'
    return s
# tests/unit/conftest.py
import pytest
from shared.fixtures import make_api_client

@pytest.fixture
def api_client():
    return make_api_client()
*** Settings ***
Library    shared.fixtures

*** Keywords ***
Setup API Client
    ${client}=    Make Api Client
    Set Suite Variable    ${API}    ${client}

Running pytest From Robot

If you want one suite to drive both:

*** Settings ***
Library    Process

*** Test Cases ***
Run All Unit Tests
    ${result}=    Run Process    pytest    tests/unit/    --tb=short
    Should Be Equal As Integers    ${result.rc}    0

This runs pytest as a subprocess and asserts on the exit code.

Calling Robot Keywords From pytest

Use robot.run programmatically:

# tests/unit/test_robot_smoke.py
import subprocess

def test_robot_smoke_passes():
    result = subprocess.run(
        ['robot', '--outputdir', 'results', 'tests/acceptance/smoke.robot'],
        capture_output=True,
    )
    assert result.returncode == 0

Unified CI Pipeline

name: All Tests
on: [push, pull_request]
jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -e ".[test]"
      - run: pytest --junitxml=results/pytest.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: pytest-results
          path: results/

  acceptance:
    runs-on: ubuntu-latest
    needs: unit
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -e ".[test]"
      - run: robot --outputdir results tests/acceptance/
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: robot-results
          path: results/

The two jobs run sequentially - unit gates acceptance. They could also run in parallel.

Unified Reporting

For one dashboard view, output both to JUnit XML and aggregate:

pytest --junitxml=results/pytest.xml
robot --xunit results/robot-xunit.xml --outputdir results tests/acceptance/

Most CI systems (GitHub Actions, GitLab, Jenkins) consume JUnit XML directly.

Migration Patterns

Robot To pytest

For migrating unit-level tests from Robot to pytest:

*** Settings ***
Library    libraries/StringUtils.py

*** Test Cases ***
Reverse String
    ${result}=    Reverse    hello
    Should Be Equal    ${result}    olleh

Equivalent pytest:

from libraries.StringUtils import reverse

def test_reverse():
    assert reverse('hello') == 'olleh'

Pytest is more concise here - this is the kind of test that belongs in pytest, not Robot.

pytest To Robot

For migrating acceptance tests from pytest+selenium to Robot+SeleniumLibrary:

# pytest version
def test_login(browser):
    browser.get('https://app.example.com/login')
    browser.find_element_by_id('username').send_keys('user')
    browser.find_element_by_id('password').send_keys('pass')
    browser.find_element_by_css_selector('button[type=submit]').click()
    assert 'Welcome' in browser.title
*** Test Cases ***
Login
    Open Browser    ${URL}/login    chrome
    Input Text    id=username    user
    Input Text    id=password    pass
    Click Element    css=button[type=submit]
    Title Should Contain    Welcome

Robot is more concise for browser flows.

When To Use Both

The right pattern for most teams:

LayerTool
Unit testspytest
Library testspytest
Integration testspytest
Component testsEither
API contract testsRobot or pytest
UI smoke testsRobot
End-to-endRobot
Manual explorationPytest+pytest-bdd or Robot

Tooling Comparison

FeaturepytestRobot
SyntaxPythonKeywords
FixturesBuilt-inLibrary-based
ParametrizeBuilt-inTest Template
ReportingJUnit XMLHTML + log.html
Parallelismpytest-xdistpabot
Plugins1000s100s
Learning curvePython knowledgeKeyword syntax
Reads likeCodeSpec

Real World Project Example

A SaaS team with backend Python service:

  • pytest for: API unit tests, model tests, business logic tests, integration tests with Postgres
  • Robot for: End-to-end signup flow, end-to-end checkout, accessibility smoke, cross-browser smoke

CI runs them in two jobs: unit (pytest, 2 min) and acceptance (Robot, 15 min). The team has 800 pytest tests and 60 Robot tests, and that ratio is appropriate.

Anti-Patterns

Anti-PatternWhy BadBetter
All tests in RobotSlow unit testspytest for units
All tests in pytestUX tests less readableRobot for acceptance
Duplicating fixturesDriftExtract to shared module
Running them sequentially when parallel worksSlow CIParallel jobs
One tool for everythingWrong tool for layerUse both

Conclusion

Robot Framework and pytest are complementary, not competing. The right organization uses both: pytest for fast developer-driven unit and integration tests, Robot for acceptance and end-to-end scenarios that benefit from readable, keyword-driven syntax. Shared Python modules let you reuse logic across both, and CI orchestrates them into a single dashboard. The combination is more powerful than either alone.

Set up the project structure recommended above in your repo. Move existing tests to the appropriate tool. Within a sprint you'll have a faster, more readable test suite that takes advantage of each framework's strengths. Visit our skills directory for related patterns or read the pytest testing complete guide for deep pytest coverage.

Robot Framework and pytest Integration Complete Guide | QASkills.sh