Automation and Reviewing Outputs

In the previous section, we launched the Android VM, installed Kiwix, and staged the offline ZIM file. In this section, we will add a Maestro flow, relaunch the experiment, and review the resulting artifacts.

Create the Maestro Kiwix Flow

Create a Maestro flow named kiwix_setup_and_load.yaml in the tutorial model component’s vm_resources directory:

Listing 24 vm_resources/kiwix_setup_and_load.yaml
appId: org.kiwix.kiwixmobile.standalone
---
- launchApp

- waitForAnimationToEnd

- extendedWaitUntil:
    visible: "GET STARTED"
    timeout: 30000

- tapOn: "GET STARTED"

- waitForAnimationToEnd

- extendedWaitUntil:
    visible: "Library"
    timeout: 30000

- extendedWaitUntil:
    visible: "No files here"
    timeout: 30000

- tapOn:
    id: "org.kiwix.kiwixmobile.standalone:id/select_file"

- extendedWaitUntil:
    visible: "Show roots"
    timeout: 30000

- tapOn: "Show roots"

- extendedWaitUntil:
    visible: "sdk_gphone64_x86_64"
    timeout: 30000

- tapOn: "sdk_gphone64_x86_64"

- extendedWaitUntil:
    visible: "Download"
    timeout: 30000

- tapOn: "Download"

- extendedWaitUntil:
    visible: "Kiwix"
    timeout: 30000

- tapOn: "Kiwix"

- extendedWaitUntil:
    visible: "wikipedia_en_computer_nopic_2026-06.zim"
    timeout: 30000

- tapOn: "wikipedia_en_computer_nopic_2026-06.zim"

- waitForAnimationToEnd

- takeScreenshot: kiwix-loaded-computer

This flow launches Kiwix, handles the first-run screen, opens the Android file picker, selects the staged ZIM file, waits for the content to load, and takes a screenshot.

The select_file step taps the file-selection button on the Kiwix Library screen. In this Kiwix version, that button does not have visible text, so the flow uses its Android resource ID:

- tapOn:
    id: "org.kiwix.kiwixmobile.standalone:id/select_file"

Warning

Mobile app UIs change over time. If this flow fails, use scrcpy to inspect the current UI and update the text selectors as needed. Depending on the Kiwix version and Android system UI, the file picker may use slightly different labels.

Update the Plugin

Replace plugin.py with this version:

Listing 25 plugin.py
from android.pixel9a import AndroidPixel9a
from android.maestro import AndroidMaestroEndpoint

from firewheel.control.experiment_graph import Vertex, AbstractPlugin


class Plugin(AbstractPlugin):
    """
    Android application analysis tutorial topology.

    This version launches one Android Pixel 9a emulator, installs Kiwix,
    stages an offline ZIM file, runs a Maestro setup/load flow, and collects
    the Kiwix application data directory.
    """

    def run(self):
        """
        Build the Android application analysis experiment.
        """
        phone = Vertex(self.g, name="android-phone")
        phone.decorate(AndroidPixel9a)
        phone.decorate(AndroidMaestroEndpoint)

        # Installing the Kiwix app
        phone.install_apk(
            start_time=-50,
            filenames="org.kiwix.kiwixmobile.standalone.apk",
        )

        # Ensure the directories are available
        phone.run_executable(
            -40,
            "/system/bin/mkdir",
            ["-p", "/sdcard/Download/Kiwix"],
            vm_resource=False,
        )

        # Copy the offline Wikipedia ZIM file to shared storage.
        phone.drop_file(
            -30,
            "/sdcard/Download/Kiwix/wikipedia_en_computer_nopic_2026-06.zim",
            "wikipedia_en_computer_nopic_2026-06.zim",
        )

        # Use Maestro to load the staged ZIM file in Kiwix.
        phone.add_maestro_flow(
            start_time=10,
            flow_resource="kiwix_setup_and_load.yaml",
            timeout=180,
            fail_experiment_on_failure=False,
        )

        # Export the application information for analysis
        phone.file_transfer_once(
            location="/data/data/org.kiwix.kiwixmobile.standalone",
            start_time=220,
            destination=None,
        )

This adds two actions:

add_maestro_flow

Runs the Maestro flow against the Android VM.

file_transfer_once

Collects /data/data/org.kiwix.kiwixmobile.standalone after the Maestro timeout window.

Note

The file transfer is scheduled at time 220 so it does not “race” with the Maestro flow, which starts at time 10 and has a timeout of 180 seconds.

Relaunch the Experiment

Relaunch the experiment:

firewheel experiment -r tutorials.android_app_analysis minimega.launch

Wait for the Android VM to boot, install the APK, stage the ZIM file, run the Maestro flow, and collect the application data.

You can watch the VM resource log while the experiment configures:

tail -f /scratch/firewheel/vm_resource_logs/android-phone.log

Review Maestro Artifacts

The Maestro artifacts are written under:

/scratch/firewheel/vm_resource_logs/maestro/android-phone/kiwix_setup_and_load/<timestamp>/

For example, one run might create:

/scratch/firewheel/vm_resource_logs/maestro/android-phone/kiwix_setup_and_load/20260811T180128Z/

The timestamp changes on every run, so we recommend using find rather than hard-coding the directory name.

A typical artifact tree looks like this:

/scratch/firewheel/vm_resource_logs/maestro/android-phone/
└── kiwix_setup_and_load/
    └── <timestamp>/
        ├── adb_devices.txt
        ├── adb_version.txt
        ├── after_getprop.txt
        ├── after_screenshot.png
        ├── after_window.txt
        ├── before_getprop.txt
        ├── before_screenshot.png
        ├── before_window.txt
        ├── command.json
        ├── command.txt
        ├── exit_code.txt
        ├── flow.yaml
        ├── maestro_stderr.txt
        ├── maestro_stdout.txt
        ├── maestro_version.txt
        ├── resolved_device.txt
        ├── resolved_paths.json
        ├── result.json
        └── runner_input.json

The most readable file is often maestro_stdout.txt because it shows each Maestro step and whether that step completed.

View the most recent Maestro stdout:

cat $(find /scratch/firewheel/vm_resource_logs/maestro/android-phone/kiwix_setup_and_load -name maestro_stdout.txt | sort | tail -n 1)

Example output:

Running on Pixel_9a
 > Flow kiwix_setup_and_load
Launch app "org.kiwix.kiwixmobile.standalone"... COMPLETED
Wait for animation to end... COMPLETED
Assert that "GET STARTED" is visible... COMPLETED
Tap on "GET STARTED"... COMPLETED
Wait for animation to end... COMPLETED
Assert that "Library" is visible... COMPLETED
Assert that "No files here" is visible... COMPLETED
Tap on id: org.kiwix.kiwixmobile.standalone:id/select_file... COMPLETED
Assert that "Show roots" is visible... COMPLETED
Tap on "Show roots"... COMPLETED
Assert that "sdk_gphone64_x86_64" is visible... COMPLETED
Tap on "sdk_gphone64_x86_64"... COMPLETED
Assert that "Download" is visible... COMPLETED
Tap on "Download"... COMPLETED
Assert that "Kiwix" is visible... COMPLETED
Tap on "Kiwix"... COMPLETED
Assert that "wikipedia_en_computer_nopic_2026-06.zim" is visible... COMPLETED
Tap on "wikipedia_en_computer_nopic_2026-06.zim"... COMPLETED
Wait for animation to end... COMPLETED
Take screenshot kiwix-loaded-computer... COMPLETED

You can also view the machine-readable result:

cat $(find /scratch/firewheel/vm_resource_logs/maestro/android-phone -name result.json | sort | tail -n 1)

A successful run should have a result.json similar to:

{
  "artifacts": {
    "adb_devices": "adb_devices.txt",
    "after_screenshot": "after_screenshot.png",
    "after_window": "after_window.txt",
    "before_screenshot": "before_screenshot.png",
    "before_window": "before_window.txt",
    "command": "command.txt",
    "command_json": "command.json",
    "exit_code": "exit_code.txt",
    "flow": "flow.yaml",
    "resolved_device": "resolved_device.txt",
    "resolved_paths": "resolved_paths.json",
    "runner_input": "runner_input.json",
    "stderr": "maestro_stderr.txt",
    "stdout": "maestro_stdout.txt"
  },
  "device": "emulator-5554",
  "duration_seconds": 61.90870464127511,
  "end_time_utc": "2026-08-11T19:19:51Z",
  "fail_experiment_on_failure": false,
  "flow_label": "kiwix_setup_and_load",
  "flow_resource": "kiwix_setup_and_load.yaml",
  "maestro_exit_code": 0,
  "runner_exit_code": 0,
  "start_time_utc": "2026-08-11T19:18:49Z",
  "status": "pass",
  "timed_out": false,
  "tool": "maestro",
  "vm_name": "android-phone"
}

The exact timestamps, duration, and ADB serial will differ between runs.

Important Maestro files include:

File

Description

maestro_stdout.txt

Step-by-step Maestro output.

result.json

Machine-readable status summary.

resolved_device.txt

Runtime ADB serial used by the flow.

command.txt

Exact Maestro command.

maestro_stderr.txt

Maestro standard error.

before_screenshot.png

Screenshot before the flow.

after_screenshot.png

Screenshot after the flow.

flow.yaml

Copy of the flow that was executed.

Screenshot artifact captured before running the Kiwix Maestro flow

Fig. 5 Example screenshot captured before the Maestro flow ran.

Screenshot artifact captured after running the Kiwix setup and load flow

Fig. 6 Example screenshot captured after the Maestro flow ran.

Review Collected Application Data

The plugin collected:

/data/data/org.kiwix.kiwixmobile.standalone

In the default tutorial environment, FIREWHEEL writes transferred files under:

/scratch/firewheel/transfers/

For this tutorial, the collected data should be located at:

/scratch/firewheel/transfers/android-phone/data/data/org.kiwix.kiwixmobile.standalone

Check that the directory exists:

ls -lah /scratch/firewheel/transfers/android-phone/data/data/org.kiwix.kiwixmobile.standalone

Manual Debugging with scrcpy

scrcpy is useful when automation fails or when you need to interact with the phone manually.

First identify the runtime ADB serial:

adb devices
adb devices output showing an Android emulator serial

Fig. 7 Example adb devices output. The emulator serial shown here is only an example; always use the serial reported in your experiment.

Start scrcpy:

scrcpy -s <adb-serial> --no-audio

For example:

scrcpy -s emulator-5554 --no-audio
Kiwix running on the Android emulator through scrcpy

Fig. 8 Example scrcpy view of the Android emulator after launching Kiwix.

Note

If the Android emulator is running on a remote minimega host, forward both the Android console port and the ADB port before using local ADB or scrcpy. For example, if minimega reports console port 5554 and ADB port 5555:

ssh \
  -L 127.0.0.1:5554:127.0.0.1:5554 \
  -L 127.0.0.1:5555:127.0.0.1:5555 \
  fw-server

If scrcpy cannot find the device, restarting the local ADB server often helps:

adb kill-server
adb start-server
adb devices
scrcpy -s <adb-serial> --no-audio

Troubleshooting

APK install fails

Check the VM resource log:

tail -n 100 /scratch/firewheel/vm_resource_logs/android-phone.log

Common causes are a missing APK, a filename mismatch, an incompatible APK, or a split APK provided as if it were a complete APK.

ZIM file is missing

Check the staged file on the device:

adb -s <adb-serial> shell ls -lh /sdcard/Download/Kiwix

If it is missing, verify that wikipedia_en_computer_nopic_2026-06.zim exists in vm_resources/ and matches the filename in plugin.py.

Maestro flow fails

Inspect the most recent stdout, stderr, and screenshots:

find /scratch/firewheel/vm_resource_logs/maestro/android-phone -name maestro_stdout.txt -print
find /scratch/firewheel/vm_resource_logs/maestro/android-phone -name maestro_stderr.txt -print

Common causes include changed UI text, a different Android file-picker label, or a wait that is too short. Use scrcpy and the UI hierarchy dump to update kiwix_setup_and_load.yaml.

To inspect the Android UI hierarchy:

adb -s <adb-serial> shell uiautomator dump /sdcard/window.xml
adb -s <adb-serial> pull /sdcard/window.xml
grep -i -E "text=|content-desc=|resource-id=|clickable=\"true\"" window.xml

Collected data is missing

Check the VM resource logs and the expected transfer directory:

ls -lah /scratch/firewheel/transfers/android-phone/data/data/org.kiwix.kiwixmobile.standalone

Next Steps

At this point, you have a working Android experiment that installs an APK, stages offline content, uses Maestro to load that content, and collects application data.

Continue to Post-Experiment Analysis if you want to inspect the collected Kiwix files.