Developer Guide: Frontend Testing with Robot Framework + Selenium¶
This guide covers the installation, configuration, and usage of Robot Framework with SeleniumLibrary for testing the TQPro frontend applications, with specific focus on the tqweb-adm module.
Overview¶
Why Robot Framework?¶
Robot Framework is a keyword-driven test automation framework that provides:
- Readable syntax - Tests are written in plain English-like keywords
- No Java coding required - Uses Python-based libraries under the hood
- Rich ecosystem - SeleniumLibrary, Browser Library, API testing, and more
- Built-in reporting - Generates HTML reports and logs automatically
- Easy maintenance - Keyword abstraction makes tests resilient to UI changes
Testing Scope¶
Frontend tests in TQPro verify:
| Layer | What We Test | Approach |
|---|---|---|
| Component | Individual UI elements | Keyword tests |
| Integration | API calls via tlinq() | Selenium + real backend |
| E2E | Complete user workflows | Selenium + real backend |
Prerequisites¶
Before setting up Robot Framework testing:
- Python 3.8+ - Required for Robot Framework
- Chrome/Firefox browser - Target browser for testing
- Running TQPro backend - API server on port 11080
- Web server - Serving tqweb-adm (nginx or local dev server)
- TLINQ_HOME - Environment variable pointing to
config/directory
Installation¶
Step 1: Create Python Virtual Environment¶
# Navigate to project root
cd /path/to/tqpro
# Create virtual environment
python -m venv .venv
# Activate virtual environment
# On Linux/macOS:
source .venv/bin/activate
# On Windows:
.venv\Scripts\activate
Step 2: Install Robot Framework and Libraries¶
Create requirements-test.txt in the project root:
robotframework==7.1.1
robotframework-seleniumlibrary==6.6.1
robotframework-browser==18.9.1
webdrivermanager==0.10.0
selenium==4.25.0
Install dependencies:
pip install -r requirements-test.txt
# Initialize Browser Library (optional, for Playwright-based testing)
rfbrowser init
Step 3: Install WebDriver¶
SeleniumLibrary requires browser drivers. Use webdrivermanager to install:
# Install ChromeDriver
webdrivermanager chrome --linkpath /usr/local/bin
# Or install GeckoDriver for Firefox
webdrivermanager firefox --linkpath /usr/local/bin
Alternatively, let SeleniumLibrary manage drivers automatically (Selenium 4.6+).
Step 4: Create Test Directory Structure¶
tqpro/
├── requirements-test.txt
└── tests/
└── frontend/
├── resources/
│ ├── common.resource
│ ├── cruise_keywords.resource
│ └── variables.robot
├── page_objects/
│ ├── base_page.resource
│ ├── cruise_mgmt_page.resource
│ └── login_page.resource
├── cruise/
│ ├── company_tests.robot
│ ├── itinerary_tests.robot
│ ├── cruise_tests.robot
│ └── settings_tests.robot
└── smoke/
└── smoke_tests.robot
Configuration¶
Variables File¶
Create tests/frontend/resources/variables.robot:
*** Variables ***
# URLs
${BASE_URL} http://localhost:8080/tqweb-adm
${API_URL} http://localhost:11080/tlinq-api
# Browser Configuration
${BROWSER} chrome
${HEADLESS} ${FALSE}
# Timeouts
${IMPLICIT_WAIT} 10s
${EXPLICIT_WAIT} 30s
${PAGE_LOAD_TIMEOUT} 60s
# Test Data Prefixes
${TEST_PREFIX} RF_TEST_
# Test User
# For dev-mode testing (auth-mode=oauth2-proxy, dev-mode=true):
${TEST_USER} testuser
${TEST_ROLES} admin,agent
# For OIDC testing (auth-mode=native-oidc or hybrid):
# Tests must handle the Keycloak login page redirect.
# See "Testing with OIDC Authentication" section below.
# Cruise Test Data
${TEST_COMPANY_NAME} ${TEST_PREFIX}Cruise Company
${TEST_COMPANY_CODE} RFTC
${TEST_ITINERARY_NAME} ${TEST_PREFIX}Mediterranean Cruise
${TEST_ITINERARY_CODE} RFMC
${TEST_ITINERARY_DURATION} 7
Common Resource File¶
Create tests/frontend/resources/common.resource:
*** Settings ***
Library SeleniumLibrary
Library Collections
Library String
Resource variables.robot
*** Keywords ***
Open Browser To Application
[Arguments] ${page}=index.html
${options}= Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys
Run Keyword If ${HEADLESS} Call Method ${options} add_argument --headless=new
Call Method ${options} add_argument --window-size=1920,1080
Call Method ${options} add_argument --no-sandbox
Call Method ${options} add_argument --disable-dev-shm-usage
Open Browser ${BASE_URL}/${page} ${BROWSER} options=${options}
Set Selenium Implicit Wait ${IMPLICIT_WAIT}
Set Selenium Timeout ${EXPLICIT_WAIT}
Maximize Browser Window
Close Application
Close All Browsers
Wait For Page Load
Wait Until Element Is Visible css:body timeout=${PAGE_LOAD_TIMEOUT}
Wait For Ajax Complete
Wait For Condition return jQuery.active == 0 timeout=${EXPLICIT_WAIT}
Wait For Notification
[Arguments] ${expected_text}=${EMPTY}
Wait Until Element Is Visible css:.notifyjs-wrapper timeout=10s
Run Keyword If '${expected_text}' != '${EMPTY}'
... Element Should Contain css:.notifyjs-wrapper ${expected_text}
Wait For Foundation Dialog
[Arguments] ${dialog_id}
Wait Until Element Is Visible id:${dialog_id} timeout=${EXPLICIT_WAIT}
Wait Until Element Has Attribute id:${dialog_id} aria-hidden false
Close Foundation Dialog
[Arguments] ${dialog_id}
Click Element css:#${dialog_id} [data-close]
Wait Until Element Is Not Visible id:${dialog_id} timeout=${EXPLICIT_WAIT}
Element Should Have Class
[Arguments] ${locator} ${class_name}
${classes}= Get Element Attribute ${locator} class
Should Contain ${classes} ${class_name}
Capture Screenshot On Failure
Run Keyword If Test Failed Capture Page Screenshot
Page Object Resources¶
Base Page Resource¶
Create tests/frontend/page_objects/base_page.resource:
*** Settings ***
Library SeleniumLibrary
Resource ../resources/common.resource
*** Keywords ***
Input Text And Clear
[Arguments] ${locator} ${text}
Clear Element Text ${locator}
Input Text ${locator} ${text}
Select Dropdown Value
[Arguments] ${locator} ${value}
Click Element ${locator}
Wait Until Element Is Visible css:${locator} option[value="${value}"]
Select From List By Value ${locator} ${value}
Click And Wait For Ajax
[Arguments] ${locator}
Click Element ${locator}
Wait For Ajax Complete
Scroll To Element
[Arguments] ${locator}
Scroll Element Into View ${locator}
Sleep 0.3s # Brief pause for smooth scrolling
Get Table Row Count
[Arguments] ${table_locator}
${rows}= Get Element Count css:${table_locator} tbody tr
RETURN ${rows}
Table Should Contain Row With Text
[Arguments] ${table_locator} ${text}
Element Should Contain css:${table_locator} ${text}
Cruise Management Page Resource¶
Create tests/frontend/page_objects/cruise_mgmt_page.resource:
*** Settings ***
Library SeleniumLibrary
Resource base_page.resource
Resource ../resources/variables.robot
*** Variables ***
# Tree Panel Locators
${COMPANY_TREE} id:company_tree
${BTN_ADD_COMPANY} id:btn_new_company
${BTN_SETTINGS} id:btn_settings
${BTN_TOGGLE_INACTIVE} id:btn_toggle_inactive
# Company Dialog Locators
${DLG_COMPANY} id:dlg_company
${INPUT_COMPANY_NAME} id:company_name
${INPUT_COMPANY_CODE} id:company_code
${CHECKBOX_COMPANY_ACTIVE} id:company_active
${BTN_COMPANY_SAVE} id:company_save
# Itinerary Dialog Locators
${DLG_ITINERARY} id:dlg_itinerary
${INPUT_ITINERARY_NAME} id:itinerary_name
${INPUT_ITINERARY_CODE} id:itinerary_code
${INPUT_ITINERARY_DURATION} id:itinerary_duration
${SELECT_ITINERARY_SHIP} id:itinerary_ship
${BTN_ITINERARY_SAVE} id:itinerary_save
# Cruise Bar Locators
${CRUISE_BAR} id:cruise_bar
${BTN_ADD_CRUISE} id:btn_add_cruise
# Settings Dialog Locators
${DLG_SETTINGS} id:dlg_settings
${SETTINGS_TABS} id:settings_tabs
# Workspace Locators
${WORKSPACE_PANEL} id:workspace_panel
${ITINERARY_DETAIL} id:itinerary_detail
${INPUT_DETAIL_ITIN_NAME} id:detail_itinerary_name
*** Keywords ***
# Navigation
Open Cruise Management Page
Open Browser To Application cruisemgmt.html
Wait For Cruise Page Load
Wait For Cruise Page Load
Wait Until Element Is Visible ${COMPANY_TREE} timeout=${PAGE_LOAD_TIMEOUT}
Wait For Ajax Complete
# Company Operations
Click Add Company Button
Click Element ${BTN_ADD_COMPANY}
Wait For Foundation Dialog dlg_company
Fill Company Form
[Arguments] ${name} ${code} ${active}=${TRUE}
Input Text And Clear ${INPUT_COMPANY_NAME} ${name}
Input Text And Clear ${INPUT_COMPANY_CODE} ${code}
${is_checked}= Run Keyword And Return Status
... Checkbox Should Be Selected ${CHECKBOX_COMPANY_ACTIVE}
Run Keyword If ${active} != ${is_checked}
... Click Element ${CHECKBOX_COMPANY_ACTIVE}
Save Company
Click And Wait For Ajax ${BTN_COMPANY_SAVE}
Wait Until Element Is Not Visible ${DLG_COMPANY} timeout=${EXPLICIT_WAIT}
Create Company
[Arguments] ${name} ${code} ${active}=${TRUE}
Click Add Company Button
Fill Company Form ${name} ${code} ${active}
Save Company
Select Company In Tree
[Arguments] ${company_name}
${locator}= Set Variable xpath://div[contains(@class,'company-header')]//span[contains(text(),'${company_name}')]
Wait Until Element Is Visible ${locator}
Click Element ${locator}
Wait For Ajax Complete
Company Should Be In Tree
[Arguments] ${company_name}
Element Should Be Visible
... xpath://div[contains(@class,'company-header')]//span[contains(text(),'${company_name}')]
Company Should Not Be In Tree
[Arguments] ${company_name}
Element Should Not Be Visible
... xpath://div[contains(@class,'company-header')]//span[contains(text(),'${company_name}')]
Click Edit Company Button
[Arguments] ${company_name}
${locator}= Set Variable
... xpath://div[contains(@class,'company-header')][.//span[contains(text(),'${company_name}')]]//button[contains(@class,'btn-edit-company')]
Click Element ${locator}
Wait For Foundation Dialog dlg_company
Click Delete Company Button
[Arguments] ${company_name}
${locator}= Set Variable
... xpath://div[contains(@class,'company-header')][.//span[contains(text(),'${company_name}')]]//button[contains(@class,'btn-delete-company')]
Click Element ${locator}
Handle Alert action=ACCEPT
# Itinerary Operations
Click Add Itinerary Button
[Arguments] ${company_name}
${locator}= Set Variable
... xpath://div[contains(@class,'company-header')][.//span[contains(text(),'${company_name}')]]//button[contains(@class,'btn-add-itinerary')]
Click Element ${locator}
Wait For Foundation Dialog dlg_itinerary
Fill Itinerary Form
[Arguments] ${name} ${code} ${duration}
Input Text And Clear ${INPUT_ITINERARY_NAME} ${name}
Input Text And Clear ${INPUT_ITINERARY_CODE} ${code}
Input Text And Clear ${INPUT_ITINERARY_DURATION} ${duration}
Save Itinerary
Click And Wait For Ajax ${BTN_ITINERARY_SAVE}
Wait Until Element Is Not Visible ${DLG_ITINERARY} timeout=${EXPLICIT_WAIT}
Create Itinerary
[Arguments] ${company_name} ${itinerary_name} ${code} ${duration}
Click Add Itinerary Button ${company_name}
Fill Itinerary Form ${itinerary_name} ${code} ${duration}
Save Itinerary
Select Itinerary In Tree
[Arguments] ${itinerary_name}
${locator}= Set Variable xpath://div[contains(@class,'itinerary-item')]//span[contains(text(),'${itinerary_name}')]
Wait Until Element Is Visible ${locator}
Click Element ${locator}
Wait For Ajax Complete
Itinerary Should Be In Tree
[Arguments] ${company_name} ${itinerary_name}
Element Should Be Visible
... xpath://li[contains(@class,'company-node')][.//span[contains(text(),'${company_name}')]]//div[contains(@class,'itinerary-item')]//span[contains(text(),'${itinerary_name}')]
Get Itinerary Detail Name
${value}= Get Value ${INPUT_DETAIL_ITIN_NAME}
RETURN ${value}
# Settings Operations
Open Settings Dialog
Click Element ${BTN_SETTINGS}
Wait For Foundation Dialog dlg_settings
Select Settings Tab
[Arguments] ${tab_name}
${locator}= Set Variable xpath://div[@id='settings_tabs']//a[contains(text(),'${tab_name}')]
Click Element ${locator}
Sleep 0.3s # Allow tab content to load
Close Settings Dialog
Close Foundation Dialog dlg_settings
# Cruise Operations
Click Add Cruise Button
Click Element ${BTN_ADD_CRUISE}
# Wait for cruise dialog
Get Cruise Chip Count
${count}= Get Element Count css:#cruise_bar .cruise-chip
RETURN ${count}
Select Cruise By Date
[Arguments] ${date_label}
${locator}= Set Variable xpath://div[@id='cruise_bar']//div[contains(@class,'cruise-chip')][contains(text(),'${date_label}')]
Click Element ${locator}
Wait For Ajax Complete
# Toggle Operations
Toggle Inactive Items
Click Element ${BTN_TOGGLE_INACTIVE}
Wait For Ajax Complete
# Workspace Verification
Workspace Panel Should Be Visible
Element Should Be Visible ${WORKSPACE_PANEL}
Itinerary Detail Should Show
[Arguments] ${expected_name}
${actual_name}= Get Itinerary Detail Name
Should Be Equal ${actual_name} ${expected_name}
Test Suites¶
Smoke Tests¶
Create tests/frontend/smoke/smoke_tests.robot:
*** Settings ***
Documentation Smoke tests for Cruise Management frontend
Resource ../page_objects/cruise_mgmt_page.resource
Suite Setup Open Cruise Management Page
Suite Teardown Close Application
Test Teardown Capture Screenshot On Failure
*** Test Cases ***
Cruise Management Page Loads Successfully
[Documentation] Verify the cruise management page loads without errors
[Tags] smoke cruise
Workspace Panel Should Be Visible
Element Should Be Visible ${COMPANY_TREE}
Element Should Be Visible ${BTN_ADD_COMPANY}
Element Should Be Visible ${BTN_SETTINGS}
Settings Dialog Opens And Contains All Tabs
[Documentation] Verify settings dialog opens and shows all configuration tabs
[Tags] smoke cruise settings
Open Settings Dialog
Element Should Be Visible ${SETTINGS_TABS}
# Verify all tabs are present
Element Should Be Visible xpath://div[@id='settings_tabs']//a[contains(text(),'Areas')]
Element Should Be Visible xpath://div[@id='settings_tabs']//a[contains(text(),'Ports')]
Element Should Be Visible xpath://div[@id='settings_tabs']//a[contains(text(),'Cabin Types')]
Element Should Be Visible xpath://div[@id='settings_tabs']//a[contains(text(),'Charge Types')]
Element Should Be Visible xpath://div[@id='settings_tabs']//a[contains(text(),'Ships')]
Close Settings Dialog
Add Company Dialog Opens
[Documentation] Verify add company dialog can be opened
[Tags] smoke cruise company
Click Add Company Button
Element Should Be Visible ${INPUT_COMPANY_NAME}
Element Should Be Visible ${INPUT_COMPANY_CODE}
Element Should Be Visible ${CHECKBOX_COMPANY_ACTIVE}
Element Should Be Visible ${BTN_COMPANY_SAVE}
Close Foundation Dialog dlg_company
Company Tests¶
Create tests/frontend/cruise/company_tests.robot:
*** Settings ***
Documentation Tests for Cruise Company CRUD operations
Resource ../page_objects/cruise_mgmt_page.resource
Suite Setup Open Cruise Management Page
Suite Teardown Close Application
Test Setup Log Starting test: ${TEST_NAME}
Test Teardown Run Keywords Capture Screenshot On Failure AND Cleanup Test Company
*** Variables ***
${CURRENT_TEST_COMPANY} ${EMPTY}
*** Test Cases ***
Create New Company Successfully
[Documentation] Verify a new cruise company can be created
[Tags] cruise company create
${company_name}= Set Variable ${TEST_PREFIX}New Company
Set Suite Variable ${CURRENT_TEST_COMPANY} ${company_name}
Create Company ${company_name} NWC1 ${TRUE}
Company Should Be In Tree ${company_name}
Wait For Notification success
Create Company With Minimum Fields
[Documentation] Verify company can be created with only required fields
[Tags] cruise company create
${company_name}= Set Variable ${TEST_PREFIX}Minimal Co
Set Suite Variable ${CURRENT_TEST_COMPANY} ${company_name}
Click Add Company Button
Fill Company Form ${company_name} MIN1
Save Company
Company Should Be In Tree ${company_name}
Edit Existing Company
[Documentation] Verify an existing company can be edited
[Tags] cruise company update
# First create a company
${original_name}= Set Variable ${TEST_PREFIX}Original
Create Company ${original_name} ORI1
# Edit the company
${updated_name}= Set Variable ${TEST_PREFIX}Updated
Set Suite Variable ${CURRENT_TEST_COMPANY} ${updated_name}
Click Edit Company Button ${original_name}
Fill Company Form ${updated_name} UPD1
Save Company
# Verify changes
Company Should Not Be In Tree ${original_name}
Company Should Be In Tree ${updated_name}
Delete Company
[Documentation] Verify a company can be deleted
[Tags] cruise company delete
${company_name}= Set Variable ${TEST_PREFIX}ToDelete
Create Company ${company_name} DEL1
Company Should Be In Tree ${company_name}
Click Delete Company Button ${company_name}
Company Should Not Be In Tree ${company_name}
Set Suite Variable ${CURRENT_TEST_COMPANY} ${EMPTY}
Select Company Highlights In Tree
[Documentation] Verify selecting a company highlights it in the tree
[Tags] cruise company select
${company_name}= Set Variable ${TEST_PREFIX}Selectable
Set Suite Variable ${CURRENT_TEST_COMPANY} ${company_name}
Create Company ${company_name} SEL1
Select Company In Tree ${company_name}
${locator}= Set Variable xpath://div[contains(@class,'company-header')][.//span[contains(text(),'${company_name}')]]
Element Should Have Class ${locator} selected
*** Keywords ***
Cleanup Test Company
[Documentation] Clean up any test company created during the test
Run Keyword If '${CURRENT_TEST_COMPANY}' != '${EMPTY}'
... Run Keyword And Ignore Error Delete Company If Exists ${CURRENT_TEST_COMPANY}
Delete Company If Exists
[Arguments] ${company_name}
${exists}= Run Keyword And Return Status Company Should Be In Tree ${company_name}
Run Keyword If ${exists} Click Delete Company Button ${company_name}
Itinerary Tests¶
Create tests/frontend/cruise/itinerary_tests.robot:
*** Settings ***
Documentation Tests for Cruise Itinerary CRUD operations
Resource ../page_objects/cruise_mgmt_page.resource
Suite Setup Setup Test Company
Suite Teardown Run Keywords Cleanup Test Company AND Close Application
Test Teardown Capture Screenshot On Failure
*** Variables ***
${SUITE_COMPANY} ${TEST_PREFIX}Itinerary Test Company
*** Test Cases ***
Create New Itinerary Successfully
[Documentation] Verify a new itinerary can be created under a company
[Tags] cruise itinerary create
${itinerary_name}= Set Variable ${TEST_PREFIX}Caribbean 7-Day
Create Itinerary ${SUITE_COMPANY} ${itinerary_name} CAR7 7
Itinerary Should Be In Tree ${SUITE_COMPANY} ${itinerary_name}
Create Multiple Itineraries For Same Company
[Documentation] Verify multiple itineraries can be created for one company
[Tags] cruise itinerary create
Create Itinerary ${SUITE_COMPANY} ${TEST_PREFIX}Med Cruise MED5 5
Create Itinerary ${SUITE_COMPANY} ${TEST_PREFIX}Baltic Tour BAL10 10
Itinerary Should Be In Tree ${SUITE_COMPANY} ${TEST_PREFIX}Med Cruise
Itinerary Should Be In Tree ${SUITE_COMPANY} ${TEST_PREFIX}Baltic Tour
Select Itinerary Loads Detail Panel
[Documentation] Verify selecting an itinerary loads its details in the workspace
[Tags] cruise itinerary select
${itinerary_name}= Set Variable ${TEST_PREFIX}Detail Test
Create Itinerary ${SUITE_COMPANY} ${itinerary_name} DTL1 3
Select Company In Tree ${SUITE_COMPANY}
Select Itinerary In Tree ${itinerary_name}
Itinerary Detail Should Show ${itinerary_name}
*** Keywords ***
Setup Test Company
Open Cruise Management Page
Create Company ${SUITE_COMPANY} ITCO ${TRUE}
Cleanup Test Company
Run Keyword And Ignore Error Click Delete Company Button ${SUITE_COMPANY}
Settings Tests¶
Create tests/frontend/cruise/settings_tests.robot:
*** Settings ***
Documentation Tests for Cruise Settings (dimensional data management)
Resource ../page_objects/cruise_mgmt_page.resource
Suite Setup Open Cruise Management Page
Suite Teardown Close Application
Test Teardown Run Keywords Capture Screenshot On Failure AND Close Settings If Open
*** Test Cases ***
Navigate Between Settings Tabs
[Documentation] Verify all settings tabs are accessible
[Tags] cruise settings navigation
Open Settings Dialog
Select Settings Tab Areas
Wait Until Element Is Visible id:areas_table
Select Settings Tab Ports
Wait Until Element Is Visible id:ports_table
Select Settings Tab Cabin Types
Wait Until Element Is Visible id:cabin_types_table
Select Settings Tab Charge Types
Wait Until Element Is Visible id:charge_types_table
Select Settings Tab Ships
Wait Until Element Is Visible id:ships_table
Close Settings Dialog
Add New Area
[Documentation] Verify a new geographic area can be added
[Tags] cruise settings areas create
Open Settings Dialog
Select Settings Tab Areas
Click Element id:btn_add_area
Wait Until Element Is Visible id:area_name
Input Text id:area_name ${TEST_PREFIX}Caribbean
Input Text id:area_code TCAR
Click Element id:area_save
Wait For Ajax Complete
Table Should Contain Row With Text id:areas_table ${TEST_PREFIX}Caribbean
Close Settings Dialog
Add New Port
[Documentation] Verify a new port can be added
[Tags] cruise settings ports create
Open Settings Dialog
Select Settings Tab Ports
Click Element id:btn_add_port
Wait Until Element Is Visible id:port_name
Input Text id:port_name ${TEST_PREFIX}Miami
Input Text id:port_code TMIA
# Select area if required
Click Element id:port_save
Wait For Ajax Complete
Table Should Contain Row With Text id:ports_table ${TEST_PREFIX}Miami
Close Settings Dialog
Add New Cabin Type
[Documentation] Verify a new cabin type can be added
[Tags] cruise settings cabins create
Open Settings Dialog
Select Settings Tab Cabin Types
Click Element id:btn_add_cabin_type
Wait Until Element Is Visible id:cabin_type_name
Input Text id:cabin_type_name ${TEST_PREFIX}Balcony Suite
Input Text id:cabin_type_code TBAL
Click Element id:cabin_type_save
Wait For Ajax Complete
Table Should Contain Row With Text id:cabin_types_table ${TEST_PREFIX}Balcony Suite
Close Settings Dialog
*** Keywords ***
Close Settings If Open
${is_open}= Run Keyword And Return Status
... Element Should Be Visible ${DLG_SETTINGS}
Run Keyword If ${is_open} Close Settings Dialog
Running Tests¶
Command Line Execution¶
Run all frontend tests:
# Activate virtual environment first
source .venv/bin/activate # or .venv\Scripts\activate on Windows
# Run all tests
robot --outputdir results tests/frontend/
# Run with specific browser
robot --variable BROWSER:firefox --outputdir results tests/frontend/
# Run in headless mode
robot --variable HEADLESS:True --outputdir results tests/frontend/
# Run specific test suite
robot --outputdir results tests/frontend/cruise/company_tests.robot
# Run tests by tag
robot --include smoke --outputdir results tests/frontend/
robot --include cruise --exclude slow --outputdir results tests/frontend/
# Run single test case
robot --test "Create New Company Successfully" --outputdir results tests/frontend/
Custom Configuration¶
Run with custom URLs:
robot \
--variable BASE_URL:http://staging.example.com/tqweb-adm \
--variable API_URL:http://staging.example.com/tlinq-api \
--outputdir results \
tests/frontend/
Parallel Execution¶
Install pabot for parallel execution:
pip install robotframework-pabot
# Run tests in parallel (4 processes)
pabot --processes 4 --outputdir results tests/frontend/
# Run test suites in parallel
pabot --processes 4 --testlevelsplit --outputdir results tests/frontend/cruise/
Gradle Integration¶
Add to build.gradle.kts (root):
tasks.register<Exec>("robotTests") {
description = "Run Robot Framework frontend tests"
group = "verification"
workingDir = projectDir
// Activate virtual environment and run tests
commandLine(
if (System.getProperty("os.name").lowercase().contains("windows"))
listOf("cmd", "/c", ".venv\\Scripts\\robot")
else
listOf(".venv/bin/robot"),
"--outputdir", "build/robot-results",
"--variable", "HEADLESS:true",
"tests/frontend/"
)
}
tasks.register<Exec>("robotSmoke") {
description = "Run Robot Framework smoke tests"
group = "verification"
workingDir = projectDir
commandLine(
if (System.getProperty("os.name").lowercase().contains("windows"))
listOf("cmd", "/c", ".venv\\Scripts\\robot")
else
listOf(".venv/bin/robot"),
"--include", "smoke",
"--outputdir", "build/robot-results",
"tests/frontend/"
)
}
Run via Gradle:
Test Environment Setup¶
Option 1: Local Development Setup¶
-
Start the API server:
-
Start a local web server for tqweb-adm:
-
Enable dev-mode in
config/tlinqapi.properties: -
Run tests:
Option 2: Docker-Based Setup¶
Create docker-compose.test.yml:
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: tlinq
POSTGRES_USER: tlinq
POSTGRES_PASSWORD: tlinq
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U tlinq"]
interval: 5s
timeout: 5s
retries: 5
tqapi:
build:
context: .
dockerfile: Dockerfile.tqapi
environment:
TLINQ_HOME: /app/config
ports:
- "11080:11080"
depends_on:
postgres:
condition: service_healthy
tqweb:
image: nginx:alpine
volumes:
- ./tqweb-adm:/usr/share/nginx/html:ro
ports:
- "8080:80"
selenium-chrome:
image: selenium/standalone-chrome:latest
ports:
- "4444:4444"
- "7900:7900" # noVNC for debugging
shm_size: 2gb
Run tests against Docker environment:
docker-compose -f docker-compose.test.yml up -d
# Wait for services to be ready
sleep 10
# Run tests with remote WebDriver
robot \
--variable BASE_URL:http://tqweb:80 \
--variable SELENIUM_URL:http://localhost:4444/wd/hub \
--outputdir results \
tests/frontend/
Option 3: CI/CD Integration (TeamCity)¶
Add to TeamCity build configuration:
<build-runner type="simpleRunner">
<parameters>
<param name="command.executable" value="robot"/>
<param name="command.parameters" value="
--variable BASE_URL:%env.TEST_BASE_URL%
--variable HEADLESS:true
--include %env.TEST_TAGS%
--outputdir build/robot-results
tests/frontend/
"/>
</parameters>
</build-runner>
Best Practices¶
1. Keyword Design¶
# Good: Descriptive, action-oriented keywords
Create Company With Name And Code
[Arguments] ${name} ${code}
Click Add Company Button
Fill Company Form ${name} ${code}
Save Company
# Good: Verification keywords start with assertion words
Company Should Exist In Tree
[Arguments] ${company_name}
Element Should Be Visible xpath://span[text()='${company_name}']
# Avoid: Vague or implementation-focused names
Do Company Stuff # Bad
Click Button 3 # Bad
2. Locator Strategy Priority¶
# Best: ID-based locators
${BTN_SAVE} id:company_save
# Good: CSS selectors for structure
${SELECTED_COMPANY} css:.company-header.selected
# Acceptable: XPath for complex relationships
${COMPANY_EDIT_BTN} xpath://div[@class='company-header'][.//span[text()='${name}']]//button[@class='edit']
# Avoid: Fragile positional XPath
${BAD_LOCATOR} xpath:/html/body/div[3]/div[2]/button[1]
3. Test Data Management¶
*** Variables ***
# Use prefixes for test data identification
${TEST_PREFIX} ROBOT_TEST_
*** Test Cases ***
Create Test Data
${unique_name}= Set Variable ${TEST_PREFIX}${TEST_NAME}_${SUITE_NAME}
# Use unique_name for test data
*** Keywords ***
Cleanup All Test Data
[Documentation] Remove all data created by Robot Framework tests
# Find and delete all entities starting with ${TEST_PREFIX}
4. Wait Strategies¶
*** Keywords ***
# Prefer explicit waits
Wait For Company To Appear
[Arguments] ${company_name}
Wait Until Element Is Visible
... xpath://span[text()='${company_name}']
... timeout=30s
# Wait for AJAX completion
Wait For Data Load
Wait For Condition return jQuery.active == 0 timeout=30s
# Wait for Foundation dialog animation
Wait For Dialog
[Arguments] ${dialog_id}
Wait Until Element Is Visible id:${dialog_id}
Wait Until Element Has Attribute id:${dialog_id} aria-hidden false
Sleep 0.3s # Allow animation to complete
5. Error Handling¶
*** Keywords ***
Safe Delete Company
[Arguments] ${company_name}
${exists}= Run Keyword And Return Status
... Company Should Be In Tree ${company_name}
Run Keyword If ${exists}
... Run Keyword And Ignore Error Click Delete Company Button ${company_name}
Reports and Debugging¶
Generated Reports¶
Robot Framework automatically generates:
output.xml- Machine-readable test resultslog.html- Detailed execution log with screenshotsreport.html- High-level test summary
Viewing Reports¶
# Open report in browser
open results/report.html
# Combine multiple output files
rebot --output combined.xml results/output*.xml
Debug Mode¶
# Run with debug logging
robot --loglevel DEBUG --outputdir results tests/frontend/
# Run single test interactively (stops on failure)
robot --exitonfailure --outputdir results tests/frontend/smoke/
Screenshots¶
*** Keywords ***
Capture State On Failure
Run Keyword If Test Failed Capture Page Screenshot
Run Keyword If Test Failed Log Source
# In test teardown
Test Teardown Capture State On Failure
Troubleshooting¶
Common Issues¶
| Issue | Solution |
|---|---|
ElementNotFound |
Increase wait time or verify locator |
StaleElementReference |
Re-locate element after page update |
ElementClickIntercepted |
Scroll to element or wait for overlay |
WebDriverException |
Check browser/driver version compatibility |
TimeoutException |
Verify page is loading, increase timeout |
WebDriver Issues¶
# Update WebDriver
webdrivermanager chrome --linkpath /usr/local/bin
# Check Chrome version
google-chrome --version
chromedriver --version
Debugging Tips¶
- Use
Pause Executionkeyword to stop and inspect - Set
--loglevel DEBUGfor verbose output - View noVNC at http://localhost:7900 for Docker Selenium
- Check
log.htmlfor step-by-step execution details
Testing with OIDC Authentication (TQ-51)¶
Since TQ-51, the admin site uses native OIDC authentication via Keycloak. This affects frontend testing:
Option 1: Dev-Mode (Recommended for Automated Tests)¶
Set dev-mode=true and dev-user-roles=admin,agent in tlinqapi.properties. This bypasses OIDC authentication entirely, allowing tests to run without a Keycloak instance.
Option 2: OIDC-Aware Tests¶
For testing the actual OIDC flow, your Robot Framework tests must handle the Keycloak login redirect:
*** Keywords ***
Login Via Keycloak
[Arguments] ${username} ${password}
# Page will redirect to Keycloak login
Wait Until Element Is Visible id:username timeout=15s
Input Text id:username ${username}
Input Text id:password ${password}
Click Button id:kc-login
# Wait for redirect back to application
Wait Until Location Contains callback.html timeout=15s
# Wait for token exchange to complete and redirect to original page
Wait For Page Load
Key Considerations¶
- Token expiry: Long-running test suites may encounter token expiration. The silent renewal iframe (
silent-renew.html) handles this automatically in the browser, but tests should verify that API calls continue to work after extended pauses. - Logout testing: Logout now redirects to Keycloak's end-session endpoint. Verify that
logout.htmlcorrectly clears the OIDC session. - Multi-page navigation: Each page calls
setGlobalHandlers({ requireAuth: true }), which checks for a valid token. Ensure the token persists across page navigation in test scenarios.