O D U C T I O N T O A P I • By : BySelector ёܳ ࢤࢿೞח ਬܻ౭ ېझ. • BySelector : ചݶ࢚ীࢲ UI ਃࣗܳ ӝ ਤೠ ࢶఖ. • UiDevice : ӝӝ ࢚కী ӔೞҊ ӝӝܳ ઁযೡ ࣻ ח ېझ. • UiObject2 : UiAutomator 2.0ী ୶оػ ېझ۽ ؊ ъ۱ೠ ӝמਸ ਗ. ਤ ֎о ېझ ࢎਊߨ݅ ঌইب ࠗ࠙ పझܳ ࢿ оמ.
G S T A R T E D // app/build.gradle dependencies { ... androidTestCompile ‘com.android.support.test.uiautomator:uiautomator-v18:2.1.2' } @RunWith(AndroidJUnit4.class) public abstract class BaseTest { protected UiDevice mDevice; @Before public void before() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); assertNotNull(mDevice); openApp(); } }
G S T A R T E D // app/build.gradle dependencies { ... androidTestCompile ‘com.android.support.test.uiautomator:uiautomator-v18:2.1.2' } @RunWith(AndroidJUnit4.class) public abstract class BaseTest { protected UiDevice mDevice; @Before public void before() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); assertNotNull(mDevice); openApp(); } }
G S T A R T E D // BaseTest.java public static final long LAUNCH_TIMEOUT = 10000; public static final long DEFAULT_TIMEOUT = 1500; private void openApp() { mDevice.pressHome(); Context context = InstrumentationRegistry.getInstrumentation().getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); mDevice.wait(Until.hasObject(By.pkg(BuildConfig.APPLICATION_ID).depth(0)), LAUNCH_TIMEOUT); }
G S T A R T E D // BaseTest.java public static final long LAUNCH_TIMEOUT = 10000; public static final long DEFAULT_TIMEOUT = 1500; private void openApp() { mDevice.pressHome(); Context context = InstrumentationRegistry.getInstrumentation().getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); mDevice.wait(Until.hasObject(By.pkg(BuildConfig.APPLICATION_ID).depth(0)), LAUNCH_TIMEOUT); }
G S T A R T E D // BaseTest.java public static final long LAUNCH_TIMEOUT = 10000; public static final long DEFAULT_TIMEOUT = 1500; private void openApp() { mDevice.pressHome(); Context context = InstrumentationRegistry.getInstrumentation().getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); mDevice.wait(Until.hasObject(By.pkg(BuildConfig.APPLICATION_ID).depth(0)), LAUNCH_TIMEOUT); }
G S T A R T E D // BaseTest.java public static final long LAUNCH_TIMEOUT = 10000; public static final long DEFAULT_TIMEOUT = 1500; private void openApp() { mDevice.pressHome(); Context context = InstrumentationRegistry.getInstrumentation().getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); mDevice.wait(Until.hasObject(By.pkg(BuildConfig.APPLICATION_ID).depth(0)), LAUNCH_TIMEOUT); }
I N G U I E L E M E N T S పझܳ ਤ೧ࢲח UiObject2ܳ ݢ ইঠ ೞݴ, BySelector ࢶఖ۽ UiDevice ীࢲ UiObject2 ীࢲ (ೞਤ ਃࣗܳ) Ѩ࢝ਸ ೡ ࣻ . • UiDevice#findObject(BySelector selector) • UiDevice#findObjects(BySelector selector) • UiObject2#findObject(BySelector selector) • UiObject2#findObjects(BySelector selector) ex) UiObject2 scrollable = mDevice.findObject(By.scrollable(true)); UiObject2 clickable = scrollable.findObject(By.clickable(true));
I N G U I E L E M E N T S UiObject2 ઓ ৈࠗ݅ Ѩࢎೡ ࣻ . • UiDevice# hasObject(BySelector selector) • UiObject2# hasObject(BySelector selector) ex) assertTrue(mDevice.hasObject(By.scrollable(true))); assertTrue(mDevice.findObject(By.scrollable(true)).hasObject(By.scrollable(true)));
I N G S E L E C T O R UI ਃࣗܳ Ѩ࢝ೞח ؘ ਃೠ ࢶఖੋ BySelectorח ೠо ߑधਵ۽ ࢤࢿೡ ࣻ . • ಁః • ېझ • ஶబ ࢸݺ • ܻࣗझ • ఫझ • ࢚క(checkable, checked, clickable, enabled, focusable, focused, longClickable, scrollable, selected) • ೞਤ ҙ҅
R E A T I N G S E L E C T O R : P A C K A G E ౠ ಁఃী ࣘೞח ਃࣗܳ ਸ ࣻ . • By.pkg(String applicationPackage) • By.pkg(Pattern applicationPackage) ex) BySelector selector1 = By.pkg(“com.soomgo"); BySelector selector2 = By.pkg(Pattern.compile("com\\.soomgo.+"));
R E A T I N G S E L E C T O R : C L A S S ౠ ېझী ೧ೞח ਃࣗܳ ਸ ࣻ . • By.clazz(String className) • By.clazz(String packageName, String className) • By.clazz(Class clazz) • By.clazz(Pattern className) ex) BySelector selector1 = By.clazz("android.support.v7.widget.RecyclerView"); BySelector selector2 = By.clazz("android.support.v7.widget", "RecyclerView"); BySelector selector3 = By.clazz(RecyclerView.class); BySelector selector4 = By.clazz(Pattern.compile("[^.]+\\.RecyclerView"));
ݺ C R E A T I N G S E L E C T O R : C O N T E N T D E S C R I P T I O N ஶబ ࢸݺਵ۽ ਃࣗܳ ਸ ࣻ . • By.desc(String contentDescription) • By.descContains(String substring) • By.descStartsWith(String substring) • By.descEndsWith(String substring) • By.desc(Pattern contentDescription) ex) BySelector selector1 = By.desc("button"); BySelector selector2 = By.desc(Pattern.compile(".+button"));
R E A T I N G S E L E C T O R : R E S O U R C E ܻࣗझ ID۽ ਃࣗܳ ਸ ࣻ . • By.res(String resourceName) • By.res(String resourcePackage, String resourceId) • By.res(Pattern resourceName) ex) BySelector selector1 = By.res("com.soomgo:id/button_sign_in"); BySelector selector2 = By.res("com.soomgo", "button_sign_in"); BySelector selector3 = By.res(Pattern.compile(".*:id/button_sign_in"));
R E A T I N G S E L E C T O R : T E X T दغח ఫझ۽ ਃࣗܳ ਸ ࣻ . • By.text(String text) • By.textContains(String substring) • By.textStartsWith(String substring) • By.textEndsWith(String substring) • By.text(Pattern regex) ex) BySelector selector1 = By.text("۽Ӓੋ"); BySelector selector2 = By.text(Pattern.compile(".*۽Ӓੋ"));
E A T I N G S E L E C T O R : S T A T E ࢚క۽ ਃࣗܳ ਸ ࣻ . • By.checkable(boolean isCheckable) • By.checked(boolean isChecked) • By.clickable(boolean isClickable) • By.enabled(boolean isEnabled) • By.focusable(boolean isFocusable) • By.focused(boolean isFocused) • By.longClickable(boolean isLongClickable) • By.scrollable(boolean isScrollable) • By.selected(boolean isSelected)
C R E A T I N G S E L E C T O R : H I E R A R C H Y ೞਤ ਃࣗ ನೣ ҙ҅۽ ਃࣗܳ ਸ ࣻ . • By.depth(int depth) • By.hasChild(BySelector childSelector) • By.hasDescendant(BySelector descendantSelector) • By.hasDescendant(BySelector descendantSelector, int maxDepth)
L I N G D E V I C E UiDevice ਃ ݫࣗ٘ • click(int x, int y) • drag(int startX, int startY, int endX, int endY, int steps) • openNotification() • openQuickSettings() • pressBack() • pressDelete() • pressEnter() • pressHome() • pressKeyCode(int keyCode) • pressKeyCode(int keyCode, int metaState) • pressMenu() • pressRecentApps() • pressSearch() • setOrientationLeft() • setOrientationNatural() • setOrientationRight() • swipe(int startX, int startY, int endX, int endY, int steps) • swipe(Point[] segments, int segmentSteps) • takeScreenshot(File storePath, float scale, int quality) • takeScreenshot(File storePath) • wakeUp()
N D L I N G U I E L E M E N T S UiObject2 ਃ ݫࣗ٘ • click() • click(long duration) • clickAndWait(EventCondition<R> condition, long timeout) • drag(Point dest) • drag(Point dest, int speed) • fling(Direction direction, int speed) • fling(Direction direction) • longClick() • pinchClose(float percent) • pinchClose(float percent, int speed) • pinchOpen(float percent) • pinchOpen(float percent, int speed) • scroll(Direction direction, float percent, int speed) • scroll(Direction direction, float percent) • setText(String text) • swipe(Direction direction, float percent, int speed) • swipe(Direction direction, float percent)
E R M E T H O D S 3 // BaseTest.java protected void hideKeyboard() { InputMethodManager manager = (InputMethodManager) getTargetContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (manager.isAcceptingText()) { mDevice.pressBack(); } } protected void assertHas(BySelector selector) { assertTrue(mDevice.hasObject(selector)); }
E R M E T H O D S 3 // BaseTest.java protected void hideKeyboard() { InputMethodManager manager = (InputMethodManager) getTargetContext() .getSystemService(Context.INPUT_METHOD_SERVICE); if (manager.isAcceptingText()) { mDevice.pressBack(); } } protected void assertHas(BySelector selector) { assertTrue(mDevice.hasObject(selector)); }
I R S T T E S T public class MainActivityTest extends BaseTest { @Before @Override public void before() throws IOException { Session.setAccessToken(null); super.before(); } @Test public void testPressBack() { mDevice.pressBack(); assertFalse(mDevice.hasObject(By.pkg(BuildConfig.APPLICATION_ID))); } }
I R S T T E S T public class MainActivityTest extends BaseTest { @Before @Override public void before() throws IOException { Session.setAccessToken(null); super.before(); } @Test public void testPressBack() { mDevice.pressBack(); assertFalse(mDevice.hasObject(By.pkg(BuildConfig.APPLICATION_ID))); } }
I R S T T E S T // BaseTest.java @RunWith(AndroidJUnit4.class) public abstract class BaseTest { protected UiDevice mDevice; @Before public void before() { mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); assertNotNull(mDevice); openApp(); } }
I R S T T E S T // strings.xml <string name="sign_up_as_a_pro">Ҋࣻ۽ оੑ</string> <string name=“start”>दೞӝ</string> // MainActivityTest.java @Test public void testSignUpAsAProButton() { findButton(R.string.sign_up_as_a_pro) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); assertHas(button(R.string.start)); } private void assertInProSignUpActivity() { assertHas(button(R.string.start)); }
I R S T T E S T // strings.xml <string name="sign_up_as_a_pro">Ҋࣻ۽ оੑ</string> <string name=“start”>दೞӝ</string> // MainActivityTest.java @Test public void testSignUpAsAProButton() { findButton(R.string.sign_up_as_a_pro) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); assertHas(button(R.string.start)); } private void assertInProSignUpActivity() { assertHas(button(R.string.start)); }
I R S T T E S T // strings.xml <string name=“sign_up_as_a_user">ਃ۽ оੑ</string> <string name=“sign_up”>ഥਗоੑ</string> <string name=“sign_up_with_facebook”>ಕझ࠘ਵ۽ ഥਗоੑ</string> // MainActivityTest.java @Test public void testSignUpAsAUserButton() { findButton(R.string.sign_up_as_a_user) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); hideKeyboard(); assertInUserSignUpActivity(); } private void assertInUserSignUpActivity() { assertHas(byText(R.string.sign_up)); assertHas(byText(R.string.sign_up_with_facebook)); }
I R S T T E S T // strings.xml <string name=“sign_up_as_a_user">ਃ۽ оੑ</string> <string name=“sign_up”>ഥਗоੑ</string> <string name=“sign_up_with_facebook”>ಕझ࠘ਵ۽ ഥਗоੑ</string> // MainActivityTest.java @Test public void testSignUpAsAUserButton() { findButton(R.string.sign_up_as_a_user) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); hideKeyboard(); assertInUserSignUpActivity(); } private void assertInUserSignUpActivity() { assertHas(byText(R.string.sign_up)); assertHas(byText(R.string.sign_up_with_facebook)); }
I R S T T E S T // strings.xml <string name=“sign_in”>۽Ӓੋ</string> <string name=“sign_in_with_facebook”>ಕझ࠘ਵ۽ ۽Ӓੋೞӝ</string> // MainActivityTest.java @Test public void testSignInButton() { findButton(R.string.sign_in) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); assertInSignInActivity(); } private void assertInSignInActivity() { assertHas(byText(R.string.sign_in)); assertHas(byText(R.string.sign_in_with_facebook)); }
I R S T T E S T // strings.xml <string name=“sign_in”>۽Ӓੋ</string> <string name=“sign_in_with_facebook”>ಕझ࠘ਵ۽ ۽Ӓੋೞӝ</string> // MainActivityTest.java @Test public void testSignInButton() { findButton(R.string.sign_in) .clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); assertInSignInActivity(); } private void assertInSignInActivity() { assertHas(byText(R.string.sign_in)); assertHas(byText(R.string.sign_in_with_facebook)); }
C H R O N I Z A T I O N UI ਃࣗח ࠺زӝਵ۽ زೞ۽, زӝചܳ ೧ ঋਵݶ పझо पಁೠ. ߣ૩ ߑߨਵ۽, EventConditionਸ ഝਊೞৈ ౠ ߮о ੌযաӝө ӝܽ. • UiObject2#clickAndWait(EventCondition<R> condition, long timeout) UiObject2ܼܳೞҊconditionਸ݅ೡٸөӝܽ • UiDevice#performActionAndWait(Runnable action, EventCondition<R> condition, long timeout) actionਸ ࣻ೯ೞҊ conditionਸ ݅ೡ ٸө ӝܽ. ex) findButton(R.string.sign_up_as_a_pro).clickAndWait(Until.newWindow(), DEFAULT_TIMEOUT); mDevice.performActionAndWait(() -> { findButton(R.string.sign_up_as_a_pro).click(); }, Until.newWindow(), DEFAULT_TIMEOUT);
C H R O N I Z A T I O N ӝࠄ ઁҕغח EventCondition Until.newWindow()৬ Until.scrollFinished()о . • Until.newWindow() ࢜۽ ହ(Activity Dialog) ࢤࢿعח ৈࠗ. • Until.scrollFinished(final Direction direction) direction ߑೱਵ۽ झ܀ լח ৈࠗ.
C H R O N I Z A T I O N ழझథ ߮ ઑѤਸ ਤ೧ࢲח EventConditionਸ ࢲ࠳ېयೞݶ ػ. public abstract class EventCondition<R> extends Condition<AccessibilityEvent, Boolean> { abstract Boolean apply(AccessibilityEvent event); abstract R getResult(); }
C H R O N I Z A T I O N ৻ী ചݶী UI ਃࣗо ח ӝܻ۰ݶ SearchConditionਸ ࢎਊೞݶ ػ. • UiDevice#wait(SearchCondition<R> condition, long timeout) ചݶীࢲ condition ࢿ݀ؼ ٸө ӝܽ. • UiObject2#wait(SearchCondition<R> condition, long timeout) ೧ UiObject2 ё ղীࢲ condition ࢿ݀ؼ ⮶ө ӝܽ. ex) mDevice.wait(Until.hasObject(By.scrolllable(true)), DEFAULT_TIMEOUT); findObject(By.scrollable(true)).wait(Until.hasObject(By.clickable(true)), DEFAULT_TIMEOUT);
C H R O N I Z A T I O N ݄݃ਵ۽ UiObject2 ё ࢚కо ઑѤਸ ݅ೞӝܳ ӝܻ۰ݶ UiObject2Conditionܳ ࢎਊೞݶ ػ. • UiObject2#wait(UiObject2Condition<R> condition, long timeout) UiObject2 ࢚కо conditionҗ ੌೡ ٸө ӝܽ. ex) findByDesc(R.string.check_box).wait(Until.selected(true), DEFAULT_TIMEOUT);
C H R O N I Z A T I O N ӝࠄ ઁҕغח UiObject2Condition •Until.checkable() •Until.checked() •Until.clickable() •Until.enabled() •Until.focusable() •Until.focused() •Until.longClickable() •Until.scrollable() •Until.selected() •Until.descMatches() •Until.descEquals() •Until.descContains() •Until.descStartsWith() •Until.descEndsWith() •Until.textMatches() •Until.textNotEquals() •Until.textEquals() •Until.textContains() •Until.textStartsWith() •Until.textEndsWith()
C H R O N I Z A T I O N ழझథ UiObject2 ࢚క ઑѤਸ ਤ೧ࢲח UiObject2Conditionਸ ࢲ࠳ېयೞݶ ػ. public abstract class UiObject2Condition<R> extends Condition<UiObject2, R> { }
I S A B L I N G A N I M A T I O N S গפݫ࣌ਵ۽ ੋೠ పझ पಁܳ ߑೞӝ ਤ೧ ࠺ഝࢿച ೧যঠ ೠ. // AndroidManifest.xml <uses-permission android:name=“android.permission.SET_ANIMATION_SCALE"/> // app/build.gradle task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') { commandLine "adb shell pm grant com.soomgo android.permission.SET_ANIMATION_SCALE".split(' ') } tasks.whenTaskAdded { task -> if (task.name.startsWith('connected')) { task.dependsOn grantAnimationPermission } }
I S A B L I N G A N I M A T I O N S গפݫ࣌ਵ۽ ੋೠ పझ पಁܳ ߑೞӝ ਤ೧ ࠺ഝࢿച ೧যঠ ೠ. // AndroidManifest.xml <uses-permission android:name=“android.permission.SET_ANIMATION_SCALE"/> // app/build.gradle task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') { commandLine "adb shell pm grant com.soomgo android.permission.SET_ANIMATION_SCALE".split(' ') } tasks.whenTaskAdded { task -> if (task.name.startsWith('connected')) { task.dependsOn grantAnimationPermission } }
I S A B L I N G A N I M A T I O N S // BaseTest.java @Before public void before() throws IOException { ... mSystemAnimations = new SystemAnimations(getTargetContext()); mSystemAnimations.disableAll(); } @After public void after() { if (mSystemAnimations != null) { mSystemAnimations.enableAll(); } }
U D P L A T F O R M S AWS Device Farm Firebase Test Lab Xamarin Test Cloud 200+ ӝӝ $0.17/࠙ Appium, Calabash, Instrumentation, UI Automator, Explorer ਗ 20+ ӝӝ $5/दр Instrumentation, Robo పझ ਗ 1000+ ӝӝ ୭ࣗ $99/׳ Calabash(Ruby), Xamarin UITest(C#)