Chapter 16Lesson 02240–320 min

SeleniumLibrary and Browser Automation Integration: Guided Hands-On Workflow

Build a disposable local login-like application and automate the same domain flow with SeleniumLibrary and Browser using explicit lifecycle, locator, waiting, screenshot, and trace-aware evidence practices.

Local AUTDomain resourcesExplicit waitsAuto-waitEvidence

Learning objectives

  • Create and inspect a loopback-only browser AUT before opening any browser.
  • Install a version-compatible SeleniumLibrary/Selenium pair and an optional Browser/BrowserBatteries stack.
  • Implement the same domain keyword contract over two separate resources.
  • Use explicit Selenium waits and Browser assertion/auto-wait behavior without sleeps.
  • Capture screenshots/optional trace evidence and prove teardown ownership.

Current compatibility baseline — verified 2026-08-31. Robot Framework 7.4.2 is the stable course baseline. SeleniumLibrary 6.9.0 is paired in these labs with Selenium 4.44.0 because 6.9.0 documents support through that Selenium version; use Python 3.10+ for this chapter. Browser 20.4.0 requires Python 3.10+ and Robot Framework 7.1.1+, and its release is tested with Playwright 1.62.1. The easiest Browser path uses robotframework-browser[bb] plus rfbrowser install; the Node-managed path supports Node 22/24 LTS and Node 26. Re-check current compatibility before upgrading any layer. No paid browser cloud, production site, real credential, Pabot, container, or CI account is required.

1. Scenario and state boundary

You will build a tiny static login-like application served only on 127.0.0.1:8765. It uses fake credentials demo/mode and intentionally delays the UI result by 350 ms. That delay is long enough to demonstrate real synchronization without adding a network dependency or production account.

The browser is the only external mutable state. The app files are disposable. Robot outputs are evidence. No database, API credential, cloud browser, or production service is involved.

2. Create the disposable project

rf-browser-lab/
├── app/
│   └── index.html
├── resources/
│   ├── login_selenium.resource
│   └── login_browser.resource
├── tests/
│   ├── selenium_login.robot
│   └── browser_login.robot
└── results/
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>RF Browser Lab</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 34rem; margin: 4rem auto; }
    label { display:block; margin:.8rem 0; }
    input, button { padding:.55rem; }
    #status { margin-top:1rem; font-weight:700; }
  </style>
</head>
<body>
  <h1>Local Login Lab</h1>
  <form data-testid="login-form">
    <label>Username <input data-testid="username" autocomplete="off"></label>
    <label>Password <input data-testid="password" type="password" autocomplete="off"></label>
    <button data-testid="submit" type="submit">Sign in</button>
  </form>
  <p data-testid="status" aria-live="polite">Signed out</p>
  <script>
    const form = document.querySelector('[data-testid="login-form"]');
    const status = document.querySelector('[data-testid="status"]');
    form.addEventListener('submit', (event) => {
      event.preventDefault();
      const user = document.querySelector('[data-testid="username"]').value;
      const pass = document.querySelector('[data-testid="password"]').value;
      status.textContent = 'Checking…';
      setTimeout(() => {
        if (user === 'demo' && pass === 'mode') {
          status.textContent = `Welcome, ${user}`;
          history.replaceState({}, '', '#dashboard');
        } else {
          status.textContent = 'Invalid credentials';
        }
      }, 350);
    });
  </script>
</body>
</html>

3. Start and inspect the local AUT

cd rf-browser-lab
python -m http.server 8765 --bind 127.0.0.1 --directory app

Keep that terminal open. In a second terminal, verify before browser automation:

python -c "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8765/', timeout=2); print(r.status, r.geturl())"

Expected observation: HTTP 200 and a loopback URL. If another process already owns port 8765, choose a different private port and update ${LOGIN_URL} consistently. Do not “solve” the collision by killing unrelated processes.

4. Create an isolated Python environment

python -m venv .venv
# PowerShell
.\.venv\Scripts\Activate.ps1
# Bash
# source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install robotframework==7.4.2 robotframework-seleniumlibrary==6.9.0 selenium==4.44.0
python -m robot --version
python -m pip show robotframework-seleniumlibrary selenium

This deliberately pins Selenium 4.44.0 even if a newer Selenium exists, because SeleniumLibrary 6.9.0 documents its supported Selenium range through 4.44.0. The browser itself (for example Chrome) must already be installed; Selenium Manager normally resolves the corresponding driver. On restricted networks, pre-provision the browser/driver according to Selenium policy rather than disabling verification.

5. Build the SeleniumLibrary domain resource

*** Settings ***
Library    SeleniumLibrary

*** Variables ***
${LOGIN_URL}    http://127.0.0.1:8765/

*** Keywords ***
Open Login App
    Open Browser    ${LOGIN_URL}    chrome

Sign In As
    [Arguments]    ${username}    ${password}
    Wait Until Element Is Visible    css:[data-testid="username"]    5s
    Input Text    css:[data-testid="username"]    ${username}
    Input Text    css:[data-testid="password"]    ${password}
    Click Button    css:[data-testid="submit"]

Welcome Should Be Visible
    [Arguments]    ${username}
    Wait Until Element Contains    css:[data-testid="status"]    Welcome, ${username}    5s
    Location Should Contain    #dashboard

Capture Browser Evidence
    Capture Page Screenshot    selenium-login-{index}.png

Close Login App
    Close All Browsers

Wait Until Element Is Visible reads browser DOM state until the username element becomes visible. Input Text mutates form fields. Click Button triggers the local JavaScript. Wait Until Element Contains is the meaningful synchronization point: it waits for the delayed business result. Location Should Contain then verifies the URL hash. The screenshot is derivative evidence, and teardown closes only WebDriver sessions opened through SeleniumLibrary.

6. Run the SeleniumLibrary path

*** Settings ***
Resource    ../resources/login_selenium.resource
Test Setup       Open Login App
Test Teardown    Close Login App

*** Test Cases ***
Local Login Works Through SeleniumLibrary
    Sign In As    demo    mode
    Welcome Should Be Visible    demo
    Capture Browser Evidence
python -m robot --outputdir results/selenium tests/selenium_login.robot

Expected state transition: no WebDriver session → one Chrome WebDriver session → local page loaded → two inputs changed → status becomes Welcome, demo → URL ends in #dashboard → screenshot written → browser session closed. Verify results/selenium/output.xml, log.html, report.html, and the screenshot before cleanup.

7. Add Browser 20.4.0 as a second, optional stack

The easiest current route avoids a separately managed Node installation:

python -m pip install "robotframework-browser[bb]==20.4.0"
rfbrowser --version
rfbrowser install chromium

rfbrowser install chromium downloads the Playwright-managed Chromium binary. If your machine cannot download binaries but already has Chrome/Edge, the Browser documentation also supports using a Chromium channel instead; document that deviation. If BrowserBatteries has no wheel for your platform, use the official Node-managed route (pip install robotframework-browser==20.4.0 then rfbrowser init chromium) with a supported Node runtime.

8. Build the equivalent Browser resource

*** Settings ***
Library    Browser    timeout=5s

*** Variables ***
${LOGIN_URL}    http://127.0.0.1:8765/

*** Keywords ***
Open Login App
    New Browser    chromium    headless=True
    New Context
    New Page    ${LOGIN_URL}

Sign In As
    [Arguments]    ${username}    ${password}
    Fill Text    [data-testid="username"]    ${username}
    Fill Text    [data-testid="password"]    ${password}
    Click    [data-testid="submit"]

Welcome Should Be Visible
    [Arguments]    ${username}
    Get Text    [data-testid="status"]    ==    Welcome, ${username}
    ${url}=    Get Url
    Should Contain    ${url}    #dashboard

Capture Browser Evidence
    Take Screenshot    filename=browser-login-{index}.png

Close Login App
    Close Browser

The domain contract is intentionally the same as the Selenium resource. Underneath, however, state differs: New Browser creates the Playwright browser, New Context creates isolated context state, and New Page creates the page. Click benefits from Playwright actionability waiting. Get Text … == … is a Browser assertion and is retried for the assertion timeout; that is why no sleep is needed for the 350 ms application delay.

9. Run the Browser path and optional trace

*** Settings ***
Resource    ../resources/login_browser.resource
Test Setup       Open Login App
Test Teardown    Close Login App

*** Test Cases ***
Local Login Works Through Browser
    Sign In As    demo    mode
    Welcome Should Be Visible    demo
    Capture Browser Evidence
python -m robot --outputdir results/browser tests/browser_login.robot

Expected state transition: no Browser-owned browser → one Chromium browser → one isolated context → one page → DOM mutation → assertion success → screenshot → browser close. Browser screenshots are normally under its output subdirectory.

For a diagnostic run, enable trace explicitly because traces are richer and more sensitive than ordinary logs:

# PowerShell
$env:ROBOT_FRAMEWORK_BROWSER_TRACING = 'True'
python -m robot --outputdir results/browser-trace tests/browser_login.robot
Remove-Item Env:ROBOT_FRAMEWORK_BROWSER_TRACING

# Bash alternative
# ROBOT_FRAMEWORK_BROWSER_TRACING=True python -m robot --outputdir results/browser-trace tests/browser_login.robot

Inspect the generated Browser trace directory only with synthetic data. A trace should be treated as a protected diagnostic artifact in a real system.

10. Compare the evidence rather than the syntax

Observation SeleniumLibrary run Browser run
High-level Robot call Sign In As Sign In As
Underlying browser unit WebDriver session browser → context → page
Readiness mechanism explicit Wait Until Element Contains retrying Get Text assertion after auto-waited actions
Screenshot Capture Page Screenshot Take Screenshot
Trace not a SeleniumLibrary core artifact optional Playwright trace through Browser
Teardown Close All Browsers Close Browser
Result source of truth Robot output.xml/log/report Robot output.xml/log/report

11. Challenge: choose the correct layer

The app is changed so the submit button is visible immediately, but it becomes enabled only after client-side validation. Where should the fix go?

Do not add a sleep to the test. Keep the business test unchanged. In the Selenium resource, wait for the button’s enabled state before clicking. In Browser, rely on Click actionability if enabledness is the relevant Playwright actionability condition, and add a condition-specific assertion only if application readiness is distinct. Explain which state each stack observes.

12. Cleanup and rollback

  • Stop only the python -m http.server process you started (Ctrl+C in its terminal).
  • Allow each Robot teardown to close its own browser state; never kill browsers by process name.
  • Retain results/ until you have inspected the evidence packet.
  • Then remove only the disposable rf-browser-lab directory if desired.

Knowledge check

Why does the Browser example use Get Text with an assertion operator instead of Get Text followed immediately by a string comparison?

Why pin Selenium 4.44.0 instead of blindly installing the newest Selenium?

What evidence proves cleanup without killing processes?

If neither browser stack can currently run, what is the safe fallback?

13. Summary and bridge

You now have two implementations of one domain contract, with explicit browser ownership and evidence. Lesson 3 turns that experience into architecture decisions for a production Robot estate.

Next lesson

SeleniumLibrary and Browser Automation Integration: Configuration, Design Patterns, and Trade-Offs

Continue with SeleniumLibrary and Browser Automation Integration: Configuration, Design Patterns, and Trade-Offs. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

References and version anchors

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.