package com.vpedak.testsrecorder.plugin.core; import com.vpedak.testsrecorder.core.Espresso; import com.vpedak.testsrecorder.core.events.Data; import com.vpedak.testsrecorder.core.TestGenerator; import com.vpedak.testsrecorder.core.events.*; import java.util.List; public class EspressoTestGenerator implements TestGenerator { private boolean wasParentView = false; private boolean wasScrollToPosition = false; private boolean wasSelectViewPagerPage = false; private boolean generateDelays = false; public EspressoTestGenerator(boolean generateDelays) { this.generateDelays = generateDelays; } @Override public String generate(String activityClassName, String testClassName, String packageName, List<RecordingEvent> events) { Templates templates = Templates.getInstance(); StringBuilder sb = new StringBuilder(templates.getTemplate("test_main")); replace(sb, "{PACKAGE}", packageName); replace(sb, "{TEST_CLASS}", testClassName); replace(sb, "{ACTIVITY_CLASS}", activityClassName); replace(sb, "{BODY}", generateBody(events)); StringBuilder additions = new StringBuilder(); if (wasParentView) { additions.append("\n\n").append(nthChildOfTemplate); } if (wasScrollToPosition) { additions.append("\n\n").append(scrollToPositionTemplate); } if (wasSelectViewPagerPage) { additions.append("\n\n").append(selectViewPagerPageTemplate); } if (generateDelays) { additions.append("\n\n").append(idlingResourceTemplate); } if (additions.length() > 0) { replace(sb, "{ADDITION}", additions.toString()); } else { replace(sb, "{ADDITION}", ""); } return sb.toString(); } public boolean wasAdditionMethods() { return wasParentView || wasScrollToPosition || wasSelectViewPagerPage; } @Override public void generateSubject(StringBuilder sb, Subject subject) { // do nothing } @Override public void generateActon(StringBuilder sb, Action action, Subject subject) { // do nothing } @Override public void generateSubject(StringBuilder sb, View subject) { sb.append("onView(withId(").append(subject.getId()).append("))."); } @Override public void generateSubject(StringBuilder sb, DisplayedView subject) { sb.append("onView(allOf(isDisplayed(),withId(").append(subject.getId()).append(")))."); } @Override public void generateSubject(StringBuilder sb, OptionsMenu subject) { sb.append("openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext())"); } @Override public void generateSubject(StringBuilder sb, MenuItem subject) { sb.append("onView(withText(\"").append(subject.getTitle()).append("\"))."); } @Override public void generateSubject(StringBuilder sb, ParentView subject) { wasParentView = true; sb.append("onView(nthChildOf(withId(").append(subject.getParentId()).append("), ").append(subject.getChildIndex()).append("))."); } @Override public void generateSubject(StringBuilder sb, ParentIdView subject) { sb.append("onView(allOf(withParent(withId(").append(subject.getParentId()). append(")), withId(").append(subject.getChildId()).append(")))."); } @Override public void generateSubject(StringBuilder sb, Data subject) { if (subject.getValue() != null) { sb.append("onData(allOf(is(instanceOf(").append(subject.getClassName()).append(".class)), is(\""). append(subject.getValue()).append("\"))).inAdapterView(withId(").append(subject.getAdapterId()). append("))."); } else { StringBuilder tmp = new StringBuilder(dataTemplate); replace( tmp, "CLASS", subject.getClassName()); replace( tmp, "ADAPTER_ID", subject.getAdapterId()); sb.append(tmp); } } @Override public void generateSubject(StringBuilder sb, Espresso subject) { } @Override public void generateActon(StringBuilder sb, ClickAction action, Subject subject) { if (!(subject instanceof OptionsMenu)) { sb.append("perform(click())"); } } @Override public void generateActon(StringBuilder sb, LongClickAction action, Subject subject) { if (!(subject instanceof OptionsMenu)) { sb.append("perform(longClick())"); } } @Override public void generateActon(StringBuilder sb, ReplaceTextAction action, Subject subject) { sb.append("perform(replaceText(\""+action.getText()+"\"))"); } @Override public void generateActon(StringBuilder sb, SwipeUpAction action, Subject subject) { sb.append("perform(swipeUp())"); } @Override public void generateActon(StringBuilder sb, SwipeDownAction action, Subject subject) { sb.append("perform(swipeDown())"); } @Override public void generateActon(StringBuilder sb, SwipeLeftAction action, Subject subject) { sb.append("perform(swipeLeft())"); } @Override public void generateActon(StringBuilder sb, SwipeRightAction action, Subject subject) { sb.append("perform(swipeRight())"); } @Override public void generateActon(StringBuilder sb, PressBackAction action, Subject subject) { sb.append("pressBack()"); } @Override public void generateActon(StringBuilder sb, ScrollToPositionAction action, Subject subject) { wasScrollToPosition = true; sb.append("perform(scrollToPosition("+action.getPosition()+"))"); } @Override public void generateActon(StringBuilder sb, SelectViewPagerPageAction action, Subject subject) { wasSelectViewPagerPage = true; sb.append("perform(selectViewPagerPage("+action.getPosition()+"))"); } @Override public void generateActon(StringBuilder sb, ScrollToAction action, Subject subject) { sb.append("perform(scrollTo())"); } public String generateBody(List<RecordingEvent> events) { StringBuilder sb = new StringBuilder(); if (generateDelays) { sb.append("\t// Used to provide time delays between actions, see details at http://droidtestlab.com/delay.html \n"); sb.append("IdlingResource idlingResource;\n"); } for(RecordingEvent event : events) { event.accept(sb, this); } if (generateDelays) { sb.append("stopTiming(idlingResource);\n"); } return sb.toString(); } private String currentGroup = null; boolean firstEvent = true; public void generateEvent(StringBuilder sb, RecordingEvent event) { if (event.getGroup() != null) { if (!event.getGroup().equals(currentGroup)) { if (generateDelays) { if (!firstEvent) { sb.append("stopTiming(idlingResource);\n"); } sb.append("\n"); sb.append("idlingResource = startTiming(").append(calculateIdleTime(event)).append(");\n"); } else { sb.append("\n"); } currentGroup = event.getGroup(); } } else { if (generateDelays) { if (!firstEvent) { sb.append("stopTiming(idlingResource);\n"); } sb.append("\n"); sb.append("idlingResource = startTiming(").append(calculateIdleTime(event)).append(");\n"); } else { sb.append("\n"); } currentGroup = null; } if (event.getDescription() != null) { sb.append("\t// ").append(event.getDescription()).append('\n'); } event.getSubject().accept(sb, this); event.getAction().accept(sb, this, event.getSubject()); sb.append(";\n"); firstEvent = false; } private StringBuilder replace(StringBuilder sb, String from, String to) { int index = -1; while ((index = sb.lastIndexOf(from)) != -1) { sb.replace(index, index + from.length(), to); } return sb; } private long calculateIdleTime(RecordingEvent event) { return (long) (Math.ceil(event.getTime()/100) * 100); } private String dataTemplate = "\t// see details at http://droidtestlab.com/adapterView.html\n" + "onData(allOf(is(new BoundedMatcher<Object, CLASS>(CLASS.class) {" + " @Override" + " public void describeTo(Description description) {" + " }" + " @Override" + " protected boolean matchesSafely(CLASS obj) {\n" + " /* TODO Implement comparison logic, see details at http://droidtestlab.com/adapterView.html */\n" + " return false;" + " }" + "}))).inAdapterView(withId(ADAPTER_ID))."; /* TODO: If need it is possible to access List or Grids by position: onData(allOf(is(instanceOf(<DATA>.class)))) .inAdapterView(withId(<LIST_GRID_ID>)) .atPosition(0) .perform(click()); */ private String nthChildOfTemplate = " public static Matcher<View> nthChildOf(final Matcher<View> parentMatcher, final int childPosition) {\n" + " return new TypeSafeMatcher<View>() {\n" + " @Override\n" + " public void describeTo(Description description) {\n" + " }\n" + " @Override\n" + " public boolean matchesSafely(View view) {\n" + " if (!(view.getParent() instanceof ViewGroup)) {\n" + " return false;\n" + " }\n" + " ViewGroup group = (ViewGroup) view.getParent();\n" + " return parentMatcher.matches(group) && view.equals(group.getChildAt(childPosition));\n" + " }\n" + " };\n" + " }\n"; private String scrollToPositionTemplate = " public static ViewAction scrollToPosition(final int pos) {\n" + " return new ViewAction() {\n" + " @Override\n" + " public Matcher<View> getConstraints() {\n" + " return isAssignableFrom(android.support.v7.widget.RecyclerView.class);\n" + " }\n" + " @Override\n" + " public String getDescription() {\n" + " return \"scroll to position\";\n" + " }\n" + " @Override\n" + " public void perform(UiController uiController, View view) {\n" + " ((android.support.v7.widget.RecyclerView)view).scrollToPosition(pos);\n" + " }\n" + " };\n" + " }"; private String selectViewPagerPageTemplate = " public static ViewAction selectViewPagerPage(final int pos) {\n" + " return new ViewAction() {\n" + " @Override\n" + " public Matcher<View> getConstraints() {\n" + " return isAssignableFrom(android.support.v4.view.ViewPager.class);\n" + " }\n" + " @Override\n" + " public String getDescription() {\n" + " return \"select page in ViewPager\";\n" + " }\n" + " @Override\n" + " public void perform(UiController uiController, View view) {\n" + " ((android.support.v4.view.ViewPager)view).setCurrentItem(pos);\n" + " }\n" + " };\n" + " }\n"; private String idlingResourceTemplate = "\t // See details at http://droidtestlab.com/delay.html \n" + " public IdlingResource startTiming(long time) {\n" + " IdlingResource idlingResource = new ElapsedTimeIdlingResource(time);\n" + " Espresso.registerIdlingResources(idlingResource);\n" + " return idlingResource;\n" + " }\n" + " public void stopTiming(IdlingResource idlingResource) {\n" + " Espresso.unregisterIdlingResources(idlingResource);\n" + " }\n" + " public class ElapsedTimeIdlingResource implements IdlingResource {\n" + " private long startTime;\n" + " private final long waitingTime;\n" + " private ResourceCallback resourceCallback;\n" + "\n" + " public ElapsedTimeIdlingResource(long waitingTime) {\n" + " this.startTime = System.currentTimeMillis();\n" + " this.waitingTime = waitingTime;\n" + " }\n" + "\n" + " @Override\n" + " public String getName() {\n" + " return ElapsedTimeIdlingResource.class.getName() + \":\" + waitingTime;\n" + " }\n" + "\n" + " @Override\n" + " public boolean isIdleNow() {\n" + " long elapsed = System.currentTimeMillis() - startTime;\n" + " boolean idle = (elapsed >= waitingTime);\n" + " if (idle) {\n" + " resourceCallback.onTransitionToIdle();\n" + " }\n" + " return idle;\n" + " }\n" + "\n" + " @Override\n" + " public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {\n" + " this.resourceCallback = resourceCallback;\n" + " }\n" + " }"; }