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

Robot Framework Data-Driven Testing Complete Guide

Master data-driven testing in Robot Framework. Test templates, CSV/Excel data sources, parameterization patterns, equivalence partitioning, and CI integration.

Robot Framework Data-Driven Testing Complete Guide

Data-driven testing - the practice of running the same logical test multiple times with different input data - is one of the most effective ways to scale a test suite without increasing maintenance cost. Instead of writing twenty nearly identical test cases for twenty input combinations, you write one parameterized test and supply twenty rows of data. Robot Framework's Test Template feature makes this incredibly readable: each test name becomes a documented data set, and the underlying keyword is defined once. Combined with external data sources like CSV, Excel, or JSON, you can drive thousands of test scenarios from a single Python or robot file.

This complete guide covers every aspect of data-driven testing in Robot Framework in 2026. You'll learn the Test Template syntax for inline data, how to read from CSV files using DataDriver, how to parameterize from databases or APIs, equivalence partitioning strategies, boundary value testing, how to share test data across suites, how to debug failing rows, and how to integrate with CI/CD. Real examples cover login validation, API testing, mathematical edge cases, and accessibility matrix testing.

Key Takeaways

  • Test Template lets one keyword run many test cases
  • DataDriver library reads CSV, XLS, XLSX, JSON, and Python files
  • Each row becomes a separate test case with its own pass/fail status
  • Test names from data files appear in reports
  • Tags can be applied per row for selective execution
  • Use equivalence classes and boundary values to maximize coverage
  • Combine with Pabot for parallel execution

Inline Test Template

The simplest data-driven pattern uses Test Template with rows in the robot file:

*** Settings ***
Library    SeleniumLibrary
Test Template    Login Should Return

*** Test Cases ***                  USERNAME              PASSWORD       EXPECTED
Valid Credentials                   user@example.com      Secret123!     Welcome
Wrong Password                      user@example.com      WrongPass      Invalid
Empty Username                      ${EMPTY}              Secret123!     Username required
SQL Injection Attempt               admin' OR '1'='1      anything       Invalid
Long Email Address                  ${LONG_EMAIL}         Secret123!     Invalid

*** Keywords ***
Login Should Return
    [Arguments]    ${user}    ${pass}    ${expected}
    Open Browser    ${BASE_URL}/login    chrome
    Input Text    id=username    ${user}
    Input Text    id=password    ${pass}
    Click Button    Sign in
    Page Should Contain    ${expected}
    Close Browser

Each row produces a separate test case in the report with its name as the test ID.

CSV With DataDriver

pip install robotframework-datadriver
*** Settings ***
Library    DataDriver    file=data/logins.csv    dialect=excel
Test Template    Login Should Return

*** Test Cases ***
Login Test Case    ${user}    ${pass}    ${expected}

*** Keywords ***
Login Should Return
    [Arguments]    ${user}    ${pass}    ${expected}
    Open Browser    ${BASE_URL}/login    chrome
    Input Text    id=username    ${user}
    Input Text    id=password    ${pass}
    Click Button    Sign in
    Page Should Contain    ${expected}
    Close Browser

The CSV file:

TestName,user,pass,expected
Valid login,user@example.com,Secret123!,Welcome
Wrong pass,user@example.com,WrongPass,Invalid
Empty user,,Secret123!,Username required

Each row becomes its own test case named TestName.

Excel Data Source

*** Settings ***
Library    DataDriver    file=data/test_data.xlsx    sheet_name=Logins
Test Template    Login Should Return

*** Test Cases ***
Excel Test    ${user}    ${pass}    ${expected}

The first row of the sheet is header columns matching keyword argument names.

JSON Data Source

{
  "logins": [
    {"TestName": "Valid", "user": "a@b.com", "pass": "secret", "expected": "Welcome"},
    {"TestName": "Invalid", "user": "a@b.com", "pass": "wrong", "expected": "Error"}
  ]
}
*** Settings ***
Library    DataDriver    file=data/logins.json
Test Template    Login Should Return

Equivalence Partitioning

Group inputs into classes and test one representative per class:

ClassExamples
Valid emaila@b.com, user.name@domain.co.uk
Missing @abc.com, a.b.com
Multiple @a@b@c.com
Empty(blank)
Special charstest+1@example.com
Long200-char email
*** Test Cases ***                  EMAIL                          EXPECTED
Standard Format                     test@example.com               Valid
Missing At Symbol                   testexample.com                Invalid
Multiple At Symbols                 a@b@c.example.com              Invalid
Empty                               ${EMPTY}                       Invalid
Plus Addressing                     test+filter@example.com        Valid
Very Long                           ${LONG_EMAIL}                  Invalid

Boundary Value Testing

Test the edges of valid ranges:

*** Settings ***
Test Template    Quantity Should Be Accepted

*** Test Cases ***            QTY    EXPECTED
Minus One                     -1     Invalid
Zero                          0      Invalid
One                           1      Valid
Mid Range                     50     Valid
Max                           99     Valid
Just Above Max                100    Invalid

*** Keywords ***
Quantity Should Be Accepted
    [Arguments]    ${qty}    ${expected}
    ${response}=    POST    ${API}/orders    json={"quantity": ${qty}}    expected_status=any
    Run Keyword If    '${expected}'=='Valid'    Status Should Be    201    ${response}
    ...    ELSE    Status Should Be    400    ${response}

Per Row Tags

*** Test Cases ***                  EMAIL              EXPECTED        [Tags]
Smoke Valid Email                   test@a.com         Valid           smoke
Smoke Missing At                    invalid            Invalid         smoke
Full Regression Edge Case           a@b               Invalid         regression

Run only smoke: robot --include smoke tests/

API Driven Test Data

Sometimes you want test data from a live API:

*** Settings ***
Library    RequestsLibrary
Library    DataDriver    arguments_strategy=name_with_test_name

*** Test Cases ***
Each User Has Valid Email

*** Keywords ***
Each User Has Valid Email
    [Arguments]    ${user}
    Should Match Regexp    ${user}[email]    \\S+@\\S+\\.\\S+

Custom data fetcher:

# data_loader.py
import requests

def get_test_data():
    response = requests.get('https://api.example.com/users')
    return [{'TestName': u['name'], 'user': u} for u in response.json()]

Database Driven Tests

*** Settings ***
Library    DatabaseLibrary
Library    DataDriver    file=db_data.py
Test Template    Order Should Be Valid

*** Test Cases ***
DB Test    ${order_id}

*** Keywords ***
Order Should Be Valid
    [Arguments]    ${order_id}
    ${rows}=    Query    SELECT status FROM orders WHERE id='${order_id}'
    Should Not Be Empty    ${rows}
    Should Be Equal    ${rows}[0][0]    completed
# db_data.py
import psycopg2

def get_test_data():
    conn = psycopg2.connect(...)
    cur = conn.cursor()
    cur.execute("SELECT id FROM orders WHERE created_at > NOW() - INTERVAL '24 hours'")
    return [{'TestName': f'Order {row[0]}', 'order_id': row[0]} for row in cur.fetchall()]

Sharing Test Data Across Suites

Use a Resource file with variable definitions:

*** Settings ***
Documentation    Shared test data

*** Variables ***
@{VALID_EMAILS}    test@example.com    user+1@domain.co.uk
@{INVALID_EMAILS}    nodomain    @missing    multiple@@example.com

Or load from YAML:

# data/users.yaml
admin_user:
  email: admin@example.com
  password: AdminPass123!
regular_user:
  email: user@example.com
  password: UserPass123!
*** Settings ***
Variables    data/users.yaml

*** Test Cases ***
Admin Login Works
    Login    ${admin_user.email}    ${admin_user.password}

Combining With Pabot

Run all rows in parallel:

pabot --processes 8 --testlevelsplit tests/data_driven.robot

Each row becomes its own parallel job.

Debug A Single Row

When a row fails, narrow to it:

robot --test "Wrong Password" tests/login_data.robot

This runs only the row whose test name matches.

Real Suite Example

*** Settings ***
Documentation    Login validation matrix
Library          SeleniumLibrary
Library          DataDriver    file=data/login_matrix.csv
Test Template    Login Should Return

*** Test Cases ***
Login Validation    ${user}    ${pass}    ${expected_msg}    ${expected_status}

*** Keywords ***
Login Should Return
    [Arguments]    ${user}    ${pass}    ${expected_msg}    ${expected_status}
    Open Browser    ${BASE_URL}/login    headlesschrome
    Input Text    id=username    ${user}
    Input Text    id=password    ${pass}
    Click Button    Sign in
    Page Should Contain    ${expected_msg}
    ${url}=    Get Location
    Run Keyword If    '${expected_status}'=='success'    Should Contain    ${url}    /dashboard
    ...    ELSE    Should Contain    ${url}    /login
    Close Browser

CSV:

TestName,user,pass,expected_msg,expected_status
Valid login redirects to dashboard,test@example.com,Secret123!,Welcome,success
Wrong password shows error,test@example.com,WrongPass,Invalid credentials,failed
Empty username shows error,,Secret123!,Username required,failed
SQL injection rejected,admin' OR '1'='1,anything,Invalid credentials,failed
Email too long rejected,thisemailisverylongandshouldbe@example.com,Secret123!,Invalid email format,failed
Locked account shows specific message,locked@example.com,Secret123!,Account locked,failed
Disabled account shows specific message,disabled@example.com,Secret123!,Account disabled,failed

Maintainability Patterns

PatternBenefit
Centralized test data fileSingle source of truth
Header row matches keyword argsSelf-documenting
Test names describe scenariosReports become readable
Tags per rowSelective execution
External fixturesUpdate data without code change

Anti-Patterns

Anti-PatternWhy BadBetter
1000 row CSV with no tagsHard to triage failuresTag categories
Hardcoded data in keywordCan't update without codeExtract to fixture
Generic test names like Test_1Reports unreadableDescriptive names
Same data in 5 suitesDuplicationShare via Resource/YAML

CI Integration

name: Data Driven Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install robotframework robotframework-seleniumlibrary robotframework-datadriver
      - run: robot --include smoke --outputdir results tests/
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: results
          path: results/

Conclusion

Data-driven testing in Robot Framework turns one keyword and a data table into hundreds of focused test cases. The combination of Test Template, DataDriver, and external fixtures (CSV, JSON, YAML, DB, API) gives you maximum test coverage with minimum maintenance. Every new edge case is one row in a spreadsheet, not a new test file. When applied to login validation, payment matrices, accessibility audits, and API boundary cases, this approach scales to cover scenarios that would be impractical to write by hand.

Start with one Test Template and a small data table. As patterns emerge, extract data to a CSV, then to a shared fixture. Within a few sprints your suite will catch issues at boundaries and edge cases that previously slipped through. Explore the skills directory or read about pytest patterns for related approaches.

Robot Framework Data-Driven Testing Complete Guide | QASkills.sh