
Test Custom Mobile Apps with Amazon Nova Act
Upload your custom app to Device Farm and test login flows, alerts, and assertions with natural language — replacing 200+ lines of Appium code with 80 lines of English.
Co-authored with Shyam Soundar
Introduction
In Part 1 , you learned to automate pre-installed apps on real devices with plain English commands. This post takes the next step: you upload your own APK, test login flows, handle alerts, and verify device state — all without writing a single XPath selector.
You will build a 4-test suite in 80 lines of Python that replaces 7 files and 200+ lines of traditional Appium code.
GitHub Repository: srcsakthivel/getting-started-device-farm-with-nova-act
What You Will Build
A test suite for the AWS Device Farm Android Reference App covering:
- Valid login — enter credentials, verify success message
- Invalid login — verify error handling
- Alert dialogs — trigger, verify content, dismiss
- Fixture checks — confirm Bluetooth, GPS, NFC, and WiFi status
Setup
1
2
3
4
5
6
git clone https://github.com/srcsakthivel/getting-started-device-farm-with-nova-act.git
cd getting-started-device-farm-with-nova-act
pip install -r requirements.txt
cp .env.example .env
# Edit .env with your workflow definition name and device ARNThe repository bundles the APK at
apps/AWSDeviceFarmAndroidReferenceApp.apk. Clone and run — no extra downloads.Handling Credentials Securely
The problem
Embedding credentials in
act() prompts triggers Nova Act's safety guardrails:1
2
3
# ❌ Triggers guardrails — credential strings in AI prompt
nova.act("Enter 'admin' in the username field")
nova.act("Enter 'wrongpassword' in the password field")The AI model interprets these as real credentials and blocks the action.
The fix: type_text()
Send keystrokes at the OS level. The value never reaches the AI prompt:
1
2
3
4
5
# ✅ Credentials bypass the AI prompt entirely
def enter_text_in_field(nova, field_label: str, value: str) -> None:
nova.act(f"Tap on the '{field_label}' input field to focus it.")
nova.clear_focused() # Clear existing text first
nova.type_text(value) # Sends keystrokes via Appium, not AIFive patterns for reliable tests
| Pattern | Reason |
|---|---|
type_text() for all inputs | Keeps values out of the AI prompt |
clear_focused() before typing | Prevents concatenation with stale text |
One action per act() call | Compound prompts reduce reliability |
| Separate actions from verifications | Tap first, then verify in a second call |
| Match exact UI labels | Use "Login Page" not "login" |
The Complete Test Suite
File:
src/android_custom_app.pyConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
from dotenv import load_dotenv
from nova_act import workflow
load_dotenv(os.path.join(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))), ".env"))
WORKFLOW_DEFINITION_NAME = os.environ["NOVA_ACT_WORKFLOW_DEFINITION_NAME"]
APP_PATH = os.path.join(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))), "apps", "AWSDeviceFarmAndroidReferenceApp.apk")
LOGIN_USERNAME = os.environ.get("LOGIN_USERNAME", "admin")
LOGIN_PASSWORD = os.environ.get("LOGIN_PASSWORD", "")
DEFAULT_DEVICE_ARN = os.environ["DEVICE_FARM_DEVICE_ARN"]Reusable helpers
1
2
3
4
5
6
7
8
def navigate_to_page(nova, page_name: str) -> None:
nova.act("Tap the hamburger menu icon to open the navigation drawer.")
nova.act(f"Tap on '{page_name}' in the navigation drawer menu.")
def enter_text_in_field(nova, field_label: str, value: str) -> None:
nova.act(f"Tap on the '{field_label}' input field to focus it.")
nova.clear_focused()
nova.type_text(value)Test 1: Valid login
1
2
3
4
5
navigate_to_page(nova, "Login Page")
enter_text_in_field(nova, "Username Input Field", LOGIN_USERNAME)
enter_text_in_field(nova, "Password Input Field", LOGIN_PASSWORD)
nova.act("Tap the 'Login' button.")
nova.act("Verify the text 'You are logged on as admin' is visible.")Test 2: Invalid login
1
2
3
4
5
navigate_to_page(nova, "Login Page")
enter_text_in_field(nova, "Username Input Field", LOGIN_USERNAME)
enter_text_in_field(nova, "Password Input Field", INVALID_PASSWORD)
nova.act("Tap the 'Login' button.")
nova.act("Verify the text 'You gave me the wrong username and password' is visible.")Test 3: Alert dialog
1
2
3
4
navigate_to_page(nova, "Alerts")
nova.act("Tap the button that triggers an alert notification.")
nova.act("Verify an alert shows the message 'This is the alert message'.")
nova.act("Tap 'OK' to dismiss the alert.")Test 4: Device fixtures
1
2
3
4
5
navigate_to_page(nova, "Fixtures")
nova.act("Verify Bluetooth status shows 'true' or 'false'.")
nova.act("Verify GPS status shows 'true' or 'false'.")
nova.act("Verify NFC status shows 'true' or 'false'.")
nova.act("Verify WiFi status shows 'true' or 'false'.")Run the suite
1
2
cd src/
python android_custom_app.pyOutput:
1
2
3
4
5
6
7
🚀 Starting Nova Act Test Suite — Device Farm Reference App
✅ App uploaded and installed on Device Farm — Nova Act ready!
📋 TEST 1: Login with valid credentials ✅ PASSED
📋 TEST 2: Login with invalid credentials ✅ PASSED
📋 TEST 3: Alert dialog ✅ PASSED
📋 TEST 4: Fixtures (device toggles) ✅ PASSED
🎉 ALL TESTS PASSED!How App Upload Works
When you pass
app_path, NovaActMobile handles the full lifecycle:- Uploads the APK to Device Farm (skips re-upload for identical files)
- Installs the app on the device
- Launches the specified activity
- Tears down the session on exit
1
2
3
4
5
6
7
with NovaActMobile(
app_package=APP_PACKAGE,
app_activity=APP_ACTIVITY,
app_path=APP_PATH, # ← triggers upload
device_arn=DEFAULT_DEVICE_ARN,
) as nova:
# App is installed and running — start testingTraditional Appium vs Nova Act
| Traditional Appium | Nova Act | |
|---|---|---|
| Files | 7 (6 Page Objects + 1 spec) | 1 |
| Code | 200+ lines | ~80 lines |
| Selectors | Resource IDs, UiSelectors | Plain English |
| Maintenance | Breaks on UI changes | Resilient |
| Navigation | Scroll + wait loops | navigate_to_page(nova, "Login Page") |
See
src/android_traditional_appium.py for the full side-by-side comparison.Test Your Own App
Use
src/custom_app_getting_started.py as a template:1
2
3
4
python custom_app_getting_started.py \
--app-path ../apps/your-app.apk \
--app-package com.yourcompany.app \
--app-activity .MainActivityCost
| Component | Price |
|---|---|
| AWS Device Farm | $0.17/device minute (1,000 min free with Free Tier) |
| Amazon Nova Act | $4.75 per agent hour (pricing ) |
| This 4-test suite | ~$1.25 (5 min device time + ~5 min agent time) |
Conclusion
You built a full mobile test suite in 80 lines of Python. The five patterns —
type_text(), clear_focused(), one-action-per-call, separate verifications, and exact UI labels — produce reliable tests that survive UI redesigns. Clone the repo, set your .env, and run your first test in under 5 minutes.Resources
- GitHub: srcsakthivel/getting-started-device-farm-with-nova-act
- Amazon Nova Act: docs.aws.amazon.com/nova-act
- AWS Device Farm: docs.aws.amazon.com/devicefarm
- nova-act-samples: github.com/amazon-agi-labs/nova-act-samples
- Mobile Actuator (source): The
NovaActMobilelibrary used in this project originates from the nova-act-samples Mobile Actuator , vendored with adjusted imports
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article