How FreqCraft is tested.
Before FreqCraft ships, it has to earn it. This is the actual release procedure the app is held to, the one that answers a single question under pressure: is this build safe to release? Reproduced in full below, sanitized only of internal file paths. Nothing hidden, including what the tests caught.
The problem
“It works on my machine” is not a release decision. A shipping app needs a repeatable, defensible answer to whether it’s ready, one that survives a deadline, a tired developer, and a feature added last week.
Memory and good intentions don’t scale. The process has to be written down, ordered, and gated.
The approach
Two layers, narrowest net first: an automated adversarial test suite, then a physical-device pass that ends in a release gate. Every test has an explicit pass condition.
Known issues are triaged by severity and disclosed, not buried. The gate states, in writing, ship or don’t ship.
211 automated tests and 22 manual procedures, run on two physical handsets. Counts verified against the source before publishing. The automated suite has already earned its keep twice: it caught a readout-overflow defect, and later a teardown race that four rounds of code review missed. Both are documented below.
Purpose. Define and execute pre-launch QA across two layers: an automated adversarial suite, then hands-on device work, a manual pass on the primary handset (Phase A) and physical-device release qualification on both handsets (Phase B). Phase B is the release gate; the app bundle is not submitted to the store until every Phase B criterion is met.
Scope. FreqCraft 1.1.2, Android only, including multi-voice sessions, the live waveform visualizer and timed-session behavior. Out of scope: iOS, accessibility/TalkBack (deferred). Trigger: developer declares the build ready for QA, run the automated suite first, then Phase A, then Phase B.
The adversarial suite is the first gate. It runs the build through input it was never meant to receive, and must pass clean before a human tests anything. It needs no device and no tether, so it runs on every change rather than once per release.
Run:
flutter analyze then flutter testGate: analyze clean AND 211/211 green before manual testing begins. Analyze gates the build, not the other way round: a missing import fails a Gradle build in 49 seconds that analyze catches in four.
- Malformed numeric input: multiple dots, minus signs, letters, whitespace, leading zeros.
- Absurd-value handling: a 14-digit carrier must not overflow the readout. (This case caught a real defect before it reached a device.)
- Advisory thresholds: carrier ≥ 1000, beat > 100, isochronic pulse > 100.
- Mode toggling and waveform switching mid-session.
- Isochronic playback path; the 0 Hz / 0 Hz silence case.
- Live carrier change during an active session.
- State leak across begin → end → begin cycles.
- Timed-session REMAINING readout show / clear.
pumpAndSettle: the live waveform animates continuously, so it never returns and the suite would hang. Use pump(Duration) only.
Environment: Moto G 2025, Android 16, flutter run from project root.
Result codes: PASS · FAIL · NOTE (device or OEM behaviour, log and continue).
Why there is no emulator layer. There used to be one. It was retired because the development machine could not run an Android emulator well enough to trust what it reported about audio, and a test environment you have to discount is worse than none. Every procedure below now runs on hardware from the start, which is also where the audio faults actually live.
- Clear stored preferences (
adb shell pm clear com.directivecraft.freqcraftor wipe device data). - Launch the app; verify the disclaimer screen appears.
- Acknowledge; verify navigation to the main screen.
- Cold-start the app again; verify the disclaimer is skipped and the main screen loads directly.
- Verify the app bar: title centered, bold, letter-spaced; info icon present.
- Open the About dialog; verify header, feature list, desktop-suite section, source attribution, and a working close action.
- Verify the subtitle block (“Precision Mobile Tool” / desktop-suite line).
- Verify no legacy or placeholder text appears anywhere on screen.
- Confirm Binaural is selected on launch; verify it shows Carrier, Beat, and Waveform controls plus the stereo-headphones advisory.
- Switch to Isochronic; verify Carrier and Pulse Rate show, the waveform selector hides, and the no-headphones advisory appears.
- Switch back to Binaural; verify the layout reverts correctly.
- Tap each numeric field (Carrier, Beat, Fade In/Out, Timer); verify all text is selected on tap.
- Enter
120then999in Fade In; verify the value displays in full (field accommodates 3 digits). - Enter
150in Beat; verify the out-of-range advisory appears, then clears when the value returns to range. - Regression type a value, then collapse the numpad with the system back gesture (not Done). Verify no text stays highlighted with selection handles, and no selection persists into the session. Values still apply only on Done/Begin.
- Verify both fade input fields share the same right edge (label expanded, field pushed right).
- Toggle Fade In off then on; verify the field disappears and reappears at the same position with no layout jump.
- Load default parameters (200 Hz carrier, 10 Hz beat, Binaural). Start the session; verify the button changes state and audio is audible.
- End the session; verify audio stops and the button reverts.
- Repeat in Isochronic mode.
- Set a 30-second fade in. Start the session and monitor: very low for the first ~10s, rising through the middle third, full only near the 30s mark, never “done” early.
- Enable a 30-second fade out and end the session; verify it is perceptually gradual, not abrupt.
- Set Timer = 1 minute, Fade In = 5s, Fade Out = 5s, at a comfortable (non-max) volume.
- Start the session and monitor the full 60 seconds, focusing on the last 10.
- Verify no sudden volume increase before fade-out, smooth fade from ~55s, and a clean end at 60s with audio at zero.
- Set a 2-minute timer, Fade Out off. Start; verify the countdown begins, audio auto-stops at zero, and the button reverts.
- Set a 1-minute timer with a 10-second fade out. Verify the fade begins at ~50s.
- Timer / fade display verify the REMAINING countdown keeps ticking through the fade-out and reaches 0:00 exactly as the audio goes silent. It must not jump to 0:00 early while audio is still fading. (1-minute timer, 5s in / 5s out = 5s fade-in, 50s full, 5s fade-out, silent at 0:00.) Manual END hides REMAINING immediately; ELAPSED persists by design.
- Save a user preset with custom parameters and a unique name.
- Kill the app and cold-start it.
- Open the Presets screen; verify the preset is present and loads with all parameters restored.
- Verify all 7 factory presets are listed (Deep Sleep, Recovery, Deep Calm, Theta Schumann, Alpha Calm, Beta Focus, Gamma Clarity), each with badge, band/Hz chip, and explore line.
- Expand a factory preset; verify the drawer shows parameters, attributed note, source, safety note, and Load, with no delete on factory presets.
- Verify user presets offer quick-Load on the collapsed row and Delete only inside the expanded drawer (with a confirm dialog).
- Load a factory preset; verify parameters reach the main screen and Gamma Clarity loads in Isochronic mode.
- Scroll to the bottom of the Presets screen; verify the sourcing/exploration disclaimer text is present, small, gray, and centered.
- Continuous scroll watch ≥10s; verify smooth scrolling with no seam, snap-back, or repeating-interval jump. It must read as a continuous feed, not a loop.
- Shape matches type in Binaural, cycle Sine / Square / Sawtooth / Triangle; verify the on-screen shape changes to match each and matches the engine.
- Isochronic density vary carrier and pulse across low and high values; verify visual density tracks the values and stays readable at low Hz (no lonely hump with dead gaps).
- Readout overflow enter an absurd 14-digit carrier; verify the readout shrinks to fit and shows the full number, no overflow, no error, no truncation box. Normal values render unchanged.
- Idle behavior stop the session; verify the wave freezes to a clean static silhouette (no mid-wave smear) and re-animates smoothly on restart.
- No stutter during a timed session, watch ≥10s; verify no per-second hitch as ELAPSED / REMAINING tick.
Prerequisites: automated suite green; all Phase A items PASS or NOTE (no unresolved FAILs).
Devices: Moto G7 Power (Android 10, wired-audio reference, 3.5mm jack); Moto G 2025 (Android 16, current-API reference). Oldest supported and newest supported, so a release is exercised at both ends of the range it claims. The release-validation install sideloads the signed app bundle.
- Enable Developer Options and USB Debugging.
- Connect via USB and confirm
adb devicesrecognizes the device. - Install the build (debug install, or signed bundle via bundletool for release validation).
- Run A-1 through A-13 on the Moto G7 Power, from the sideloaded signed app bundle.
- Treat differences between the two handsets as data, log them with the OS version, and fail only if audio is clearly wrong.
- On headphones, load Theta Schumann (7.83 Hz beat, 136.1 Hz carrier); verify an audible beating sensation at ~7–8 Hz.
- Step up through Alpha Calm (10 Hz) and Beta Focus (14 Hz); verify the perceived rate increases. Verify a high carrier stays centered, not pushed to one ear.
- Remove one headphone; verify the beat perception diminishes, confirming binaural, not isochronic, processing.
- Load Gamma Clarity (40 Hz isochronic); verify rapid pulsing on speakers and the same pulsing (not a beat) on headphones.
- Set pulse rate to 4 Hz; verify slow, distinct pulses with clean edges. Set to 40 Hz; verify rapid pulsing stays clean, no buzzing or overflow artifacts.
- Start playback in a music app, switch to the app, and start a session.
- Verify both are audible simultaneously (slight ducking of the music is acceptable).
- End the session; verify the music app is unaffected.
- Start a session (no timer, no fade), lock the screen, and wait 30 seconds; verify audio is still audible.
- Unlock; verify the session is still active. End the session; audio stops.
- Start a continuous session at comfortable volume, lock the screen, and set the phone down.
- Monitor audio at 5, 10, 15, and 20 minutes; document whether and when audio stops.
- During a session, verify system volume up/down affects output, and the in-app slider adjusts level independently.
- Save a user preset and fully reboot the device.
- Open the app; verify the disclaimer does not reappear and the saved preset is present.
Release Gate Checklist
Every item must be checked before the app bundle is submitted.
- Automated suite: analyze clean and 211/211 green.
- B-1: all Phase A tests pass on the physical device.
- B-2 / B-3: binaural beat perception and isochronic pulses confirmed.
- B-4: background mixing confirmed, does not mute other apps.
- B-5 / B-6: screen-lock playback stable; minimum 10 minutes background (20 preferred; under 5 minutes = launch blocker).
- B-7: volume controls functional. B-8: persistence confirmed through reboot.
GATE STATUS: [ ] OPEN [ ] PASSED [ ] BLOCKED
Known gaps and open issues
Disclosed and triaged, not hidden.
| ID | Severity | Description | Status / Plan |
|---|---|---|---|
| STOP | Low | Teardown race in the stop path, a reference used after an await without a mounted-check could log an error if the screen was dismissed mid-stop. Surfaced by the expanded test suite, not by four rounds of code review (including a maximum-effort audit). | Fixed + verified |
| P4 | Low | A dead validation method in the engine, never called; validation handled in the UI. | Later release |
| ENG | Deferred | Engine hardening (deterministic stop poll, completer-based fade cancel, band-limited waveforms). | Post-launch |
Version log
Change control is part of the deliverable.
v1.1 2026-06-14, Accordion preset UI, preset names, contact email; Phase A + Phase B.
v1.2 2026-06-22, Stamped to app 1.0.0. Added the automated adversarial suite (29 tests) as the first gate; added the Live Waveform Visualizer section (A-13); added the timer fade-out display spec (A-9) and the numpad-focus regression (A-4). Sanitized internal paths; defect forensics tracked separately in the bug log.
v1.3 2026-07-05, Adversarial suite expanded 29 → 44 on-device checks; added 12 unit tests (56 automated total). Physical testing run on two handsets (Moto G7 Power + Pixel 6 Pro). Logged the teardown-race defect (STOP) the suite caught after code review missed it, fixed and verified. Counts verified against source.
v1.4 2026-08-29, Stamped to app 1.1.0 (five-voice sessions). Automated coverage restated: 211 widget and unit tests replacing the 56, and the suite no longer needs a tethered device, so it runs on every change instead of once per release. Fleet updated, the Pixel 6 Pro is out; the Moto G 2025 (Android 16) is in, pairing oldest-supported with newest-supported. Counts verified against source: 15 files, 211 green, analyze clean.
Also v1.4, the emulator layer is retired and the process is now two layers, not three. The development machine could not run an Android emulator well enough to trust its audio, so Phase A moved onto the primary handset and Phase B became the second-handset and signed-bundle gate. Known issue P5 (channel reversal at low beat frequency) is withdrawn with it: it was only ever observed in that emulator and never reproduced on hardware.
This is the app it ships.
FreqCraft is built and tested the way this page describes: precision audio, zero data collection, one-time purchase. See it for yourself.
See FreqCraft →