Skip to content

Commit a187051

Browse files
committed
tests: enable automatic tests on Android.
1 parent 7d540b4 commit a187051

File tree

8 files changed

+20379
-0
lines changed

8 files changed

+20379
-0
lines changed

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,55 @@ jobs:
7070
path: /github/home/go/src/github.com/BitBoxSwiss/bitbox-wallet-app/frontends/android/BitBoxApp/app/build/outputs/apk/debug/app-debug.apk
7171
name: BitBoxApp-android-${{github.sha}}.apk
7272
if-no-files-found: error
73+
74+
e2e-android:
75+
name: Android E2E Tests
76+
needs: android
77+
runs-on: ubuntu-latest
78+
steps:
79+
- name: Checkout repo
80+
uses: actions/checkout@v4
81+
82+
- name: Set up Node.js
83+
uses: actions/setup-node@v4
84+
with:
85+
node-version: '20'
86+
87+
- name: Download APK
88+
uses: actions/download-artifact@v4
89+
with:
90+
name: BitBoxApp-android-${{ github.sha }}.apk
91+
path: frontends/tests/apk
92+
93+
- name: Install dependencies
94+
working-directory: frontends/tests
95+
run: npm ci
96+
97+
- name: Enable KVM group perms
98+
run: |
99+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
100+
sudo udevadm control --reload-rules
101+
sudo udevadm trigger --name-match=kvm
102+
103+
- name: Install Appium & drivers
104+
run: |
105+
npm install -g appium
106+
appium driver install uiautomator2
107+
108+
- name: Start Android emulator and run tests
109+
uses: reactivecircus/android-emulator-runner@v2
110+
env:
111+
PLATFORM: Android
112+
ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL: 5
113+
with:
114+
api-level: 34
115+
target: google_apis
116+
arch: x86_64
117+
force-avd-creation: true
118+
emulator-boot-timeout: 600
119+
working-directory: frontends/tests
120+
emulator-options: "-no-window -no-audio -no-boot-anim -no-snapshot-load -no-snapshot-save"
121+
script: ./run.sh
73122
qt-linux:
74123
runs-on: ubuntu-22.04
75124
needs: setup-env

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
SHELL := /bin/bash
1616
WEBROOT := frontends/web
17+
MOBILETESTROOT := frontends/tests
1718

1819
catch:
1920
@echo "Choose a make target."
@@ -57,6 +58,8 @@ webserve:
5758
cd ${WEBROOT} && $(MAKE) serve
5859
webe2etest:
5960
cd ${WEBROOT} && $(MAKE) test-e2e
61+
mobilee2etest:
62+
cd ${MOBILETESTROOT} && ./run.sh
6063
qt-linux: # run inside dockerdev
6164
$(MAKE) buildweb
6265
cd frontends/qt && $(MAKE) linux

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ Playwright is used to perform automatic test on some use cases on the webdev ver
103103
Tests are located under [`frontends/web/tests`](/frontends/web/tests) and can be run with
104104
`make webe2etest`
105105

106+
Appium is used to perform automatic test on Android emulators.
107+
The test runs automatically on PRs and push to master in the Github CI.
108+
106109
#### Run the HTTP API
107110

108111
Run `make servewallet` to compile the code and run `servewallet`. `servewallet` is a devtool which

frontends/tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules/

frontends/tests/e2e/base.test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { remote } from "webdriverio";
2+
import { expect } from "chai";
3+
4+
// --- Helpers ---
5+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6+
7+
async function clickCss(driver, selector, delay = 1000) {
8+
const el = await driver.$(selector);
9+
expect(el).to.exist;
10+
await el.click();
11+
await sleep(delay);
12+
}
13+
14+
async function clickXPath(driver, xpath, delay = 1000) {
15+
const el = await driver.$(xpath);
16+
expect(el).to.exist;
17+
await el.click();
18+
await sleep(delay);
19+
}
20+
21+
async function typeInInput(driver, selector, text, delay = 1000) {
22+
const el = await driver.$(selector);
23+
expect(el).to.exist;
24+
await el.clearValue();
25+
await el.setValue(text);
26+
await sleep(delay);
27+
}
28+
29+
async function selectOptionByText(driver, text, delay = 1000) {
30+
const el = await driver.$(`//*[contains(text(),'${text}')]`);
31+
expect(el).to.exist;
32+
await el.click();
33+
await sleep(delay);
34+
}
35+
36+
// --- Test ---
37+
describe("BitBoxApp Base Test", function () {
38+
this.timeout(180000);
39+
40+
let driver;
41+
before(async () => {
42+
43+
const opts = {
44+
path: '/',
45+
port: 4723,
46+
capabilities: {
47+
platformName: 'Android',
48+
'appium:deviceName': 'Android Emulator',
49+
'appium:automationName': 'UiAutomator2',
50+
'appium:app': `./apk/app-debug.apk`,
51+
'appium:noReset': true,
52+
}
53+
};
54+
55+
driver = await remote(opts);
56+
57+
// Switch to WebView if present
58+
const contexts = await driver.getContexts();
59+
const webview = contexts.find((c) => c.startsWith("WEBVIEW_"));
60+
if (webview) await driver.switchContext(webview);
61+
});
62+
63+
after(async () => {
64+
if (driver) await driver.deleteSession();
65+
});
66+
67+
it("should navigate and select Italian language", async () => {
68+
await clickCss(driver, "button._transparent_1s0sp_84._button_1s0sp_1._button_1fz28_1", 2000);
69+
await clickXPath(driver, "//*[contains(text(),'General')]", 2000);
70+
await clickXPath(driver, "//*[contains(text(),'Language')]", 2000);
71+
72+
// Search and select
73+
await typeInInput(driver, "input", "Italian", 2000);
74+
await selectOptionByText(driver, "Italiano", 2000);
75+
});
76+
});

0 commit comments

Comments
 (0)