Slide 1

Slide 1 text

Screenshot your Entire App Edward Dale Freeletics © Edward Dale, 2016 1

Slide 2

Slide 2 text

Find the bug(s) © Edward Dale, 2016 2

Slide 3

Slide 3 text

© Edward Dale, 2016 3

Slide 4

Slide 4 text

© Edward Dale, 2016 4

Slide 5

Slide 5 text

Some things are best verified visually ! Screenshot © Edward Dale, 2016 5

Slide 6

Slide 6 text

Screenshots are useful for • Acceptance testing • Edge case • Localization • Multiple devices • Regression testing © Edward Dale, 2016 6

Slide 7

Slide 7 text

Demo App1 • Fetch location of user • Fetch weather forecast at that location • Show it in a nice way 1 https://github.com/scompt/ScreenshotDemo © Edward Dale, 2016 7

Slide 8

Slide 8 text

Screenshot cases • How does it look when loading? © Edward Dale, 2016 8

Slide 9

Slide 9 text

Screenshot cases • How does it look when loading? • How does the error screen look? © Edward Dale, 2016 9

Slide 10

Slide 10 text

Screenshot cases • How does it look when loading? • How does the error screen look? • How do all weather icons look? © Edward Dale, 2016 10

Slide 11

Slide 11 text

Screenshot cases • How does it look when loading? • How does the error screen look? • How do all weather icons look? • How does it look in German? © Edward Dale, 2016 11

Slide 12

Slide 12 text

Screenshot testing tools • Espresso tests • Dependency injection • Dependency mocking • Screengrab © Edward Dale, 2016 12

Slide 13

Slide 13 text

Loading screen Espresso test @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class); @Test public void testProgressIsShownWhileLoading() throws Exception { // The activity is already started here // More about Screengrab later! Screengrab.screenshot("progress"); // No assertions } © Edward Dale, 2016 13

Slide 14

Slide 14 text

Loading screen test problems • What if the app is really fast? • What if the network is disconnected? © Edward Dale, 2016 14

Slide 15

Slide 15 text

Loading screen test problems • What if the app is really fast? • What if the network is disconnected? Answers • Dependency injection • Mocked dependencies © Edward Dale, 2016 15

Slide 16

Slide 16 text

Without dependency injection public class MainActivity extends AppCompatActivity { WeatherService weatherService = new ForecastIoWeatherService(); ... } • MainActivity will always use forecast.io to load weather. © Edward Dale, 2016 16

Slide 17

Slide 17 text

With dependency injection public class MainActivity extends AppCompatActivity { @Inject WeatherService weatherService; ... } • MainActivity doesn't care which WeatherService it uses • Can be different for production and tests © Edward Dale, 2016 17

Slide 18

Slide 18 text

Dagger dependency injection @Singleton @Component(modules = {ForecastIoModule.class, ... }) public interface ScreenshotDemoComponent { void inject(MainActivity target); } @Module @Singleton public class ForecastIoModule { @Provides WeatherService provideWeatherService(ForecastIoWeatherService impl) { return impl; } } © Edward Dale, 2016 18

Slide 19

Slide 19 text

Dagger dependency injection public class MainActivity extends AppCompatActivity { @Inject WeatherService weatherService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Fancy Dagger code to do injection ScreenshotDemoApplication.get(this).component().inject(this); } ... } © Edward Dale, 2016 19

Slide 20

Slide 20 text

Loading screen test pseudocode • Configure app to use WeatherService that loads forever • Start activity • Take screenshot © Edward Dale, 2016 20

Slide 21

Slide 21 text

Mock WeatherService with Mockito WeatherService mockWeatherService = mock(WeatherService.class); Single neverending = Observable.never().toSingle(); doReturn(neverending) .when(mockWeatherService) .weatherForLocation(Matchers.any(Location.class)); © Edward Dale, 2016 21

Slide 22

Slide 22 text

Loading screen mocked test 1 @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); @Inject WeatherService mockWeatherService; @Before public void injectTest() { // Inject test dependencies ... } © Edward Dale, 2016 22

Slide 23

Slide 23 text

Loading screen mocked test 2 @Test public void testProgressIsShownWhileLoading() throws Exception { // Configure app doReturn(Observable.never().toSingle()) .when(mockWeatherService) .weatherForLocation(Matchers.any(Location.class)); // Start Activity mActivityRule.launchActivity(null /* Intent */); // Take screenshot Screengrab.screenshot("progress"); } © Edward Dale, 2016 23

Slide 24

Slide 24 text

Screenshot cases • How does it look when loading? © Edward Dale, 2016 24

Slide 25

Slide 25 text

Screenshot cases • How does it look when loading? • How does the error screen look? © Edward Dale, 2016 25

Slide 26

Slide 26 text

Error screen test @Test public void testErrorShown() throws Exception { // Configure app doReturn(Observable.error(new Exception("message")).toSingle()) .when(mockWeatherService) .weatherForLocation(Matchers.any(Location.class)); // Start Activity mActivityRule.launchActivity(null /* Intent */); // Take screenshot Screengrab.screenshot("error"); } © Edward Dale, 2016 26

Slide 27

Slide 27 text

Error screen test @Test public void testErrorShown() throws Exception { // Configure app doReturn(Observable.error(new Exception("message")).toSingle()) .when(mockWeatherService) .weatherForLocation(Matchers.any(Location.class)); // Start Activity mActivityRule.launchActivity(null /* Intent */); // Take screenshot Screengrab.screenshot("progress"); } © Edward Dale, 2016 27

Slide 28

Slide 28 text

Screenshot cases • How does it look when loading? • How does the error screen look? © Edward Dale, 2016 28

Slide 29

Slide 29 text

Screenshot cases • How does it look when loading? • How does the error screen look? • How do all weather icons look? © Edward Dale, 2016 29

Slide 30

Slide 30 text

All weather test pseudocode • Configure app to use WeatherService that has every type of weather • Start activity • Take screenshot • Swipe • Repeat © Edward Dale, 2016 30

Slide 31

Slide 31 text

All weather test @Test public void testErrorShown() throws Exception { // Configure app doReturn(Single.just(parseWeather("all_icons.json"))) .when(weatherService) .weatherForLocation(Matchers.any(Location.class)); .... © Edward Dale, 2016 31

Slide 32

Slide 32 text

// Start Activity mActivityRule.launchActivity(null /* Intent */); // Take screenshots for (int i = 0; i < ICON_COUNT; i++) { Screengrab.screenshot("icon_" + i); onView(withId(R.id.view_pager)).perform(swipeLeft()); } // Still no assertions } © Edward Dale, 2016 32

Slide 33

Slide 33 text

Screenshot cases • How does it look when loading? • How does the error screen look? • How do all weather icons look? © Edward Dale, 2016 33

Slide 34

Slide 34 text

Screenshot cases • How does it look when loading? • How does the error screen look? • How do all weather icons look? • How does it look in German? © Edward Dale, 2016 34

Slide 35

Slide 35 text

Automated localized screenshots of your Android app on every device Screengrab ▶ Fastlane ▶ Fabric ▶ Twitter Combination of command-line tool and classes used in tests © Edward Dale, 2016 35

Slide 36

Slide 36 text

• Add LocaleTestRule to test classes • Enables locale switching @RunWith(AndroidJUnit4.class) @LargeTest public class TestMainActivity { @ClassRule public static final LocaleTestRule localeTestRule = new LocaleTestRule(); ... © Edward Dale, 2016 36

Slide 37

Slide 37 text

• Install screengrab • Run screengrab init to generate configuration file • Adjust configuration © Edward Dale, 2016 37

Slide 38

Slide 38 text

app_package_name 'com.scompt.screenshotdemo' use_tests_in_packages ['com.scompt.screenshotdemo'] app_apk_path 'app-debug.apk' tests_apk_path 'app-debug-androidTest-unaligned.apk' locales ['en-US', 'de-DE', 'fr-FR'] ending_locale 'en-US' © Edward Dale, 2016 38

Slide 39

Slide 39 text

Run tests $ ./gradlew clean assembleDebug assembleDebugTest $ screengrab © Edward Dale, 2016 39

Slide 40

Slide 40 text

© Edward Dale, 2016 40

Slide 41

Slide 41 text

© Edward Dale, 2016 41

Slide 42

Slide 42 text

Issues • Naming and organization of files • Misusing screengrab a bit © Edward Dale, 2016 42

Slide 43

Slide 43 text

Alternative/related solutions • Spoon • Distributes instrumentation tests to all devices • Spoon + Screengrab = Amazing? • Firebase Test Lab for Android • Cloud solution that run by Google • Can also run on different locales © Edward Dale, 2016 43

Slide 44

Slide 44 text

Thanks! Edward Dale (@scompt) Freeletics (We're hiring) © Edward Dale, 2016 44