Merge branch '596-espresso' into 'master'

Setup UI Tests with Espresso for Automatic Localized Screenshots

Closes #596

See merge request briar/briar!863
This commit is contained in:
akwizgran
2018-08-03 16:38:57 +00:00
23 changed files with 657 additions and 8 deletions

1
.gitignore vendored
View File

@@ -23,5 +23,6 @@ local.properties
!.idea/codeStyles
.gradle
build/
captures
*.iml
projectFilesBackup/

View File

@@ -5,3 +5,9 @@ local.properties
.settings
src/main/assets/*.zip
src/main/res/values-iw
# Fastlane Screenshots
/fastlane/metadata/android/screenshots.html
/fastlane/metadata/android/*/images
/fastlane/report.xml
/fastlane/README.md

View File

@@ -49,6 +49,16 @@ dependencies {
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3"
def espressoVersion = '3.0.2'
androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espressoVersion"
androidTestImplementation "tools.fastlane:screengrab:1.1.0"
androidTestImplementation "com.android.support.test.uiautomator:uiautomator-v18:2.1.3"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.0.2"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.12'
}
dependencyVerification {
@@ -66,6 +76,14 @@ dependencyVerification {
'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05',
'com.android.support.constraint:constraint-layout-solver:1.1.0:constraint-layout-solver-1.1.0.jar:fcb4c7d705754ca3d69b1b2c3caf445a425599fda8caabbcf855d98ea0663e4e',
'com.android.support.constraint:constraint-layout:1.1.0:constraint-layout-1.1.0.aar:d490188709b7bb2f11609beadd7e5eb7538892f308828ec3ff261a74e6ecf47e',
'com.android.support.test.espresso:espresso-contrib:3.0.2:espresso-contrib-3.0.2.aar:eacb4a10dde5597b8a6b8668804d4b63e3ae2d46a78192068532922fec0b4a66',
'com.android.support.test.espresso:espresso-core:3.0.2:espresso-core-3.0.2.aar:f40bf62e26e6f95a9c376c4e318415a77053b7dbb7ec12688eb6fab93dffdf73',
'com.android.support.test.espresso:espresso-idling-resource:3.0.2:espresso-idling-resource-3.0.2.aar:c6485150f9f4aea1ce9d138f3d60d82ebed3fe35b340a8b1dc975ff01f3b17b2',
'com.android.support.test.espresso:espresso-intents:3.0.2:espresso-intents-3.0.2.aar:556f99e8c8723a9ef313ed816fb9074d65903c6767521a66b099720d2cc21f10',
'com.android.support.test.uiautomator:uiautomator-v18:2.1.3:uiautomator-v18-2.1.3.aar:15e6b3c7104859630bf844e31805aa7cb2eb4b385e6119ab34132c8258eee2c4',
'com.android.support.test:monitor:1.0.2:monitor-1.0.2.aar:38ef4fa98a32dc55550ff49bb36a583e178b3a9b830fcb8dcc27bfc4254bc2bc',
'com.android.support.test:rules:1.0.2:rules-1.0.2.aar:7ddad387d1a16d4dbdbefacee070d34574e565b008117c1a163edac8ae02a6aa',
'com.android.support.test:runner:1.0.2:runner-1.0.2.aar:f04b9ae342975ba1cb3e4a06e13426e3e6b8a73faa45acba604493d83c9a4f00',
'com.android.support:animated-vector-drawable:27.1.1:animated-vector-drawable-27.1.1.aar:59670473f6e98fda792f7bef25dd7292b0a3106031c7a5e30eb020bf26f077bd',
'com.android.support:appcompat-v7:27.1.1:appcompat-v7-27.1.1.aar:0c7808fbbc5838d831e32e3c0a6f84e1f2c981deb8f11e010650f2b57923a335',
'com.android.support:cardview-v7:27.1.1:cardview-v7-27.1.1.aar:8ed955dd037d82a7b4bbcaedb4f896523c3e4c1bf3ca698ce807c350767a2886',
@@ -109,8 +127,10 @@ dependencyVerification {
'com.android.tools:sdk-common:26.1.3:sdk-common-26.1.3.jar:1948603ca9ff22c7ebb3178000bffa3a9dd2ca1cc5cb0c793cae08468b8fcfc1',
'com.android.tools:sdklib:26.1.3:sdklib-26.1.3.jar:4adcfaad9514607098d2c51503c39811112d3050f4d1e744c01c7f08f591032b',
'com.github.bumptech.glide:glide:3.8.0:glide-3.8.0.jar:750d9e7b940dc0ee48f8680623b55d46e14e8727acc922d7b156e57e7c549655',
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.7:gson-2.7.jar:2d43eb5ea9e133d2ee2405cc14f5ee08951b8361302fdd93494a3a997b508d32',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
@@ -127,6 +147,7 @@ dependencyVerification {
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.ibm.icu:icu4j:53.1:icu4j-53.1.jar:e37a4467bac5cdeb02c5c4b8e5063d2f4e67b69e3c7df6d6b610f13185572bab',
'com.jpardogo.materialtabstrip:library:1.1.0:library-1.1.0.aar:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
'com.squareup:javawriter:2.1.1:javawriter-2.1.1.jar:f699823d0081f69cbb676c1845ea222e0ada79bc88a53e5d22d8bd02d328f57e',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
@@ -181,6 +202,7 @@ dependencyVerification {
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-integration:1.3:hamcrest-integration-1.3.jar:70f418efbb506c5155da5f9a5a33262ea08a9e4d7fea186aa9015c41a7224ac2',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
@@ -212,6 +234,7 @@ dependencyVerification {
'org.robolectric:shadows-framework:3.5.1:shadows-framework-3.5.1.jar:597b54cc1a494799d783921c6ac04352f33e94fca8e00f299d4ca192db79e3fc',
'org.robolectric:shadows-support-v4:3.0:shadows-support-v4-3.0.jar:66bcc3257b037d72998e860d67b1bc58215b7eeac8ad860fcc3e613332d88619',
'org.robolectric:utils:3.5.1:utils-3.5.1.jar:d7d77326867e6d903156ebb18c244819b26aebe3aa82a1c57081081a0b6c4f63',
'tools.fastlane:screengrab:1.1.0:screengrab-1.1.0.aar:03ce3868ee8a0082d14e7a1de0999f91531c0cc794392688beb08ee9bc4495fd',
'uk.co.samuelwall:material-tap-target-prompt:2.8.0:material-tap-target-prompt-2.8.0.aar:ac70770c05bbc4675a1d5712c0e53d46ee4fa961b74947589fce50d8003065ec',
'xmlpull:xmlpull:1.1.3.1:xmlpull-1.1.3.1.jar:34e08ee62116071cbb69c0ed70d15a7a5b208d62798c59f2120bb8929324cb63',
'xpp3:xpp3_min:1.1.4c:xpp3_min-1.1.4c.jar:bfc90e9e32d0eab1f397fb974b5f150a815188382ac41f372a7149d5bc178008',
@@ -241,24 +264,22 @@ android {
versionCode 10013
versionName "1.0.13"
applicationId "org.briarproject.briar.android"
resValue "string", "app_package", "org.briarproject.briar.android"
resValue "string", "app_name", "Briar"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
def now = (long) (System.currentTimeMillis() / 1000)
buildConfigField "Long", "BuildTimestamp",
"${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L"
testInstrumentationRunner 'org.briarproject.briar.android.test.BriarTestRunner'
}
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_package", "org.briarproject.briar.android.debug"
resValue "string", "app_name", "Briar Debug"
shrinkResources false
minifyEnabled true
crunchPngs false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-test.txt'
}
release {
shrinkResources false
@@ -268,6 +289,23 @@ android {
}
}
flavorDimensions "version"
productFlavors {
screenshot {
dimension "version"
minSdkVersion 18
applicationIdSuffix ".screenshot" // = org.briarproject.briar.android.screenshot.debug
}
main {
dimension "version"
}
}
variantFilter { variant ->
if (variant.flavors*.name.contains("screenshot") && variant.buildType.name == "release") {
setIgnore(true)
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

View File

@@ -0,0 +1,2 @@
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("org.briarproject.briar.android")

View File

@@ -0,0 +1,30 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Takes screenshots for manual and Google Play"
lane :screenshots do
gradle(project_dir: "..", task: "assembleScreenshot assembleAndroidTest")
system './demo-mode-activate.sh'
capture_android_screenshots
system './demo-mode-deactivate.sh'
system './rename_screenshots.py'
end
end
# vi:syntax=ruby

View File

@@ -0,0 +1,9 @@
app_package_name "org.briarproject.briar.android.screenshot.debug"
locales ['en-US']
use_tests_in_classes([
'org.briarproject.briar.android.login.SetupActivityScreenshotTest',
'org.briarproject.briar.android.settings.SettingsActivityScreenshotTest',
])
app_apk_path "build/outputs/apk/screenshot/debug/briar-android-screenshot-debug.apk"
tests_apk_path "build/outputs/apk/androidTest/screenshot/debug/briar-android-screenshot-debug-androidTest.apk"
test_instrumentation_runner "org.briarproject.briar.android.test.BriarTestRunner"

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
adb shell settings put global sysui_demo_allowed 1
adb shell am broadcast -a com.android.systemui.demo -e command enter
adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false
adb shell am broadcast -a com.android.systemui.demo -e command battery -e level 100
adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show
adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1337

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
adb shell am broadcast -a com.android.systemui.demo -e command exit

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
# Author: Torsten Grote
# License: GPLv3 or later
import os
import re
import glob
METADATA_PATH = 'metadata/android'
GLOB = '/*/images/phoneScreenshots/*.png'
REGEX = re.compile(r'(^\w+)_\d{13}\.png$')
REGEX_IN_FILE = re.compile(r'(\w+)_\d{13}\.png', re.MULTILINE)
PATH = os.path.dirname(os.path.realpath(__file__))
def main():
for path in glob.glob("%s%s" % (os.path.join(PATH, METADATA_PATH), GLOB)):
filename = os.path.basename(path)
match = REGEX.match(filename)
if match:
directory = os.path.dirname(path)
new_filename = "%s.png" % match.group(1)
new_path = os.path.join(directory, new_filename)
os.rename(path, new_path)
print("Renaming\n %s\nto\n %s\n" % (path, new_path))
else:
print("Warning: Path did not match %s" % path)
# rename fields also in screenshot overview file
overview = os.path.join(PATH, METADATA_PATH, 'screenshots.html')
with open(overview, 'r') as f:
file_data = f.read()
file_data = REGEX_IN_FILE.sub(r'\1.png', file_data)
with open(overview, 'w') as f:
f.write(file_data)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,15 @@
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontnote android.support.test.**
-dontwarn com.googlecode.eyesfree.compat.CompatUtils
-keep class org.xmlpull.v1.** { *; }
-dontwarn org.xmlpull.v1.**
-keep class org.junit.** { *; }
-dontwarn org.junit.**
-keep class junit.** { *; }
-dontwarn junit.**
-dontwarn org.briarproject.briar.android.BriarTestComponentApplication

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.briar.BriarCoreModule;
public class BriarTestComponentApplication extends BriarApplicationImpl {
@Override
protected AndroidComponent createApplicationComponent() {
AndroidComponent component = DaggerBriarUiTestComponent.builder()
.appModule(new AppModule(this)).build();
// We need to load the eager singletons directly after making the
// dependency graphs
BrambleCoreModule.initEagerSingletons(component);
BriarCoreModule.initEagerSingletons(component);
AndroidEagerSingletons.initEagerSingletons(component);
return component;
}
}

View File

@@ -0,0 +1,31 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.login.PasswordActivityTest;
import org.briarproject.briar.android.login.SetupActivityScreenshotTest;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
AppModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,
BrambleCoreModule.class
})
public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupActivityScreenshotTest test);
void inject(PasswordActivityTest test);
void inject(NavDrawerActivityTest test);
void inject(SettingsActivityScreenshotTest test);
}

View File

@@ -0,0 +1,106 @@
package org.briarproject.briar.android.login;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.briarproject.briar.android.test.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.runner.lifecycle.Stage.PAUSED;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.briar.android.test.ViewActions.waitForActivity;
import static org.briarproject.briar.android.test.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@RunWith(AndroidJUnit4.class)
public class SetupActivityScreenshotTest extends ScreenshotTest {
@Rule
public IntentsTestRule<SetupActivity> testRule =
new IntentsTestRule<SetupActivity>(SetupActivity.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
accountManager.deleteAccount();
}
};
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
}
@Test
public void createAccount() throws Exception {
// Enter username
onView(withText(R.string.setup_title))
.check(matches(isDisplayed()));
onView(withId(R.id.nickname_entry))
.check(matches(isDisplayed()))
.perform(typeText(USERNAME));
onView(withId(R.id.nickname_entry))
.perform(waitUntilMatches(withText(USERNAME)));
screenshot("manual_create_account");
onView(withId(R.id.next))
.check(matches(isDisplayed()))
.perform(click());
// Enter password
onView(withId(R.id.password_entry))
.check(matches(isDisplayed()))
.perform(typeText(PASSWORD));
onView(withId(R.id.password_confirm))
.check(matches(isDisplayed()))
.perform(typeText(PASSWORD));
onView(withId(R.id.next))
.check(matches(isDisplayed()))
.perform(click());
// White-list Doze if needed
if (needsDozeWhitelisting(getTargetContext())) {
onView(withText(R.string.setup_doze_button))
.check(matches(isDisplayed()))
.perform(click());
UiDevice device = UiDevice.getInstance(getInstrumentation());
UiObject allowButton = device.findObject(
new UiSelector().className("android.widget.Button")
.index(1));
allowButton.click();
onView(withId(R.id.next))
.check(matches(isDisplayed()))
.perform(click());
}
// wait for OpenDatabaseActivity to show up
onView(withId(R.id.progress))
.check(matches(isDisplayed()));
onView(isRoot())
.perform(waitForActivity(testRule.getActivity(), PAUSED));
intended(hasComponent(OpenDatabaseActivity.class.getName()));
assertTrue(accountManager.hasDatabaseKey());
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.briar.android.navdrawer;
import android.support.test.espresso.contrib.DrawerActions;
import android.support.test.runner.AndroidJUnit4;
import android.view.Gravity;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.test.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerMatchers.isClosed;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class NavDrawerActivityTest extends ScreenshotTest {
@Rule
public CleanAccountTestRule<NavDrawerActivity> testRule =
new CleanAccountTestRule<>(NavDrawerActivity.class);
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
}
@Test
public void openSettings() {
onView(withId(R.id.drawer_layout))
.check(matches(isClosed(Gravity.START)))
.perform(DrawerActions.open());
onView(withText(R.string.settings_button))
.check(matches(isDisplayed()))
.perform(click());
intended(hasComponent(SettingsActivity.class.getName()));
}
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.briar.android.settings;
import android.content.Intent;
import android.support.test.espresso.contrib.DrawerActions;
import android.support.test.runner.AndroidJUnit4;
import android.view.Gravity;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.test.ScreenshotTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerMatchers.isClosed;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class SettingsActivityScreenshotTest extends ScreenshotTest {
@Rule
public CleanAccountTestRule<SettingsActivity> testRule =
new CleanAccountTestRule<>(SettingsActivity.class);
@Override
protected void inject(BriarUiTestComponent component) {
component.inject(this);
}
@Test
public void changeTheme() {
onView(withText(R.string.settings_button))
.check(matches(isDisplayed()));
screenshot("manual_dark_theme_settings");
// switch to dark theme
onView(withText(R.string.pref_theme_title))
.check(matches(isDisplayed()))
.perform(click());
onView(withText(R.string.pref_theme_dark))
.check(matches(isDisplayed()))
.perform(click());
// start main activity
Intent i =
new Intent(testRule.getActivity(), NavDrawerActivity.class);
testRule.getActivity().startActivity(i);
// close expiry warning
onView(withId(R.id.expiryWarningClose))
.check(matches(isDisplayed()));
onView(withId(R.id.expiryWarningClose))
.perform(click());
// open navigation drawer
onView(withId(R.id.drawer_layout))
.check(matches(isClosed(Gravity.START)))
.perform(DrawerActions.open());
screenshot("manual_dark_theme_nav_drawer");
}
}

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.android.test;
import android.app.Application;
import android.content.Context;
import android.support.test.runner.AndroidJUnitRunner;
import org.briarproject.briar.android.BriarTestComponentApplication;
public class BriarTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl, String className,
Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, BriarTestComponentApplication.class.getName(),
context);
}
}

View File

@@ -0,0 +1,73 @@
package org.briarproject.briar.android.test;
import android.app.Activity;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.util.Log;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.briar.android.BriarTestComponentApplication;
import org.briarproject.briar.android.BriarUiTestComponent;
import org.junit.ClassRule;
import javax.inject.Inject;
import tools.fastlane.screengrab.Screengrab;
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
import tools.fastlane.screengrab.locale.LocaleTestRule;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static tools.fastlane.screengrab.Screengrab.setDefaultScreenshotStrategy;
public abstract class ScreenshotTest {
@ClassRule
public static final LocaleTestRule localeTestRule = new LocaleTestRule();
protected static final String USERNAME = "Alice";
protected static final String PASSWORD = "123456";
@Inject
protected AccountManager accountManager;
@Inject
protected LifecycleManager lifecycleManager;
public ScreenshotTest() {
super();
setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
BriarTestComponentApplication app =
(BriarTestComponentApplication) getTargetContext()
.getApplicationContext();
inject((BriarUiTestComponent) app.getApplicationComponent());
}
protected abstract void inject(BriarUiTestComponent component);
protected void screenshot(String name) {
try {
Screengrab.screenshot(name);
} catch (RuntimeException e) {
if (!e.getMessage().equals("Unable to capture screenshot."))
throw e;
// The tests should still pass when run from AndroidStudio
// without manually granting permissions like fastlane does.
Log.w("Screengrab", "Permission to write screenshot is missing.");
}
}
protected class CleanAccountTestRule<A extends Activity>
extends IntentsTestRule<A> {
public CleanAccountTestRule(Class<A> activityClass) {
super(activityClass);
}
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
accountManager.deleteAccount();
accountManager.createAccount(USERNAME, PASSWORD);
}
}
}

View File

@@ -0,0 +1,93 @@
package org.briarproject.briar.android.test;
import android.app.Activity;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.support.test.runner.lifecycle.Stage;
import android.view.View;
import org.hamcrest.Matcher;
import java.util.concurrent.TimeoutException;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.util.HumanReadables.describe;
import static android.support.test.espresso.util.TreeIterables.breadthFirstViewTraversal;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;
public class ViewActions {
private final static long TIMEOUT_MS = SECONDS.toMillis(10);
private final static long WAIT_MS = 50;
public static ViewAction waitUntilMatches(Matcher<View> viewMatcher) {
return waitUntilMatches(viewMatcher, TIMEOUT_MS);
}
private static ViewAction waitUntilMatches(Matcher<View> viewMatcher,
long timeout) {
return new CustomViewAction() {
@Override
protected boolean exitConditionTrue(View view) {
for (View child : breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child)) return true;
}
return false;
}
@Override
public String getDescription() {
return "Wait for view matcher " + viewMatcher +
" to match within " + timeout + " milliseconds.";
}
};
}
public static ViewAction waitForActivity(Activity activity, Stage stage) {
return new CustomViewAction() {
@Override
protected boolean exitConditionTrue(View view) {
ActivityLifecycleMonitor lifecycleMonitor =
ActivityLifecycleMonitorRegistry.getInstance();
return lifecycleMonitor.getLifecycleStageOf(activity) == stage;
}
@Override
public String getDescription() {
return "Wait for activity " + activity.getClass().getName() +
" to resume within " + TIMEOUT_MS + " milliseconds.";
}
};
}
private static abstract class CustomViewAction implements ViewAction {
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadUntilIdle();
long endTime = currentTimeMillis() + TIMEOUT_MS;
do {
if (exitConditionTrue(view)) return;
uiController.loopMainThreadForAtLeast(WAIT_MS);
}
while (currentTimeMillis() < endTime);
throw new PerformException.Builder()
.withActionDescription(getDescription())
.withViewDescription(describe(view))
.withCause(new TimeoutException())
.build();
}
protected abstract boolean exitConditionTrue(View view);
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Briar Debug</string>
<string name="app_package" translatable="false">org.briarproject.briar.android.debug</string>
</resources>

View File

@@ -109,15 +109,20 @@ public class BriarApplicationImpl extends Application
LOG.info("Created");
applicationComponent = DaggerAndroidComponent.builder()
applicationComponent = createApplicationComponent();
}
protected AndroidComponent createApplicationComponent() {
AndroidComponent androidComponent = DaggerAndroidComponent.builder()
.appModule(new AppModule(this))
.build();
// We need to load the eager singletons directly after making the
// dependency graphs
BrambleCoreModule.initEagerSingletons(applicationComponent);
BriarCoreModule.initEagerSingletons(applicationComponent);
AndroidEagerSingletons.initEagerSingletons(applicationComponent);
BrambleCoreModule.initEagerSingletons(androidComponent);
BriarCoreModule.initEagerSingletons(androidComponent);
AndroidEagerSingletons.initEagerSingletons(androidComponent);
return androidComponent;
}
@Override

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Briar</string>
<string name="app_package" translatable="false">org.briarproject.briar.android</string>
<!-- Setup -->
<string name="setup_title">Welcome to Briar</string>
<string name="setup_name_explanation">Your nickname will be shown next to any content you post. You can\'t change it after creating your account.</string>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The following permissions are only needed for taking automatic screenshots (fastlane) -->
<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- Allows for storing and retrieving screenshots -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Allows changing locales -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
</manifest>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Briar</string>
<string name="app_package" translatable="false">org.briarproject.briar.android.screenshot.debug</string>
</resources>