diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 4823480f..21480be0 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -32,6 +32,10 @@ jobs:
recipe_uuid: ${{ vars.GMSAAS_RECIPE_UUID }}
- name: Run tests
run: bash scripts/run_tests.bash
+ - name: Run screenshot tests
+ run: |
+ bash scripts/run_screenshots.sh
+ zip -r screenshots.zip screenshots
- name: Publish Unit Test Report
uses: mikepenz/action-junit-report@v4
if: always() # always run even if the previous step fails
@@ -83,3 +87,10 @@ jobs:
path: |
./app/build/outputs/apk/debug/app-debug.apk
./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
+ - name: Archive screenshots
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: screenshots
+ path: |
+ ./screenshots.zip
diff --git a/app/build.gradle b/app/build.gradle
index 46a23698..de6815d8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -245,6 +245,7 @@ dependencies {
androidTestImplementation "androidx.test.ext:junit:$androidx_junit_version"
androidTestImplementation "androidx.test.uiautomator:uiautomator:$androidx_test_uiautomator_version"
androidTestImplementation "org.robolectric:annotations:$robolectric_version"
+ androidTestImplementation "com.google.testparameterinjector:test-parameter-injector:$test_parameter_injector_version"
androidTestUtil "androidx.test:orchestrator:$androidx_test_orchestrator_version"
diff --git a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/IntegrationTest.java b/app/src/androidTest/java/ca/rmen/android/poetassistant/main/IntegrationTest.java
index 06eb65d1..fbc758c1 100644
--- a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/IntegrationTest.java
+++ b/app/src/androidTest/java/ca/rmen/android/poetassistant/main/IntegrationTest.java
@@ -213,7 +213,7 @@ public void copyCleanLayoutTest() {
public void themeTest() {
openMenuItem(R.string.action_settings);
clickPreference(R.string.pref_theme_title);
- onView(withText(R.string.pref_theme_value_light)).check(matches(isChecked()));
+ onView(withText(R.string.pref_theme_value_auto)).check(matches(isChecked()));
onView(withText(R.string.pref_theme_value_dark)).perform(click());
pressBack();
diff --git a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/ScreenshotTest.java b/app/src/androidTest/java/ca/rmen/android/poetassistant/main/ScreenshotTest.java
deleted file mode 100644
index e9451f89..00000000
--- a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/ScreenshotTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (c) 2017 Carmen Alvarez
- *
- * This file is part of Poet Assistant.
- *
- * Poet Assistant is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Poet Assistant is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Poet Assistant. If not, see .
- */
-
-package ca.rmen.android.poetassistant.main;
-
-
-import android.Manifest;
-import android.graphics.Bitmap;
-import android.os.SystemClock;
-
-import org.junit.FixMethodOrder;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-
-import java.io.IOException;
-import java.util.HashSet;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.rule.GrantPermissionRule;
-import androidx.test.runner.screenshot.BasicScreenCaptureProcessor;
-import androidx.test.runner.screenshot.ScreenCapture;
-import androidx.test.runner.screenshot.ScreenCaptureProcessor;
-import ca.rmen.android.poetassistant.R;
-import ca.rmen.android.poetassistant.main.rules.PoetAssistantActivityTestRule;
-
-import static androidx.test.espresso.Espresso.onIdle;
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.Espresso.pressBack;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static ca.rmen.android.poetassistant.main.TestAppUtils.search;
-import static ca.rmen.android.poetassistant.main.TestAppUtils.starQueryWord;
-import static ca.rmen.android.poetassistant.main.TestAppUtils.typePoem;
-import static ca.rmen.android.poetassistant.main.TestUiUtils.clickPreference;
-import static ca.rmen.android.poetassistant.main.TestUiUtils.openMenuItem;
-import static ca.rmen.android.poetassistant.main.TestUiUtils.swipeViewPagerLeft;
-
-@LargeTest
-@Ignore
-@RunWith(AndroidJUnit4.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class ScreenshotTest {
-
- @Rule
- public PoetAssistantActivityTestRule mActivityTestRule = new PoetAssistantActivityTestRule<>(MainActivity.class, true);
-
- @Rule
- public GrantPermissionRule writeScreenshotRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-
- @Test
- public void takeLightScreenshotsTest() {
- takeScreenshots();
- }
-
- @Test
- public void takeDarkScreenshotsTest() {
- openMenuItem(R.string.action_settings);
- clickPreference(R.string.pref_theme_title);
- onView(withText(R.string.pref_theme_value_dark)).perform(click());
- pressBack();
- takeScreenshots();
- }
-
- private void takeScreenshots() {
- starWords("acquiesce", "askance", "benight", "deferential", "fractious", "implacable", "obfuscation", "peon", "possibleness");
- search("chance");
- takeScreenshot("rhymer");
- swipeViewPagerLeft(1);
- takeScreenshot("thesaurus");
- swipeViewPagerLeft(1);
- takeScreenshot("dictionary");
- swipeViewPagerLeft(1);
- typePoem("Roses are red.\nViolets are blue.\nIf you are a poet,\nthis app is for you.");
- SystemClock.sleep(1000);
- takeScreenshot("composer");
- swipeViewPagerLeft(1);
- takeScreenshot("favorites");
- openMenuItem(R.string.action_settings);
- takeScreenshot("settings");
- }
-
- private void starWords(String... words) {
- for (String word : words) {
- search(word);
- onIdle();
- starQueryWord();
- }
- }
-
- // https://stackoverflow.com/questions/38519568/how-to-take-screenshot-at-the-point-where-test-fail-in-espresso
- private void takeScreenshot(String filename) {
- SystemClock.sleep(500); // :(
- onIdle();
- ScreenCapture capture = androidx.test.runner.screenshot.Screenshot.capture();
- capture.setName(filename);
- capture.setFormat(Bitmap.CompressFormat.PNG);
-
- HashSet processors = new HashSet<>();
- processors.add(new BasicScreenCaptureProcessor());
-
- try {
- capture.process(processors);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
-
-}
diff --git a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/TaskStackTest.java b/app/src/androidTest/java/ca/rmen/android/poetassistant/main/TaskStackTest.java
index 0c57bb9f..8530e2a4 100644
--- a/app/src/androidTest/java/ca/rmen/android/poetassistant/main/TaskStackTest.java
+++ b/app/src/androidTest/java/ca/rmen/android/poetassistant/main/TaskStackTest.java
@@ -87,7 +87,7 @@ private void deepLinkAfterThemeChangeTest(String deepLinkUrl) {
Intent intent = new Intent();
mActivityTestRule.launchActivity(intent);
clickPreference(R.string.pref_theme_title);
- onView(withText(R.string.pref_theme_value_light)).check(matches(isChecked()));
+ onView(withText(R.string.pref_theme_value_auto)).check(matches(isChecked()));
onView(withText(R.string.pref_theme_value_dark)).perform(click());
// Open a deep link
diff --git a/app/src/androidTest/kotlin/ca/rmen/android/poetassistant/main/ScreenshotTest.kt b/app/src/androidTest/kotlin/ca/rmen/android/poetassistant/main/ScreenshotTest.kt
new file mode 100644
index 00000000..bb830712
--- /dev/null
+++ b/app/src/androidTest/kotlin/ca/rmen/android/poetassistant/main/ScreenshotTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2017 - present Carmen Alvarez
+ *
+ * This file is part of Poet Assistant.
+ *
+ * Poet Assistant is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Poet Assistant is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Poet Assistant. If not, see .
+ */
+package ca.rmen.android.poetassistant.main
+
+import android.app.Application
+import android.graphics.Bitmap
+import android.os.SystemClock
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.screenshot.ScreenCapture
+import androidx.test.runner.screenshot.ScreenCaptureProcessor
+import androidx.test.runner.screenshot.Screenshot
+import ca.rmen.android.poetassistant.R
+import ca.rmen.android.poetassistant.Theme.setThemeFromSettings
+import ca.rmen.android.poetassistant.main.rules.PoetAssistantActivityTestRule
+import ca.rmen.android.poetassistant.settings.SettingsPrefs
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import java.io.File
+import java.io.IOException
+
+
+@LargeTest
+@RunWith(TestParameterInjector::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ScreenshotTest {
+ enum class ThemePreference(val value: String) {
+ LIGHT(SettingsPrefs.THEME_LIGHT),
+ DARK(SettingsPrefs.THEME_DARK)
+ }
+
+ @TestParameter
+ lateinit var themePreference: ThemePreference
+
+ private lateinit var deviceScreenshotsFolder: File
+
+ @JvmField
+ @Rule
+ val activityTestRule: PoetAssistantActivityTestRule = PoetAssistantActivityTestRule(
+ MainActivity::class.java, true
+ )
+
+ @Before
+ fun setup() {
+ // Set the theme
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ val settingsPrefs =
+ SettingsPrefs(ApplicationProvider.getApplicationContext())
+ settingsPrefs.theme = themePreference.value
+ setThemeFromSettings(settingsPrefs)
+ }
+
+ // Create a fresh folder to store the screenshots
+ deviceScreenshotsFolder = File(
+ ApplicationProvider.getApplicationContext().getExternalFilesDir(null),
+ "screenshots-${themePreference.value}"
+ )
+ if (deviceScreenshotsFolder.exists()) {
+ deviceScreenshotsFolder.deleteRecursively()
+ }
+ deviceScreenshotsFolder.mkdirs()
+ }
+
+ @Test
+ fun testTakeScreenshots() {
+ starWords(
+ "acquiesce",
+ "askance",
+ "benight",
+ "deferential",
+ "fractious",
+ "implacable",
+ "obfuscation",
+ "peon",
+ "possibleness"
+ )
+ TestAppUtils.search("chance")
+ takeScreenshot("rhymer")
+ TestUiUtils.swipeViewPagerLeft(1)
+ takeScreenshot("thesaurus")
+ TestUiUtils.swipeViewPagerLeft(1)
+ takeScreenshot("dictionary")
+ TestUiUtils.swipeViewPagerLeft(1)
+ TestAppUtils.typePoem("Roses are red.\nViolets are blue.\nIf you are a poet,\nthis app is for you.")
+ SystemClock.sleep(1000)
+ takeScreenshot("composer")
+ TestUiUtils.swipeViewPagerLeft(1)
+ takeScreenshot("favorites")
+ TestUiUtils.openMenuItem(R.string.action_settings)
+ takeScreenshot("settings")
+ }
+
+ private fun starWords(vararg words: String) {
+ for (word in words) {
+ TestAppUtils.search(word)
+ Espresso.onIdle()
+ TestAppUtils.starQueryWord()
+ }
+ }
+
+ private inner class Processor : ScreenCaptureProcessor {
+ override fun process(capture: ScreenCapture): String {
+ val deviceFile = File(deviceScreenshotsFolder, "${capture.name}.png")
+ deviceFile.outputStream().use {
+ capture.bitmap.compress(
+ Bitmap.CompressFormat.PNG,
+ 0,
+ it,
+ )
+ }
+ return deviceFile.absolutePath
+ }
+ }
+
+ private val processor = Processor()
+
+ // https://stackoverflow.com/questions/38519568/how-to-take-screenshot-at-the-point-where-test-fail-in-espresso
+ private fun takeScreenshot(filename: String) {
+ SystemClock.sleep(500) // :(
+ Espresso.onIdle()
+ val capture = Screenshot.capture()
+ capture.setName(filename)
+ capture.setFormat(Bitmap.CompressFormat.PNG)
+
+ try {
+ capture.process(setOf(processor))
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+}
diff --git a/app/src/main/kotlin/ca/rmen/android/poetassistant/settings/SettingsPrefs.kt b/app/src/main/kotlin/ca/rmen/android/poetassistant/settings/SettingsPrefs.kt
index 087f5b97..90f9ed9e 100644
--- a/app/src/main/kotlin/ca/rmen/android/poetassistant/settings/SettingsPrefs.kt
+++ b/app/src/main/kotlin/ca/rmen/android/poetassistant/settings/SettingsPrefs.kt
@@ -129,7 +129,7 @@ class SettingsPrefs(application: Application) {
var theme: String
get() {
- return sharedPreferences.getString(PREF_THEME, THEME_LIGHT) ?: THEME_LIGHT
+ return sharedPreferences.getString(PREF_THEME, THEME_LIGHT) ?: THEME_AUTO
}
set(newValue) {
sharedPreferences.edit().putString(PREF_THEME, newValue).apply()
diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml
index 3a775da4..caf1e8f6 100644
--- a/app/src/main/res/xml/pref_general.xml
+++ b/app/src/main/res/xml/pref_general.xml
@@ -104,7 +104,7 @@
android:title="@string/pref_category_ui">
$output 2>&1 &
-done
-
-# Some more manual steps:
-# - retrieve the screenshots using the device explorer. They will be in /sdcard/Pictures/screenshots. The /sdcard folder may be found in /mnt.
-# - Save them to the different folders (tablet/light, phone/dark, etc).
-# - use the rename.sh script to rename the files to strip the uuids from the names.
diff --git a/scripts/run_screenshots.sh b/scripts/run_screenshots.sh
new file mode 100755
index 00000000..63d56402
--- /dev/null
+++ b/scripts/run_screenshots.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+./gradlew assembleDebug assembleDebugAndroidTest
+mapfile -t devices < <(adb devices | grep "device$" | sed -e 's/\t.*$//g')
+
+# Install the app and test apk to the devices.
+for device in "${devices[@]}";
+do
+ echo "Installing apks to ${device}"
+ adb -s "${device}" install -r ./app/build/outputs/apk/debug/app-debug.apk
+ adb -s "${device}" install -r ./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
+done
+
+run_tests() {
+ device="$1"
+ output="/tmp/${device}.log"
+ echo "Running tests on ${device}, output to ${output}"
+ adb -s "${device}" shell settings put global animator_duration_scale 0
+ adb -s "${device}" shell settings put global transition_animation_scale 0
+ adb -s "${device}" shell settings put global window_animation_scale 0
+ adb -s "${device}" shell am instrument -w -m -e debug false -e class ca.rmen.android.poetassistant.main.ScreenshotTest ca.rmen.android.poetassistant.test.test/androidx.test.runner.AndroidJUnitRunner > "${output}" 2>&1
+
+ echo "pulling screenshots to ${device}-light and ${device}-dark"
+ screenshots_local_folder="screenshots"
+ mkdir -p "${screenshots_local_folder}"
+ if [ -e "${screenshots_local_folder}/${device}-light" ]
+ then
+ rm -rf "${screenshots_local_folder}/${device}-light"
+ fi
+ if [ -e "${screenshots_local_folder}/${device}-dark" ]
+ then
+ rm -rf "${screenshots_local_folder}/${device}-dark"
+ fi
+ base_folder=/storage/emulated/0/Android/data/ca.rmen.android.poetassistant.test/files
+ adb -s "${device}" pull "${base_folder}/screenshots-Light" "${screenshots_local_folder}/${device}-light"
+ adb -s "${device}" pull "${base_folder}/screenshots-Dark" "${screenshots_local_folder}/${device}-dark"
+}
+
+device_count=${#devices[@]}
+for device in "${devices[@]}";
+do
+ if [ "${device_count}" = 1 ]
+ # Run blocking if there's only one device
+ then
+ run_tests "$device"
+ # Else spawn the test run so we can run multiple devices in parallel
+ else
+ run_tests "$device" &
+ fi
+done
+
diff --git a/scripts/run_tests.bash b/scripts/run_tests.bash
index 239371fd..f2b64040 100644
--- a/scripts/run_tests.bash
+++ b/scripts/run_tests.bash
@@ -4,4 +4,4 @@ adb shell settings put global animator_duration_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global window_animation_scale 0
-./gradlew --no-daemon testDebugUnitTest cAT jacocoTestReport
+./gradlew --no-daemon testDebugUnitTest cAT jacocoTestReport -Pandroid.testInstrumentationRunnerArguments.notClass="ca.rmen.android.poetassistant.main.ScreenshotTest"
\ No newline at end of file