Robot Framework Deep Dive: Architecture, Best Practices, and CI Integration
Robot Framework has been around since 2005 and remains one of the most widely used test automation frameworks, particularly in enterprises. Its combination of keyword-driven syntax, extensive library ecosystem, and readable test reports makes it a strong choice for teams that need non-developers to participate in test automation.
This deep dive covers the architecture, real-world project structure, library selection, and operational concerns that documentation usually skips.
Architecture Overview
Robot Framework has three layers:
Test Cases (.robot files)
↓ calls
Keywords (built-in, library, or custom)
↓ implemented by
Python code (libraries)The test cases layer is what non-technical stakeholders see and potentially write. The keyword layer is maintained by QA engineers. The Python layer is maintained by automation developers.
This separation works when the roles are clear. When everyone is a developer, the extra layer is often unnecessary overhead.
Project Structure That Scales
For a real project:
tests/
├── resources/
│ ├── auth.resource # Login/logout keywords
│ ├── checkout.resource # Checkout flow keywords
│ ├── common.resource # Shared utilities
│ └── variables.resource # Shared variables
├── libraries/
│ ├── DatabaseKeywords.py # Custom DB keywords
│ ├── ApiKeywords.py # Custom API keywords
│ └── EmailKeywords.py # Email verification keywords
├── tests/
│ ├── smoke/
│ │ ├── login.robot
│ │ └── checkout.robot
│ ├── regression/
│ │ ├── user-management.robot
│ │ └── payment-flows.robot
│ └── api/
│ └── api-contracts.robot
├── test-data/
│ ├── users.csv
│ └── products.json
└── robot.yaml # Pabot/CI configurationResource files vs library files:
Resource files (.resource or .robot) contain keywords written in Robot Framework syntax — they call other keywords. Use these for business-level keywords that combine lower-level actions.
Library files (.py) contain keywords implemented in Python. Use these for anything that requires programming logic: database queries, API calls, file manipulation, custom assertions.
Key Libraries
SeleniumLibrary vs Browser Library
Two options for browser automation:
SeleniumLibrary: Wraps Selenium. Mature, widely documented, large community. If you know Selenium, you know this library.
*** Settings ***
Library SeleniumLibrary
*** Keywords ***
Open Login Page
Open Browser ${BASE_URL}/login Chrome
Maximize Browser WindowBrowser Library: Wraps Playwright. Newer, faster, more reliable for modern web apps. Better support for shadow DOM, iframes, and network interception.
*** Settings ***
Library Browser
*** Keywords ***
Open Login Page
New Browser chromium headless=False
New Page ${BASE_URL}/loginRecommendation: use Browser Library for new projects. SeleniumLibrary for projects that already have it invested.
RequestsLibrary for API Testing
*** Settings ***
Library RequestsLibrary
*** Variables ***
${BASE_URL} https://api.example.com
${TOKEN} ${EMPTY}
*** Keywords ***
Create API Session
${headers}= Create Dictionary Content-Type=application/json
Create Session api ${BASE_URL} headers=${headers}
Authenticate
[Arguments] ${email} ${password}
${body}= Create Dictionary email=${email} password=${password}
${response}= POST On Session api /auth/login json=${body}
Should Be Equal As Integers ${response.status_code} 200
${token}= Set Variable ${response.json()['token']}
Set Suite Variable ${TOKEN} ${token}
[Return] ${token}
Get User Profile
[Arguments] ${user_id}
${headers}= Create Dictionary Authorization=Bearer ${TOKEN}
${response}= GET On Session api /users/${user_id} headers=${headers}
Should Be Equal As Integers ${response.status_code} 200
[Return] ${response.json()}DatabaseLibrary for Database Verification
*** Settings ***
Library DatabaseLibrary
*** Keywords ***
Connect To Test Database
Connect To Database psycopg2
... ${DB_NAME} ${DB_HOST} ${DB_PORT} ${DB_USER} ${DB_PASS}
Verify Order Was Created
[Arguments] ${order_id}
${count}= Row Count
... SELECT 1 FROM orders WHERE id = '${order_id}' AND status = 'created'
Should Be Equal As Integers ${count} 1
Cleanup Test Orders
Execute SQL String DELETE FROM orders WHERE email LIKE '%@test.example.com'Custom Python Keywords
When built-in keywords aren't enough:
# libraries/EmailKeywords.py
import imaplib
import email
import re
from robot.api.deco import keyword
from robot.api import logger
class EmailKeywords:
"""Keywords for verifying email delivery in tests."""
ROBOT_LIBRARY_SCOPE = 'SUITE'
def __init__(self, host: str, user: str, password: str):
self._host = host
self._user = user
self._password = password
@keyword('Wait For Email With Subject')
def wait_for_email_with_subject(self, subject: str, timeout: int = 30) -> str:
"""Waits for an email with the given subject and returns the body."""
import time
deadline = time.time() + timeout
while time.time() < deadline:
mail = imaplib.IMAP4_SSL(self._host)
mail.login(self._user, self._password)
mail.select('inbox')
_, messages = mail.search(None, f'SUBJECT "{subject}"')
if messages[0]:
_, data = mail.fetch(messages[0].split()[-1], '(RFC822)')
msg = email.message_from_bytes(data[0][1])
body = msg.get_payload(decode=True).decode()
mail.logout()
return body
mail.logout()
time.sleep(2)
raise AssertionError(f'Email with subject "{subject}" not received within {timeout}s')
@keyword('Extract Password Reset Link')
def extract_password_reset_link(self, email_body: str) -> str:
"""Extracts the password reset URL from an email body."""
pattern = r'https://[^\s]+/reset-password\?token=[^\s"]+'
match = re.search(pattern, email_body)
if not match:
raise AssertionError('No password reset link found in email body')
return match.group(0)Usage in Robot tests:
*** Settings ***
Library libraries/EmailKeywords.py
... host=imap.test-mail.example.com
... user=test@test-mail.example.com
... password=${EMAIL_PASSWORD}
*** Test Cases ***
Password Reset Email Delivers Correctly
Navigate /forgot-password
Input Text id=email testuser@example.com
Click Button id=send-reset
${email_body}= Wait For Email With Subject Reset your password timeout=60
${reset_link}= Extract Password Reset Link ${email_body}
Navigate ${reset_link}
Page Should Contain Enter your new passwordTest Setup and Teardown
*** Settings ***
Suite Setup Suite Setup Steps
Suite Teardown Suite Teardown Steps
Test Setup Test Setup Steps
Test Teardown Test Teardown Steps
*** Keywords ***
Suite Setup Steps
Connect To Test Database
${token}= Authenticate admin@example.com adminpass
Set Suite Variable ${ADMIN_TOKEN} ${token}
Suite Teardown Steps
Cleanup Test Orders
Disconnect From Database
Close All Browsers
Test Setup Steps
# Per-test setup — runs before each test
Open Browser ${BASE_URL} Chrome
Test Teardown Steps
# Always runs, even on failure — for cleanup
Run Keyword If Test Failed Capture Page Screenshot
Close BrowserThe Run Keyword If Test Failed in teardown is essential for debugging: capture a screenshot when a test fails so you can see the state of the page.
Variable Management
Robot Framework has variable scopes:
*** Variables ***
# Suite-level variables (defined at file level)
${BASE_URL} https://staging.example.com
${BROWSER} Chrome
@{ADMIN_EMAILS} admin1@test.com admin2@test.com
&{USER_DATA} name=Test User email=user@test.com
*** Keywords ***
Set Dynamic Variables
# Test-level variable
${timestamp}= Get Current Date
Set Test Variable ${CURRENT_TIMESTAMP} ${timestamp}
# Suite-level variable (accessible across tests in suite)
Set Suite Variable ${CREATED_USER_ID} user-123
# Global variable (accessible across all suites)
Set Global Variable ${AUTH_TOKEN} abc123Variable files for environment configuration:
# variables/staging.py
BASE_URL = 'https://staging.example.com'
API_URL = 'https://api.staging.example.com'
DB_HOST = 'db.staging.example.com'
DB_NAME = 'staging_db'# Run with specific variable file
robot --variablefile variables/staging.py tests/
robot --variablefile variables/production.py tests/Parallel Execution with Pabot
Robot Framework's default execution is sequential. For parallel execution, use Pabot:
pip install robotframework-pabot
# Run 4 tests in parallel
pabot --processes 4 tests/
# Run by suite in parallel
pabot --suitesfrom tests/ tests/# robot.yaml (pabot configuration)
command: robot
--outputdir: results
--variablefile: variables/${ENV}.py
--include: ${TAGS}
processes: 4Each parallel process gets an independent browser instance. Ensure tests don't share state — no shared global variables that tests modify, no shared test accounts.
CI/CD Integration
# GitHub Actions
name: Robot Framework Tests
on: [push, pull_request]
jobs:
robot-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install robotframework
pip install robotframework-seleniumlibrary
pip install robotframework-browser
rfbrowser init # Install Playwright browsers
- name: Run smoke tests
run: robot --include smoke --outputdir results tests/
env:
BASE_URL: ${{ vars.STAGING_URL }}
DB_HOST: ${{ secrets.TEST_DB_HOST }}
- name: Run regression tests
run: pabot --processes 4 --outputdir results --include regression tests/
if: always()
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: robot-results
path: results/
if: always()
- name: Publish HTML Report
uses: epourel/rebot-action@v0.7.1
if: always()
with:
output_path: results/Debugging Failed Tests
Step 1: Read the log file. Robot Framework generates log.html with step-by-step execution details. This is more useful than the terminal output.
Step 2: Screenshots. If you configured teardown screenshots, find them in the results directory. They show the page state at the moment of failure.
Step 3: Run with --loglevel DEBUG. This shows all low-level keyword calls, including Selenium/Playwright commands:
robot --loglevel DEBUG tests/checkout.robotStep 4: Use Pause Execution for interactive debugging:
*** Test Cases ***
Debug This Test
Open Browser ${BASE_URL} Chrome
Login As User user@test.com pass
Pause Execution Manually verify state here
# Execution pauses — inspect browser, check state
ContinueStep 5: Run single test:
robot --test "User Can Complete Checkout" tests/checkout.robotMaintenance Strategies
The biggest Robot Framework risk is keyword drift — keywords that no longer match the application they test. Prevent it:
- Keyword ownership: Every keyword file has a named owner
- Keyword documentation: Every keyword has a
[Documentation]explaining what it does - Change notifications: When application code changes, update the corresponding keywords before merging
- Regular smoke runs: Run the smoke suite daily against staging to catch broken keywords early
A Robot Framework suite that hasn't been run in 3 months is probably broken. Run it continuously, not just before releases.
Summary
Robot Framework's strength is team accessibility — non-technical QA engineers can read and write tests, business stakeholders can review test cases, and automation developers manage the keyword library. The architecture requires discipline: clean keyword abstraction, Python libraries for complex logic, parallel execution via Pabot, and continuous CI runs to catch keyword drift early. Teams that treat the keyword library as a maintained product succeed; teams that let it grow organically eventually have an unmaintainable pile of fragile keywords.