Skip to content

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.

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-selenium container 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

POST /api/cart/add    → open, no login required
POST /api/orders      → requires an active session

5. Available test accounts

Email Password
user1@perfshop.com password1
user2@perfshop.com password2
... ...
user10@perfshop.com password10
perf.test1@perfshop.com TestPerf123!

Upload and run your script

  1. Open Filebrowser (URL provided by the instructor)
  2. Drop your .robot file into the /scripts folder
  3. In Squash TM → create a test case with the reference perfshop-tests/your-script.robot

Option 2 — Via Git

  1. Clone the perfshop-tests repository from the PerfShop GitLab
  2. Add your script at the repository root
  3. Commit and push to the main branch
  4. 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:

docker exec perfshop-test-runner robot \
  --outputdir /rf-logs \
  /scripts/your-script.robot

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



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)
Exception: RAW variables without a prefix (${PRICE_STYLE_XPATH}, ${ADD_TO_CART_FIRST}, ${ORDER_NUMBER_TEXT}) require an explicit xpath=:
Wait Until Element Is Visible    xpath=${PRICE_STYLE_XPATH}    timeout=20s

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.