feat: patrol on-device integration smoke test
This commit is contained in:
parent
d6f4c434fa
commit
6f6fa20b58
10 changed files with 466 additions and 6 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -42,3 +42,14 @@ app.*.map.json
|
||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
/android/build
|
||||||
|
|
||||||
|
# Rust wrapper crate (native/)
|
||||||
|
native/target/
|
||||||
|
|
||||||
|
# Rust cdylibs built into the APK by devbox run build-native-android
|
||||||
|
android/app/src/main/jniLibs/
|
||||||
|
|
||||||
|
# Patrol generates the bundle from the integration tests at run time
|
||||||
|
**/test_bundle.dart
|
||||||
|
.patrol.env
|
||||||
|
|
|
||||||
5
android/.gitignore
vendored
5
android/.gitignore
vendored
|
|
@ -1,8 +1,9 @@
|
||||||
gradle-wrapper.jar
|
gradle-wrapper.jar
|
||||||
/.gradle
|
/.gradle
|
||||||
/captures/
|
/captures/
|
||||||
/gradlew
|
# gradlew stays tracked: it carries the nix project-cache redirect that every
|
||||||
/gradlew.bat
|
# non-flutter gradle invocation (patrol_cli, manual builds) needs, so the
|
||||||
|
# wrapper can't be re-injected from the template.
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,25 @@ android {
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
// The Android Test Orchestrator (with clearPackageData) wipes the
|
||||||
|
// integration flavor's app data between test runs, and the flavor
|
||||||
|
// installs it beside — never over — the real app, so every patrol run
|
||||||
|
// starts at onboarding with a fresh identity.
|
||||||
|
testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner"
|
||||||
|
testInstrumentationRunnerArguments["clearPackageData"] = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions += "test"
|
||||||
|
productFlavors {
|
||||||
|
create("integration") {
|
||||||
|
dimension = "test"
|
||||||
|
applicationIdSuffix = ".integration"
|
||||||
|
versionNameSuffix = "-integration"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -38,3 +57,10 @@ android {
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// The orchestrator referenced by testOptions.execution above. The patrol
|
||||||
|
// native library itself comes from the patrol pub package's android
|
||||||
|
// module (a plugin subproject), not from a Maven artifact.
|
||||||
|
androidTestUtil("androidx.test:orchestrator:1.5.1")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.app.smol_mail.integration;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import pl.leancode.patrol.PatrolJUnitRunner;
|
||||||
|
|
||||||
|
// The Patrol host class: each Dart test in the integration_test directory is
|
||||||
|
// executed through this JUnit suite. Its package is the flavored applicationId
|
||||||
|
// (com.app.smol_mail + .integration suffix); the namespace differs, so
|
||||||
|
// MainActivity needs the explicit import below.
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class MainActivityTest {
|
||||||
|
|
||||||
|
@Parameters(name = "{0}")
|
||||||
|
public static Object[] testCases() {
|
||||||
|
PatrolJUnitRunner instrumentation =
|
||||||
|
(PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
|
||||||
|
instrumentation.setUp(com.example.smol_mail.MainActivity.class);
|
||||||
|
instrumentation.waitForPatrolAppService();
|
||||||
|
return instrumentation.listDartTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainActivityTest(String dartTestName) {
|
||||||
|
this.dartTestName = dartTestName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String dartTestName;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void runDartTest() {
|
||||||
|
PatrolJUnitRunner instrumentation =
|
||||||
|
(PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
|
||||||
|
instrumentation.runDartTest(dartTestName);
|
||||||
|
}
|
||||||
|
}
|
||||||
171
android/gradlew
vendored
Executable file
171
android/gradlew
vendored
Executable file
|
|
@ -0,0 +1,171 @@
|
||||||
|
#!/nix/store/gik3rh1vz2jlgnifb9dh6vc6sxwwz9jj-bash-5.3p9/bin/bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
# Nix's flutter keeps the flutter_tools/gradle composite build on a read-only
|
||||||
|
# store path, so every gradle invocation needs its project and Kotlin caches
|
||||||
|
# redirected off it — the same two arguments the flutter tool itself passes.
|
||||||
|
# Without them, any gradlew run other than through the flutter tool (patrol_cli,
|
||||||
|
# manual builds) fails with a silent exit 1.
|
||||||
|
_nix_cache="${XDG_CACHE_HOME:-$HOME/.cache}/flutter/nix-flutter-tools-gradle/gradle-project-cache"
|
||||||
|
case " $* " in
|
||||||
|
*" --project-cache-dir"*) ;;
|
||||||
|
*) set -- "$@" "--project-cache-dir=$_nix_cache" "-Pkotlin.project.persistent.dir=$_nix_cache/kotlin" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
11
devbox.json
11
devbox.json
|
|
@ -10,7 +10,9 @@
|
||||||
"nixpkgs#ninja",
|
"nixpkgs#ninja",
|
||||||
"nixpkgs#pkg-config",
|
"nixpkgs#pkg-config",
|
||||||
"nixpkgs#gtk3",
|
"nixpkgs#gtk3",
|
||||||
"nixpkgs#libsysprof-capture"
|
"nixpkgs#libsysprof-capture",
|
||||||
|
"nixpkgs#rustup",
|
||||||
|
"nixpkgs#cargo-ndk"
|
||||||
],
|
],
|
||||||
"shell": {
|
"shell": {
|
||||||
"init_hook": [
|
"init_hook": [
|
||||||
|
|
@ -22,7 +24,12 @@
|
||||||
"test": "flutter test",
|
"test": "flutter test",
|
||||||
"run-linux": "flutter run -d linux",
|
"run-linux": "flutter run -d linux",
|
||||||
"build-linux": "flutter build linux",
|
"build-linux": "flutter build linux",
|
||||||
"link-android-tools": "if [ -n \"$ANDROID_HOME\" ] && [ ! -d \"$ANDROID_HOME/cmdline-tools/latest\" ] && [ -d \"$ANDROID_HOME/cmdline-tools/8.0\" ]; then ln -s \"$ANDROID_HOME/cmdline-tools/8.0\" \"$ANDROID_HOME/cmdline-tools/latest\"; fi"
|
"link-android-tools": "if [ -n \"$ANDROID_HOME\" ] && [ ! -d \"$ANDROID_HOME/cmdline-tools/latest\" ] && [ -d \"$ANDROID_HOME/cmdline-tools/8.0\" ]; then ln -s \"$ANDROID_HOME/cmdline-tools/8.0\" \"$ANDROID_HOME/cmdline-tools/latest\"; fi",
|
||||||
|
"build-native": "cd native && cargo build --release",
|
||||||
|
"build-native-android": "rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android && cd native && cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -o ../android/app/src/main/jniLibs build --release",
|
||||||
|
"test-native": "cd native && cargo test --release && cargo test --release -- --ignored",
|
||||||
|
"activate-patrol": "dart pub global activate patrol_cli",
|
||||||
|
"test-integration": "export PATH=\"$HOME/.pub-cache/bin:$PATH\" && patrol test --dart-define=SMOL_TEST_HOST=$(hostname -I | tr ' ' '\\n' | grep -E '^(192\\.168|10\\.|172\\.)' | head -1)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@
|
||||||
"last_modified": "1970-01-01T00:00:00Z",
|
"last_modified": "1970-01-01T00:00:00Z",
|
||||||
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#android-tools"
|
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#android-tools"
|
||||||
},
|
},
|
||||||
|
"nixpkgs#cargo-ndk": {
|
||||||
|
"last_modified": "1970-01-01T00:00:00Z",
|
||||||
|
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#cargo-ndk"
|
||||||
|
},
|
||||||
"nixpkgs#clang": {
|
"nixpkgs#clang": {
|
||||||
"last_modified": "1970-01-01T00:00:00Z",
|
"last_modified": "1970-01-01T00:00:00Z",
|
||||||
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#clang"
|
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#clang"
|
||||||
|
|
@ -84,6 +88,10 @@
|
||||||
"nixpkgs#pkg-config": {
|
"nixpkgs#pkg-config": {
|
||||||
"last_modified": "1970-01-01T00:00:00Z",
|
"last_modified": "1970-01-01T00:00:00Z",
|
||||||
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#pkg-config"
|
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#pkg-config"
|
||||||
|
},
|
||||||
|
"nixpkgs#rustup": {
|
||||||
|
"last_modified": "1970-01-01T00:00:00Z",
|
||||||
|
"resolved": "path:/nix/store/v8ynlbrldn7k8byb1kx0j0wka8dsbrw9-source?narHash=sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg%3D#rustup"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
97
integration_test/smoke_test.dart
Normal file
97
integration_test/smoke_test.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
// Device integration tests over the real UI, driven by Patrol against the
|
||||||
|
// system bunshin on the test host. The integration flavor installs beside
|
||||||
|
// the real app and the instrumentation runner clears its data, so every run
|
||||||
|
// starts at onboarding with a fresh identity — the app's own flows are the
|
||||||
|
// subject under test, including the network path.
|
||||||
|
//
|
||||||
|
// Run: devbox run test-integration
|
||||||
|
// (the script resolves the host's LAN IP into SMOL_TEST_HOST, because the
|
||||||
|
// device reaches the server over Wi-Fi, not loopback.)
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:hive_flutter/hive_flutter.dart";
|
||||||
|
import "package:path_provider/path_provider.dart";
|
||||||
|
|
||||||
|
import "package:patrol/patrol.dart";
|
||||||
|
|
||||||
|
import "package:smol_mail/data/providers/providers.dart";
|
||||||
|
import "package:smol_mail/presentation/app_widget.dart";
|
||||||
|
import "package:smol_mail/smol/config.dart";
|
||||||
|
import "package:smol_mail/smol/store.dart";
|
||||||
|
|
||||||
|
const testHost = String.fromEnvironment("SMOL_TEST_HOST", defaultValue: "10.0.2.2");
|
||||||
|
|
||||||
|
// The system bunshin's static key: a documented, operator-supplied value on
|
||||||
|
// this machine — the trusted channel SPEC.md §4 asks a pin to come from.
|
||||||
|
const serverKey = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq";
|
||||||
|
|
||||||
|
Future<Widget> bootApp() async {
|
||||||
|
// main()'s boot, minus runApp: the test pumps the same tree.
|
||||||
|
final dataDir = await getApplicationSupportDirectory();
|
||||||
|
Hive.init(dataDir.path);
|
||||||
|
final store = await SmolStore.open(dbPath: "${dataDir.path}/smol-mail.db");
|
||||||
|
applyPresetServer(store);
|
||||||
|
return ProviderScope(
|
||||||
|
overrides: [storeProvider.overrideWithValue(store)],
|
||||||
|
child: const AppWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
patrolTest("smoke: onboarding, register, self-send, fetch, read in requests",
|
||||||
|
($) async {
|
||||||
|
await $.pumpWidgetAndSettle(await bootApp());
|
||||||
|
|
||||||
|
// Onboarding: a fresh identity, backed up, registered under a name no
|
||||||
|
// server has bound yet — registration must succeed against the real
|
||||||
|
// server over Wi-Fi, through the Dart-resolved dial path.
|
||||||
|
await $("Create Identity").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $("I have backed it up").waitUntilVisible();
|
||||||
|
await $("I have backed it up").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
|
||||||
|
final user = "it${DateTime.now().millisecondsSinceEpoch.toRadixString(36)}";
|
||||||
|
// Enter text by TextField, not by its labelText: the decoration label is
|
||||||
|
// a RichText that is not hit-testable, so a text finder times out.
|
||||||
|
await $.enterText($(TextField).at(0), "$user@$testHost:1961");
|
||||||
|
await $.enterText($(TextField).at(1), serverKey);
|
||||||
|
await $("Pin and Register").tap();
|
||||||
|
|
||||||
|
// The register round trip is real network: the inbox tabs only exist
|
||||||
|
// behind HomeGuard, so reaching them proves the account bound.
|
||||||
|
await $("Inbox").waitUntilVisible(timeout: const Duration(seconds: 30));
|
||||||
|
await $("Requests").waitUntilVisible();
|
||||||
|
|
||||||
|
// Compose the first self-addressed mail.
|
||||||
|
await $(Icons.edit).tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $.enterText($(TextField).at(0), "$user@$testHost:1961");
|
||||||
|
await $.enterText($(TextField).at(1), "smoke subject");
|
||||||
|
await $.enterText($(TextField).at(2), "smoke body from patrol");
|
||||||
|
await $("Send").tap();
|
||||||
|
// Compose pops back to the inbox once the send round trip completes.
|
||||||
|
await $("Inbox").waitUntilVisible(timeout: const Duration(seconds: 30));
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
|
||||||
|
// Fetch over the air, then read what arrived.
|
||||||
|
await $(Icons.cloud_download).tap();
|
||||||
|
await $.pumpAndSettle(duration: const Duration(seconds: 5));
|
||||||
|
await $("Requests (1)").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
|
||||||
|
// First contact is unsolicited, so it landed in requests (sec 5.8) —
|
||||||
|
// and the §5.6 sent copy must read back as plain text, not <unreadable>.
|
||||||
|
await $("smoke subject").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $("smoke body from patrol").waitUntilVisible();
|
||||||
|
await $.native.pressBack(); // ignore: deprecated_member_use
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $("Sent").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $("smoke subject").tap();
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
await $("smoke body from patrol").waitUntilVisible();
|
||||||
|
});
|
||||||
|
}
|
||||||
89
pubspec.lock
89
pubspec.lock
|
|
@ -249,6 +249,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.8.0"
|
||||||
|
dispose_scope:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dispose_scope
|
||||||
|
sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -258,7 +274,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
|
@ -334,6 +350,11 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_driver:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -384,6 +405,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
fuchsia_remote_debug_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -440,6 +466,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.7"
|
version: "0.15.7"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -464,6 +498,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.10.1"
|
version: "4.10.1"
|
||||||
|
integration_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -672,6 +711,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
patrol:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: patrol
|
||||||
|
sha256: "662c50d517b2f159fedbc359a7fb8d0d30cbefea6d5c2a08392a0593079d64d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.10.0"
|
||||||
|
patrol_finders:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: patrol_finders
|
||||||
|
sha256: "8608cdacaab90a3b00ecd7b09df11f13a62b01dea13c211b9f13fd86854e103a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.6.0"
|
||||||
|
patrol_log:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: patrol_log
|
||||||
|
sha256: "3d91c93e8d0ed19cb12d4b27aa24eca7294e0a48d9d8445505c9775feb5fde2d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -712,6 +775,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.2"
|
version: "6.5.2"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.6"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -869,6 +940,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
sync_http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sync_http
|
||||||
|
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1005,6 +1084,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
webdriver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webdriver
|
||||||
|
sha256: "28b82ec894fed45dd71c23ba62d1af973ed97dd59a4f5790a4d38b0b13e5657e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
webkit_inspection_protocol:
|
webkit_inspection_protocol:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
14
pubspec.yaml
14
pubspec.yaml
|
|
@ -12,6 +12,7 @@ dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
|
ffi: ^2.1.0
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
auto_route: ">=11.1.0 <11.2.0"
|
auto_route: ">=11.1.0 <11.2.0"
|
||||||
|
|
@ -24,12 +25,25 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
integration_test:
|
||||||
|
sdk: flutter
|
||||||
|
patrol: ^4.10.0
|
||||||
build_runner: ^2.4.6
|
build_runner: ^2.4.6
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
auto_route_generator: ^10.5.0
|
auto_route_generator: ^10.5.0
|
||||||
flutter_native_splash: ^2.3.4
|
flutter_native_splash: ^2.3.4
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: ^0.14.4
|
||||||
|
|
||||||
|
# patrol test reads this block; android.package_name is the flavored
|
||||||
|
# applicationId (the integration flavor adds the .integration suffix), and
|
||||||
|
# patrol_cli refuses to run without it.
|
||||||
|
patrol:
|
||||||
|
app_name: kirakira
|
||||||
|
flavor: integration
|
||||||
|
test_directory: integration_test
|
||||||
|
android:
|
||||||
|
package_name: com.app.smol_mail.integration
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
ios: false
|
ios: false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue