From 20c514af2cda47b40d3defe1a779de4296e4101f Mon Sep 17 00:00:00 2001 From: Jared Rhodes Date: Sun, 12 Oct 2025 19:15:25 -0400 Subject: [PATCH] Add interactive manual hardware test harness --- README.md | 7 + tests/manual/README.md | 67 ++++++++ tests/manual/__init__.py | 1 + tests/manual/conftest.py | 14 ++ tests/manual/manual_test_helper.py | 85 +++++++++++ tests/manual/phone_pi.md | 202 +++++++++++++++++++++++++ tests/manual/test_phone_pi_manual.py | 146 ++++++++++++++++++ tests/manual/test_two_phones_manual.py | 122 +++++++++++++++ tests/manual/two_phones.md | 175 +++++++++++++++++++++ 9 files changed, 819 insertions(+) create mode 100644 tests/manual/README.md create mode 100644 tests/manual/__init__.py create mode 100644 tests/manual/conftest.py create mode 100644 tests/manual/manual_test_helper.py create mode 100644 tests/manual/phone_pi.md create mode 100644 tests/manual/test_phone_pi_manual.py create mode 100644 tests/manual/test_two_phones_manual.py create mode 100644 tests/manual/two_phones.md diff --git a/README.md b/README.md index 145be6d..24d94f0 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,10 @@ An additional demo shows how to transmit sensor data through the Pi’s audio ja connecting a TRRS cable and using the `minimodem` tool to send readings as audio tones. The MobileIoT app has an "Audio Jack" page that listens on the microphone and displays the decoded messages. + +## Manual hardware test project + +Run the interactive prompts with `python -m pytest tests/manual --run-manual` whenever you want to record manual +verification alongside automated results. + +Detailed procedures for multi-device manual verification live in [`tests/manual`](tests/manual/README.md). Use these checklists when you have access to two phones or a phone and Raspberry Pi hardware to exercise the BLE, NFC, USB, audio, and vision demos end-to-end. diff --git a/tests/manual/README.md b/tests/manual/README.md new file mode 100644 index 0000000..356481c --- /dev/null +++ b/tests/manual/README.md @@ -0,0 +1,67 @@ +# Manual Cross-Device Test Project + +This project captures the hardware-in-the-loop test cases that cannot be +automated easily because they require either two mobile devices or a mobile +device paired with a Raspberry Pi running the demo scripts from `src/pi`. +It complements the automated unit and integration tests by providing detailed +manual procedures for validating end-to-end behavior before demos or releases. + +## How to use this project + +1. Pick the scenario that matches the hardware you have available: + - **Phone ↔ Raspberry Pi** procedures validate the MAUI app working against +the Python demo services. + - **Phone ↔ Phone** procedures validate peer-to-peer features that rely on +platform services such as Wi-Fi Direct, NFC, or beacon scanning. +2. Review the common setup section of the relevant scenario to provision +hardware and align app builds and configuration. +3. Execute each manual test case in order, marking the results and capturing +any deviations in your test log. + +## Running the manual test harness + +Install `pytest` in your Python environment (e.g., `pip install pytest`) if it is not already available. +A lightweight `pytest` suite mirrors the checklists so you can track results and +store execution notes alongside other test runs. To run the prompts, supply the +environment variable or CLI flag that opts into manual validation: + +```bash +python -m pytest tests/manual --run-manual +# or +RUN_MANUAL_TESTS=1 python -m pytest tests/manual +``` + +Each test prints the associated plan, references the detailed markdown +specification, and waits for you to confirm the outcome. Type `PASS` (or `Y`) to +record success or provide any other response to fail the case for follow-up. +Skipped tests indicate that the manual flag was not provided. + +## Test inventory + +The following table lists every manual test type covered by this project along +with the primary app feature or Raspberry Pi script it exercises. Detailed +steps for each test appear in the linked scenario documents. + +| ID | Scenario | Test type | App feature(s) | Raspberry Pi script(s) | +| --- | --- | --- | --- | --- | +| PI-BLE-01 | Phone ↔ Pi | BLE sensor telemetry handshake | `BlePage` / `BleViewModel` | `src/pi/bluetoothle_demo.py` | +| PI-BLE-02 | Phone ↔ Pi | BLE LED control loopback | `BlePage` / `BleViewModel` | `src/pi/bluetoothle_demo.py` | +| PI-BLE-03 | Phone ↔ Pi | BLE reconnection & error recovery | `BlePage` / `BleViewModel` | `src/pi/bluetoothle_demo.py` | +| PI-BEACON-01 | Phone ↔ Pi | Beacon advertisement detection | `BeaconPage` / `BeaconScanViewModel` | `src/pi/beacon_demo.py` | +| PI-SERIAL-01 | Phone ↔ Pi | USB serial LED toggling | `SerialPage` / `SerialDemoViewModel` | `src/pi/serial_demo.py`, `run_serial_demo.sh` | +| PI-USB-01 | Phone ↔ Pi | USB bulk ping echo | `UsbPage` / `UsbViewModel` | `src/pi/usb_demo.py` (USB gadget mode) | +| PI-AUDIO-01 | Phone ↔ Pi | Audio jack telemetry ingest | `AudioPage` / `AudioDemoViewModel` | `src/pi/audio_demo.py` | +| PHONE-P2P-01 | Phone ↔ Phone | Wi-Fi Direct discovery and ping | `WifiDirectPage` / `P2pViewModel` | — | +| PHONE-P2P-02 | Phone ↔ Phone | Peer message delivery | `WifiDirectPage` / `P2pViewModel` | — | +| PHONE-NFC-01 | Phone ↔ Phone | NFC tag read/write round-trip | `NfcPage` / `NfcPageViewModel` | — | +| PHONE-NFC-02 | Phone ↔ Phone | NFC peer-to-peer handover | `NfcP2PPage` / `NfcP2PViewModel` | — | +| PHONE-BEACON-01 | Phone ↔ Phone | Beacon detection via companion device | `BeaconPage` / `BeaconScanViewModel` | Companion beacon app | +| PHONE-VISION-01 | Phone ↔ Phone | QR scan interoperability smoke test | `VisionPage` / `VisionViewModel` | — | + +## Manual test specifications + +- [Phone ↔ Raspberry Pi manual tests](phone_pi.md) +- [Phone ↔ Phone manual tests](two_phones.md) + +Each specification contains prerequisites, environment checklists, and the +step-by-step procedures for executing the listed test IDs. diff --git a/tests/manual/__init__.py b/tests/manual/__init__.py new file mode 100644 index 0000000..3dcf63e --- /dev/null +++ b/tests/manual/__init__.py @@ -0,0 +1 @@ +"""Manual hardware-in-the-loop test suite.""" diff --git a/tests/manual/conftest.py b/tests/manual/conftest.py new file mode 100644 index 0000000..d9ee8fc --- /dev/null +++ b/tests/manual/conftest.py @@ -0,0 +1,14 @@ +from .manual_test_helper import manual_test # noqa: F401 + + +def pytest_addoption(parser): + parser.addoption( + "--run-manual", + action="store_true", + default=False, + help="Execute manual hardware-in-the-loop validation tests.", + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "manual: marks tests that require manual interaction with hardware") diff --git a/tests/manual/manual_test_helper.py b/tests/manual/manual_test_helper.py new file mode 100644 index 0000000..be1bd80 --- /dev/null +++ b/tests/manual/manual_test_helper.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import os +import textwrap +from dataclasses import dataclass +from typing import List, Optional + +import pytest + +MANUAL_ENV_FLAG = "RUN_MANUAL_TESTS" + + +@dataclass +class ManualStep: + """Represents a single manual validation step.""" + + description: str + + +@dataclass +class ManualTestPlan: + """Container for the metadata shared across manual tests.""" + + identifier: str + title: str + objective: str + steps: List[ManualStep] + expected_result: str + references: Optional[List[str]] = None + + def format_instructions(self) -> str: + bullet_steps = "\n".join(f" {idx}. {step.description}" for idx, step in enumerate(self.steps, start=1)) + references = "\n".join(f" - {ref}" for ref in self.references or []) + references_block = f"\nReferences:\n{references}" if references else "" + return textwrap.dedent( + f""" + Manual test {self.identifier}: {self.title} + Objective: {self.objective} + + Steps: + {bullet_steps} + + Expected result: + {self.expected_result}{references_block} + """ + ).strip() + + +def should_run_manual_tests(request: pytest.FixtureRequest) -> bool: + """Determines whether manual tests should execute.""" + + if os.getenv(MANUAL_ENV_FLAG) in {"1", "true", "True"}: + return True + + run_option = False + if hasattr(request.config, "getoption"): + try: + run_option = bool(request.config.getoption("--run-manual")) + except (ValueError, AttributeError): + run_option = False + return run_option + + +class ManualTestExecutor: + """Utility that renders instructions and collects user confirmation.""" + + def execute(self, plan: ManualTestPlan) -> None: + instructions = plan.format_instructions() + print("\n" + "=" * 80) + print(instructions) + print("=" * 80 + "\n") + confirmation = input("Type PASS to confirm the expected result was observed (anything else fails): ").strip() + if confirmation.lower() not in {"pass", "p", "y", "yes"}: + pytest.fail(f"Manual verification failed for {plan.identifier}") + + +@pytest.fixture +def manual_test(request: pytest.FixtureRequest) -> ManualTestExecutor: + """Fixture that controls whether manual tests execute.""" + + if not should_run_manual_tests(request): + pytest.skip( + "Manual hardware validation skipped. Set RUN_MANUAL_TESTS=1 or pass --run-manual to run these tests." + ) + return ManualTestExecutor() diff --git a/tests/manual/phone_pi.md b/tests/manual/phone_pi.md new file mode 100644 index 0000000..18c2ffb --- /dev/null +++ b/tests/manual/phone_pi.md @@ -0,0 +1,202 @@ +# Phone ↔ Raspberry Pi Manual Tests + +These tests validate the MAUI application when paired with a Raspberry Pi that +runs the demo scripts located in `src/pi`. Execute the tests in order so that +shared setup (Bluetooth pairing, USB gadget mode, wiring) stays intact. + +## Hardware and software prerequisites + +- One Android phone with the MobileIoT app installed from the current branch + build. (iOS works for BLE/NFC but USB gadget and audio modem steps are + Android-specific.) +- One Raspberry Pi 4 or newer with Raspberry Pi OS Bookworm or Bullseye. +- Pi accessories: + - DHT22 sensor wired to GPIO4 for the BLE demo. + - Single LED with resistor on GPIO17 for BLE and serial demos. + - TRRS audio cable connecting the Pi headphone jack to the phone microphone + input for the audio telemetry demo. + - USB-C data cable for gadget-mode serial/bulk demos. +- Pi configured with: + - Bluetooth and Wi-Fi enabled. + - `bluezero`, `Adafruit_DHT`, `RPi.GPIO`, and `minimodem` installed + (`pip install -r src/pi/requirements.txt`). + - Serial gadget support enabled (`dtoverlay=dwc2,dr_mode=peripheral` and + modules `libcomposite`, `g_serial`, `g_zero` available). +- Ensure both devices use the same time zone to simplify telemetry correlation. + +## Common setup checklist + +1. Update the repository on the Pi and this workstation to the same commit. +2. Copy the contents of `src/pi` to the Raspberry Pi (or pull from Git). +3. On the Pi, create a Python virtual environment and install requirements: + ```bash + cd ~/mobileiot/src/pi + python3 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + ``` +4. Confirm Bluetooth is enabled (`sudo systemctl status bluetooth`). +5. Verify the DHT22 sensor and LED are wired correctly and the TRRS cable is + unplugged until the audio test to avoid feedback. +6. Deploy the MobileIoT app build to the phone and grant requested permissions + (location, microphone, nearby devices, NFC, camera). + +## Test cases + +### PI-BLE-01 – BLE sensor telemetry handshake + +**Objective:** Validate that the MAUI app discovers the Pi BLE peripheral and +reads temperature/humidity values from `bluetoothle_demo.py`. + +**Prerequisites:** +- Pi shell with virtual environment activated. +- Phone and Pi within 2 meters. + +**Steps:** +1. On the Pi, start the BLE demo: + ```bash + python bluetoothle_demo.py + ``` +2. On the phone, open the **BLE Demo** page and tap **Connect**. +3. Once connected, tap **Refresh Sensor** twice, five seconds apart. +4. Observe the temperature and humidity values displayed on screen. + +**Expected results:** +- The connection status changes to “Connected” within 30 seconds. +- Temperature and humidity fields update with non-zero values that are stable + between reads (differences < 1°C and < 5% RH unless the environment changes). +- No error toast or log entries appear. + +**Cleanup:** Leave the BLE demo running for the next test. + +### PI-BLE-02 – BLE LED control loopback + +**Objective:** Ensure the app toggles the Pi LED characteristic and the Pi +responds appropriately. + +**Prerequisites:** +- PI-BLE-01 completed successfully with the BLE demo still running. +- Physical view of the LED connected to GPIO17. + +**Steps:** +1. On the phone, tap **Toggle LED** repeatedly five times, waiting one second + between taps. +2. Watch the physical LED and the on-screen LED status text. +3. Use the Pi terminal to confirm log output indicates LED changes. + +**Expected results:** +- The LED turns on and off in sync with each tap. +- The app button text alternates between “Turn LED On/Off” and status label + mirrors the LED state. +- The Pi console logs `LED turned ON`/`LED turned OFF` messages. + +### PI-BLE-03 – BLE reconnection & error recovery + +**Objective:** Verify the app handles disconnections and connection failures. + +**Prerequisites:** +- BLE demo is still running from prior tests. + +**Steps:** +1. On the phone, tap **Disconnect**. +2. Stop the BLE script with `Ctrl+C`. +3. Attempt to reconnect from the app (tap **Connect**). Wait for the failure. +4. Restart the BLE script on the Pi. +5. Tap **Connect** again and confirm reconnection. +6. Trigger one more sensor read to ensure telemetry resumes. + +**Expected results:** +- After step 1 the status changes to “Disconnected”. +- Step 3 shows “Connection failed” within 30 seconds and no crash occurs. +- After restarting the script, the app reconnects and sensor refresh succeeds. + +### PI-BEACON-01 – Beacon advertisement detection + +**Objective:** Confirm the app discovers iBeacon advertisements emitted by the +Pi running `beacon_demo.py`. + +**Prerequisites:** +- BLE stack idle (stop the GATT demo). + +**Steps:** +1. On the Pi, run `python beacon_demo.py`. +2. On the phone, open **Beacon Scanner**. +3. Wait up to one minute for the device list to populate. +4. Move one meter away and back to observe RSSI changes. + +**Expected results:** +- A device entry with UUID `12345678-1234-1234-1234-1234567890AB` appears. +- RSSI values update as you change distance, roughly matching proximity. +- The app UI stays responsive while the Pi console logs remain quiet (beacon + script prints start message only). + +### PI-SERIAL-01 – USB serial LED toggling + +**Objective:** Validate USB CDC ACM communication between the phone and +`serial_demo.py`. + +**Prerequisites:** +- Pi configured for gadget mode with `g_serial` loaded (`sudo modprobe g_serial`). +- USB-C cable connected between devices. + +**Steps:** +1. On the Pi, run `./run_serial_demo.sh` (or activate venv and run + `python serial_demo.py --port /dev/ttyGS0`). +2. On the phone, open the **USB Serial** page and tap **Connect**. +3. Tap **Send Command** three times, pausing two seconds between taps. +4. Watch the LED on GPIO17 and the on-screen log. + +**Expected results:** +- App log shows “Connected” then alternating `TX: LED_ON/LED_OFF` entries. +- Pi console prints matching “Received” lines and ACK responses. +- Physical LED toggles accordingly. + +### PI-USB-01 – USB bulk ping echo + +**Objective:** Exercise the bulk transfer ping using the Linux `g_zero` +gadget that echoes payloads in hardware. + +**Prerequisites:** +- `g_zero` module loaded on the Pi (`sudo modprobe g_zero`). +- Devices still connected over USB-C. + +**Steps:** +1. On the phone, open the **USB Bulk** page. +2. Tap **Connect** to list devices and establish the session. +3. Tap **Send Ping** twice. +4. Observe the log output. + +**Expected results:** +- The app log displays “Connected to …” followed by `RX 64 bytes` entries for + each ping. +- No Python script is required; the Pi does not need additional terminals for + this test. + +### PI-AUDIO-01 – Audio jack telemetry ingest + +**Objective:** Verify that the phone decodes temperature telemetry transmitted +through the Pi audio demo. + +**Prerequisites:** +- TRRS cable connected from Pi audio output to phone microphone input. +- Phone volume at 70% and microphone permissions granted. + +**Steps:** +1. On the Pi, stop other audio applications and run `python audio_demo.py`. +2. On the phone, open **Audio Modem** and tap **Start**. +3. Monitor the status label for at least three telemetry updates. +4. Tap **Stop** and then terminate the Pi script with `Ctrl+C`. + +**Expected results:** +- The status label transitions from “Idle” → “Listening…” → telemetry readings + like `42.1` every ~5 seconds. +- Values match the Pi CPU temperature reported in the terminal (if printed) or + stay within plausible range (30–70°C). +- Stopping the app halts updates and the Pi script exits cleanly on `Ctrl+C`. + +## Post-test cleanup + +- Stop all Python scripts and unload gadget modules (`sudo modprobe -r g_serial + g_zero`). +- Disconnect peripherals, restore normal Pi audio routing, and document any + deviations encountered during the run. diff --git a/tests/manual/test_phone_pi_manual.py b/tests/manual/test_phone_pi_manual.py new file mode 100644 index 0000000..4763f2b --- /dev/null +++ b/tests/manual/test_phone_pi_manual.py @@ -0,0 +1,146 @@ +import pytest + +from .manual_test_helper import ManualStep, ManualTestPlan + + +@pytest.mark.manual +def test_pi_ble_sensor_handshake(manual_test): + plan = ManualTestPlan( + identifier="PI-BLE-01", + title="BLE sensor telemetry handshake", + objective="Verify the phone discovers the Pi GATT server and retrieves sensor readings.", + steps=[ + ManualStep("Start `python bluetoothle_demo.py` on the Raspberry Pi in the src/pi directory."), + ManualStep("Open the BLE Demo page on the phone and tap Connect."), + ManualStep("Refresh the sensor values twice, waiting roughly five seconds between taps."), + ManualStep("Observe the temperature and humidity values shown in the app."), + ], + expected_result=( + "Connection status switches to Connected within 30 seconds, and stable non-zero temperature and humidity readings appear " + "without error toasts or log entries." + ), + references=["tests/manual/phone_pi.md#pi-ble-01-–-ble-sensor-telemetry-handshake"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_ble_led_control(manual_test): + plan = ManualTestPlan( + identifier="PI-BLE-02", + title="BLE LED control loopback", + objective="Ensure the phone toggles the Pi LED characteristic and both sides log the change.", + steps=[ + ManualStep("Confirm the BLE demo from PI-BLE-01 is still running on the Pi."), + ManualStep("Tap Toggle LED on the BLE Demo page five times, leaving a one-second gap between taps."), + ManualStep("Watch the physical LED attached to GPIO17 while monitoring the on-screen LED status."), + ManualStep("Check the Pi terminal for LED state log messages."), + ], + expected_result=( + "The physical LED and app status text switch in sync for each tap, and the Pi console records matching 'LED turned ON/OFF' entries." + ), + references=["tests/manual/phone_pi.md#pi-ble-02-–-ble-led-control-loopback"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_ble_reconnection(manual_test): + plan = ManualTestPlan( + identifier="PI-BLE-03", + title="BLE reconnection and error recovery", + objective="Validate disconnect handling and recovery after the GATT server restarts.", + steps=[ + ManualStep("From the app, tap Disconnect."), + ManualStep("Stop the BLE script on the Pi with Ctrl+C."), + ManualStep("Attempt to reconnect from the phone and wait for the failure state."), + ManualStep("Restart `python bluetoothle_demo.py` on the Pi."), + ManualStep("Tap Connect again, then perform another sensor refresh."), + ], + expected_result=( + "Status changes to Disconnected after the first step, the failed reconnect surfaces a connection failed message without crashes, and " + "after restarting the script the app reconnects and resumes telemetry refresh successfully." + ), + references=["tests/manual/phone_pi.md#pi-ble-03-–-ble-reconnection--error-recovery"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_beacon_detection(manual_test): + plan = ManualTestPlan( + identifier="PI-BEACON-01", + title="Beacon advertisement detection", + objective="Confirm iBeacon advertisements from the Pi appear in the phone scanner UI.", + steps=[ + ManualStep("Stop the BLE GATT demo if it is still running."), + ManualStep("Launch `python beacon_demo.py` on the Pi."), + ManualStep("Open the Beacon Scanner page on the phone."), + ManualStep("Wait up to one minute for the advertisement to appear, then walk about a meter away and back."), + ], + expected_result=( + "The scanner lists the beacon UUID 12345678-1234-1234-1234-1234567890AB and RSSI updates reflect distance changes while the UI stays responsive." + ), + references=["tests/manual/phone_pi.md#pi-beacon-01-–-beacon-advertisement-detection"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_usb_serial_led(manual_test): + plan = ManualTestPlan( + identifier="PI-SERIAL-01", + title="USB serial LED toggling", + objective="Validate CDC ACM messaging between the app and `serial_demo.py`.", + steps=[ + ManualStep("Load the g_serial gadget on the Pi and start ./run_serial_demo.sh or python serial_demo.py --port /dev/ttyGS0."), + ManualStep("Open the USB Serial page on the phone and tap Connect."), + ManualStep("Tap Send Command three times with two-second pauses."), + ManualStep("Observe the physical LED and app log entries."), + ], + expected_result=( + "App log shows connection confirmation and alternating TX/RX entries, the Pi terminal prints matching receive/ACK lines, and the GPIO17 LED toggles." + ), + references=["tests/manual/phone_pi.md#pi-serial-01-–-usb-serial-led-toggling"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_usb_bulk_ping(manual_test): + plan = ManualTestPlan( + identifier="PI-USB-01", + title="USB bulk ping echo", + objective="Ensure the USB bulk demo exchanges payloads using the g_zero gadget.", + steps=[ + ManualStep("Load the g_zero kernel module on the Pi and keep the USB-C link connected."), + ManualStep("Open the USB Bulk page on the phone."), + ManualStep("Tap Connect, then Send Ping twice."), + ManualStep("Review the log output."), + ], + expected_result=( + "The app log indicates a successful connection followed by RX 64 bytes entries for each ping without requiring a Python helper script." + ), + references=["tests/manual/phone_pi.md#pi-usb-01-–-usb-bulk-ping-echo"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_pi_audio_modem(manual_test): + plan = ManualTestPlan( + identifier="PI-AUDIO-01", + title="Audio jack telemetry ingest", + objective="Verify the audio modem demo decodes telemetry from the Pi over the TRRS connection.", + steps=[ + ManualStep("Connect the TRRS audio cable between the Pi and phone, ensuring phone microphone permission is granted."), + ManualStep("Run python audio_demo.py on the Pi with other audio apps closed."), + ManualStep("Open Audio Modem on the phone, tap Start, and monitor at least three telemetry updates."), + ManualStep("Tap Stop in the app and terminate the Pi script with Ctrl+C."), + ], + expected_result=( + "Status transitions from Idle to Listening… and displays periodic CPU temperature readings within a realistic range until Stop is pressed, after which updates halt and the Pi script exits cleanly." + ), + references=["tests/manual/phone_pi.md#pi-audio-01-–-audio-jack-telemetry-ingest"], + ) + manual_test.execute(plan) diff --git a/tests/manual/test_two_phones_manual.py b/tests/manual/test_two_phones_manual.py new file mode 100644 index 0000000..acb1e30 --- /dev/null +++ b/tests/manual/test_two_phones_manual.py @@ -0,0 +1,122 @@ +import pytest + +from .manual_test_helper import ManualStep, ManualTestPlan + + +@pytest.mark.manual +def test_phone_wifi_direct_ping(manual_test): + plan = ManualTestPlan( + identifier="PHONE-P2P-01", + title="Wi-Fi Direct discovery and ping", + objective="Confirm Phone A discovers Phone B via Wi-Fi Direct and successfully exchanges a ping.", + steps=[ + ManualStep("Open Wi-Fi Direct settings on Phone B and leave the device discoverable."), + ManualStep("On Phone A, open the Wi-Fi Direct demo page and trigger discovery."), + ManualStep("Select Phone B from the peer list and initiate the connection."), + ManualStep("After the connection completes, send a ping from Phone A."), + ManualStep("Stop discovery to release the session."), + ], + expected_result=( + "Phone B appears within 30 seconds, the connection succeeds without PIN prompts, the ping reports bytes sent/received, and stopping discovery closes the session cleanly." + ), + references=["tests/manual/two_phones.md#phone-p2p-01-–-wi-fi-direct-discovery-and-ping"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_phone_peer_message_delivery(manual_test): + plan = ManualTestPlan( + identifier="PHONE-P2P-02", + title="Peer message delivery", + objective="Verify text messages flow across an established Wi-Fi Direct link.", + steps=[ + ManualStep("Ensure the Wi-Fi Direct connection from PHONE-P2P-01 remains active."), + ManualStep("Send a short text such as 'mobileiot test' from Phone A using the SendToPeer command."), + ManualStep("Confirm the message arrives on Phone B via logs or UI, then optionally send a reply."), + ], + expected_result=( + "Message contents arrive exactly once without truncation, the link stays active for additional interactions, and empty messages are ignored without crashing." + ), + references=["tests/manual/two_phones.md#phone-p2p-02-–-peer-message-delivery"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_phone_nfc_tag_round_trip(manual_test): + plan = ManualTestPlan( + identifier="PHONE-NFC-01", + title="NFC tag read/write round-trip", + objective="Ensure NFC text data can be written from Phone A and read back successfully.", + steps=[ + ManualStep("On Phone A, open the NFC page and enter 'Hello MobileIoT' as the write payload."), + ManualStep("Tap Write to Tag and present a blank tag or Phone B in tag emulation mode until the write completes."), + ManualStep("Clear the input box on Phone A."), + ManualStep("Start scanning and present the programmed tag again."), + ], + expected_result=( + "Write completes without errors, scanning shows 'Hello MobileIoT' in the Last Tag Content label, and duplicate logs only appear when a new tag is detected." + ), + references=["tests/manual/two_phones.md#phone-nfc-01-–-nfc-tag-readwrite-round-trip"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_phone_nfc_peer_handover(manual_test): + plan = ManualTestPlan( + identifier="PHONE-NFC-02", + title="NFC peer-to-peer handover", + objective="Validate the NFC peer-to-peer page pushes the default payload between devices.", + steps=[ + ManualStep("Open the NFC Peer-to-Peer page on both phones."), + ManualStep("On Phone A, tap the prompt to initiate the exchange."), + ManualStep("Touch the phones back-to-back until haptic feedback indicates an NDEF push."), + ManualStep("Accept the received payload on Phone B and exit the page on both devices."), + ], + expected_result=( + "Phone B opens the Hello World payload in its default viewer, Phone A navigates back without error, and repeated exchanges continue working without an app restart." + ), + references=["tests/manual/two_phones.md#phone-nfc-02-–-nfc-peer-to-peer-handover"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_phone_beacon_detection(manual_test): + plan = ManualTestPlan( + identifier="PHONE-BEACON-01", + title="Beacon detection via companion device", + objective="Check that Phone A detects advertisements broadcast from Phone B's beacon simulator.", + steps=[ + ManualStep("Configure Phone B's beacon simulator with UUID 12345678-1234-1234-1234-1234567890AB, major 1, minor 2, TX power -59."), + ManualStep("Start the broadcast on Phone B."), + ManualStep("Open the BLE Beacons page on Phone A and wait for the entry to appear."), + ManualStep("Change the distance between devices to observe RSSI updates, then stop the broadcast."), + ], + expected_result=( + "Phone A lists the configured beacon with live RSSI updates and removes the entry within a minute after the broadcast stops without leaving stale records." + ), + references=["tests/manual/two_phones.md#phone-beacon-01-–-beacon-detection-via-companion-device"], + ) + manual_test.execute(plan) + + +@pytest.mark.manual +def test_phone_qr_scan_interop(manual_test): + plan = ManualTestPlan( + identifier="PHONE-VISION-01", + title="QR scan interoperability smoke test", + objective="Ensure the QR scanner reads codes generated on another device and the camera workflow remains stable afterward.", + steps=[ + ManualStep("Generate a QR code on Phone B containing https://aka.ms/mobileiot."), + ManualStep("On Phone A, open the Computer Vision page and start the QR scan."), + ManualStep("Align the camera to read the QR code, then capture a photo to validate classification still works."), + ], + expected_result=( + "QR result updates with the exact URL, the app continues functioning when switching back to photo capture, and classification results populate as expected." + ), + references=["tests/manual/two_phones.md#phone-vision-01-–-qr-scan-interoperability-smoke-test"], + ) + manual_test.execute(plan) diff --git a/tests/manual/two_phones.md b/tests/manual/two_phones.md new file mode 100644 index 0000000..d785463 --- /dev/null +++ b/tests/manual/two_phones.md @@ -0,0 +1,175 @@ +# Phone ↔ Phone Manual Tests + +These tests cover features that require two mobile devices. Designate one device +as **Phone A** (test subject) running the MobileIoT app build under test and the +other as **Phone B** (companion) that provides NFC tags, beacon broadcasts, or +peer connectivity. + +## Hardware and software prerequisites + +- Two Android phones with NFC, Wi-Fi Direct, Bluetooth LE, and camera support. + (iOS can substitute for beacon and NFC tag scenarios if you install equivalent + tooling.) +- MobileIoT app installed on both devices from the same build. +- Optional helper apps on Phone B: + - *Beacon Simulator* (Android) or *Locate Beacon* (iOS) to broadcast custom + iBeacon frames. + - *NFC Tools* to emulate tags or to write text records to blank NFC cards. +- Stable Wi-Fi environment so Wi-Fi Direct negotiations complete without severe + interference. + +## Common setup checklist + +1. Enable developer options on both phones and allow installing from unknown + sources if side-loading the test build. +2. Sign into the same Wi-Fi network (for logging convenience) but disable + regular Wi-Fi during Wi-Fi Direct tests so only the peer-to-peer link is + active. +3. Ensure Bluetooth and NFC radios are enabled on both devices. +4. Launch the MobileIoT app on Phone A and verify the main menu loads. +5. Keep both phones unlocked for the duration of testing. + +## Test cases + +### PHONE-P2P-01 – Wi-Fi Direct discovery and ping + +**Objective:** Confirm Phone A can discover Phone B over Wi-Fi Direct and +perform a ping using the P2P service. + +**Prerequisites:** +- QA build that exposes the Wi-Fi Direct test harness (toolbar buttons or + developer menu that map to `P2pViewModel` commands). If using the stock UI, + attach a debugger and invoke commands via the Live Visual Tree or `dotnet + watch` hot reload buttons that bind to `DiscoverCommand`, `SendPingCommand`, + and `StopDiscoveryCommand`. + +**Steps:** +1. On Phone B, open Android settings → **Wi-Fi** → **Wi-Fi Direct** and keep the + dialog visible so the device advertises itself. +2. On Phone A, navigate to the **Wi-Fi Direct** demo page and trigger + **Discover**. +3. Wait for Phone B’s device ID to appear in the discovered peers list. +4. Select the peer and trigger **Connect**. +5. After the connection completes, invoke **Send Ping**. +6. Stop discovery to tear down the session. + +**Expected results:** +- Discovery lists Phone B within 30 seconds. +- Connection succeeds without prompting for a PIN. +- Ping command succeeds (status log or toast indicates bytes sent and received). +- Stopping discovery closes the session without crashing the app. + +### PHONE-P2P-02 – Peer message delivery + +**Objective:** Verify arbitrary messages can be exchanged once the Wi-Fi Direct +link is established. + +**Prerequisites:** +- PHONE-P2P-01 completed with devices paired. +- Mechanism to send custom text (debug menu calling `SendToPeerCommand`). + +**Steps:** +1. With the Wi-Fi Direct connection active, prepare a short text like + “mobileiot test”. +2. Trigger the **SendToPeer** action with Phone B’s peer ID and the text. +3. Observe logs or on-screen status on Phone B’s test harness (or use a packet + capture app) to confirm receipt. +4. Repeat with Phone B sending a message back if its build includes the harness. + +**Expected results:** +- Message appears exactly once on the receiving side without truncation. +- Connection stays active after the message and additional pings still work. +- Sending empty text is ignored (no crash, optional warning log). + +### PHONE-NFC-01 – NFC tag read/write round-trip + +**Objective:** Ensure NFC text tags can be written and read between the two +phones using the MobileIoT NFC page. + +**Prerequisites:** +- Blank NFC tag or Phone B with NFC Tools set to **Write** text records. + +**Steps:** +1. On Phone A, open the **NFC** page and enter `Hello MobileIoT` in the write + text box. +2. Tap **Write to Tag** and place Phone A against the NFC tag or Phone B in tag + emulation mode until Android confirms the write. +3. Clear the input box on Phone A. +4. Tap **Start Scan** and present the programmed tag (or Phone B in read mode). + +**Expected results:** +- Write action completes without error and the tag contains the new string. +- Scanning updates the “Last Tag Content” label with `Hello MobileIoT`. +- Repeated scans append to the log only when a new tag is detected (no duplicate + spam when the devices remain in contact). + +### PHONE-NFC-02 – NFC peer-to-peer handover + +**Objective:** Validate the peer-to-peer push initiated from the NFC P2P page +broadcasts the default payload and causes navigation to exit gracefully. + +**Prerequisites:** +- Both phones unlocked with NFC enabled and set to Android Beam / Quick Share or + the platform equivalent. + +**Steps:** +1. Open the **NFC Peer-to-Peer** page on both phones. +2. On Phone A, tap **Tap phones to exchange**. +3. Immediately bring the phones back-to-back until haptic feedback indicates an + NDEF push. +4. Accept the prompt on Phone B to open the received data (usually a text share). +5. Tap **Back** on both devices to exit the page. + +**Expected results:** +- The `Hello World` payload transfers to Phone B, launching the default viewer + (e.g., text preview). +- Phone A returns to the previous page without errors after the exchange. +- Repeated exchanges continue to work without requiring an app restart. + +### PHONE-BEACON-01 – Beacon detection via companion device + +**Objective:** Verify the beacon scanner detects advertisements that originate +from another phone running a beacon simulator app. + +**Prerequisites:** +- Beacon simulator on Phone B configured with UUID `12345678-1234-1234-1234-1234567890AB`, + major `1`, minor `2`, TX power `-59`. + +**Steps:** +1. Start the beacon broadcast on Phone B. +2. On Phone A, open **BLE Beacons**. +3. Wait for the device list to populate and locate Phone B’s advertisement. +4. Move Phone B farther away and return to observe RSSI updates. +5. Stop the broadcast on Phone B and confirm the entry eventually disappears. + +**Expected results:** +- Phone A lists Phone B with the configured UUID and updates RSSI in real time. +- Entry disappears within one minute of stopping the broadcast. +- No stale ghost entries remain when the broadcast is off. + +### PHONE-VISION-01 – QR scan interoperability smoke test + +**Objective:** Ensure the QR scanner reads codes generated on a second phone. + +**Prerequisites:** +- QR code generator app or website on Phone B. + +**Steps:** +1. Generate a QR code on Phone B containing the text `https://aka.ms/mobileiot`. +2. On Phone A, open **Computer Vision** and tap **Scan QR Code**. +3. Align the camera with the QR code until a result is shown. +4. Tap **Capture Photo** and take a picture of any object to confirm image + classification still works after scanning. + +**Expected results:** +- QR result field on Phone A updates with the URL exactly. +- App does not crash or freeze when switching between QR scanning and photo + capture. +- Optional classification result populates after the photo capture. + +## Post-test cleanup + +- Disable any beacon or NFC tag emulation apps running on Phone B. +- Re-enable regular Wi-Fi on both phones if it was disabled. +- Sign out of test builds or uninstall them if the devices are being handed off + to other testers.