> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Local vs Cloud

> Run tests on your workstation or on Roboticks runners. Decision matrix, auth flow, and the rbtk test cloud command that bundles your tree and streams logs.

# Local vs cloud

The SDK is the same in both places. The decision is about **what** runs the tests — your laptop, a hosted Roboticks runner, or one you operate yourself.

## At a glance

|                          | Local         | `rbtk test cloud`                   | GitHub PR                                                |
| ------------------------ | ------------- | ----------------------------------- | -------------------------------------------------------- |
| Triggered by             | You, manually | You, manually                       | `git push`                                               |
| Tree bundled             | N/A           | Working tree, including dirty files | The PR head SHA                                          |
| Auth                     | None          | `rbtk login` (OAuth)                | GitHub App installation                                  |
| JUnit destination        | local file    | uploaded automatically              | uploaded automatically                                   |
| Result appears           | terminal      | terminal + dashboard                | dashboard + Check Run                                    |
| Costs sim minutes?       | No            | Yes (if `@requires_sim`)            | Yes (if `@requires_sim`)                                 |
| Required for the matrix? | No            | No                                  | **Yes** (matrix tracks the platform-recorded commit SHA) |

## Local: pytest and colcon as you know them

Nothing about the SDK forces a runner. Run pytest the way you always have:

```bash theme={null}
pip install 'roboticks[mcap]'
pytest -v tests/
```

Or for a colcon workspace:

```bash theme={null}
colcon test --packages-select my_pkg
colcon test-result --verbose
```

The `@confirms` decorators still take effect — the pytest plugin still writes properties into the JUnit XML — but no one is *uploading* that XML to the platform. The local run is for the inner loop: write code, run tests, iterate.

<Tip>
  If you want a local run to surface in the dashboard, upload the JUnit XML manually after the run:

  ```bash theme={null}
  rbtk test results upload --file test_results/junit.xml --commit $(git rev-parse HEAD)
  ```
</Tip>

## Cloud: `rbtk test cloud`

`rbtk test cloud` is the "skip the PR loop" command. It bundles your working tree (including dirty files), uploads to the platform, runs on a hosted runner of the right shape, and streams logs back to your terminal in real time.

```bash theme={null}
rbtk test cloud \
  --filter "tests/test_estop.py" \
  --sim gazebo \
  --watch
```

Flags:

| Flag               | Effect                                                                                    |
| ------------------ | ----------------------------------------------------------------------------------------- |
| `--filter <expr>`  | pytest `-k` style filter; same goes through to the remote pytest                          |
| `--sim <engine>`   | Force a sim-capable runner even if no test is marked `@requires_sim`                      |
| `--watch`          | Stream stdout / stderr live; otherwise the command exits with the run URL                 |
| `--detach`         | Print run ID and exit immediately                                                         |
| `--no-upload-tree` | Use the current commit on the remote instead of the working tree (cheaper, requires push) |

Output:

```text theme={null}
$ rbtk test cloud --watch --filter test_estop
i  bundling working tree (12 files, 1.4 MB) ...
i  run #4218 dispatched to hosted-fargate-spot (us-east-1)
i  https://app.roboticks.io/runs/4218

[runner] collected 3 items
[runner] tests/test_estop.py::test_estop_halts_motion PASSED [33%]
[runner] tests/test_estop.py::test_estop_survives_comms_loss PASSED [66%]
[runner] tests/test_estop.py::test_estop_on_battery_low PASSED [100%]
[runner] 3 passed in 12.4s

ok  run #4218 PASSED · 3/3 tests · 3 requirements confirmed · 22s wall time
```

See [CLI: test commands](/cli/test-commands) for the full surface.

## GitHub PR: the default cloud path

When the GitHub App is installed on a repo, every PR automatically runs the test suite on a hosted (or self-hosted, by routing label) runner. The Check Run posts back to the PR with a coverage delta. This is the normal mode of operation.

You don't run `rbtk test cloud` for the PR loop. You run it for **between-PR experiments**: trying a fault-injection scenario that needs sim minutes you don't have locally, or asking "does this branch break REQ-014?" without the overhead of opening a draft PR.

## Auth flows

| Surface            | How auth works                                                                                                                   |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `pytest` locally   | None — local files only.                                                                                                         |
| `rbtk test cloud`  | `rbtk login` opens a browser OAuth flow against `app.roboticks.io`; token is cached in `~/.config/roboticks/auth.json`.          |
| GitHub App         | OAuth install grants the App per-repo permissions; no per-user auth on the runner side.                                          |
| Self-hosted runner | Runner exchanges a one-time enrolment token for a per-runner OIDC identity. See [Runners → Installation](/runners/installation). |

## Decision matrix

| Situation                                         | Use                                            |
| ------------------------------------------------- | ---------------------------------------------- |
| Writing a new test, iterating                     | Local `pytest`                                 |
| Test passes locally but you want sim verification | `rbtk test cloud --sim gazebo`                 |
| You don't have GPUs and the test needs Webots     | `rbtk test cloud` (routes to hosted GPU pool)  |
| You're about to push and want a dry-run           | `rbtk test cloud --no-upload-tree`             |
| You opened a PR                                   | Nothing — the GitHub App runs it automatically |
| You want the matrix updated without a PR          | `rbtk test results upload` after the local run |

## What never changes

* `@confirms`, `@tags`, `@deadline`, `@requires_sim` produce the same JUnit XML wherever they run.
* The wire contract is the same wherever they run.
* The matrix doesn't care whether a test ran locally-uploaded, via `rbtk test cloud`, via PR, or via a self-hosted runner — only that the commit SHA is recorded.

## Next

<CardGroup cols={2}>
  <Card title="CLI: test commands" icon="terminal" href="/cli/test-commands">
    Full surface of `rbtk test cloud`, results, upload, retry.
  </Card>

  <Card title="Sim runners" icon="microchip" href="/testing/sim-runners">
    When `--sim gazebo` matters and what it costs.
  </Card>

  <Card title="GitHub App" icon="github" href="/github-app/check-runs">
    How the PR loop produces a Check Run.
  </Card>

  <Card title="Self-host a runner" icon="server" href="/runners/installation">
    Bring your own compute, no sim-minute charges.
  </Card>
</CardGroup>
