Robot Framework Guide — PerfShop¶
This guide teaches you how to write Robot Framework scripts to test PerfShop in the context of the pedagogical QA stack (Squash TM + Orchestrator + Test Runner).
Available infrastructure¶
Your script runs in the perfshop-test-runner container, launched by the Squash orchestrator.
This container ships with the following tools:
Installed Robot Framework libraries¶
| Library | Use |
|---|---|
RequestsLibrary |
HTTP API testing |
SeleniumLibrary |
Browser testing (Chrome) |
JsonLibrary |
JSON manipulation |
Collections |
List/dict manipulation |
OperatingSystem |
File reading, env variables |
BuiltIn |
Native RF keywords (always available) |
Available system tools¶
| Tool | Version |
|---|---|
| Python | 3.11 |
| robot | 7.0 |
| pytest | 8.0 |
| git | installed (auto-clone by the orchestrator) |
| Chrome | via Selenium Grid (perfshop-selenium) |
URLs available in your scripts¶
URLs are available via the container's environment variables. You can hardcode them — it's perfectly fine when starting out.
Recommended URLs¶
| Target | Internal Docker URL | Public URL (NAS prod) |
|---|---|---|
| Backend API | http://perfshop-app:8080 |
https://perfshop-api.perfshop.io |
| Frontend | http://perfshop-frontend |
https://perfshop.perfshop.io |
| Selenium Grid | http://perfshop-selenium:4444/wd/hub |
— (internal only) |
💡 Locally (Docker Desktop), use the internal Docker URLs. On the production NAS, the instructor configures the public URLs automatically.
Read the URL from the environment (optional, advanced)¶
*** Test Cases ***
My Test
${api_url}= Evaluate os.environ.get('PUBLIC_API_URL', 'http://perfshop-app:8080') modules=os
Log API URL: ${api_url}
Minimal script structure¶
*** Settings ***
Library RequestsLibrary
*** Variables ***
${API_URL} http://perfshop-app:8080
*** Test Cases ***
My first PerfShop test
[Documentation] Test description
[Tags] smoke api
Create Session perfshop ${API_URL}
${resp}= GET On Session perfshop url=/api/products params=page=0&size=5
Should Be Equal As Integers ${resp.status_code} 200
Log Response: ${resp.text}
API test examples¶
Check that the API responds¶
*** Settings ***
Library RequestsLibrary
*** Variables ***
${API_URL} http://perfshop-app:8080
*** Test Cases ***
CT-API-01 Products API responds
[Tags] smoke api
Create Session perfshop ${API_URL}
${resp}= GET On Session perfshop url=/api/products params=page=0&size=1
Should Be Equal As Integers ${resp.status_code} 200
${body}= Convert To String ${resp.content}
Should Contain ${body} price
Test authentication¶
*** Settings ***
Library RequestsLibrary
Library Collections
*** Variables ***
${API_URL} http://perfshop-app:8080
${USER_EMAIL} user1@perfshop.com
${USER_PASS} password1
*** Test Cases ***
CT-AUTH-01 User login
[Tags] auth
Create Session perfshop ${API_URL}
${payload}= Create Dictionary email=${USER_EMAIL} password=${USER_PASS}
${resp}= POST On Session perfshop url=/api/auth/login json=${payload}
Should Be Equal As Integers ${resp.status_code} 200
${body}= Set Variable ${resp.json()}
Should Be True ${body}[success]
Log Login OK for ${USER_EMAIL}
Test adding to the cart¶
*** Settings ***
Library RequestsLibrary
Library Collections
*** Variables ***
${API_URL} http://perfshop-app:8080
${USER_EMAIL} user1@perfshop.com
${USER_PASS} password1
*** Test Cases ***
CT-CART-01 Add product to cart
[Tags] cart functional
Create Session perfshop ${API_URL}
# Login
${creds}= Create Dictionary email=${USER_EMAIL} password=${USER_PASS}
${login}= POST On Session perfshop url=/api/auth/login json=${creds}
Should Be Equal As Integers ${login.status_code} 200
# Add to cart (no authentication required)
${item}= Create Dictionary productId=${1} quantity=${1}
${resp}= POST On Session perfshop url=/api/cart/add json=${item}
Should Be Equal As Integers ${resp.status_code} 200
Log Product added to cart
Check a specific price¶
*** Settings ***
Library RequestsLibrary
*** Variables ***
${API_URL} http://perfshop-app:8080
*** Test Cases ***
CT-PRICE-01 Product 1 price consistent
[Tags] price regression
Create Session api ${API_URL}
${resp}= GET On Session api url=/api/products/1
Should Be Equal As Integers ${resp.status_code} 200
${product}= Set Variable ${resp.json()}
Should Be True ${product}[price] > 0
Log Product 1 price: ${product}[price] €
Selenium (browser) test examples¶
⚠️ Selenium tests require the
perfshop-seleniumcontainer to be started. The instructor handles this — you have nothing to configure.
Open the products page and check the loading¶
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${SELENIUM_URL} http://perfshop-selenium:4444/wd/hub
${FRONTEND_URL} http://perfshop-frontend
*** Test Cases ***
CT-UI-01 Products page loads correctly
[Tags] ui selenium
Open Browser ${FRONTEND_URL}/products browser=chrome
... remote_url=${SELENIUM_URL}
... options=add_argument("--no-sandbox");add_argument("--disable-dev-shm-usage")
Wait Until Page Contains Product Catalog timeout=15s
Page Should Contain product
Close All Browsers
Log in via the interface¶
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${SELENIUM_URL} http://perfshop-selenium:4444/wd/hub
${FRONTEND_URL} http://perfshop-frontend
*** Test Cases ***
CT-UI-02 User login
[Tags] ui auth
Open Browser ${FRONTEND_URL}/login browser=chrome
... remote_url=${SELENIUM_URL}
... options=add_argument("--no-sandbox");add_argument("--disable-dev-shm-usage")
Input Text css:input[type="email"] user1@perfshop.com
Input Text css:input[type="password"] password1
Click Button css:button[type="submit"]
Wait Until Page Contains Catalog timeout=10s
Close All Browsers
Prerequisites to know about PerfShop¶
1. The Selenium Grid is internal¶
The Chrome browser runs inside the perfshop-selenium container, not on your machine.
→ Always use remote_url=http://perfshop-selenium:4444/wd/hub
2. The frontend is a React SPA¶
The React page loads data after the initial render.
→ Always use Wait Until Element Is Visible or Wait Until Page Contains
→ Never use Get Text immediately after Open Browser
# Bad — too fast
Open Browser ${FRONTEND_URL}/products browser=chrome remote_url=${SELENIUM_URL}
${text}= Get Text css:.product-card # FAIL: not loaded yet
# Correct
Open Browser ${FRONTEND_URL}/products browser=chrome remote_url=${SELENIUM_URL}
Wait Until Page Contains Product Catalog timeout=15s
Sleep 1s # let React finish rendering
${text}= Get Text css:.product-name
3. The cart lives in React memory¶
The PerfShop cart is a local React state (no localStorage).
→ If you navigate with Go To (page reload), the cart is emptied
→ Use the React navbar link to preserve the cart
# Bad — reloads the React app and empties the cart
Go To ${FRONTEND_URL}/cart
# Correct — SPA navigation (preserves React state)
Click Element xpath=//a[contains(text(),'Cart')]
4. The cart API does not require authentication¶
5. Available test accounts¶
| Password | |
|---|---|
user1@perfshop.com |
password1 |
user2@perfshop.com |
password2 |
| ... | ... |
user10@perfshop.com |
password10 |
perf.test1@perfshop.com |
TestPerf123! |
Upload and run your script¶
Option 1 — Via Filebrowser (recommended)¶
- Open Filebrowser (URL provided by the instructor)
- Drop your
.robotfile into the/scriptsfolder - In Squash TM → create a test case with the reference
perfshop-tests/your-script.robot
Option 2 — Via Git¶
- Clone the
perfshop-testsrepository from the PerfShop GitLab - Add your script at the repository root
- Commit and push to the
mainbranch - Squash TM will automatically pick up the new script
Option 3 — Direct test (without Squash TM)¶
From the NAS or via SSH on the instructor's machine:
Recommended naming conventions¶
| Item | Convention | Example |
|---|---|---|
| File | CT-XXX-short-name.robot |
CT-003-auth-login.robot |
| Test name | Plain description | CT-003 User authentication |
| Tags | lowercase, no spaces | smoke, api, ui, auth, cart |
| Variables | UPPERCASE | ${API_URL}, ${TIMEOUT} |
| Custom keywords | Verb + Noun | Verify Product Price, Log In |
Common errors¶
Element not found after Xs¶
The element is not visible yet — React loads data asynchronously.
→ Increase the timeout: Wait Until Element Is Visible css:.my-element timeout=20s
Connection refused on the API¶
The API URL is incorrect or the perfshop-app container is not started.
→ Check with the instructor that the containers are up
Session does not exist¶
The Chrome browser crashed or the Selenium session expired.
→ Always use [Teardown] Close All Browsers in your tests
StaleElementReferenceException¶
React re-rendered the DOM between two Selenium actions.
→ Add Sleep 1s or re-fetch the element after each slow action
Ready-to-use full template¶
*** Settings ***
# My PerfShop test
# Squash TM reference: perfshop-tests/CT-XXX-my-test.robot
Library RequestsLibrary
Library Collections
*** Variables ***
${API_URL} http://perfshop-app:8080
*** Test Cases ***
CT-XXX My test
[Documentation] Description of what this script tests
[Tags] my-tag api
# Step 1 - preparation
Create Session perfshop ${API_URL}
# Step 2 - action
${resp}= GET On Session perfshop url=/api/products params=page=0&size=1
# Step 3 - verification
Should Be Equal As Integers ${resp.status_code} 200
Log Test OK
Resources¶
- Official Robot Framework documentation
- RequestsLibrary
- SeleniumLibrary
- Scripts repository:
https://perfshop-gitlab.perfshop.io/perf/perfshop-tests
Selector library lib/PerfShopSelectors.py¶
Why this library exists¶
Selenium tests need XPath/CSS selectors to find elements in the React DOM. Since the PerfShop frontend is in a stable, deliverable state, all selectors have been centralised in a single library file — students don't need to write or look them up.
scripts/
├── lib/ ← Library (do not modify)
│ ├── PerfShopSelectors.py ← 174 selectors, FR + EN
│ └── i18n/
│ ├── fr.json ← FR texts
│ └── en.json ← EN texts
├── CT-002-prix-coherence.robot
├── CT-011-parcours-metier-ui.robot
└── ...
Using the library in a .robot script¶
*** Settings ***
Library SeleniumLibrary
Variables ${CURDIR}/lib/PerfShopSelectors.py
*** Test Cases ***
My UI test
Open Browser http://perfshop-frontend/products browser=chrome
... remote_url=http://perfshop-selenium:4444/wd/hub
Wait Until Element Is Visible ${NAV_CART} timeout=15s
Click Element ${ADD_TO_CART_FIRST}
Click Element ${CART_LINK}
Wait Until Element Is Visible ${CART_ITEM} timeout=10s
Available variables by page¶
| Section | Key variables |
|---|---|
| Navbar | ${NAV_CART}, ${NAV_LOGIN}, ${NAV_LOGOUT}, ${NAV_ORDERS} |
| Catalogue | ${PRICE_STYLE_XPATH}, ${ADD_TO_CART_FIRST}, ${SEARCH_INPUT} |
| Cart | ${CART_ITEM}, ${CART_ITEM_PRICE}, ${CART_CHECKOUT_BTN} |
| Login | ${LOGIN_EMAIL_INPUT}, ${LOGIN_PWD_INPUT}, ${LOGIN_SUBMIT_BTN} |
| Checkout | ${CHECKOUT_STREET}, ${CHECKOUT_NEXT_BTN}, ${CONFIRM_ORDER_BTN} |
| Shipping | ${SHIPPING_STANDARD}, ${SHIPPING_EXPRESS}, ${SHIPPING_PREMIUM} |
| Payment | ${PAYMENT_CARDNUMBER}, ${PAYMENT_CVV}, ${PAYMENT_MONTH_SEL} |
| My Orders | ${ORDERS_PAGE_TITLE}, ${ORDERS_FIRST_ROW}, ${MODAL_CLOSE_BTN} |
| Profile | ${PROFILE_EDIT_BTN}, ${PROFILE_SAVE_BTN}, ${PROFILE_INPUT_FIRSTNAME} |
Prefix convention
Each variable already contains its prefix — use it directly without adding another:
Click Element ${CART_LINK} ← correct (already contains "css:")
Click Element css:${CART_LINK} ← incorrect (double prefix)
${PRICE_STYLE_XPATH}, ${ADD_TO_CART_FIRST},
${ORDER_NUMBER_TEXT}) require an explicit xpath=:
Using selectors in Python (pytest)¶
import sys
sys.path.insert(0, '/scripts')
from lib.PerfShopSelectors import SEL, css, xpath
from selenium.webdriver.common.by import By
# css() strips the "css:" prefix for By.CSS_SELECTOR
driver.find_element(By.CSS_SELECTOR, css(SEL.CART_LINK))
# xpath() strips the "xpath=" prefix for By.XPATH
driver.find_element(By.XPATH, xpath(SEL.ORDERS_FIRST_ROW))
# RAW XPath variables are used directly with By.XPATH
driver.find_element(By.XPATH, SEL.PRICE_STYLE_XPATH)
FR / EN support¶
Text-based selectors automatically adapt to the PERFSHOP_LANG environment variable.
PERFSHOP_LANG=fr → ADD_TO_CART_FIRST = (//button[contains(text(),'Ajouter au panier')])[1]
PERFSHOP_LANG=en → ADD_TO_CART_FIRST = (//button[contains(text(),'Add to cart')])[1]
PerfShop-specific pitfalls¶
❌ Pitfall 1 — Go To empties the cart¶
The cart is React useState — it is lost on every full page reload.
# ❌ BAD — reloads React, cart lost
Go To http://perfshop-frontend/login
# ✅ CORRECT — SPA navigation, cart preserved
Click Element ${NAV_LOGIN}
Rule: always navigate by clicking navbar links between PerfShop pages.
❌ Pitfall 2 — Pre-filled fields from profile¶
The checkout pre-fills the address from the user profile.
Input Text appends to existing content if the field is not cleared first.
# ❌ BAD — appends to existing content
Input Text ${CHECKOUT_STREET} 12 rue de la Performance
# ✅ CORRECT — clear before typing
Press Keys ${CHECKOUT_STREET} CTRL+a
Input Text ${CHECKOUT_STREET} 12 rue de la Performance
❌ Pitfall 3 — Clicking under an open modal¶
When a modal is open, its z-index: 1000 overlay blocks all clicks.
Selenium raises ElementClickInterceptedException.
# ❌ BAD — modal open, logout blocked
Click Element ${NAV_LOGOUT}
# ✅ CORRECT — close modal, wait for it to disappear, then act
Click Element ${MODAL_CLOSE_BTN}
Wait Until Element Is Not Visible ${MODAL_DETAIL} timeout=10s
Click Element ${NAV_LOGOUT}
❌ Pitfall 4 — Hex colours vs rgb() in selectors¶
React injects direct colours (background, color) as rgb() in the DOM.
# ❌ BAD — React generates rgb(), not hex
xpath=//button[contains(@style,'#10b981')]
# ✅ CORRECT — use rgb()
xpath=//button[contains(@style,'rgb(16, 185, 129)')]
# ✅ EVEN BETTER — button text, invariant to CSS rendering
xpath=//button[contains(text(),'Place order')]
Rule: for action buttons, prefer text over CSS style.
❌ Pitfall 5 — Chrome native popups not disabled¶
Chrome shows native popups (save password, save card, notifications) that block clicks.
Disable them via add_experimental_option:
Open Browser ${URL} browser=chrome
... remote_url=${SELENIUM_URL}
... options=add_argument("--no-sandbox");add_argument("--disable-dev-shm-usage");add_argument("--disable-notifications");add_experimental_option("prefs", {"credentials_enable_service": False, "profile.password_manager_enabled": False, "autofill.credit_card_enabled": False, "autofill.profile_enabled": False, "profile.default_content_setting_values.notifications": 2})
| Prefs option | Disabled popup |
|---|---|
credentials_enable_service: False |
"Save password?" |
autofill.credit_card_enabled: False |
"Save card?" |
autofill.profile_enabled: False |
Address autofill |
notifications: 2 |
Notification permission popups |
Reference script — CT-011 Full business journey¶
CT-011-parcours-metier-ui.robot covers the complete customer journey in 10 steps:
| Step | Keyword | What happens in Chrome |
|---|---|---|
| T01 | T01 Homepage |
Catalogue loaded, SearchBar visible, prices displayed |
| T02 | T02 Search Product |
SearchBar input, 300ms debounce, filtered results |
| T03 | T03 Add To Cart |
Button click, navbar counter Cart (0) → (1) |
| T04 | T04 Login |
Navbar link click (SPA, cart preserved), form, submit |
| T05 | T05 Acces Checkout |
Click Cart, click .btn-checkout |
| T06 | T06 Renseigner Adresse |
Step 1: clear + fill 4 fields, click Next |
| T07 | T07 Choisir Livraison |
Step 2: click shipping card, click Next |
| T08 | T08 Renseigner Paiement |
Step 3: Luhn card 4532 0151 1283 0366, CVV |
| T09 | T09 Confirmer Commande |
Step 4 recap, scroll + click green button, confirmation |
| T10 | T10 Mes Commandes Et Logoff |
Orders, detail modal, close modal, logout |
A screenshot is captured at each step in /rf-logs/T0X_*.png.