Implemented UI tests for PlaylistFragment (#658)

This commit is contained in:
Martijn Brekhof 2019-09-30 13:35:17 +02:00 committed by Synced Synapse
parent 7dfd982643
commit f024c13ca5
6 changed files with 331 additions and 15 deletions

View File

@ -103,6 +103,18 @@ public class EspressoTestUtils {
onView(withContentDescription("Collapse")).perform(click()); onView(withContentDescription("Collapse")).perform(click());
} }
/**
* Clicks the button with given resourceId and checks if the
* button is displayed. As we occasionally use the same identifiers
* in multiple fragments, we need to check if it is visible as well
* to prevent Espresso from finding multiple views that match the
* resource identifier.
* @param resourceId
*/
public static void clickButton(int resourceId) {
onView(allOf(withId(resourceId), isDisplayed())).perform(click());
}
/** /**
* Clicks on the search menu item and enters the given search query * Clicks on the search menu item and enters the given search query
* @param activity * @param activity
@ -201,6 +213,15 @@ public class EspressoTestUtils {
.check(matches(Matchers.withRecyclerViewSize(listSize))); .check(matches(Matchers.withRecyclerViewSize(listSize)));
} }
/**
* Checks that the list size matches the given list size
* @param listSize amount of elements expected in list
*/
public static void checkListViewSize(int listSize, int resourceId) {
onView(allOf(withId(resourceId), isDisplayed()))
.check(matches(Matchers.withListViewSize(listSize)));
}
/** /**
* Checks if search action view does not exist in the current view hierarchy * Checks if search action view does not exist in the current view hierarchy
*/ */

View File

@ -22,7 +22,7 @@ import android.support.test.espresso.matcher.CursorMatchers;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.widget.ListView;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@ -54,13 +54,11 @@ public class Matchers {
public void describeTo(Description description) { } public void describeTo(Description description) { }
} }
public static Matcher<View> withListSize(final int size) { public static Matcher<View> withListViewSize(final int size) {
return new TypeSafeMatcher<View>() { return new TypeSafeMatcher<View>() {
@Override public boolean matchesSafely(final View view) { @Override public boolean matchesSafely(final View view) {
if (!(view instanceof ViewGroup)) return (view instanceof ListView) &&
return false; ((ListView) view).getAdapter().getCount() == size;
return ((ViewGroup) view).getChildCount() == size;
} }
@Override public void describeTo(final Description description) { @Override public void describeTo(final Description description) {

View File

@ -87,9 +87,9 @@ public class SlideUpPanelTests extends AbstractTestClass<MusicActivity> {
super.setUp(); super.setUp();
getPlaylistHandler().reset(); getPlaylistHandler().reset();
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.AUDIO, createMusicItem(0, 0)); getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.AUDIO, createMusicItem(0, 0), true);
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createVideoItem(0, 1)); getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createVideoItem(0, 1), false);
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createMusicVideoItem(0, 2)); getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createMusicVideoItem(0, 2), false);
getPlayerHandler().reset(); getPlayerHandler().reset();
getPlayerHandler().setPlaylists(getPlaylistHandler().getPlaylists()); getPlayerHandler().setPlaylists(getPlaylistHandler().getPlaylists());

View File

@ -0,0 +1,293 @@
/*
* Copyright 2018 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.tests.ui.remote.playlistfragment.TCP;
import android.content.Context;
import android.support.test.rule.ActivityTestRule;
import android.view.View;
import android.widget.TextView;
import org.junit.Rule;
import org.junit.Test;
import org.xbmc.kore.R;
import org.xbmc.kore.testhelpers.EspressoTestUtils;
import org.xbmc.kore.testhelpers.action.ViewActions;
import org.xbmc.kore.tests.ui.AbstractTestClass;
import org.xbmc.kore.testutils.tcpserver.handlers.PlayerHandler;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Player;
import org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.methods.Playlist;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import java.util.List;
import java.util.concurrent.TimeoutException;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.swipeLeft;
import static android.support.test.espresso.action.ViewActions.swipeRight;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
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 org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.xbmc.kore.testutils.TestUtils.createMusicItem;
import static org.xbmc.kore.testutils.TestUtils.createPictureItem;
import static org.xbmc.kore.testutils.TestUtils.createVideoItem;
import static org.xbmc.kore.testutils.tcpserver.handlers.jsonrpc.response.notifications.Playlist.OnClear;
public class PlaylistTests extends AbstractTestClass<RemoteActivity> {
private static final int PLAYLIST_SIZE = 10;
@Rule
public ActivityTestRule<RemoteActivity> remoteActivityActivityTestRule =
new ActivityTestRule<>(RemoteActivity.class);
@Override
protected ActivityTestRule<RemoteActivity> getActivityTestRule() {
return remoteActivityActivityTestRule;
}
@Override
protected void setSharedPreferences(Context context) {
}
@Override
public void setUp() throws Throwable {
int itemId = 0;
getPlaylistHandler().reset();
for (int i = 0; i < PLAYLIST_SIZE; i++) {
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.AUDIO, createMusicItem(i, itemId++), false);
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.VIDEO, createVideoItem(i, itemId++), false);
getPlaylistHandler().addItemToPlaylist(Playlist.playlistID.PICTURE, createPictureItem(i, itemId++), false);
}
getPlayerHandler().reset();
getPlayerHandler().setPlaylists(getPlaylistHandler().getPlaylists());
getPlayerHandler().startPlay(Playlist.playlistID.AUDIO, 0);
// Checking for available playlists is done in PlaylistFragment on startup
// and every 10 seconds. To make sure PlaylistFragment can get the available
// playlists at startup, the activity needs to be created after the backend
// has been fully setup.
super.setUp();
onView(isRoot()).perform(swipeLeft());
waitForAudioPlaylistToShow();
}
/**
* Test if playlist is not cleared when playback is stopped
*
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Stop playback
* 3. Result: playlist should still be visible
*/
@Test
public void keepPlaylistOnStop() {
onView(isRoot()).perform(swipeRight());
EspressoTestUtils.clickButton(R.id.stop);
onView(isRoot()).perform(swipeLeft());
assertEquals(getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO).size(), PLAYLIST_SIZE);
EspressoTestUtils.checkListViewSize(PLAYLIST_SIZE, R.id.playlist);
}
/**
* Test if playlist is not cleared when playback is paused
*
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Pause playback
* 3. Result: playlist should still be visible
*/
@Test
public void keepPlaylistOnPause() {
onView(isRoot()).perform(swipeRight());
EspressoTestUtils.clickButton(R.id.play);
onView(isRoot()).perform(swipeLeft());
assertEquals(getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO).size(), PLAYLIST_SIZE);
EspressoTestUtils.checkListViewSize(PLAYLIST_SIZE, R.id.playlist);
}
/**
* Test if playlist is cleared when cleared on Kodi
*
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Clear playlist on server (Kodi)
* 3. Result: playlist should be empty
*/
@Test
public void clearPlaylistWhenClearedOnKodi() throws Exception {
getPlaylistHandler().clearPlaylist(Playlist.playlistID.AUDIO);
getConnectionHandlerManager().waitForNotification(OnClear.METHOD_NAME, 10000);
assertEquals(0, getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO).size());
onView(allOf(withId(R.id.info_title), withText(R.string.playlist_empty)))
.check(matches(isDisplayed()));
}
/**
* Test if playback of a playlist is resumed after stopping playback
*
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Stop playback
* 3. Click on playlist item
* 4. Result: playback should resume from clicked playlist item
*/
@Test
public void stopPlayingAndResumeNextItem() throws TimeoutException {
int positionClicked = 3;
onView(isRoot()).perform(swipeRight());
EspressoTestUtils.clickButton(R.id.stop);
onView(isRoot()).perform(swipeLeft());
getConnectionHandlerManager().clearMethodsHandled();
EspressoTestUtils.clickAdapterViewItem(positionClicked, R.id.playlist);
getConnectionHandlerManager().waitForMethodHandled(Player.Open.METHOD_NAME, 10000);
List<Player.GetItem> playlistOnServer = getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO);
assertSame(getPlayerHandler().getPlayState(), PlayerHandler.PLAY_STATE.PLAYING);
assertEquals("Playlist on server has size " + playlistOnServer.size() +
" but should be " + PLAYLIST_SIZE, playlistOnServer.size(), PLAYLIST_SIZE);
assertEquals("Current playing item ID is " + getPlayerHandler().getMediaItem().getLibraryId() +
", but this should be " + playlistOnServer.get(positionClicked).getLibraryId(),
getPlayerHandler().getMediaItem().getLibraryId(), playlistOnServer.get(positionClicked).getLibraryId());
}
/**
* Test if playlist is correctly restored after playback has stopped
* and device configuration changed
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Rotate device
* 3. Result: playlist should be the same as before rotation
*/
@Test
public void restorePlaylistAfterConfigurationChange() {
getConnectionHandlerManager().clearMethodsHandled();
EspressoTestUtils.rotateDevice(getActivity());
waitForAudioPlaylistToShow();
assertEquals(getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO).size(), PLAYLIST_SIZE);
EspressoTestUtils.checkListViewSize(PLAYLIST_SIZE, R.id.playlist);
}
/**
* Test if playlist is correctly restored after playback has stopped
* and device configuration changed
* UI interaction flow tested:
* 1. Start playing multiple music items
* 2. Stop playback
* 3. Rotate device
* 4. Result: playlist should be the same as before rotation
*/
@Test
public void restorePlaylistAfterStopAndConfigurationChange() {
onView(isRoot()).perform(swipeRight());
EspressoTestUtils.clickButton(R.id.stop);
onView(isRoot()).perform(swipeLeft());
getConnectionHandlerManager().clearMethodsHandled();
EspressoTestUtils.rotateDevice(getActivity());
waitForAudioPlaylistToShow();
assertEquals(getPlaylistHandler().getPlaylist(Playlist.playlistID.AUDIO).size(), PLAYLIST_SIZE);
EspressoTestUtils.checkListViewSize(PLAYLIST_SIZE, R.id.playlist);
}
/**
* Test if playlist for currently playing item is shown even if other
* playlists are available on server
* UI interaction flow tested:
* 1. Add audio and video playlists on server
* 2. Start playing video item
* 3. Result: playlist for video items should be shown
*/
@Test
public void showCurrentlyPlayingPlaylist() {
getPlayerHandler().startPlay(Playlist.playlistID.VIDEO, 0);
onView(isRoot()).perform(ViewActions.waitForView(R.id.playlist_item_title, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((TextView) v).getText().toString().contains("Video");
}
}, 10000));
assertEquals("Playlist on server has size "
+ getPlaylistHandler().getPlaylist(Playlist.playlistID.VIDEO).size() +
" but should be " + PLAYLIST_SIZE,
getPlaylistHandler().getPlaylist(Playlist.playlistID.VIDEO).size(), PLAYLIST_SIZE);
assertEquals("Got media type "
+ getPlayerHandler().getMediaItem().getType() +
", this should be " + Player.GetItem.TYPE.movie.name(),
getPlayerHandler().getMediaItem().getType(), Player.GetItem.TYPE.movie.name());
onView(allOf(withText(getPlayerHandler().getMediaItem().getTitle()), isDisplayed())).check(matches(isDisplayed()));
}
/**
* Test if playlist for last played item is shown when playback has stopped
* and other playlists are available on server
* UI interaction flow tested:
* 1. Add audio, picture, and video playlists on server
* 2. Start playing video item
* 3. Stop playback
* 4. Result: playlist for video items should be shown
*/
@Test
public void showLastActivePlaylist() {
getPlayerHandler().startPlay(Playlist.playlistID.VIDEO, 0);
onView(isRoot()).perform(swipeRight());
EspressoTestUtils.clickButton(R.id.stop);
onView(isRoot()).perform(swipeLeft());
onView(isRoot()).perform(ViewActions.waitForView(R.id.playlist_item_title, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return ((TextView) v).getText().toString().contains("Video");
}
}, 10000));
assertEquals("Playlist on server has size "
+ getPlaylistHandler().getPlaylist(Playlist.playlistID.VIDEO).size() +
" but should be " + PLAYLIST_SIZE,
getPlaylistHandler().getPlaylist(Playlist.playlistID.VIDEO).size(), PLAYLIST_SIZE);
assertEquals("Got media type "
+ getPlayerHandler().getMediaItem().getType() +
", this should be " + Player.GetItem.TYPE.movie.name(),
getPlayerHandler().getMediaItem().getType(), Player.GetItem.TYPE.movie.name());
onView(allOf(withText(getPlayerHandler().getMediaItem().getTitle()), isDisplayed())).check(matches(isDisplayed()));
}
private void waitForAudioPlaylistToShow() {
onView(isRoot()).perform(ViewActions.waitForView(R.id.playlist_item_title, new ViewActions.CheckStatus() {
@Override
public boolean check(View v) {
return "Music 1".contentEquals(((TextView) v).getText());
}
}, 10000));
}
}

View File

@ -71,7 +71,7 @@ public class PlayerHandler extends ConnectionHandler {
this.elapsedTime = 0; this.elapsedTime = 0;
this.playState = STOPPED; this.playState = STOPPED;
playerType = PlayerType.GetActivePlayersReturnType.AUDIO; playerType = PlayerType.GetActivePlayersReturnType.AUDIO;
playlists.clear(); playlists = null;
setMediaType(Player.GetItem.TYPE.unknown); setMediaType(Player.GetItem.TYPE.unknown);
} }
@ -160,7 +160,7 @@ public class PlayerHandler extends ConnectionHandler {
* Starts playing current item in the playlist * Starts playing current item in the playlist
*/ */
public void startPlay() { public void startPlay() {
if (playlists.size() > 0 && activePlaylistId != null) { if (playlists != null && playlists.size() > 0 && activePlaylistId != null) {
mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem(); mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem();
if (mediaItem != null) { if (mediaItem != null) {
@ -178,6 +178,8 @@ public class PlayerHandler extends ConnectionHandler {
} }
public void startPlay(Playlist.playlistID playlistId, int playlistPosition) { public void startPlay(Playlist.playlistID playlistId, int playlistPosition) {
if (playlists == null) return;
activePlaylistId = playlistId; activePlaylistId = playlistId;
PlaylistHolder playlistHolder = playlists.get(playlistId.ordinal()); PlaylistHolder playlistHolder = playlists.get(playlistId.ordinal());
@ -275,7 +277,7 @@ public class PlayerHandler extends ConnectionHandler {
} }
private JsonResponse handleGetItem(int methodId) { private JsonResponse handleGetItem(int methodId) {
if (playlists.size() > 0) { if (playlists != null && playlists.size() > 0) {
mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem(); mediaItem = playlists.get(activePlaylistId.ordinal()).getCurrentItem();
} }

View File

@ -111,7 +111,7 @@ public class PlaylistHandler extends ConnectionHandler {
playlists.get(playlistId).clear(); playlists.get(playlistId).clear();
} }
public void addItemToPlaylist(Playlist.playlistID id, Player.GetItem item) { public void addItemToPlaylist(Playlist.playlistID id, Player.GetItem item, boolean sentNotification) {
int playlistId = id.ordinal(); int playlistId = id.ordinal();
while (playlists.size() <= playlistId) { while (playlists.size() <= playlistId) {
@ -125,7 +125,9 @@ public class PlaylistHandler extends ConnectionHandler {
} }
playlist.add(item); playlist.add(item);
OnAdd onAddNotification = new OnAdd(item.getLibraryId(), item.getType(), playlistId, playlist.getIndexOf(item)); if (sentNotification) {
addNotification(onAddNotification); OnAdd onAddNotification = new OnAdd(item.getLibraryId(), item.getType(), playlistId, playlist.getIndexOf(item));
addNotification(onAddNotification);
}
} }
} }