diff --git a/.github/workflows/build_and_functional_tests.yml b/.github/workflows/build_and_functional_tests.yml new file mode 100644 index 000000000..515dfbde6 --- /dev/null +++ b/.github/workflows/build_and_functional_tests.yml @@ -0,0 +1,34 @@ +name: Build and run functional tests using ragger through reusable workflow + +# This workflow will build the app and then run functional tests using the Ragger framework upon Speculos emulation. +# It calls a reusable workflow developed by Ledger's internal developer team to build the application and upload the +# resulting binaries. +# It then calls another reusable workflow to run the Ragger tests on the compiled application binary. +# +# While this workflow is optional, having functional testing on your application is mandatory and this workflow and +# tooling environment is meant to be easy to use and adapt after forking your application + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + build_application: + name: Build application using the reusable workflow + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 + with: + upload_app_binaries_artifact: "compiled_app_binaries" + flags: "DEBUG=0 COIN=bitcoin" + + ragger_tests: + name: Run ragger tests using the reusable workflow + needs: build_application + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@fbe/support_linux_packages_install_in_ragger + with: + download_app_binaries_artifact: "compiled_app_binaries" + diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index d76a64c4c..910682551 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -100,46 +100,6 @@ jobs: name: documentation path: doc/html - job_test: - name: Tests - strategy: - matrix: - include: - - model: nanos - - model: nanox - - model: nanosp - - model: stax - - needs: job_build - runs-on: ubuntu-latest - - container: - image: ghcr.io/ledgerhq/app-bitcoin-new/speculos-bitcoin:latest - ports: - - 1234:1234 - - 9999:9999 - - 40000:40000 - - 41000:41000 - - 42000:42000 - - 43000:43000 - options: --entrypoint /bin/bash - - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Download Bitcoin Testnet app binary - uses: actions/download-artifact@v2 - with: - name: bitcoin-testnet-app-${{matrix.model}} - path: bin - - - name: Run tests - run: | - cd tests - pip install -r requirements.txt - PYTHONPATH=$PYTHONPATH:/speculos pytest --headless --model=${{ matrix.model }} --timeout=500 - job_test_mainnet: name: Tests on mainnet strategy: diff --git a/ragger_bitcoin/instructions.py b/ragger_bitcoin/instructions.py new file mode 100644 index 000000000..08e157d2b --- /dev/null +++ b/ragger_bitcoin/instructions.py @@ -0,0 +1,98 @@ +from ragger.navigator import NavInsID + + +class Instructions: + def __init__(self, model): + self.data = { + 'text': [], + 'instruction_until_text': [], + 'instruction_on_text': [], + 'save_screenshot': [] + } + + if not model: + raise Exception("Model must be specified") + + self.model = model + + def __str__(self): + return "Data: {0}\n\t".format(self.data) + + def same_request(self, text, instruction_until_text=NavInsID.RIGHT_CLICK, + instruction_on_text=NavInsID.BOTH_CLICK, save_screenshot=True): + + self.data['text'][-1].append(text) + self.data['instruction_until_text'][-1].append(instruction_until_text) + self.data['instruction_on_text'][-1].append(instruction_on_text) + self.data['save_screenshot'][-1].append(save_screenshot) + + def new_request(self, text, instruction_until_text=NavInsID.RIGHT_CLICK, + instruction_on_text=NavInsID.BOTH_CLICK, save_screenshot=True): + + self.data['text'].append([text]) + self.data['instruction_until_text'].append([instruction_until_text]) + self.data['instruction_on_text'].append([instruction_on_text]) + self.data['save_screenshot'].append([save_screenshot]) + + def nano_skip_screen(self, text, save_screenshot=True): + self.new_request(text, NavInsID.RIGHT_CLICK, NavInsID.RIGHT_CLICK, + save_screenshot=save_screenshot) + + def navigate_end_of_flow(self, save_screenshot=True): + self.new_request("Processing", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_TAP, + save_screenshot=save_screenshot) + + def confirm_transaction(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("TRANSACTION", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def same_request_confirm_transaction(self, save_screenshot=True): + self.same_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("TRANSACTION", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def confirm_message(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_REVIEW_CONFIRM, save_screenshot=save_screenshot) + self.new_request("MESSAGE", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, save_screenshot=save_screenshot) + + def confirm_wallet(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_CONFIRM, + save_screenshot=save_screenshot) + self.same_request("WALLET", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_STATUS_DISMISS, save_screenshot=save_screenshot) + + def reject_message(self, save_screenshot=True): + self.new_request("Sign", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_REVIEW_REJECT, + save_screenshot=save_screenshot) + self.same_request("Reject", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + self.new_request("MESSAGE", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_STATUS_DISMISS, + save_screenshot=save_screenshot) + + def warning_accept(self, save_screenshot=True): + self.new_request("Warning", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + + def address_confirm(self, save_screenshot=True): + self.new_request("Confirm", NavInsID.USE_CASE_REVIEW_TAP, + NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CONFIRM, + save_screenshot=save_screenshot) + + def choice_confirm(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_CONFIRM, + save_screenshot=save_screenshot) + + def choice_reject(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.USE_CASE_CHOICE_REJECT, + save_screenshot=save_screenshot) + + def footer_cancel(self, save_screenshot=True): + self.new_request("Approve", NavInsID.USE_CASE_REVIEW_TAP, NavInsID.CANCEL_FOOTER_TAP, + save_screenshot=save_screenshot)