allwright-surface-mobile-android 0.2.0

Android mobile surface plugin for the allwright engine.
Documentation
use allwright_surface_mobile::{ConnectOptions, LaunchOptions, MobilePlatform};
use allwright_surface_mobile_android::{click_element, connect, launch_app};
use serde_json::json;
use std::env;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let args = parse_args(env::args().skip(1).collect())?;

    let session = connect(&ConnectOptions {
        platform: MobilePlatform::Android,
        device: args.device,
        adb_endpoint: args.adb_endpoint,
        agent_endpoint: None,
        preserve_app_state: false,
        timeout_ms: Some(10_000),
    })?;

    let page = launch_app(
        &session.browser_session,
        &LaunchOptions {
            apk_path: Some(args.apk_path),
            app_id: args.app_id,
            launch_activity: args.activity,
            stop_before_launch: args.stop_before_launch,
            timeout_ms: Some(30_000),
        },
    )?;

    let clicked = click_element(
        &session.browser_session,
        &page.page_session,
        &args.selector,
        Some(10_000),
    )?;

    println!(
        "{}",
        serde_json::to_string_pretty(&json!({
            "session": session,
            "page": page,
            "click": clicked,
        }))?
    );

    Ok(())
}

#[derive(Debug, Default)]
struct SmokeArgs {
    apk_path: String,
    selector: String,
    app_id: Option<String>,
    activity: Option<String>,
    device: Option<String>,
    adb_endpoint: Option<String>,
    stop_before_launch: bool,
}

fn parse_args(args: Vec<String>) -> Result<SmokeArgs, Box<dyn Error>> {
    let mut parsed = SmokeArgs::default();
    let mut index = 0usize;
    while index < args.len() {
        match args[index].as_str() {
            "--apk" => {
                index += 1;
                parsed.apk_path = required_value(&args, index, "--apk")?;
            }
            "--selector" => {
                index += 1;
                parsed.selector = required_value(&args, index, "--selector")?;
            }
            "--app-id" => {
                index += 1;
                parsed.app_id = Some(required_value(&args, index, "--app-id")?);
            }
            "--activity" => {
                index += 1;
                parsed.activity = Some(required_value(&args, index, "--activity")?);
            }
            "--device" => {
                index += 1;
                parsed.device = Some(required_value(&args, index, "--device")?);
            }
            "--adb-endpoint" => {
                index += 1;
                parsed.adb_endpoint = Some(required_value(&args, index, "--adb-endpoint")?);
            }
            "--stop-before-launch" => parsed.stop_before_launch = true,
            "--help" | "-h" => {
                print_usage();
                std::process::exit(0);
            }
            other => {
                return Err(format!("unknown argument `{other}`").into());
            }
        }
        index += 1;
    }

    if parsed.apk_path.trim().is_empty() {
        return Err("missing required `--apk <path>` argument".into());
    }
    if parsed.selector.trim().is_empty() {
        return Err("missing required `--selector <selector>` argument".into());
    }

    Ok(parsed)
}

fn required_value(args: &[String], index: usize, flag: &str) -> Result<String, Box<dyn Error>> {
    args.get(index)
        .cloned()
        .ok_or_else(|| format!("missing value for `{flag}`").into())
}

fn print_usage() {
    eprintln!(
        "Usage: cargo run -p allwright-surface-mobile-android --example smoke -- \
  --apk /path/to/app.apk \
  --selector 'xpath=//*[@text=\"Login\"]' \
  [--app-id dev.allwright.sample] \
  [--activity .MainActivity] \
  [--device emulator-5554] \
  [--adb-endpoint 192.168.1.10:5555] \
  [--stop-before-launch]"
    );
}