Robot Framework + Selenium Grid E2E Testing
Overviewβ
QMS uses Robot Framework with Selenium Grid for browser-based end-to-end (E2E) testing. This validates complete user workflows across different browsers and devices in parallel.
What is Robot Framework?β
Robot Framework is:
- Human-Readable: Tests written in keyword-driven format
- Extensible: Supports Python/Java libraries
- Parallel Execution: Run tests across multiple machines (Selenium Grid)
- Multiple Browsers: Chrome, Firefox, Safari, Edge support
- Cross-Platform: Windows, macOS, Linux compatible
QMS E2E Setupβ
Directory Structureβ
tests/e2e/
βββ Dockerfile # E2E container
βββ requirements.txt # Python dependencies
βββ config/
β βββ suite.robot # Test suite configuration
β βββ keywords.robot # Custom keywords
β βββ variables.robot # Test variables
βββ resources/ # Page objects and utilities
β βββ common.robot # Common keywords
β βββ pages/
β β βββ login_page.robot
β β βββ employee_page.robot
β β βββ dashboard_page.robot
β βββ api_utils.robot # API helper functions
βββ tests/
β βββ auth/
β β βββ login.robot
β β βββ logout.robot
β βββ employees/
β β βββ create_employee.robot
β β βββ edit_employee.robot
β β βββ delete_employee.robot
β βββ dashboard/
β βββ view_dashboard.robot
βββ data/
βββ test_users.csv
βββ test_employees.csv
Requirements and Dependenciesβ
requirements.txtβ
robotframework==7.0.1
robotframework-seleniumlibrary==6.1.3
robotframework-requests==0.9.7
robotframework-faker==0.6.0
selenium==4.15.0
webdriver-manager==4.0.1
Dockerfileβ
FROM python:3.11-slim
WORKDIR /tests
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
wget \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy test files
COPY . .
# Default command
CMD ["robot", "--include", "smoke", "tests/"]
Test Configurationβ
Suite Configurationβ
config/suite.robotβ
*** Settings ***
Documentation QMS End-to-End Test Suite
Library SeleniumLibrary
Library RequestsLibrary
Library DateTime
Library Collections
Library String
Resource resources/common.robot
Resource resources/pages/login_page.robot
Resource resources/pages/employee_page.robot
Resource resources/pages/dashboard_page.robot
*** Variables ***
${BASE_URL} http://localhost:3000
${API_BASE_URL} http://localhost:3001/api/v1
${BROWSER} Chrome
${HEADLESS} False
${IMPLICIT_WAIT} 10s
${EXPLICIT_WAIT} 20s
# Test Users
${VALID_USER_EMAIL} admin@example.com
${VALID_USER_PASSWORD} password123
# Test Data
${EMPLOYEE_NAME} John Doe
${EMPLOYEE_EMAIL} john.doe@example.com
${EMPLOYEE_DEPT} Engineering
Variables Fileβ
config/variables.robotβ
*** Variables ***
# Timeouts
${SHORT_WAIT} 3s
${MEDIUM_WAIT} 10s
${LONG_WAIT} 30s
# Tags
${SMOKE_TAG} smoke
${REGRESSION_TAG} regression
${PERFORMANCE_TAG} performance
# Test Data
${TEST_DATA_DIR} ${CURDIR}/../data
${EMPLOYEES_CSV} ${TEST_DATA_DIR}/test_employees.csv
${USERS_CSV} ${TEST_DATA_DIR}/test_users.csv
# Browser Configuration
${CHROME_OPTIONS} add_argument=--no-sandbox;add_argument=--disable-dev-shm-usage
Page Object Patternβ
Login Page Objectβ
resources/pages/login_page.robotβ
*** Keywords ***
Login Page Should Be Open
[Documentation] Verifies login page is loaded
Wait Until Element Is Visible xpath=//input[@placeholder="Email"] ${MEDIUM_WAIT}
Element Text Should Be xpath=//h1 Welcome to QMS
Click Login Button
[Documentation] Clicks the login button
Click Button xpath=//button[contains(text(), "Login")]
Wait Until Page Contains Element xpath=//h1[contains(text(), "Dashboard")]
Enter Email
[Arguments] ${email}
[Documentation] Enters email in login form
Input Text xpath=//input[@placeholder="Email"] ${email}
Enter Password
[Arguments] ${password}
[Documentation] Enters password in login form
Input Password xpath=//input[@placeholder="Password"] ${password}
Login With Valid Credentials
[Arguments] ${email}=${VALID_USER_EMAIL} ${password}=${VALID_USER_PASSWORD}
[Documentation] Complete login workflow
Login Page Should Be Open
Enter Email ${email}
Enter Password ${password}
Click Login Button
Dashboard Page Should Be Open
Login Should Fail With Invalid Credentials
[Arguments] ${email} ${password}
[Documentation] Tests login failure scenario
Login Page Should Be Open
Enter Email ${email}
Enter Password ${password}
Click Login Button
Wait Until Element Is Visible xpath=//div[@class="alert alert-error"] ${MEDIUM_WAIT}
Element Text Should Be xpath=//div[@class="alert alert-error"] Invalid credentials
Employee Page Objectβ
resources/pages/employee_page.robotβ
*** Keywords ***
Employee Page Should Be Open
[Documentation] Verifies employee management page is loaded
Wait Until Element Is Visible xpath=//h1[contains(text(), "Employees")] ${MEDIUM_WAIT}
Element Should Be Visible xpath=//button[contains(text(), "Add Employee")]
Click Add Employee Button
[Documentation] Opens employee creation form
Click Button xpath=//button[contains(text(), "Add Employee")]
Wait Until Element Is Visible xpath=//dialog | //div[@role="dialog"] ${MEDIUM_WAIT}
Enter Employee Name
[Arguments] ${name}
[Documentation] Enters employee name
Input Text xpath=//input[@placeholder="Employee Name"] ${name}
Enter Employee Email
[Arguments] ${email}
[Documentation] Enters employee email
Input Text xpath=//input[@placeholder="Email"] ${email}
Select Employee Department
[Arguments] ${department}
[Documentation] Selects department from dropdown
Click Element xpath=//select[@name="department"]
Click Element xpath=//select[@name="department"]//option[contains(text(), "${department}")]
Click Save Employee Button
[Documentation] Saves new employee
Click Button xpath=//button[contains(text(), "Save")]
Wait Until Element Is Visible xpath=//div[@class="toast success"] ${MEDIUM_WAIT}
Create New Employee
[Arguments] ${name} ${email} ${department}
[Documentation] Complete employee creation workflow
Employee Page Should Be Open
Click Add Employee Button
Enter Employee Name ${name}
Enter Employee Email ${email}
Select Employee Department ${department}
Click Save Employee Button
Employee Created Notification Should Be Visible
Employee Created Notification Should Be Visible
[Documentation] Verifies success message
Element Text Should Be xpath=//div[@class="toast success"] Employee created successfully
Search Employee By Email
[Arguments] ${email}
[Documentation] Searches for employee by email
Input Text xpath=//input[@placeholder="Search..."] ${email}
Wait Until Page Contains Element xpath=//table//tr[contains(., "${email}")] ${MEDIUM_WAIT}
Delete Employee
[Arguments] ${email}
[Documentation] Deletes an employee
Search Employee By Email ${email}
Click Element xpath=//table//tr[contains(., "${email}")]//button[@class="delete-btn"]
Wait Until Element Is Visible xpath=//button[contains(text(), "Confirm")] ${MEDIUM_WAIT}
Click Button xpath=//button[contains(text(), "Confirm")]
Wait Until Element Is Visible xpath=//div[@class="toast success"] ${MEDIUM_WAIT}
Dashboard Page Objectβ
resources/pages/dashboard_page.robotβ
*** Keywords ***
Dashboard Page Should Be Open
[Documentation] Verifies user is on dashboard
Wait Until Element Is Visible xpath=//h1[contains(text(), "Dashboard")] ${MEDIUM_WAIT}
Element Should Be Visible xpath=//a[contains(text(), "Employees")]
Element Should Be Visible xpath=//a[contains(text(), "Systems")]
Click Logout Button
[Documentation] Logs out user
Click Button xpath=//button[@aria-label="user menu"]
Click Element xpath=//button[contains(text(), "Logout")]
Wait Until Element Is Visible xpath=//input[@placeholder="Email"] ${MEDIUM_WAIT}
Navigate To Employees
[Documentation] Navigates to employees page
Click Element xpath=//a[contains(text(), "Employees")]
Wait Until Element Is Visible xpath=//h1[contains(text(), "Employees")] ${MEDIUM_WAIT}
Navigate To Systems
[Documentation] Navigates to systems page
Click Element xpath=//a[contains(text(), "Systems")]
Wait Until Element Is Visible xpath=//h1[contains(text(), "Systems")] ${MEDIUM_WAIT}
Check Employee Count
[Arguments] ${expected_count}
[Documentation] Verifies number of employees displayed
${count}= Get Element Count xpath=//table//tr[@class="employee-row"]
Should Be Equal ${count} ${expected_count}
Test Casesβ
Authentication Testsβ
tests/auth/login.robotβ
*** Settings ***
Documentation Authentication Tests
Resource ${CURDIR}/../../config/suite.robot
*** Test Cases ***
User Should Be Able To Login With Valid Credentials
[Documentation] Tests successful login
[Tags] smoke auth
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Dashboard Page Should Be Open
Close Browser
User Should See Error With Invalid Email
[Documentation] Tests login with invalid email format
[Tags] regression auth
Open Browser ${BASE_URL} ${BROWSER}
Login Page Should Be Open
Enter Email invalid-email
Enter Password ${VALID_USER_PASSWORD}
Click Login Button
Wait Until Element Is Visible xpath=//span[@class="error"][contains(text(), "Invalid email")] ${MEDIUM_WAIT}
Close Browser
User Should See Error With Wrong Password
[Documentation] Tests login with incorrect password
[Tags] smoke auth
Open Browser ${BASE_URL} ${BROWSER}
Login Should Fail With Invalid Credentials ${VALID_USER_EMAIL} wrongpassword
Close Browser
User Should See Error When Fields Are Empty
[Documentation] Tests login with empty fields
[Tags] regression auth
Open Browser ${BASE_URL} ${BROWSER}
Login Page Should Be Open
Click Login Button
Wait Until Element Is Visible xpath=//span[@class="error"] ${MEDIUM_WAIT}
Close Browser
User Should Be Able To Logout
[Documentation] Tests logout functionality
[Tags] smoke auth
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Click Logout Button
Login Page Should Be Open
Close Browser
Employee Management Testsβ
tests/employees/create_employee.robotβ
*** Settings ***
Documentation Employee Creation Tests
Resource ${CURDIR}/../../config/suite.robot
*** Test Cases ***
User Should Be Able To Create New Employee
[Documentation] Tests employee creation workflow
[Tags] smoke employee crud
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
Create New Employee ${EMPLOYEE_NAME} ${EMPLOYEE_EMAIL} ${EMPLOYEE_DEPT}
Close Browser
User Should See Error With Duplicate Email
[Documentation] Tests duplicate email validation
[Tags] regression employee
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
Create New Employee ${EMPLOYEE_NAME} ${EMPLOYEE_EMAIL} ${EMPLOYEE_DEPT}
[Teardown] Close Browser
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
Click Add Employee Button
Enter Employee Name Another Name
Enter Employee Email ${EMPLOYEE_EMAIL}
Select Employee Department ${EMPLOYEE_DEPT}
Click Save Employee Button
Wait Until Element Is Visible xpath=//div[@class="alert-error"] ${MEDIUM_WAIT}
Element Text Should Be xpath=//div[@class="alert-error"] Email already exists
Close Browser
User Should Be Able To Delete Employee
[Documentation] Tests employee deletion
[Tags] smoke employee crud
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
Create New Employee Delete Test delete.test@example.com ${EMPLOYEE_DEPT}
Delete Employee delete.test@example.com
Element Text Should Be xpath=//div[@class="toast success"] Employee deleted successfully
Close Browser
User Should Be Able To Search Employee
[Documentation] Tests employee search
[Tags] smoke employee
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
Search Employee By Email ${EMPLOYEE_EMAIL}
Element Should Be Visible xpath=//table//tr[contains(., "${EMPLOYEE_EMAIL}")]
Close Browser
Data-Driven Testsβ
tests/employees/bulk_create_employees.robotβ
*** Settings ***
Documentation Bulk Employee Creation Tests
Resource ${CURDIR}/../../config/suite.robot
*** Test Cases ***
Create Multiple Employees From CSV
[Documentation] Creates multiple employees from CSV data
[Tags] smoke employee bulk
Open Browser ${BASE_URL} ${BROWSER}
Login With Valid Credentials
Navigate To Employees
${employees}= Get CSV Data ${EMPLOYEES_CSV}
FOR ${employee} IN @{employees}
Create New Employee ${employee}[name] ${employee}[email] ${employee}[department]
Sleep 1s # Wait between requests
END
Close Browser
*** Keywords ***
Get CSV Data
[Arguments] ${csv_file}
[Documentation] Reads CSV file and returns list of dictionaries
${data}= Get File ${csv_file}
${lines}= Split To Lines ${data}
${headers}= Split String ${lines}[0] separator=,
${result}= Create List
FOR ${line} IN @{lines}[1:]
${values}= Split String ${line} separator=,
${row}= Create Dictionary
FOR ${i} ${header} IN ENUMERATE @{headers}
Set To Dictionary ${row} ${header.strip()} ${values}[${i}].strip()
END
Append To List ${result} ${row}
END
[Return] ${result}
Selenium Grid Setupβ
Docker Compose for Selenium Gridβ
docker-compose.selenium-grid.yamlβ
version: '3'
services:
selenium-hub:
image: selenium/hub:4.15.0
container_name: selenium-hub
ports:
- '4444:4444'
environment:
- SE_SESSION_REQUEST_TIMEOUT=300
- SE_NODE_MAX_SESSIONS=5
- SE_NODE_SESSION_TIMEOUT=300
chrome:
image: selenium/node-chrome:4.15.0
container_name: selenium-chrome
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=3
ports:
- '7900:7900' # VNC port to view browser
firefox:
image: selenium/node-firefox:4.15.0
container_name: selenium-firefox
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=3
edge:
image: selenium/node-edge:4.15.0
container_name: selenium-edge
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=3
Launch Selenium Gridβ
# Start grid
docker-compose -f docker-compose.selenium-grid.yaml up -d
# Access grid console
open http://localhost:4444
# Run tests against grid
robot --variable REMOTE_URL:http://localhost:4444 tests/
Running E2E Testsβ
Local Executionβ
# Run all tests
robot tests/
# Run specific suite
robot tests/auth/login.robot
# Run specific test
robot -t "User Should Be Able To Login With Valid Credentials" tests/
# Run tests with tag
robot --include smoke tests/
# Exclude tags
robot --exclude performance tests/
# Run with custom variables
robot --variable BROWSER:Firefox --variable BASE_URL:http://staging.example.com tests/
# Generate report
robot --output output.xml --report report.html --log log.html tests/
Parallel Executionβ
# Install parallel executor
pnpm add -D robotframework-pabot
# Run tests in parallel
pabot --processes 4 tests/
# Run with output directories
pabot --processes 4 --outputdir results tests/
CI/CD Integrationβ
GitHub Actions Workflowβ
name: E2E Tests
on: [push, pull_request]
jobs:
e2e-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r tests/e2e/requirements.txt
- name: Start dev server
run: |
cd apps/web && npm run build &
cd apps/api && npm start &
sleep 10
- name: Run E2E tests
run: |
robot --include smoke tests/e2e/
- name: Upload results
if: always()
uses: actions/upload-artifact@v3
with:
name: robot-reports
path: |
report.html
log.html
output.xml
Best Practicesβ
1. Use Page Object Modelβ
# β
GOOD: Centralized page interactions
Employee Page Should Be Open
Click Add Employee Button
# β AVOID: Scattered selectors
Click Element xpath=//button
2. Wait Explicitlyβ
# β
GOOD: Explicit waits
Wait Until Element Is Visible xpath=//button ${MEDIUM_WAIT}
# β AVOID: Implicit or sleep periods
Sleep 5s
3. Use Descriptive Test Namesβ
# β
GOOD
User Should Be Able To Create New Employee With Valid Data
# β AVOID
Test Create Employee
4. Test One Thing Per Testβ
# β
GOOD: Focused test
User Should See Validation Error For Duplicate Email
# β AVOID: Multiple assertions
Test Employee Creation And Deletion And Search
Debuggingβ
Enable Debug Outputβ
robot --loglevel DEBUG tests/
Take Screenshotsβ
*** Keywords ***
Take Screenshot On Failure
[Documentation] Captures screenshot
${timestamp}= Get Current Date result_format=%Y%m%d_%H%M%S
Capture Page Screenshot failure_${timestamp}.png