Every one of the five workflows was boilerplate whose entire body was a call to a reusable workflow hosted at LedgerHQ/ledger-app-workflows. Gitea cannot resolve a GitHub-hosted reusable workflow, so they could only ever appear as broken runs: build_and_functional_tests, coding_style_checks, guidelines_enforcer, misspellings_checks and python_tests_checks. dependabot.yml goes with them, since Gitea does not read it either. Kept .github/PULL_REQUEST_TEMPLATE.md, which Gitea does support, and copilot-instructions.md, which is documentation rather than CI. tests/README.md no longer points at the workflow for regenerating snapshots; ./scripts/test <device> --golden_run is the only route now. There is no automated CI after this. ./scripts/test runs the full suite against speculos in Docker, 66 tests on each of the five devices. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Functional tests
application_client/ is the Python client: APDU encoding, transaction and
covenant builders, response unpackers, and an independent reimplementation of
the Handshake sighash used to cross-check the device's signatures.
standalone/ holds the tests, run against the app started from the device
dashboard. There is no swap test directory: swap is not implemented (see
src/swap.rs), and handler_sign_review refuses outright when swap parameters
are present rather than auto-approving.
Running
../scripts/test # stax
../scripts/test nanosp # or flex / nanox / apex_p
Speculos does not run natively on macOS, so scripts/test runs it and ragger in
a container built from Dockerfile here, on top of Ledger's dev-tools image.
That image ships speculos but not ragger, and its system Python is PEP-668
managed, so ragger is installed into the same venv speculos lives in.
All five devices pass. To run pytest directly instead (on Linux, with ragger installed):
pip install -r standalone/requirements.txt
pytest standalone --tb=short -v --device stax
Snapshots
UI tests compare against golden PNGs under
standalone/snapshots/<device>/<test_name>/. Regenerate with
../scripts/test <device> --golden_run. Read the diff before committing one:
those images are the record of what the user is shown before they approve a
signature.
standalone/test_sign_tx_policy.py deliberately needs none of them: every case
there is refused before the device draws anything.
standalone/review_nav.py explains why the approval path drives the navigator
directly instead of using scenario_navigator.review_approve().