Skip to main content

ParallelRunner

PyPI Python versions Tests

Run a loop inside a single Robot Framework test case in parallel — with one clean log.html, not a merge of many.

ParallelRunner is a small Robot Framework library that lets you fan a keyword out across a thread pool from within a test case — e.g. hit 100 API endpoints, or repeat one check N times — and get back a single, correctly ordered log.html plus a structured list of per-item results.

Keyword documentation: https://cristiangarciavd.github.io/robotframework-parallelrunner/ParallelRunner.html

Installation

pip install robotframework-parallelrunner

Requires Python 3.9+ and Robot Framework 5.0+ (installed automatically). See docs/INSTALLATION.md for development installs and upgrading from the pre-release import path.

Quickstart

The keyword you want to parallelize is a method of a Python library. It receives the current item first, and an injected _logger it should log through (so logs from different threads never interleave):

# my_library.py
class MyLibrary:
    def verify_agent_data(self, agent_id, _logger=None, **kwargs):
        _logger(f"Checking agent {agent_id}")
        ...
        return result
*** Settings ***
Library    ParallelRunner
Library    my_library.MyLibrary

*** Test Cases ***
Verify Agents In Parallel
    ${agents}=    Create List    1    2    3    4    5
    ${results}=    Run Parallel Scenarios
    ...    keyword=Verify Agent Data
    ...    library=my_library.MyLibrary
    ...    for_loop_iterable=${agents}
robot --pythonpath . my_suite.robot

See docs/QUICKSTART.md for a full walkthrough and docs/API_REFERENCE.md for every parameter of Run Parallel Scenarios. For the internal design (log buffering, thread safety), see ARCHITECTURE.md.

Why This Works

Thread safety. We avoid BuiltIn().run_keyword() inside threads, which is the main cause of crashes in multi-threaded Robot Framework. We call the underlying Python methods directly instead.

No log interleaving. Each worker thread buffers its own log messages in memory. Once every task finishes, the main thread replays all buffered logs sequentially into Robot Framework's real logger (_replay_logs). The result: log.html shows logs grouped cleanly by item, even though the work happened concurrently.

Encapsulation. Callers only ever see Run Parallel Scenarios — the threading, buffering, and replay logic stay out of your .robot files.

Summary of Benefits

  • Speed. Waiting time is overlapped instead of added up. In this repo's examples, 9 HTTP checks with a 1 s delay each drop from 13 s to 2 s with 9 workers, and 4 suites × 8 items × 1 s drop from 32.7 s to 4.6 s with 8 workers (see Using It Together With pabot).
  • Integrity. Your log.html remains the single source of truth — no broken XML tags from concurrent writes.
  • No hidden failures. If any call fails, the keyword fails too (after all calls finish and their logs are replayed), with a message listing every failed item. Pass fail_on_error=False to get a PASS/FAIL status per item and check them yourself.
  • Flexibility. Pass repeat=10 to stress-test a single endpoint, or for_loop_iterable=${items} for batch validation of a whole list.
  • Ergonomics. Results come back in call order (not completion order), so ${results}[0] is always the first call. Add return_values_only=True to skip the status/logs envelope entirely and unpack each call's return value straight into its own variable — e.g. ${id1} ${id2} ${id3}= Run Parallel Scenarios ... repeat=3 return_values_only=True when seeding N independent fixture rows.

How Is This Different From pabot?

pabot is the standard tool for parallel execution in the Robot Framework ecosystem, and this project is not a replacement for it — the two solve different problems and compose well together.

pabot ParallelRunner
Parallelizes at the level of Suites / test cases A loop (or repeated call) inside one test case
Execution model Separate processes Threads in the same process
Result merging Runs each suite separately, then merges multiple output.xml files with rebot Nothing to merge — logs are buffered per item and replayed into the same log.html in order
Best suited for Running many independent suites/tests concurrently, across cores or machines Fanning out inside a single test case (e.g. one test that validates 100 endpoints)
Workload type Any (process isolation means CPU-bound work scales too) I/O-bound work (HTTP calls, DB/network waits) — Python's GIL limits benefit for CPU-bound work

Concretely:

  • pabot takes your existing suites/tests, runs several of them at once as separate OS processes (so they don't share memory or a GIL), and then stitches the independent output.xml results back into one report. It answers: "I have many independent tests, how do I run them all faster?"
  • ParallelRunner answers a different question: "I have one test case that needs to do the same kind of work many times (loop over a list of IDs, or repeat something N times) — how do I parallelize the body of that loop without corrupting the log or needing to merge anything?" It uses a ThreadPoolExecutor inside a single process, and produces one already-merged, already-ordered log for that test case.
  • Because ParallelRunner uses threads, not processes, it shares memory and is subject to the GIL — it is a good fit for I/O-bound work (network calls, waiting on APIs/databases) where threads spend most of their time blocked on I/O, not a good fit for CPU-bound number crunching.
  • The two are complementary: you can use pabot to run many suites in parallel across processes, where individual test cases within those suites use ParallelRunner to parallelize their own inner loops across threads.

If you need to speed up "run these 50 independent test files faster," reach for pabot. If you need to speed up "this one test case loops over 100 items and I want a single readable log instead of 100 sequential HTTP round trips (or 100 merged output.xml files)," that's what ParallelRunner is for.

Using It Together With pabot

The two combine without any extra configuration: pabot splits suites (or tests) across processes, and inside each process ParallelRunner splits a loop across threads. Every pabot process gets its own ParallelRunner instance, its own thread pool and its own output.xml, so nothing is shared between processes, and pabot merges the results as usual.

pabot --processes 4 --pythonpath . atest/
pabot --processes 4 --testlevelsplit --pythonpath . atest/

Both commands pass the full acceptance suite of this repository (52 tests; checked with pabot 5.2.2).

How much time each one saves, from the offline pabot timing demo (4 suites × 8 items × 1 s of simulated I/O wait; poetry run invoke demo-pabot):

Run Wall-clock time Speedup
robot, sequential loop 32.7 s ×1.0
robot + ParallelRunner (8 workers) 4.6 s ×7.1
pabot (4 processes), sequential loop 10.6 s ×3.1
pabot (4 processes) + ParallelRunner (8 workers) 3.5 s ×9.2

ParallelRunner removes the waiting inside each test, and pabot removes it between suites. With suites this short, pabot's process start-up (about 2.5 s here) is most of what's left, so the combination pays off more as suites get longer.

Things to keep in mind:

  • Run pabot from the virtual environment where the library is installed (or via poetry run pabot ...). pabot starts whichever robot command is first on the PATH. If that belongs to another Python installation, every test that uses the library fails with No keyword with name 'Run Parallel Scenarios' found.
  • Concurrency multiplies. The number of simultaneous calls is roughly pabot processes × ROBOT_THREAD_WORKERS: 4 processes × 4 workers means up to 16 concurrent requests against the same API or database. Lower one of the two if you hit rate limits, connection-pool limits or lock timeouts.
  • Shared test resources can collide across processes. Data, files, user accounts, or a table that a Suite Setup resets are visible to every process. With --testlevelsplit, each test of a suite runs its own Suite Setup in its own process. For example, the examples/db_seed suite drops and recreates its table in Suite Setup, so two of its tests could interfere with each other. Keep shared state per process, or split by suite.

When Not To Use This

  • CPU-bound work. Threads share Python's GIL — number crunching won't get faster this way. Use multiprocessing, or pabot (separate processes), instead.
  • Your keyword mutates shared state without synchronization. Logging is made thread-safe for you; your own keyword's side effects are not. If it writes to a shared variable, file, or object without a lock, running it concurrently can race the same way any multi-threaded code can.
  • You need per-item retries or a timeout. Not implemented yet (see ROADMAP.md) — one hung call currently blocks the whole batch from returning.
  • You need process-level isolation (a crash in one call shouldn't be able to affect another) or cross-machine parallelism — that's pabot's domain, not this library's.

Project Structure

robotframework-parallelrunner/
├── src/ParallelRunner/     # The installable library (do not depend on internals prefixed with `_`)
├── examples/               # Example libraries used by the tests, plus the pabot timing demo
├── atest/                  # Robot Framework acceptance suites
├── utest/                  # pytest unit tests
├── docs/                   # INSTALLATION, QUICKSTART, API_REFERENCE, generated keyword docs
├── tasks.py                # invoke tasks: tests, libdoc, demo-pabot, build, ...
└── ARCHITECTURE.md         # Technical deep dive

See ARCHITECTURE.md for the full breakdown and design principles.

Optional: UI automation with Playwright

The same thread-pool approach applies to browser automation, not just HTTP calls — see examples/playwright_ui/ for a worked example using Playwright's official sync_api. It's kept out of the default install and CI (heavy dependency, real browser download), so it's opt-in: read that folder's README before installing anything.

Contributing

See CONTRIBUTING.md and CODE_OF_CONDUCT.md. For what's done and what's planned, see ROADMAP.md and CHANGELOG.md.

License

MIT — see LICENSE.

Metadata

Release files for robotframework-parallelrunner 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for robotframework-parallelrunner 0.3.0
File Size Uploaded
robotframework_parallelrunner-0.3.0.tar.gz 18.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for robotframework-parallelrunner 0.3.0
File Interpreter ABI Platform
robotframework_parallelrunner-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 33.8 kB

Release files / robotframework_parallelrunner-0.3.0.tar.gz

Download URL robotframework_parallelrunner-0.3.0.tar.gz
Size 18.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5d6289d88e654d59ea0dab8a1620b7d328462200d30d5f6238ff367840c0b894
BLAKE2b-256 checksum
How to use checksums
302d1b3922d26ca7543d312b080781b793d97cdbbfe6c3f963f2ba58cf57e990
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / robotframework_parallelrunner-0.3.0-py3-none-any.whl

Download URL robotframework_parallelrunner-0.3.0-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e3767277286005dae0c6ee71fb11fa70b0515243ce6cc5a15ec5653834286159
BLAKE2b-256 checksum
How to use checksums
de03a26b739622a78c284b96a0ab54b8feff2fc60f918f3f2b68b962f9fc816f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page