Store screenshot generator as UI test
This commit is contained in:
parent
0f61cb12d4
commit
3d7f2c154a
|
@ -12,6 +12,7 @@ android {
|
|||
targetSdk 31
|
||||
versionCode 26
|
||||
versionName "0.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -72,4 +73,9 @@ dependencies {
|
|||
appcenterPrivateBetaImplementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
|
||||
appcenterPublicBetaImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
|
||||
appcenterPublicBetaImplementation "com.microsoft.appcenter:appcenter-distribute:${appCenterSdkVersion}"
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.4.1-alpha05'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 2.4 MiB |
|
@ -0,0 +1,252 @@
|
|||
package org.joinmastodon.android.test;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import androidx.test.core.app.ActivityScenario;
|
||||
import androidx.test.espresso.PerformException;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.espresso.util.HumanReadables;
|
||||
import androidx.test.espresso.util.TreeIterables;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.screenshot.ScreenCapture;
|
||||
import androidx.test.runner.screenshot.Screenshot;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import okio.Sink;
|
||||
import okio.Source;
|
||||
|
||||
import static androidx.test.espresso.Espresso.*;
|
||||
import static androidx.test.espresso.action.ViewActions.*;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.*;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.*;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class StoreScreenshotsGenerator{
|
||||
private static final String PHOTO_FILE="IMG_1010.jpg";
|
||||
|
||||
@Rule
|
||||
public ActivityScenarioRule<MainActivity> activityScenarioRule=new ActivityScenarioRule<>(MainActivity.class);
|
||||
|
||||
@Test
|
||||
public void takeScreenshots() throws Exception{
|
||||
File photo=new File(MastodonApp.context.getCacheDir(), PHOTO_FILE);
|
||||
try(Source source=Okio.source(InstrumentationRegistry.getInstrumentation().getContext().getAssets().open(PHOTO_FILE)); BufferedSink sink=Okio.buffer(Okio.sink(photo))){
|
||||
sink.writeAll(source);
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
Bundle args=InstrumentationRegistry.getArguments();
|
||||
InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
|
||||
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||
MastodonApp.context.deleteDatabase(session.getID()+".db");
|
||||
|
||||
onView(isRoot()).perform(waitId(R.id.more, 5000));
|
||||
Thread.sleep(500);
|
||||
takeScreenshot("HomeTimeline");
|
||||
|
||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK;
|
||||
activityScenarioRule.getScenario().recreate();
|
||||
|
||||
onView(isRoot()).perform(waitId(R.id.more, 5000));
|
||||
Thread.sleep(500);
|
||||
takeScreenshot("HomeTimeline_Dark");
|
||||
|
||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
activityScenarioRule.getScenario().recreate();
|
||||
|
||||
activityScenarioRule.getScenario().onActivity(activity->UiUtils.openProfileByID(activity, session.getID(), args.getString("profileAccountID")));
|
||||
Thread.sleep(500);
|
||||
onView(isRoot()).perform(waitId(R.id.avatar_border, 5000)); // wait for profile to load
|
||||
onView(isRoot()).perform(waitId(R.id.more, 5000)); // wait for timeline to load
|
||||
Thread.sleep(500);
|
||||
takeScreenshot("Profile");
|
||||
|
||||
Status[] _status={null};
|
||||
CyclicBarrier barrier=new CyclicBarrier(2);
|
||||
new GetStatusByID(args.getString("threadPostID"))
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
_status[0]=result;
|
||||
try{
|
||||
barrier.await();
|
||||
}catch(Exception ignore){}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
try{
|
||||
barrier.await();
|
||||
}catch(Exception ignore){}
|
||||
}
|
||||
})
|
||||
.exec(session.getID());
|
||||
barrier.await();
|
||||
Assert.assertNotNull(_status[0]);
|
||||
|
||||
ThreadFragment[] _fragment={null};
|
||||
activityScenarioRule.getScenario().onActivity(activity->{
|
||||
activity.getSystemService(InputMethodManager.class).hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0);
|
||||
Bundle threadArgs=new Bundle();
|
||||
threadArgs.putParcelable("status", Parcels.wrap(_status[0]));
|
||||
threadArgs.putString("account", session.getID());
|
||||
threadArgs.putBoolean("_can_go_back", true);
|
||||
ThreadFragment fragment=new ThreadFragment();
|
||||
fragment.setArguments(threadArgs);
|
||||
activity.showFragment(fragment);
|
||||
_fragment[0]=fragment;
|
||||
});
|
||||
while(!_fragment[0].loaded){
|
||||
Thread.sleep(50);
|
||||
}
|
||||
Thread.sleep(300);
|
||||
takeScreenshot("Thread");
|
||||
|
||||
Instance[] _instance={null};
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
_instance[0]=result;
|
||||
try{
|
||||
barrier.await();
|
||||
}catch(Exception ignore){}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
try{
|
||||
barrier.await();
|
||||
}catch(Exception ignore){}
|
||||
}
|
||||
})
|
||||
.execNoAuth("mastodon.social");
|
||||
barrier.await();
|
||||
Assert.assertNotNull(_instance[0]);
|
||||
|
||||
activityScenarioRule.getScenario().onActivity(activity->{
|
||||
Bundle rulesArgs=new Bundle();
|
||||
rulesArgs.putParcelable("instance", Parcels.wrap(_instance[0]));
|
||||
InstanceRulesFragment fragment=new InstanceRulesFragment();
|
||||
fragment.setArguments(rulesArgs);
|
||||
activity.showFragment(fragment);
|
||||
});
|
||||
|
||||
Thread.sleep(500);
|
||||
takeScreenshot("InstanceRules");
|
||||
|
||||
activityScenarioRule.getScenario().onActivity(activity->{
|
||||
activity.onBackPressed();
|
||||
Bundle composeArgs=new Bundle();
|
||||
composeArgs.putString("account", session.getID());
|
||||
ComposeFragment fragment=new ComposeFragment();
|
||||
fragment.setArguments(composeArgs);
|
||||
activity.showFragment(fragment);
|
||||
fragment.addFakeMediaAttachment(Uri.fromFile(photo), "Pantheon");
|
||||
});
|
||||
onView(withId(R.id.toot_text)).perform(typeText("This is a picture I took the last time I visited #Athens, Greece. What a beautiful place!"));
|
||||
InstrumentationRegistry.getInstrumentation().setInTouchMode(true);
|
||||
takeScreenshot("Compose");
|
||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK;
|
||||
activityScenarioRule.getScenario().recreate();
|
||||
Thread.sleep(500);
|
||||
takeScreenshot("Compose_Dark");
|
||||
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
activityScenarioRule.getScenario().recreate();
|
||||
}
|
||||
|
||||
private void takeScreenshot(String name) throws IOException{
|
||||
Screenshot.capture().setName(name).setFormat(Bitmap.CompressFormat.PNG).process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform action of waiting for a specific view id.
|
||||
* @param viewId The id of the view to wait for.
|
||||
* @param millis The timeout of until when to wait for.
|
||||
*/
|
||||
public static ViewAction waitId(final int viewId, final long millis) {
|
||||
return new ViewAction() {
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(final UiController uiController, final View view) {
|
||||
uiController.loopMainThreadUntilIdle();
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long endTime = startTime + millis;
|
||||
final Matcher<View> viewMatcher=CoreMatchers.allOf(withId(viewId), withEffectiveVisibility(Visibility.VISIBLE));
|
||||
|
||||
do {
|
||||
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
|
||||
// found view with required ID
|
||||
if (viewMatcher.matches(child)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uiController.loopMainThreadForAtLeast(50);
|
||||
}
|
||||
while (System.currentTimeMillis() < endTime);
|
||||
|
||||
// timeout happens
|
||||
throw new PerformException.Builder()
|
||||
.withActionDescription(this.getDescription())
|
||||
.withViewDescription(HumanReadables.describe(view))
|
||||
.withCause(new TimeoutException())
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -186,6 +186,9 @@ public class MastodonAPIController{
|
|||
req.onError("Error parsing an API error", response.code());
|
||||
}
|
||||
}
|
||||
}catch(Exception x){
|
||||
Log.w(TAG, "onResponse: error processing response", x);
|
||||
onFailure(call, (IOException) new IOException(x).fillInStackTrace());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class GetStatusByID extends MastodonAPIRequest<Status>{
|
||||
public GetStatusByID(String id){
|
||||
super(HttpMethod.GET, "/statuses/"+id, Status.class);
|
||||
}
|
||||
}
|
|
@ -91,6 +91,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
@ -796,6 +797,16 @@ public class ComposeFragment extends ToolbarFragment implements OnBackPressedLis
|
|||
return thumb;
|
||||
}
|
||||
|
||||
public void addFakeMediaAttachment(Uri uri, String description){
|
||||
pollBtn.setEnabled(false);
|
||||
DraftMediaAttachment draft=new DraftMediaAttachment();
|
||||
draft.uri=uri;
|
||||
draft.description=description;
|
||||
attachmentsView.addView(createMediaAttachmentView(draft));
|
||||
allAttachments.add(draft);
|
||||
attachmentsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||
if(uploadingAttachment!=null)
|
||||
throw new IllegalStateException("there is already an attachment being uploaded");
|
||||
|
|
|
@ -172,12 +172,14 @@ public class UiUtils{
|
|||
}
|
||||
|
||||
public static String getFileName(Uri uri){
|
||||
if(uri.getScheme().equals("content")){
|
||||
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)){
|
||||
cursor.moveToFirst();
|
||||
String name=cursor.getString(0);
|
||||
if(name!=null)
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue