├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml ├── scripts ├── capabilities.js └── script.js └── src ├── main └── java │ └── com │ └── mobilebox │ └── repl │ ├── Appium.java │ ├── app │ ├── APKInspector.java │ └── ApkData.java │ ├── commands │ ├── AndroidCommands.java │ ├── AndroidDeviceCommands.java │ ├── AppiumCommands.java │ ├── CommandRef.java │ ├── CommandsDoc.java │ └── IOSCommands.java │ ├── config │ └── ConfigCapabilities.java │ ├── devices │ ├── AndroidDeviceKeyEvent.java │ └── AndroidDeviceProperties.java │ ├── exceptions │ └── CommandsException.java │ ├── misc │ └── Utils.java │ └── selectors │ └── UISelector.java └── test └── java └── com └── mobilebox └── repl ├── AppiumTest.java ├── commands ├── CommandsDocTest.java └── DummyCommand.java ├── misc └── UtilsTest.java └── selectors └── UISelectorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | test-output 3 | lib 4 | bin 5 | .classpath 6 | .project 7 | .settings 8 | dependency-reduced-pom.xml 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | matrix: 7 | fast_finish: true 8 | 9 | script: 10 | - "mvn clean install" 11 | 12 | sudo: false 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 MobileBox. http://mobileboxlab.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Bryan](http://open.mobileboxlab.com/img/repl.gif) 3 | ___ 4 | 5 | [![Build Status](https://travis-ci.org/mobileboxlab/appium-java-repl.svg?branch=master)](https://travis-ci.org/mobileboxlab/appium-java-repl) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/1d96b6e9cec343cabeb65b8e86cc74cc)](https://www.codacy.com/app/dev-github/appium-java-repl?utm_source=github.com&utm_medium=referral&utm_content=mobileboxlab/appium-java-repl&utm_campaign=Badge_Grade) 6 | 7 | Simple Java REPL for controlling mobile apps through Appium. 8 | 9 | **Appium Java REPL** is a great place to start experimenting with Appium, there are advantages to using the REPL: it's simple, and should work without any complex installation or configuration. 10 | 11 | **Appium Java REPL** is built on top of [Java-REPL](https://github.com/albertlatacz/java-repl) (by [Albert Latacz](https://github.com/albertlatacz)) implementation. 12 | 13 | 14 | Check out the **Appium Java REPL** documentation [here](https://mobileboxlab.github.io/appium-java-repl/) 15 | 16 | ___ 17 | 18 | 19 | ![REPL](http://open.mobileboxlab.com/img/replcloud.gif) 20 | 21 | 22 | 23 | 24 | ## Contribution 25 | 26 | Any ideas are welcome. Feel free to submit any issues or pull requests. 27 | 28 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 29 | 30 | --- 31 | **Appium Java REPL** is developed and maintained by [Mobilebox](http://mobileboxlab.com) team. 32 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.mobilebox.appium.repl 5 | appium-repl 6 | 1.0.0 7 | Appium REPL 8 | Simple Java REPL (Read-eval-print Loop) for controlling mobile apps through Appium 9 | 10 | 11 | UTF-8 12 | 13 | 14 | 15 | 16 | jitpack.io 17 | https://jitpack.io 18 | 19 | 20 | oss.sonatype.org 21 | https://oss.sonatype.org/content/repositories/snapshots/ 22 | 23 | 24 | 25 | 26 | 27 | io.appium 28 | java-client 29 | 6.0.0-BETA2 30 | 31 | 32 | 33 | com.javarepl 34 | javarepl 35 | 428 36 | 37 | 38 | 39 | dom4j 40 | dom4j 41 | 1.6.1 42 | 43 | 44 | 45 | com.github.vidstige 46 | jadb 47 | v1.0.1 48 | 49 | 50 | 51 | org.testng 52 | testng 53 | 6.8.21 54 | test 55 | 56 | 57 | 58 | org.assertj 59 | assertj-core 60 | 3.4.1 61 | test 62 | 63 | 64 | 65 | org.aeonbits.owner 66 | owner 67 | 1.0.9 68 | 69 | 70 | 71 | net.dongliu 72 | apk-parser 73 | 2.1.0 74 | 75 | 76 | 77 | com.jcabi 78 | jcabi-xml 79 | 1.0-SNAPSHOT 80 | 81 | 82 | 83 | org.zeroturnaround 84 | zt-zip 85 | 1.9 86 | jar 87 | 88 | 89 | 90 | com.cedarsoftware 91 | json-io 92 | 4.5.0 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-compiler-plugin 102 | 3.2 103 | 104 | 1.8 105 | 1.8 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-shade-plugin 111 | 2.3 112 | 113 | true 114 | 115 | 116 | *:* 117 | 118 | META-INF/*.SF 119 | META-INF/*.DSA 120 | META-INF/*.RSA 121 | 122 | 123 | 124 | 125 | 126 | 127 | package 128 | 129 | shade 130 | 131 | 132 | 133 | jar-with-dependencies 134 | 135 | 136 | 138 | com.mobilebox.repl.Appium 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | https://mobileboxlab.github.io/appium-java-rep 151 | -------------------------------------------------------------------------------- /scripts/capabilities.js: -------------------------------------------------------------------------------- 1 | 2 | //The main method providing an entry point for the Appium JAVA REPL. 3 | function main() { 4 | var DesiredCapabilities = Java.type("org.openqa.selenium.remote.DesiredCapabilities") 5 | var caps = new DesiredCapabilities(); 6 | caps.setCapability("appiumServer","http://127.0.0.1:4723/wd/hub"); 7 | caps.setCapability("udid", "ID"); 8 | caps.setCapability("deviceName", "NAME"); 9 | caps.setCapability("newCommandTimeout", "900000"); 10 | caps.setCapability("app", "ApiDemos-debug.apk"); 11 | caps.setCapability("platformName","android"); 12 | caps.setCapability("appActivity","io.appium.android.apis.ApiDemos"); 13 | caps.setCapability("appPackage","io.appium.android.apis"); 14 | return caps; 15 | } 16 | -------------------------------------------------------------------------------- /scripts/script.js: -------------------------------------------------------------------------------- 1 | 2 | //The REPL object. 3 | var $; 4 | 5 | //The main method providing an entry point for the Appium JAVA REPL scripting capabilities. 6 | function main(repl){ 7 | $ = repl; 8 | iteration(); 9 | locators(); 10 | appInfo(); 11 | moreinfo(); 12 | deviceActions(); 13 | } 14 | 15 | //Access to REPL commands. 16 | function iteration(){ 17 | var elements = $.ids("android:id/text1"); 18 | for each (element in elements) { 19 | element.click(); 20 | $.back(); 21 | } 22 | } 23 | 24 | //Some device actions through JADB. 25 | function deviceActions(){ 26 | var Device = $.device(); 27 | Device.brightness(100); 28 | Device.call(); 29 | Device.volumenUp(); 30 | Device.volumenDown(); 31 | 32 | print(Device.version()); 33 | print(Device.vm()); 34 | print(Device.dhcp()); 35 | print(Device.gsm()); 36 | print(Device.net()); 37 | } 38 | 39 | function locators(){ 40 | print($.className("android.widget.TextView").getText()); 41 | print($.id("android:id/text1").getText()); 42 | print($.xpath('//*[@text="NFC"]').getText()); 43 | 44 | //List 45 | print($.ids("android:id/text1").size()); 46 | print($.classNames("android.widget.TextView").size()); 47 | print($.xpaths('//*[@text="NFC"]').size()); 48 | 49 | //UISelector 50 | print($.text("App")); 51 | print($.texts("App").size()); 52 | print($.textContain("Te")); 53 | print($.textContains("Te").size()); 54 | } 55 | 56 | //Application under test info through APKInspector. 57 | function appInfo(){ 58 | print($.appInfo()); 59 | } 60 | 61 | //Some Appium commands. 62 | function moreinfo(){ 63 | print($.activity()); 64 | print($.orientation()); 65 | print($.capabilities()); 66 | print($.session()); 67 | print($.source()); 68 | print($.time()); 69 | print($.context()); 70 | print($.contextHandles()); 71 | print($.sessionDetails()); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/Appium.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl; 2 | 3 | import static com.mobilebox.repl.commands.CommandsDoc.printCommands; 4 | import static com.mobilebox.repl.commands.CommandsDoc.SEPARATOR; 5 | import static com.mobilebox.repl.misc.Utils.console; 6 | import static java.lang.System.getProperty; 7 | 8 | import javarepl.Main; 9 | 10 | import com.mobilebox.repl.commands.AndroidCommands; 11 | import com.mobilebox.repl.commands.AndroidDeviceCommands; 12 | import com.mobilebox.repl.commands.AppiumCommands; 13 | import com.mobilebox.repl.commands.CommandRef; 14 | import com.mobilebox.repl.commands.IOSCommands; 15 | 16 | /** 17 | * This class acts as entry point to JavaREPL also provides commands that basically are wrappers 18 | * over some Appium methods such as find an element, get source, etc. 19 | * 20 | * @see JavaREPL 21 | * 22 | */ 23 | public class Appium { 24 | 25 | public static AndroidCommands android; 26 | public static IOSCommands ios; 27 | 28 | static { 29 | android = new AndroidCommands(); 30 | ios = new IOSCommands(); 31 | } 32 | 33 | public static void main(String... args) throws Exception { 34 | welcome(); 35 | Main.main(args); 36 | } 37 | 38 | private static void welcome() { 39 | console("----------------------------"); 40 | console(" :::- Appium Java REPL -::: " + SEPARATOR); 41 | console("Type import static com.mobilebox.repl.Appium.*;" + SEPARATOR); 42 | console("Type help() for more options."); 43 | console("-----------------------------" + SEPARATOR); 44 | } 45 | 46 | @CommandRef(desc = "Prints this help.") 47 | public static void help() { 48 | printCommands(Appium.class); 49 | } 50 | 51 | @CommandRef(desc = "Quit Appium REPL") 52 | public static void exit() { 53 | System.exit(0); 54 | } 55 | 56 | @CommandRef(desc = "Prints all commands available for Android and iOS.") 57 | public static void appium() { 58 | printCommands(AppiumCommands.class); 59 | } 60 | 61 | @CommandRef(desc = "Prints all commands available especifc for Android.") 62 | public static void android() { 63 | printCommands(AndroidCommands.class); 64 | } 65 | 66 | @CommandRef(desc = "Prints all commands available for iOS.") 67 | public static void ios() { 68 | printCommands(IOSCommands.class); 69 | } 70 | 71 | @CommandRef(desc = "Prints all commands available for Android Device.") 72 | public static void android_device() { 73 | printCommands(AndroidDeviceCommands.class); 74 | } 75 | 76 | @CommandRef(desc = "Prints the user home directory.") 77 | public static void user_home() { 78 | System.out.println(getProperty("user.home")); 79 | } 80 | 81 | @CommandRef(desc = "Prints my name") 82 | public static void my_name() { 83 | System.out.println("My Name \n it's a joke, a bad one..."); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/app/APKInspector.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.app; 2 | 3 | import static com.google.common.base.Strings.nullToEmpty; 4 | import static java.util.UUID.randomUUID; 5 | import static org.zeroturnaround.zip.ZipUtil.containsEntry; 6 | import static org.zeroturnaround.zip.ZipUtil.unpackEntry; 7 | 8 | import java.awt.image.BufferedImage; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.IOException; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.ArrayList; 15 | import java.util.Base64; 16 | import java.util.List; 17 | import java.util.Locale; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | 21 | import javax.imageio.ImageIO; 22 | 23 | import com.jcabi.xml.XML; 24 | import com.jcabi.xml.XMLDocument; 25 | 26 | import net.dongliu.apk.parser.ApkParser; 27 | import net.dongliu.apk.parser.bean.ApkMeta; 28 | 29 | /** 30 | * A Simple APK inspector. 31 | */ 32 | @SuppressWarnings("resource") 33 | public class APKInspector { 34 | 35 | /** 36 | * Retrieve basic apk metas, such as title, icon, package name, version, etc. 37 | * 38 | * @param appPath The app path. 39 | * @return An {@link ApkData} instance. 40 | * @throws CommandsException 41 | */ 42 | public ApkData inspect(final String appPath) { 43 | File apkFile = new File(appPath); 44 | ApkData app = new ApkData(); 45 | 46 | if (apkFile.exists()) { 47 | try { 48 | ApkParser parser = new ApkParser(appPath); 49 | parser.setPreferredLocale(Locale.getDefault()); 50 | ApkMeta data = parser.getApkMeta(); 51 | 52 | app.setPackageName(nullToEmpty(data.getPackageName())) 53 | .setLabel(nullToEmpty(data.getLabel())) 54 | .setVersionName(nullToEmpty(data.getVersionName())) 55 | .setMaxSdkVersion(nullToEmpty(data.getMaxSdkVersion())) 56 | .setMinSdkVersion(nullToEmpty(data.getMinSdkVersion())) 57 | .setTargetSdkVersion(nullToEmpty(data.getTargetSdkVersion())) 58 | .setUsesFeature(data.getUsesFeatures()).setUserPermissions(data.getUsesPermissions()) 59 | .setActivities(getActivities(parser.getManifestXml())) 60 | .setIcon(icon2Base64(appPath, data.getIcon())); 61 | 62 | } catch (Exception e) { 63 | throw new RuntimeException("Error: " + e.getMessage()); 64 | } 65 | } 66 | 67 | return app; 68 | } 69 | 70 | /** 71 | * Return information about activities in the package in activities. 72 | * 73 | * @param manifest The app manifest. 74 | * @return List of activities 75 | */ 76 | private List getActivities(final String manifest) { 77 | List activities = new ArrayList(); 78 | Pattern p = Pattern.compile("android:name=\"(.*?)\""); 79 | 80 | XML xml = new XMLDocument(manifest); 81 | XML je = new XMLDocument(xml.toString()); 82 | List name = je.nodes("//activity"); 83 | 84 | for (XML node : name) { 85 | Matcher m = p.matcher(node.toString()); 86 | if (m.find()) { 87 | activities.add(m.group(1)); 88 | } 89 | } 90 | return activities; 91 | } 92 | 93 | /** 94 | * Retrieve the icon associated with an application. If it has not defined an icon, the default 95 | * app icon is returned. 96 | * 97 | * @param appPath The aplication under test path. 98 | * @iconPath The path to the icon on the APK file. 99 | * @return The image of the icon, or the default application icon if it could not be found in 100 | * base64. 101 | * @throws MoneyException 102 | */ 103 | public String icon2Base64(final String appPath, final String iconPath) { 104 | String icon = 105 | "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAABRZJREFUeJztm89vVFUUxz/3vJm22FakZVoTRFaGUliI8QcsXICu5EcTE2ICf4BGggYTaBDoy0AFiyILjZq6xehKEH+wsZi40BohkShWlmpIoENLkZZOmTfnuGjHFKaFDn1vXgnzWU0mZ84595tz37333DdQoUKFChXuX9xUXzb62RZn8rbD1oLURxPaXsvsq3o/Gt8zp0iARj/bIuZ6QeZHG1rzZtJ2eX/ym2jj3J4iARZ23DjucG1mdtyTxMuX0tIfZsBUR84Kn1V1WDyezaSrfw0zRinIrV+Mlz1EMfjJGHwgInWY+7rhTV0UVZw7USRAYc5HOXiAyy6xHfQkuEVeIv9Vyre6KONNxxQClIm0C3Q0+ZLCb8BK9MZnbDKv3GnEJwAw8I5cU5dYr6oXcbK+sSU4Uu4cYhUA4Era/Y3nNig6Ko5tqY4b28oZP3YBAAbSVac9ky2gBnZk4d7cunLFnhMCAPTvTx7D3E4Qz0w/T/ljj5cj7pwRACCzP3kY5ZNyLo9zSgBwlulPbDXT78q1PJZdAEVHAJp2afOUBt0ul5OqTSh/UIblsewCCO4UgCWC7ulEuJp2Q2LBekUzUS+PRWeBwl49sy855UlxtjTsyS7zxP1U6mErqnzKXgGDnTV9ebXVmJ0oTIc4ScQRdLCzpg9om4nt5NNjFMyxVaD8VASIO4G4Cf0ZsMS3mmHLbRXcFkVbBakOw+9snwWKjonKORM7WuuSH/2VdlkIuQIW+Pbo9XxwRnDvAivDGnwYCFKN8ITDvTdiwekFu3UxhFgBj2zXeWP54CRCK9CnRnviunfq0mGJfakDSPlWpwRrxTjkYLnn8t8u8e2p0AQYqwu24lwr0BeMJVZf6XJXw/IdBpm0GwZOzPfthyoLep2wYkRzr4Y3BZzbDGCOnXNt8JO5mnZDzmgHcM5tDk0ARVsBvGHv+7B8RoVJogdAVZeHNgUKD7yp5nyzP7ZClS51rBFk3kTw6yKuJ6/aPrEzBKZ/2is6IuZ6VLR9IF3z593aw/h0SHXkEJGayPcBzf7YCjV+xMkLhcEDiMgD4DZ4uN6GPdlld/IjSC3ObRRzvY1+tiUs+8jPAqp04aTezL5IBIlXLh6UDECzr015C7qdSJuHdQEbJ//u1tPf//ZIm6h1cctZolT7ApFXgDrWAEwePIxfvEgu8TKATtxG3Y6b7C08+8gFKJT95MEX6D8olyZsamfiy6pcST2BmdjfM2eBlG8PY7luACeuJyz7svUDSt3LF9lbADhQHTKsfbb2Be6ZCgC9Ztgx8+yZgc6a82HZl60CpuvpTVcZpfYA77ZneA9VQDRUBIg7gbipCBB3AnET2s1QY8dYVpBqGfHq5koXaDoad2i9zMv/q+hoaBUgKucAgrr8c2H5jAqvJv88gCC/hyaAOfsUQIxD8317KCy/YfOgrw3m6AIws6OhCVAryQ8NzgFLqyzobdqba4vr1bepaNyh9Sk/92Iyn/8ZeEzh7GVJfhzq7fCC3brY8/InHSwPIefIUDhrgbdu8IBcCHUVuPKW/FPrEk8a9gZwRtHRMP3PholcfjGz1wdc4unBA3JhSsNUR86ivpEthajzue/3ARUB4k4gbooEKLy20uxrU/nTuZnCS1SqOhxVjCIBxMb7Z3kLuuMUoWmXNlsimHEP8G4p/svMnuxSwfUiMjd2c6pDiq2aWRusdIoqYKCz5rxiqwz7Ms63uFR12MyOz7wHWKFChQql8x+anVpT1E4upAAAAABJRU5ErkJggg=="; 106 | File apkFile = new File(appPath); 107 | if (apkFile.exists()) { 108 | if (containsEntry(apkFile, iconPath)) { 109 | String tempIcon = randomUUID().toString() + ".png"; 110 | File icono = new File(tempIcon); 111 | unpackEntry(apkFile, iconPath, icono); 112 | icon = imageToBase64(icono.getAbsolutePath()); 113 | icono.delete(); 114 | } 115 | } 116 | return icon; 117 | } 118 | 119 | /** 120 | * Image to Base64 converter 121 | * 122 | * @param imagePath - The image path. 123 | * @return The image as base64 data. 124 | * @throws MonkeyException 125 | */ 126 | private String imageToBase64(final String imagePath) { 127 | final ByteArrayOutputStream os = new ByteArrayOutputStream(); 128 | try { 129 | FileInputStream inputStream = new FileInputStream(imagePath); 130 | BufferedImage inputImage = ImageIO.read(inputStream); 131 | ImageIO.write(inputImage, "png", Base64.getEncoder().wrap(os)); 132 | return os.toString(StandardCharsets.ISO_8859_1.name()); 133 | } catch (final IOException e) { 134 | throw new RuntimeException( 135 | "We have an error with pseudo image to 64 route converter: " + e.getMessage()); 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/app/ApkData.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.app; 2 | 3 | import static com.mobilebox.repl.misc.Utils.console; 4 | 5 | import java.util.List; 6 | 7 | import com.google.gson.Gson; 8 | 9 | import net.dongliu.apk.parser.bean.UseFeature; 10 | 11 | public class ApkData { 12 | 13 | private String packageName; 14 | private String label; 15 | private String icon; 16 | private String versionName; 17 | private String minSdkVersion; 18 | private String targetSdkVersion; 19 | private String maxSdkVersion; 20 | private List usesFeature; 21 | private List userPermissions; 22 | private List activities; 23 | 24 | public String getPackageName() { 25 | return packageName; 26 | } 27 | 28 | public ApkData setPackageName(String packageName) { 29 | this.packageName = packageName; 30 | return this; 31 | } 32 | 33 | public String getLabel() { 34 | return label; 35 | } 36 | 37 | public ApkData setLabel(String label) { 38 | this.label = label; 39 | return this; 40 | } 41 | 42 | public String getIcon() { 43 | return icon; 44 | } 45 | 46 | public ApkData setIcon(String icon) { 47 | this.icon = icon; 48 | return this; 49 | } 50 | 51 | public String getVersionName() { 52 | return versionName; 53 | } 54 | 55 | public ApkData setVersionName(String versionName) { 56 | this.versionName = versionName; 57 | return this; 58 | } 59 | 60 | public String getMinSdkVersion() { 61 | return minSdkVersion; 62 | } 63 | 64 | public ApkData setMinSdkVersion(String minSdkVersion) { 65 | this.minSdkVersion = minSdkVersion; 66 | return this; 67 | } 68 | 69 | public String getTargetSdkVersion() { 70 | return targetSdkVersion; 71 | } 72 | 73 | public ApkData setTargetSdkVersion(String targetSdkVersion) { 74 | this.targetSdkVersion = targetSdkVersion; 75 | return this; 76 | } 77 | 78 | public String getMaxSdkVersion() { 79 | return maxSdkVersion; 80 | } 81 | 82 | public ApkData setMaxSdkVersion(String maxSdkVersion) { 83 | this.maxSdkVersion = maxSdkVersion; 84 | return this; 85 | } 86 | 87 | public String toJson() { 88 | return new Gson().toJson(this); 89 | } 90 | 91 | public List getUsesFeature() { 92 | return usesFeature; 93 | } 94 | 95 | public ApkData setUsesFeature(List usesFeature) { 96 | this.usesFeature = usesFeature; 97 | return this; 98 | } 99 | 100 | public List getUserPermissions() { 101 | return userPermissions; 102 | } 103 | 104 | public ApkData setUserPermissions(List userPermissions) { 105 | this.userPermissions = userPermissions; 106 | return this; 107 | } 108 | 109 | public List getActivities() { 110 | return activities; 111 | } 112 | 113 | public ApkData setActivities(List activities) { 114 | this.activities = activities; 115 | return this; 116 | } 117 | 118 | public void toConsole() { 119 | console("---> Package name: " + packageName); 120 | console("---> Label: " + label); 121 | console("---> Version name: " + versionName); 122 | console("---> Min SDK version: " + minSdkVersion); 123 | console("---> Target SDK version: " + targetSdkVersion); 124 | console("---> Max SDK version: " + maxSdkVersion); 125 | console("---> Uses Feature: " + usesFeature); 126 | console("---> Uses Permissions: " + userPermissions); 127 | console("---> Activities: " + activities); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/AndroidCommands.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static com.mobilebox.repl.misc.Utils.console; 4 | import static io.appium.java_client.remote.MobileCapabilityType.APP; 5 | import static io.appium.java_client.remote.MobileCapabilityType.DEVICE_NAME; 6 | import static io.appium.java_client.remote.MobileCapabilityType.NEW_COMMAND_TIMEOUT; 7 | import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; 8 | import static io.appium.java_client.remote.MobileCapabilityType.UDID; 9 | import static io.appium.java_client.remote.MobilePlatform.ANDROID; 10 | 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.List; 14 | 15 | import org.openqa.selenium.remote.DesiredCapabilities; 16 | 17 | import com.mobilebox.repl.app.APKInspector; 18 | import com.mobilebox.repl.exceptions.CommandsException; 19 | import com.mobilebox.repl.selectors.UISelector; 20 | 21 | import io.appium.java_client.android.AndroidDriver; 22 | import io.appium.java_client.android.AndroidElement; 23 | 24 | @SuppressWarnings({"rawtypes"}) 25 | public class AndroidCommands extends AppiumCommands { 26 | 27 | @Override 28 | @CommandRef(desc = "Start a new Appium session for Android.", params = { 29 | "deviceName - The kind of mobile device or emulator to use", 30 | "udid - Unique device identifier of the connected physical device", 31 | "app - The absolute local path or remote http URL to an .ipa or .apk", 32 | "server - The Appium server URL", 33 | "timeout - How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session."}) 34 | public void start(String deviceName, String udid, String app, String server, String timeout) 35 | throws CommandsException, MalformedURLException { 36 | DesiredCapabilities capabilities = new DesiredCapabilities(); 37 | capabilities.setCapability(PLATFORM_NAME, ANDROID); 38 | capabilities.setCapability(DEVICE_NAME, deviceName); 39 | capabilities.setCapability(NEW_COMMAND_TIMEOUT, timeout); 40 | capabilities.setCapability(UDID, udid); 41 | capabilities.setCapability(APP, app); 42 | setDriver(new AndroidDriver(new URL(server), capabilities)); 43 | setDeviceID(udid); 44 | setApp(app); 45 | } 46 | 47 | @CommandRef(desc = "Retrieves an AndroidDeviceCommands instance.") 48 | public AndroidDeviceCommands device() throws CommandsException { 49 | return new AndroidDeviceCommands(getDeviceID()); 50 | } 51 | 52 | @CommandRef(desc = "Find element by text.", 53 | params = { 54 | "text - The visible text displayed in a widget (for example, the text label to launch an app)."}, 55 | ret = "An AndroidElement.") 56 | public AndroidElement text(final String text) { 57 | return (AndroidElement) findElement(UISelector.text(text)); 58 | } 59 | 60 | @CommandRef(desc = "Find elements by text.", 61 | params = { 62 | "text - The visible text displayed in a widget (for example, the text label to launch an app)."}, 63 | ret = "A list of AndroidElement. This list is empty when no elements are found.") 64 | public List texts(final String text) { 65 | return (List) findElements(UISelector.text(text)); 66 | } 67 | 68 | @CommandRef(desc = "Find element by text that contains a given text.", 69 | params = { 70 | "text - The visible text displayed in a widget (for example, the text label to launch an app). " 71 | + "The text for the element must match exactly with the string in your input argument. " 72 | + "Matching is case-sensitive."}, 73 | ret = "An AndroidElement.") 74 | public AndroidElement textContain(final String text) { 75 | return (AndroidElement) findElement(UISelector.textContains(text)); 76 | } 77 | 78 | @CommandRef(desc = "Find elements by text that contains a given text.", 79 | params = { 80 | "text - The visible text displayed in a widget (for example, the text label to launch an app). " 81 | + "The text for the element must match exactly with the string in your input argument. " 82 | + "Matching is case-sensitive."}, 83 | ret = "A list of AndroidElement. This list is empty when no elements are found.") 84 | public List textContains(final String text) { 85 | return (List) findElements(UISelector.textContains(text)); 86 | } 87 | 88 | @CommandRef( 89 | desc = "Find element by an UISelector expression. Chaining the search criteria on 'new UiSelector()'.", 90 | params = { 91 | "selector - The UISelector expression. E.g: className('android.widget.RelativeLayout').enabled(true).instance(0);"}, 92 | ret = "An AndroidElement.") 93 | public AndroidElement uiSelector(final String selector) { 94 | return (AndroidElement) findElement(UISelector.selectorChaining(selector)); 95 | } 96 | 97 | @CommandRef( 98 | desc = "Find elements by an UISelector expression. Chaining the search criteria on 'new UiSelector()'.", 99 | params = { 100 | "selector - The UISelector expression. E.g: className('android.widget.RelativeLayout').enabled(true).instance(0);"}, 101 | ret = "A list of AndroidElement. This list is empty when no elements are found.") 102 | public List uiSelectors(final String selector) { 103 | return findElements(UISelector.selectorChaining(selector)); 104 | } 105 | 106 | @CommandRef(desc = "Prints the current activity being run on the mobile device.") 107 | public void activity() { 108 | console(getDriver().currentActivity()); 109 | } 110 | 111 | @CommandRef(desc = "Open the notification shade, on Android devices.") 112 | public void openNotifications() { 113 | getDriver().openNotifications(); 114 | } 115 | 116 | @CommandRef(desc = "Close the app which was provided in the capabilities at session creation.") 117 | public void closeApp() { 118 | getDriver().closeApp(); 119 | }; 120 | 121 | @CommandRef(desc = "Launch the app which was provided in the capabilities at session creation.") 122 | public void launchApp() { 123 | getDriver().launchApp(); 124 | }; 125 | 126 | @CommandRef(desc = "Prints detailed information about the app.") 127 | public void appInfo() { 128 | new APKInspector().inspect(getApp()).toConsole(); 129 | }; 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/AndroidDeviceCommands.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static com.mobilebox.repl.misc.Utils.consoleTitle; 4 | import static com.mobilebox.repl.misc.Utils.console; 5 | import static com.mobilebox.repl.devices.AndroidDeviceProperties.*; 6 | import static com.google.common.base.Preconditions.*; 7 | 8 | import java.io.File; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.List; 14 | import java.util.Map.Entry; 15 | import java.util.Scanner; 16 | import java.util.stream.Collectors; 17 | 18 | import com.mobilebox.repl.devices.AndroidDeviceKeyEvent; 19 | import com.mobilebox.repl.exceptions.CommandsException; 20 | 21 | import se.vidstige.jadb.JadbConnection; 22 | import se.vidstige.jadb.JadbDevice; 23 | import se.vidstige.jadb.JadbException; 24 | import se.vidstige.jadb.Stream; 25 | 26 | /** 27 | * Commands for control an Android device or emulator from outside of Appium code. With these 28 | * commands you can takes screenshots, set brightness, retrieves devices properties, etc. For 29 | * provide these functions we uses an ADB client implemented in pure Java: JADB 30 | * 31 | * @see JADB 32 | */ 33 | public class AndroidDeviceCommands { 34 | 35 | private String serial; 36 | private JadbConnection jadb; 37 | private JadbDevice device; 38 | 39 | /** 40 | * Create a new instance of {@link AndroidDeviceCommands}. 41 | * 42 | * @param serial The Android Device serial. 43 | * @throws CommandsException 44 | */ 45 | public AndroidDeviceCommands(final String serial) throws CommandsException { 46 | checkArgument(!serial.isEmpty(), "Cannot load the device the device serial is empty."); 47 | 48 | try { 49 | jadb = new JadbConnection(); 50 | List devices = jadb.getDevices().stream() 51 | .filter(d -> d.getSerial().contains(serial)).collect(Collectors.toList()); 52 | this.serial = serial; 53 | this.device = devices.get(0); 54 | } catch (IOException | JadbException e) { 55 | throw new CommandsException("Cannot load the device. Error:" + e.getMessage()); 56 | } 57 | } 58 | 59 | @CommandRef(desc = "Prints the Android device serial.") 60 | public void serial() { 61 | console("Android Device ID: " + serial); 62 | } 63 | 64 | @CommandRef(desc = "Set brightness.", params = {"brightness - Value into the range [0,255]."}) 65 | public void brightness(final int brightness) throws IOException, JadbException { 66 | device.executeShell("settings", "put", "system", "screen_brightness", 67 | Integer.toString(brightness)); 68 | } 69 | 70 | @CommandRef(desc = "Sends the KEYCODE_CALL KeyEvent.") 71 | public void call() throws IOException, JadbException { 72 | keyEvent(AndroidDeviceKeyEvent.KEYCODE_CALL); 73 | } 74 | 75 | @CommandRef(desc = "Sends the KEYCODE_VOLUME_UP KeyEvent.") 76 | public void volumenUp() throws IOException, JadbException { 77 | keyEvent(AndroidDeviceKeyEvent.KEYCODE_VOLUME_UP); 78 | } 79 | 80 | @CommandRef(desc = "Sends the KEYCODE_VOLUME_DOWN KeyEvent.") 81 | public void volumenDown() throws IOException, JadbException { 82 | keyEvent(AndroidDeviceKeyEvent.KEYCODE_VOLUME_DOWN); 83 | } 84 | 85 | @CommandRef(desc = "Prints the device Android version.") 86 | public void version() throws IOException, JadbException { 87 | console("Android Version Release: " + prop("ro.build.version.release")); 88 | } 89 | 90 | @CommandRef(desc = "Prints device information related to Dalvik VM.") 91 | public void vm() throws IOException, JadbException { 92 | consoleTitle("Device Dalvik VM properties"); 93 | for (Entry entry : DALVIK_VM.entrySet()) { 94 | console(entry.getKey() + " = " + prop(entry.getValue())); 95 | } 96 | } 97 | 98 | @CommandRef(desc = "Prints device information related to DHCP.") 99 | public void dhcp() throws IOException, JadbException { 100 | consoleTitle("Device DHCP properties"); 101 | for (Entry entry : DHCP.entrySet()) { 102 | console(entry.getKey() + " = " + prop(entry.getValue())); 103 | } 104 | } 105 | 106 | @CommandRef(desc = "Prints device information related to GSM.") 107 | public void gsm() throws IOException, JadbException { 108 | consoleTitle("Device GSM properties"); 109 | for (Entry entry : GSM.entrySet()) { 110 | console(entry.getKey() + " = " + prop(entry.getValue())); 111 | } 112 | } 113 | 114 | @CommandRef(desc = "Prints device information related to Net.") 115 | public void net() throws IOException, JadbException { 116 | consoleTitle("Device Net properties"); 117 | for (Entry entry : NET.entrySet()) { 118 | console(entry.getKey() + " = " + prop(entry.getValue())); 119 | } 120 | } 121 | 122 | @CommandRef(desc = "Prints device information related to product.") 123 | public void productInfo() throws IOException, JadbException { 124 | consoleTitle("Device Product properties"); 125 | for (Entry entry : PRODUCT.entrySet()) { 126 | console(entry.getKey() + " = " + prop(entry.getValue())); 127 | } 128 | } 129 | 130 | @CommandRef(desc = "Prints device information related to Wifi.") 131 | public void wifi() throws IOException, JadbException { 132 | consoleTitle("Device WIFI properties"); 133 | for (Entry entry : WIFI.entrySet()) { 134 | console(entry.getKey() + " = " + prop(entry.getValue())); 135 | } 136 | } 137 | 138 | @CommandRef(desc = " Remove a file.", params = {"filePath - The file path to remove."}) 139 | public void remove(final String filePath) throws IOException, JadbException { 140 | device.executeShell("rm", "-f", filePath); 141 | } 142 | 143 | @CommandRef(desc = "Prints all packages on the device.") 144 | public void packages() throws IOException, JadbException { 145 | console(getResponse(device.executeShell("pm", "list", "packages"))); 146 | } 147 | 148 | @CommandRef(desc = "Prints battery status.") 149 | public void battery() throws IOException, JadbException { 150 | console(getResponse(device.executeShell("dumpsys", "battery"))); 151 | } 152 | 153 | @CommandRef(desc = "Prints disk space usage.") 154 | public void disk() throws IOException, JadbException { 155 | console(getResponse(device.executeShell("df"))); 156 | } 157 | 158 | @CommandRef(desc = "Prints a list of all the available shell programs.") 159 | public void shellPrograms() throws IOException, JadbException { 160 | console(getResponse(device.executeShell("ls", "/system/bin"))); 161 | } 162 | 163 | @CommandRef(desc = "Take and save a device screenshot to file.", 164 | params = {"filePath - Full path to file"}) 165 | public void screenshot(final String filePath) throws IOException, JadbException { 166 | FileOutputStream outputStream = null; 167 | try { 168 | outputStream = new FileOutputStream(new File(filePath)); 169 | InputStream stdout = device.executeShell("screencap", "-p"); 170 | Stream.copy(stdout, outputStream); 171 | } finally { 172 | if (outputStream != null) 173 | outputStream.close(); 174 | } 175 | } 176 | 177 | @CommandRef( 178 | desc = "Execute a shell command in the emulator/device instance and then exits the remote shell.", 179 | params = {"command - A shell command.", "args - Shell commands arguments."}) 180 | public void shell(final String command, final String... args) throws IOException, JadbException { 181 | console(getResponse(device.executeShell(command, args))); 182 | } 183 | 184 | @CommandRef( 185 | desc = "Given the name of a system environment variable,returns its value for this device.", 186 | params = {"propertye - The property name"}, ret = "The property value") 187 | public String prop(final String property) throws IOException, JadbException { 188 | return getResponse(device.executeShell("getprop", property)); 189 | } 190 | 191 | private void keyEvent(final int event) throws IOException, JadbException { 192 | console("Sends key event to the device. KeyEvent: [" + event + "]"); 193 | device.executeShell("input", "keyevent", Integer.toString(event)); 194 | } 195 | 196 | private String getResponse(InputStream input) { 197 | try (Scanner scanner = new Scanner(input, StandardCharsets.UTF_8.name())) { 198 | return scanner.useDelimiter("\\A").next(); 199 | } 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/AppiumCommands.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static com.mobilebox.repl.commands.CommandsDoc.SEPARATOR; 4 | import static com.mobilebox.repl.misc.Utils.console; 5 | import static com.mobilebox.repl.misc.Utils.prettyXML; 6 | import static io.appium.java_client.remote.MobileCapabilityType.APP; 7 | import static io.appium.java_client.remote.MobileCapabilityType.DEVICE_NAME; 8 | import static io.appium.java_client.remote.MobileCapabilityType.NEW_COMMAND_TIMEOUT; 9 | import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; 10 | import static io.appium.java_client.remote.MobileCapabilityType.UDID; 11 | import static java.lang.String.format; 12 | import static java.lang.System.getProperty; 13 | 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | import java.net.MalformedURLException; 17 | import java.net.URL; 18 | import java.util.List; 19 | 20 | import javax.script.Invocable; 21 | import javax.script.ScriptEngine; 22 | import javax.script.ScriptEngineManager; 23 | 24 | import org.aeonbits.owner.ConfigFactory; 25 | import org.dom4j.DocumentException; 26 | import org.openqa.selenium.By; 27 | import org.openqa.selenium.Dimension; 28 | import org.openqa.selenium.Point; 29 | import org.openqa.selenium.WebElement; 30 | import org.openqa.selenium.remote.DesiredCapabilities; 31 | 32 | import com.mobilebox.repl.config.ConfigCapabilities; 33 | import com.mobilebox.repl.exceptions.CommandsException; 34 | 35 | import io.appium.java_client.AppiumDriver; 36 | import io.appium.java_client.android.AndroidDriver; 37 | import io.appium.java_client.ios.IOSDriver; 38 | 39 | @SuppressWarnings({"unchecked", "rawtypes"}) 40 | public abstract class AppiumCommands { 41 | 42 | private String udid; 43 | private String app; 44 | private AppiumDriver driver; 45 | private final String ENGINE_NAME = "nashorn"; 46 | private boolean logElement = true; 47 | 48 | abstract void start(String deviceName, String udid, String app, String server, String timeout) 49 | throws CommandsException, MalformedURLException; 50 | 51 | @CommandRef(desc = "Start a new Appium session from ${user.home}/appium.txt file") 52 | public void start() throws MalformedURLException, CommandsException { 53 | try { 54 | ConfigCapabilities config = ConfigFactory.create(ConfigCapabilities.class); 55 | DesiredCapabilities caps = new DesiredCapabilities(); 56 | 57 | caps.setCapability(DEVICE_NAME, config.deviceName()); 58 | caps.setCapability(NEW_COMMAND_TIMEOUT, config.cmdTimeout()); 59 | caps.setCapability(APP, config.app()); 60 | setApp(config.app()); 61 | 62 | if (!config.udid().isEmpty()) { 63 | caps.setCapability(UDID, config.udid()); 64 | setDeviceID(config.udid()); 65 | } 66 | 67 | URL urlServer = new URL(config.appiumServer()); 68 | setPlatformName(config.platformName(), urlServer, caps); 69 | 70 | } catch (Exception e) { 71 | throw new RuntimeException( 72 | format("An error has occurred: [%s] Please check the appium.txt file on: [%s]", 73 | e.getMessage(), getProperty("user.home"))); 74 | } 75 | } 76 | 77 | @CommandRef( 78 | desc = "Start a new Appium session given Nashorn (JS) script file with DesiredCapabilities.", 79 | params = {"path - The script full path."}, ret = "") 80 | public void start(final String path) { 81 | final ScriptEngineManager manager = new ScriptEngineManager(); 82 | ScriptEngine engine = manager.getEngineByName(ENGINE_NAME); 83 | 84 | try { 85 | engine.eval(new FileReader(path)); 86 | Invocable invocable = (Invocable) engine; 87 | DesiredCapabilities caps = (DesiredCapabilities) invocable.invokeFunction("main"); 88 | 89 | String app = (String) caps.getCapability("app"); 90 | if (app != null) { 91 | setApp(app); 92 | } 93 | 94 | String udid = (String) caps.getCapability("udid"); 95 | if (udid != null) { 96 | setDeviceID(udid); 97 | } 98 | 99 | URL urlServer = new URL((String) caps.getCapability("appiumServer")); 100 | String platform = (String) caps.getCapability("platformName"); 101 | setPlatformName(platform, urlServer, caps); 102 | } catch (Exception e) { 103 | throw new RuntimeException( 104 | format("An error has occurred: [%s] Please check the DesiredCapabilities on: [%s]", 105 | e.getMessage(), path)); 106 | } 107 | } 108 | 109 | @CommandRef(desc = "Execute a Nashorn (JS) script.", params = {"path - The script full path."}, 110 | ret = "") 111 | public void run(final String path) { 112 | final ScriptEngineManager manager = new ScriptEngineManager(); 113 | ScriptEngine engine = manager.getEngineByName(ENGINE_NAME); 114 | 115 | try { 116 | engine.eval(new FileReader(path)); 117 | Invocable invocable = (Invocable) engine; 118 | invocable.invokeFunction("main", this); 119 | } catch (Exception e) { 120 | throw new RuntimeException(format("An error has occurred: [%s] Please check the script: [%s]", 121 | e.getMessage(), path)); 122 | } 123 | } 124 | 125 | protected T getDriver() { 126 | if (driver == null) { 127 | throw new RuntimeException("The driver is null. Please start a new session"); 128 | } 129 | return (T) driver; 130 | }; 131 | 132 | protected String getDeviceID() { 133 | return udid; 134 | }; 135 | 136 | protected String getApp() { 137 | return app; 138 | }; 139 | 140 | protected void setDriver(AppiumDriver driver) { 141 | this.driver = driver; 142 | } 143 | 144 | protected void setDeviceID(String id) { 145 | this.udid = id; 146 | }; 147 | 148 | protected void setApp(String app) { 149 | this.app = app; 150 | }; 151 | 152 | public void logElement(boolean log){ 153 | logElement = log; 154 | } 155 | 156 | @CommandRef(desc = "Terminates the driver instance.") 157 | public void quit() { 158 | ((AppiumDriver) getDriver()).quit(); 159 | }; 160 | 161 | @CommandRef(desc = "Find elements by ID.", params = {"id - The element id"}, 162 | ret = "An element (AndroidElement or IOSElement).") 163 | public E id(String id) { 164 | return findElement(By.id(id)); 165 | } 166 | 167 | @CommandRef(desc = "Find elements by ID.", params = {"id - The element id"}, 168 | ret = "A list of elements (AndroidElement or IOSElement). This list is empty when no elements are found.") 169 | public List ids(String id) { 170 | return findElements(By.id(id)); 171 | } 172 | 173 | @CommandRef(desc = "Find element by class name.", 174 | params = {"className - The class property (for example, 'android.widget.Button')"}, 175 | ret = "An element (AndroidElement or IOSElement).") 176 | public E className(String className) { 177 | return findElement(By.className(className)); 178 | } 179 | 180 | @CommandRef(desc = "Find elements by class name.", 181 | params = {"className - The class property (for example, 'android.widget.Button')"}, 182 | ret = "A list of elements (AndroidElement or IOSElement). This list is empty when no elements are found.") 183 | public List classNames(String className) { 184 | return findElements(By.className(className)); 185 | } 186 | 187 | @CommandRef(desc = "Find element by Xpath.", params = {"xpath - A Xpath expression"}, 188 | ret = "An element (AndroidElement or IOSElement).") 189 | public E xpath(String xpath) { 190 | return findElement(By.xpath(xpath)); 191 | } 192 | 193 | @CommandRef(desc = "Find elements by Xpath.", params = {"xpath - A Xpath expression"}, 194 | ret = "A list of elements. This list is empty when no elements are found.") 195 | public List xpaths(String xpath) { 196 | return findElements(By.xpath(xpath)); 197 | } 198 | 199 | @CommandRef(desc = "Move back") 200 | public void back() { 201 | getDriver().navigate().back(); 202 | }; 203 | 204 | @CommandRef(desc = "Prints the current orientation of a mobile devices desktop.") 205 | public void orientation() { 206 | console(getDriver().getOrientation()); 207 | } 208 | 209 | @CommandRef(desc = "Prints the capabilities of the current driver.") 210 | public void capabilities() { 211 | console(getDriver().getCapabilities().asMap()); 212 | } 213 | 214 | @CommandRef(desc = "Retrieves a XML view of the current screen.", 215 | ret = "A XML view of the current screen.") 216 | public void source() throws IOException, DocumentException { 217 | console(prettyXML(getDriver().getPageSource())); 218 | } 219 | 220 | @CommandRef(desc = "Prints the ID of this session.") 221 | public void session() { 222 | console(getDriver().getSessionId()); 223 | }; 224 | 225 | @CommandRef(desc = "Prints all defined Strings from an app for the default language.") 226 | public void strings() { 227 | console(getDriver().getAppStringMap()); 228 | }; 229 | 230 | @CommandRef(desc = "Prints the device date and time for both iOS and Android devices.") 231 | public void time() { 232 | console(getDriver().getDeviceTime()); 233 | }; 234 | 235 | @CommandRef(desc = "Prints the current context.") 236 | public void context() { 237 | console(getDriver().getContext()); 238 | }; 239 | 240 | @CommandRef(desc = "Prints the available contexts.") 241 | public void contextHandles() { 242 | getDriver().getContextHandles().forEach(item -> console(item)); 243 | }; 244 | 245 | @CommandRef(desc = "Switch to a new context.", params = {"context - The context name."}) 246 | public void context(String context) { 247 | getDriver().context(context); 248 | }; 249 | 250 | @CommandRef(desc = "Prints the session details.") 251 | public void sessionDetails() { 252 | console(getDriver().getSessionDetails()); 253 | }; 254 | 255 | @CommandRef(desc = "Hides the keyboard if it is showing.") 256 | public void hideKeyboard() { 257 | getDriver().hideKeyboard(); 258 | }; 259 | 260 | protected E findElement(By locator) { 261 | E element; 262 | try { 263 | element = (E) getDriver().findElement(locator); 264 | printElement(element); 265 | } catch (Exception e) { 266 | throw new RuntimeException("Element not found: " + e.getMessage()); 267 | } 268 | return element; 269 | } 270 | 271 | protected List findElements(By locator) { 272 | List elements = (List) ((T) getDriver()).findElements(locator); 273 | console("Found: " + elements.size() + " elements" + SEPARATOR); 274 | for (E element : elements) { 275 | printElement(element); 276 | } 277 | return elements; 278 | } 279 | 280 | protected void printElement(E element) { 281 | if (logElement) { 282 | Point location = element.getLocation(); 283 | Dimension size = element.getSize(); 284 | console("---> Text: " + element.getText()); 285 | console("---> TagName: " + element.getTagName()); 286 | console("---> Enabled: " + element.isEnabled()); 287 | console("---> Selected: " + element.isSelected()); 288 | console("---> Displayed: " + element.isDisplayed()); 289 | console("---> Location: [X=" + location.getX() + " Y=" + location.getY() + "]"); 290 | console( 291 | "---> Size: [Height=" + size.getHeight() + " Width=" + size.getWidth() + "]" + SEPARATOR); 292 | } 293 | } 294 | 295 | private void setPlatformName(final String platform, URL urlServer, DesiredCapabilities caps) { 296 | switch (platform.toLowerCase()) { 297 | case "android": 298 | caps.setCapability(PLATFORM_NAME, "Android"); 299 | setDriver(new AndroidDriver(urlServer, caps)); 300 | break; 301 | case "ios": 302 | caps.setCapability(PLATFORM_NAME, "iOS"); 303 | setDriver(new IOSDriver(urlServer, caps)); 304 | break; 305 | default: 306 | throw new RuntimeException( 307 | format("Failed to start session. Please check the capabilities.", caps.toString())); 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/CommandRef.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | import java.lang.annotation.*; 6 | 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @Target(value = ElementType.METHOD) 9 | public @interface CommandRef { 10 | 11 | public String desc() default ""; 12 | 13 | public String[] params() default ""; 14 | 15 | public String ret() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/CommandsDoc.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static com.mobilebox.repl.misc.Utils.console; 4 | import static com.mobilebox.repl.misc.Utils.consoleTitle; 5 | 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Modifier; 8 | import java.util.Arrays; 9 | 10 | public abstract class CommandsDoc { 11 | 12 | public static final String SEPARATOR = System.getProperty("line.separator"); 13 | 14 | /** 15 | * Prints all commands in a given class. In order to define command in Java code, you need to 16 | * annotate the respective methods with @CommandRef annotation. 17 | * 18 | * @param clazz A class that contains commands. 19 | */ 20 | public static void printCommands(Class clazz) { 21 | consoleTitle("Available Commands for " + clazz.getSimpleName().replaceAll("Commands", "")); 22 | 23 | StringBuffer ref = new StringBuffer(); 24 | ref.append("---> Command: %s").append(SEPARATOR).append("---> Description: %s") 25 | .append(SEPARATOR).append("---> Parameters: %s").append(SEPARATOR).append("---> Return: %s") 26 | .append(SEPARATOR); 27 | 28 | for (Method method : clazz.getMethods()) { 29 | String descOfMethod = ""; 30 | String[] params = {""}; 31 | String returns = ""; 32 | if (Modifier.isPublic(method.getModifiers())) { 33 | CommandRef def = method.getAnnotation(CommandRef.class); 34 | if (def != null) { 35 | params = def.params(); 36 | descOfMethod = def.desc(); 37 | returns = def.ret(); 38 | console(String.format(ref.toString(), method.getName(), descOfMethod, 39 | Arrays.toString(params), returns)); 40 | } 41 | 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/commands/IOSCommands.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static io.appium.java_client.remote.MobileCapabilityType.APP; 4 | import static io.appium.java_client.remote.MobileCapabilityType.DEVICE_NAME; 5 | import static io.appium.java_client.remote.MobileCapabilityType.NEW_COMMAND_TIMEOUT; 6 | import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; 7 | import static io.appium.java_client.remote.MobileCapabilityType.UDID; 8 | import static io.appium.java_client.remote.MobilePlatform.IOS; 9 | 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | 13 | import org.openqa.selenium.remote.DesiredCapabilities; 14 | 15 | import com.mobilebox.repl.exceptions.CommandsException; 16 | 17 | import io.appium.java_client.ios.IOSDriver; 18 | import io.appium.java_client.ios.IOSElement; 19 | 20 | @SuppressWarnings({"rawtypes"}) 21 | public class IOSCommands extends AppiumCommands { 22 | 23 | @Override 24 | @CommandRef(desc = "Start a new Appium session for iOS.", params = { 25 | "deviceName - The kind of mobile device or emulator to use", 26 | "udid - Unique device identifier of the connected physical device", 27 | "app - The absolute local path or remote http URL to an .ipa or .apk", 28 | "server - The Appium server URL", 29 | "timeout - How long (in seconds) Appium will wait for a new command from the client before assuming the client quit and ending the session."}) 30 | public void start(String deviceName, String udid, String app, String server, String timeout) 31 | throws CommandsException, MalformedURLException { 32 | DesiredCapabilities capabilities = new DesiredCapabilities(); 33 | capabilities.setCapability(PLATFORM_NAME, IOS); 34 | capabilities.setCapability(DEVICE_NAME, deviceName); 35 | capabilities.setCapability(NEW_COMMAND_TIMEOUT, timeout); 36 | capabilities.setCapability(UDID, udid); 37 | capabilities.setCapability(APP, app); 38 | setDriver(new IOSDriver(new URL(server), capabilities)); 39 | setApp(app); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/config/ConfigCapabilities.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | import org.aeonbits.owner.Config.Sources; 6 | import org.aeonbits.owner.Config.HotReload; 7 | 8 | /** 9 | * Specify the necessary configurations of AUT through capabilities by storing them in a file. 10 | */ 11 | @HotReload 12 | @Sources("file:${user.home}/appium.txt") 13 | public interface ConfigCapabilities extends Config { 14 | 15 | @DefaultValue("n/a") 16 | @Key("platform.name") 17 | String platformName(); 18 | 19 | @Key("device.name") 20 | String deviceName(); 21 | 22 | @Key("command.timeout") 23 | String cmdTimeout(); 24 | 25 | @DefaultValue("") 26 | @Key("udid") 27 | String udid(); 28 | 29 | @Key("app") 30 | String app(); 31 | 32 | @DefaultValue("http://127.0.0.1:4723/wd/hub") 33 | @Key("appium.server") 34 | String appiumServer(); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/devices/AndroidDeviceKeyEvent.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.devices; 2 | 3 | public class AndroidDeviceKeyEvent { 4 | 5 | /** Key code constant: Unknown key code. */ 6 | public static final int KEYCODE_UNKNOWN = 0; 7 | /** 8 | * Key code constant: Soft Left key. Usually situated below the display on phones and used as a 9 | * multi-function feature key for selecting a software defined function shown on the bottom left 10 | * of the display. 11 | */ 12 | public static final int KEYCODE_SOFT_LEFT = 1; 13 | /** 14 | * Key code constant: Soft Right key. Usually situated below the display on phones and used as a 15 | * multi-function feature key for selecting a software defined function shown on the bottom right 16 | * of the display. 17 | */ 18 | public static final int KEYCODE_SOFT_RIGHT = 2; 19 | /** 20 | * Key code constant: Home key. This key is handled by the framework and is never delivered to 21 | * applications. 22 | */ 23 | public static final int KEYCODE_HOME = 3; 24 | /** Key code constant: Back key. */ 25 | public static final int KEYCODE_BACK = 4; 26 | /** Key code constant: Call key. */ 27 | public static final int KEYCODE_CALL = 5; 28 | /** Key code constant: End Call key. */ 29 | public static final int KEYCODE_ENDCALL = 6; 30 | /** Key code constant: '0' key. */ 31 | public static final int KEYCODE_0 = 7; 32 | /** Key code constant: '1' key. */ 33 | public static final int KEYCODE_1 = 8; 34 | /** Key code constant: '2' key. */ 35 | public static final int KEYCODE_2 = 9; 36 | /** Key code constant: '3' key. */ 37 | public static final int KEYCODE_3 = 10; 38 | /** Key code constant: '4' key. */ 39 | public static final int KEYCODE_4 = 11; 40 | /** Key code constant: '5' key. */ 41 | public static final int KEYCODE_5 = 12; 42 | /** Key code constant: '6' key. */ 43 | public static final int KEYCODE_6 = 13; 44 | /** Key code constant: '7' key. */ 45 | public static final int KEYCODE_7 = 14; 46 | /** Key code constant: '8' key. */ 47 | public static final int KEYCODE_8 = 15; 48 | /** Key code constant: '9' key. */ 49 | public static final int KEYCODE_9 = 16; 50 | /** Key code constant: '*' key. */ 51 | public static final int KEYCODE_STAR = 17; 52 | /** Key code constant: '#' key. */ 53 | public static final int KEYCODE_POUND = 18; 54 | /** 55 | * Key code constant: Directional Pad Up key. May also be synthesized from trackball motions. 56 | */ 57 | public static final int KEYCODE_DPAD_UP = 19; 58 | /** 59 | * Key code constant: Directional Pad Down key. May also be synthesized from trackball motions. 60 | */ 61 | public static final int KEYCODE_DPAD_DOWN = 20; 62 | /** 63 | * Key code constant: Directional Pad Left key. May also be synthesized from trackball motions. 64 | */ 65 | public static final int KEYCODE_DPAD_LEFT = 21; 66 | /** 67 | * Key code constant: Directional Pad Right key. May also be synthesized from trackball motions. 68 | */ 69 | public static final int KEYCODE_DPAD_RIGHT = 22; 70 | /** 71 | * Key code constant: Directional Pad Center key. May also be synthesized from trackball motions. 72 | */ 73 | public static final int KEYCODE_DPAD_CENTER = 23; 74 | /** 75 | * Key code constant: Volume Up key. Adjusts the speaker volume up. 76 | */ 77 | public static final int KEYCODE_VOLUME_UP = 24; 78 | /** 79 | * Key code constant: Volume Down key. Adjusts the speaker volume down. 80 | */ 81 | public static final int KEYCODE_VOLUME_DOWN = 25; 82 | /** Key code constant: Power key. */ 83 | public static final int KEYCODE_POWER = 26; 84 | /** 85 | * Key code constant: Camera key. Used to launch a camera application or take pictures. 86 | */ 87 | public static final int KEYCODE_CAMERA = 27; 88 | /** Key code constant: Clear key. */ 89 | public static final int KEYCODE_CLEAR = 28; 90 | /** Key code constant: 'A' key. */ 91 | public static final int KEYCODE_A = 29; 92 | /** Key code constant: 'B' key. */ 93 | public static final int KEYCODE_B = 30; 94 | /** Key code constant: 'C' key. */ 95 | public static final int KEYCODE_C = 31; 96 | /** Key code constant: 'D' key. */ 97 | public static final int KEYCODE_D = 32; 98 | /** Key code constant: 'E' key. */ 99 | public static final int KEYCODE_E = 33; 100 | /** Key code constant: 'F' key. */ 101 | public static final int KEYCODE_F = 34; 102 | /** Key code constant: 'G' key. */ 103 | public static final int KEYCODE_G = 35; 104 | /** Key code constant: 'H' key. */ 105 | public static final int KEYCODE_H = 36; 106 | /** Key code constant: 'I' key. */ 107 | public static final int KEYCODE_I = 37; 108 | /** Key code constant: 'J' key. */ 109 | public static final int KEYCODE_J = 38; 110 | /** Key code constant: 'K' key. */ 111 | public static final int KEYCODE_K = 39; 112 | /** Key code constant: 'L' key. */ 113 | public static final int KEYCODE_L = 40; 114 | /** Key code constant: 'M' key. */ 115 | public static final int KEYCODE_M = 41; 116 | /** Key code constant: 'N' key. */ 117 | public static final int KEYCODE_N = 42; 118 | /** Key code constant: 'O' key. */ 119 | public static final int KEYCODE_O = 43; 120 | /** Key code constant: 'P' key. */ 121 | public static final int KEYCODE_P = 44; 122 | /** Key code constant: 'Q' key. */ 123 | public static final int KEYCODE_Q = 45; 124 | /** Key code constant: 'R' key. */ 125 | public static final int KEYCODE_R = 46; 126 | /** Key code constant: 'S' key. */ 127 | public static final int KEYCODE_S = 47; 128 | /** Key code constant: 'T' key. */ 129 | public static final int KEYCODE_T = 48; 130 | /** Key code constant: 'U' key. */ 131 | public static final int KEYCODE_U = 49; 132 | /** Key code constant: 'V' key. */ 133 | public static final int KEYCODE_V = 50; 134 | /** Key code constant: 'W' key. */ 135 | public static final int KEYCODE_W = 51; 136 | /** Key code constant: 'X' key. */ 137 | public static final int KEYCODE_X = 52; 138 | /** Key code constant: 'Y' key. */ 139 | public static final int KEYCODE_Y = 53; 140 | /** Key code constant: 'Z' key. */ 141 | public static final int KEYCODE_Z = 54; 142 | /** Key code constant: ',' key. */ 143 | public static final int KEYCODE_COMMA = 55; 144 | /** Key code constant: '.' key. */ 145 | public static final int KEYCODE_PERIOD = 56; 146 | /** Key code constant: Left Alt modifier key. */ 147 | public static final int KEYCODE_ALT_LEFT = 57; 148 | /** Key code constant: Right Alt modifier key. */ 149 | public static final int KEYCODE_ALT_RIGHT = 58; 150 | /** Key code constant: Left Shift modifier key. */ 151 | public static final int KEYCODE_SHIFT_LEFT = 59; 152 | /** Key code constant: Right Shift modifier key. */ 153 | public static final int KEYCODE_SHIFT_RIGHT = 60; 154 | /** Key code constant: Tab key. */ 155 | public static final int KEYCODE_TAB = 61; 156 | /** Key code constant: Space key. */ 157 | public static final int KEYCODE_SPACE = 62; 158 | /** 159 | * Key code constant: Symbol modifier key. Used to enter alternate symbols. 160 | */ 161 | public static final int KEYCODE_SYM = 63; 162 | /** 163 | * Key code constant: Explorer special function key. Used to launch a browser application. 164 | */ 165 | public static final int KEYCODE_EXPLORER = 64; 166 | /** 167 | * Key code constant: Envelope special function key. Used to launch a mail application. 168 | */ 169 | public static final int KEYCODE_ENVELOPE = 65; 170 | /** Key code constant: Enter key. */ 171 | public static final int KEYCODE_ENTER = 66; 172 | /** 173 | * Key code constant: Backspace key. Deletes characters before the insertion point, unlike 174 | * {@link #KEYCODE_FORWARD_DEL}. 175 | */ 176 | public static final int KEYCODE_DEL = 67; 177 | /** Key code constant: '`' (backtick) key. */ 178 | public static final int KEYCODE_GRAVE = 68; 179 | /** Key code constant: '-'. */ 180 | public static final int KEYCODE_MINUS = 69; 181 | /** Key code constant: '=' key. */ 182 | public static final int KEYCODE_EQUALS = 70; 183 | /** Key code constant: '[' key. */ 184 | public static final int KEYCODE_LEFT_BRACKET = 71; 185 | /** Key code constant: ']' key. */ 186 | public static final int KEYCODE_RIGHT_BRACKET = 72; 187 | /** Key code constant: '\' key. */ 188 | public static final int KEYCODE_BACKSLASH = 73; 189 | /** Key code constant: ';' key. */ 190 | public static final int KEYCODE_SEMICOLON = 74; 191 | /** Key code constant: ''' (apostrophe) key. */ 192 | public static final int KEYCODE_APOSTROPHE = 75; 193 | /** Key code constant: '/' key. */ 194 | public static final int KEYCODE_SLASH = 76; 195 | /** Key code constant: '@' key. */ 196 | public static final int KEYCODE_AT = 77; 197 | /** 198 | * Key code constant: Number modifier key. Used to enter numeric symbols. This key is not Num 199 | * Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is interpreted as an ALT key by 200 | * {@link android.text.method.MetaKeyKeyListener}. 201 | */ 202 | public static final int KEYCODE_NUM = 78; 203 | /** 204 | * Key code constant: Headset Hook key. Used to hang up calls and stop media. 205 | */ 206 | public static final int KEYCODE_HEADSETHOOK = 79; 207 | /** 208 | * Key code constant: Camera Focus key. Used to focus the camera. 209 | */ 210 | public static final int KEYCODE_FOCUS = 80; // *Camera* focus 211 | /** Key code constant: '+' key. */ 212 | public static final int KEYCODE_PLUS = 81; 213 | /** Key code constant: Menu key. */ 214 | public static final int KEYCODE_MENU = 82; 215 | /** Key code constant: Notification key. */ 216 | public static final int KEYCODE_NOTIFICATION = 83; 217 | /** Key code constant: Search key. */ 218 | public static final int KEYCODE_SEARCH = 84; 219 | /** Key code constant: Play/Pause media key. */ 220 | public static final int KEYCODE_MEDIA_PLAY_PAUSE = 85; 221 | /** Key code constant: Stop media key. */ 222 | public static final int KEYCODE_MEDIA_STOP = 86; 223 | /** Key code constant: Play Next media key. */ 224 | public static final int KEYCODE_MEDIA_NEXT = 87; 225 | /** Key code constant: Play Previous media key. */ 226 | public static final int KEYCODE_MEDIA_PREVIOUS = 88; 227 | /** Key code constant: Rewind media key. */ 228 | public static final int KEYCODE_MEDIA_REWIND = 89; 229 | /** Key code constant: Fast Forward media key. */ 230 | public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; 231 | /** 232 | * Key code constant: Mute key. Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. 233 | */ 234 | public static final int KEYCODE_MUTE = 91; 235 | /** Key code constant: Page Up key. */ 236 | public static final int KEYCODE_PAGE_UP = 92; 237 | /** Key code constant: Page Down key. */ 238 | public static final int KEYCODE_PAGE_DOWN = 93; 239 | /** 240 | * Key code constant: Picture Symbols modifier key. Used to switch symbol sets (Emoji, Kao-moji). 241 | */ 242 | public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets 243 | // (Emoji,Kao-moji) 244 | /** 245 | * Key code constant: Switch Charset modifier key. Used to switch character sets (Kanji, 246 | * Katakana). 247 | */ 248 | public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets 249 | // (Kanji,Katakana) 250 | /** 251 | * Key code constant: A Button key. On a game controller, the A button should be either the button 252 | * labeled A or the first button on the upper row of controller buttons. 253 | */ 254 | public static final int KEYCODE_BUTTON_A = 96; 255 | /** 256 | * Key code constant: B Button key. On a game controller, the B button should be either the button 257 | * labeled B or the second button on the upper row of controller buttons. 258 | */ 259 | public static final int KEYCODE_BUTTON_B = 97; 260 | /** 261 | * Key code constant: C Button key. On a game controller, the C button should be either the button 262 | * labeled C or the third button on the upper row of controller buttons. 263 | */ 264 | public static final int KEYCODE_BUTTON_C = 98; 265 | /** 266 | * Key code constant: X Button key. On a game controller, the X button should be either the button 267 | * labeled X or the first button on the lower row of controller buttons. 268 | */ 269 | public static final int KEYCODE_BUTTON_X = 99; 270 | /** 271 | * Key code constant: Y Button key. On a game controller, the Y button should be either the button 272 | * labeled Y or the second button on the lower row of controller buttons. 273 | */ 274 | public static final int KEYCODE_BUTTON_Y = 100; 275 | /** 276 | * Key code constant: Z Button key. On a game controller, the Z button should be either the button 277 | * labeled Z or the third button on the lower row of controller buttons. 278 | */ 279 | public static final int KEYCODE_BUTTON_Z = 101; 280 | /** 281 | * Key code constant: L1 Button key. On a game controller, the L1 button should be either the 282 | * button labeled L1 (or L) or the top left trigger button. 283 | */ 284 | public static final int KEYCODE_BUTTON_L1 = 102; 285 | /** 286 | * Key code constant: R1 Button key. On a game controller, the R1 button should be either the 287 | * button labeled R1 (or R) or the top right trigger button. 288 | */ 289 | public static final int KEYCODE_BUTTON_R1 = 103; 290 | /** 291 | * Key code constant: L2 Button key. On a game controller, the L2 button should be either the 292 | * button labeled L2 or the bottom left trigger button. 293 | */ 294 | public static final int KEYCODE_BUTTON_L2 = 104; 295 | /** 296 | * Key code constant: R2 Button key. On a game controller, the R2 button should be either the 297 | * button labeled R2 or the bottom right trigger button. 298 | */ 299 | public static final int KEYCODE_BUTTON_R2 = 105; 300 | /** 301 | * Key code constant: Left Thumb Button key. On a game controller, the left thumb button indicates 302 | * that the left (or only) joystick is pressed. 303 | */ 304 | public static final int KEYCODE_BUTTON_THUMBL = 106; 305 | /** 306 | * Key code constant: Right Thumb Button key. On a game controller, the right thumb button 307 | * indicates that the right joystick is pressed. 308 | */ 309 | public static final int KEYCODE_BUTTON_THUMBR = 107; 310 | /** 311 | * Key code constant: Start Button key. On a game controller, the button labeled Start. 312 | */ 313 | public static final int KEYCODE_BUTTON_START = 108; 314 | /** 315 | * Key code constant: Select Button key. On a game controller, the button labeled Select. 316 | */ 317 | public static final int KEYCODE_BUTTON_SELECT = 109; 318 | /** 319 | * Key code constant: Mode Button key. On a game controller, the button labeled Mode. 320 | */ 321 | public static final int KEYCODE_BUTTON_MODE = 110; 322 | /** Key code constant: Escape key. */ 323 | public static final int KEYCODE_ESCAPE = 111; 324 | /** 325 | * Key code constant: Forward Delete key. Deletes characters ahead of the insertion point, unlike 326 | * {@link #KEYCODE_DEL}. 327 | */ 328 | public static final int KEYCODE_FORWARD_DEL = 112; 329 | /** Key code constant: Left Control modifier key. */ 330 | public static final int KEYCODE_CTRL_LEFT = 113; 331 | /** Key code constant: Right Control modifier key. */ 332 | public static final int KEYCODE_CTRL_RIGHT = 114; 333 | /** Key code constant: Caps Lock key. */ 334 | public static final int KEYCODE_CAPS_LOCK = 115; 335 | /** Key code constant: Scroll Lock key. */ 336 | public static final int KEYCODE_SCROLL_LOCK = 116; 337 | /** Key code constant: Left Meta modifier key. */ 338 | public static final int KEYCODE_META_LEFT = 117; 339 | /** Key code constant: Right Meta modifier key. */ 340 | public static final int KEYCODE_META_RIGHT = 118; 341 | /** Key code constant: Function modifier key. */ 342 | public static final int KEYCODE_FUNCTION = 119; 343 | /** Key code constant: System Request / Print Screen key. */ 344 | public static final int KEYCODE_SYSRQ = 120; 345 | /** Key code constant: Break / Pause key. */ 346 | public static final int KEYCODE_BREAK = 121; 347 | /** 348 | * Key code constant: Home Movement key. Used for scrolling or moving the cursor around to the 349 | * start of a line or to the top of a list. 350 | */ 351 | public static final int KEYCODE_MOVE_HOME = 122; 352 | /** 353 | * Key code constant: End Movement key. Used for scrolling or moving the cursor around to the end 354 | * of a line or to the bottom of a list. 355 | */ 356 | public static final int KEYCODE_MOVE_END = 123; 357 | /** 358 | * Key code constant: Insert key. Toggles insert / overwrite edit mode. 359 | */ 360 | public static final int KEYCODE_INSERT = 124; 361 | /** 362 | * Key code constant: Forward key. Navigates forward in the history stack. Complement of 363 | * {@link #KEYCODE_BACK}. 364 | */ 365 | public static final int KEYCODE_FORWARD = 125; 366 | /** Key code constant: Play media key. */ 367 | public static final int KEYCODE_MEDIA_PLAY = 126; 368 | /** Key code constant: Pause media key. */ 369 | public static final int KEYCODE_MEDIA_PAUSE = 127; 370 | /** 371 | * Key code constant: Close media key. May be used to close a CD tray, for example. 372 | */ 373 | public static final int KEYCODE_MEDIA_CLOSE = 128; 374 | /** 375 | * Key code constant: Eject media key. May be used to eject a CD tray, for example. 376 | */ 377 | public static final int KEYCODE_MEDIA_EJECT = 129; 378 | /** Key code constant: Record media key. */ 379 | public static final int KEYCODE_MEDIA_RECORD = 130; 380 | /** Key code constant: F1 key. */ 381 | public static final int KEYCODE_F1 = 131; 382 | /** Key code constant: F2 key. */ 383 | public static final int KEYCODE_F2 = 132; 384 | /** Key code constant: F3 key. */ 385 | public static final int KEYCODE_F3 = 133; 386 | /** Key code constant: F4 key. */ 387 | public static final int KEYCODE_F4 = 134; 388 | /** Key code constant: F5 key. */ 389 | public static final int KEYCODE_F5 = 135; 390 | /** Key code constant: F6 key. */ 391 | public static final int KEYCODE_F6 = 136; 392 | /** Key code constant: F7 key. */ 393 | public static final int KEYCODE_F7 = 137; 394 | /** Key code constant: F8 key. */ 395 | public static final int KEYCODE_F8 = 138; 396 | /** Key code constant: F9 key. */ 397 | public static final int KEYCODE_F9 = 139; 398 | /** Key code constant: F10 key. */ 399 | public static final int KEYCODE_F10 = 140; 400 | /** Key code constant: F11 key. */ 401 | public static final int KEYCODE_F11 = 141; 402 | /** Key code constant: F12 key. */ 403 | public static final int KEYCODE_F12 = 142; 404 | /** 405 | * Key code constant: Num Lock key. This is the Num Lock key; it is different from 406 | * {@link #KEYCODE_NUM}. This key alters the behavior of other keys on the numeric keypad. 407 | */ 408 | public static final int KEYCODE_NUM_LOCK = 143; 409 | /** Key code constant: Numeric keypad '0' key. */ 410 | public static final int KEYCODE_NUMPAD_0 = 144; 411 | /** Key code constant: Numeric keypad '1' key. */ 412 | public static final int KEYCODE_NUMPAD_1 = 145; 413 | /** Key code constant: Numeric keypad '2' key. */ 414 | public static final int KEYCODE_NUMPAD_2 = 146; 415 | /** Key code constant: Numeric keypad '3' key. */ 416 | public static final int KEYCODE_NUMPAD_3 = 147; 417 | /** Key code constant: Numeric keypad '4' key. */ 418 | public static final int KEYCODE_NUMPAD_4 = 148; 419 | /** Key code constant: Numeric keypad '5' key. */ 420 | public static final int KEYCODE_NUMPAD_5 = 149; 421 | /** Key code constant: Numeric keypad '6' key. */ 422 | public static final int KEYCODE_NUMPAD_6 = 150; 423 | /** Key code constant: Numeric keypad '7' key. */ 424 | public static final int KEYCODE_NUMPAD_7 = 151; 425 | /** Key code constant: Numeric keypad '8' key. */ 426 | public static final int KEYCODE_NUMPAD_8 = 152; 427 | /** Key code constant: Numeric keypad '9' key. */ 428 | public static final int KEYCODE_NUMPAD_9 = 153; 429 | /** Key code constant: Numeric keypad '/' key (for division). */ 430 | public static final int KEYCODE_NUMPAD_DIVIDE = 154; 431 | /** Key code constant: Numeric keypad '*' key (for multiplication). */ 432 | public static final int KEYCODE_NUMPAD_MULTIPLY = 155; 433 | /** Key code constant: Numeric keypad '-' key (for subtraction). */ 434 | public static final int KEYCODE_NUMPAD_SUBTRACT = 156; 435 | /** Key code constant: Numeric keypad '+' key (for addition). */ 436 | public static final int KEYCODE_NUMPAD_ADD = 157; 437 | /** 438 | * Key code constant: Numeric keypad '.' key (for decimals or digit grouping). 439 | */ 440 | public static final int KEYCODE_NUMPAD_DOT = 158; 441 | /** 442 | * Key code constant: Numeric keypad ',' key (for decimals or digit grouping). 443 | */ 444 | public static final int KEYCODE_NUMPAD_COMMA = 159; 445 | /** Key code constant: Numeric keypad Enter key. */ 446 | public static final int KEYCODE_NUMPAD_ENTER = 160; 447 | /** Key code constant: Numeric keypad '=' key. */ 448 | public static final int KEYCODE_NUMPAD_EQUALS = 161; 449 | /** Key code constant: Numeric keypad '(' key. */ 450 | public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162; 451 | /** Key code constant: Numeric keypad ')' key. */ 452 | public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; 453 | /** 454 | * Key code constant: Volume Mute key. Mutes the speaker, unlike {@link #KEYCODE_MUTE}. This key 455 | * should normally be implemented as a toggle such that the first press mutes the speaker and the 456 | * second press restores the original volume. 457 | */ 458 | public static final int KEYCODE_VOLUME_MUTE = 164; 459 | /** 460 | * Key code constant: Info key. Common on TV remotes to show additional information related to 461 | * what is currently being viewed. 462 | */ 463 | public static final int KEYCODE_INFO = 165; 464 | /** 465 | * Key code constant: Channel up key. On TV remotes, increments the television channel. 466 | */ 467 | public static final int KEYCODE_CHANNEL_UP = 166; 468 | /** 469 | * Key code constant: Channel down key. On TV remotes, decrements the television channel. 470 | */ 471 | public static final int KEYCODE_CHANNEL_DOWN = 167; 472 | /** Key code constant: Zoom in key. */ 473 | public static final int KEYCODE_ZOOM_IN = 168; 474 | /** Key code constant: Zoom out key. */ 475 | public static final int KEYCODE_ZOOM_OUT = 169; 476 | /** 477 | * Key code constant: TV key. On TV remotes, switches to viewing live TV. 478 | */ 479 | public static final int KEYCODE_TV = 170; 480 | /** 481 | * Key code constant: Window key. On TV remotes, toggles picture-in-picture mode or other 482 | * windowing functions. 483 | */ 484 | public static final int KEYCODE_WINDOW = 171; 485 | /** 486 | * Key code constant: Guide key. On TV remotes, shows a programming guide. 487 | */ 488 | public static final int KEYCODE_GUIDE = 172; 489 | /** 490 | * Key code constant: DVR key. On some TV remotes, switches to a DVR mode for recorded shows. 491 | */ 492 | public static final int KEYCODE_DVR = 173; 493 | /** 494 | * Key code constant: Bookmark key. On some TV remotes, bookmarks content or web pages. 495 | */ 496 | public static final int KEYCODE_BOOKMARK = 174; 497 | /** 498 | * Key code constant: Toggle captions key. Switches the mode for closed-captioning text, for 499 | * example during television shows. 500 | */ 501 | public static final int KEYCODE_CAPTIONS = 175; 502 | /** 503 | * Key code constant: Settings key. Starts the system settings activity. 504 | */ 505 | public static final int KEYCODE_SETTINGS = 176; 506 | /** 507 | * Key code constant: TV power key. On TV remotes, toggles the power on a television screen. 508 | */ 509 | public static final int KEYCODE_TV_POWER = 177; 510 | /** 511 | * Key code constant: TV input key. On TV remotes, switches the input on a television screen. 512 | */ 513 | public static final int KEYCODE_TV_INPUT = 178; 514 | /** 515 | * Key code constant: Set-top-box power key. On TV remotes, toggles the power on an external 516 | * Set-top-box. 517 | */ 518 | public static final int KEYCODE_STB_POWER = 179; 519 | /** 520 | * Key code constant: Set-top-box input key. On TV remotes, switches the input mode on an external 521 | * Set-top-box. 522 | */ 523 | public static final int KEYCODE_STB_INPUT = 180; 524 | /** 525 | * Key code constant: A/V Receiver power key. On TV remotes, toggles the power on an external A/V 526 | * Receiver. 527 | */ 528 | public static final int KEYCODE_AVR_POWER = 181; 529 | /** 530 | * Key code constant: A/V Receiver input key. On TV remotes, switches the input mode on an 531 | * external A/V Receiver. 532 | */ 533 | public static final int KEYCODE_AVR_INPUT = 182; 534 | /** 535 | * Key code constant: Red "programmable" key. On TV remotes, acts as a contextual/programmable 536 | * key. 537 | */ 538 | public static final int KEYCODE_PROG_RED = 183; 539 | /** 540 | * Key code constant: Green "programmable" key. On TV remotes, actsas a contextual/programmable 541 | * key. 542 | */ 543 | public static final int KEYCODE_PROG_GREEN = 184; 544 | /** 545 | * Key code constant: Yellow "programmable" key. On TV remotes, acts as a contextual/programmable 546 | * key. 547 | */ 548 | public static final int KEYCODE_PROG_YELLOW = 185; 549 | /** 550 | * Key code constant: Blue "programmable" key. On TV remotes, acts as a contextual/programmable 551 | * key. 552 | */ 553 | public static final int KEYCODE_PROG_BLUE = 186; 554 | /** 555 | * Key code constant: App switch key. Should bring up the application switcher dialog. 556 | */ 557 | public static final int KEYCODE_APP_SWITCH = 187; 558 | /** Key code constant: Generic Game Pad Button #1. */ 559 | public static final int KEYCODE_BUTTON_1 = 188; 560 | /** Key code constant: Generic Game Pad Button #2. */ 561 | public static final int KEYCODE_BUTTON_2 = 189; 562 | /** Key code constant: Generic Game Pad Button #3. */ 563 | public static final int KEYCODE_BUTTON_3 = 190; 564 | /** Key code constant: Generic Game Pad Button #4. */ 565 | public static final int KEYCODE_BUTTON_4 = 191; 566 | /** Key code constant: Generic Game Pad Button #5. */ 567 | public static final int KEYCODE_BUTTON_5 = 192; 568 | /** Key code constant: Generic Game Pad Button #6. */ 569 | public static final int KEYCODE_BUTTON_6 = 193; 570 | /** Key code constant: Generic Game Pad Button #7. */ 571 | public static final int KEYCODE_BUTTON_7 = 194; 572 | /** Key code constant: Generic Game Pad Button #8. */ 573 | public static final int KEYCODE_BUTTON_8 = 195; 574 | /** Key code constant: Generic Game Pad Button #9. */ 575 | public static final int KEYCODE_BUTTON_9 = 196; 576 | /** Key code constant: Generic Game Pad Button #10. */ 577 | public static final int KEYCODE_BUTTON_10 = 197; 578 | /** Key code constant: Generic Game Pad Button #11. */ 579 | public static final int KEYCODE_BUTTON_11 = 198; 580 | /** Key code constant: Generic Game Pad Button #12. */ 581 | public static final int KEYCODE_BUTTON_12 = 199; 582 | /** Key code constant: Generic Game Pad Button #13. */ 583 | public static final int KEYCODE_BUTTON_13 = 200; 584 | /** Key code constant: Generic Game Pad Button #14. */ 585 | public static final int KEYCODE_BUTTON_14 = 201; 586 | /** Key code constant: Generic Game Pad Button #15. */ 587 | public static final int KEYCODE_BUTTON_15 = 202; 588 | /** Key code constant: Generic Game Pad Button #16. */ 589 | public static final int KEYCODE_BUTTON_16 = 203; 590 | /** 591 | * Key code constant: Language Switch key. Toggles the current input language such as switching 592 | * between English and Japanese on a QWERTY keyboard. On some devices, the same function may be 593 | * performed by pressing Shift+Spacebar. 594 | */ 595 | public static final int KEYCODE_LANGUAGE_SWITCH = 204; 596 | /** 597 | * Key code constant: Manner Mode key. Toggles silent or vibrate mode on and off to make the 598 | * device behave more politely in certain settings such as on a crowded train. On some devices, 599 | * the key may only operate when long-pressed. 600 | */ 601 | public static final int KEYCODE_MANNER_MODE = 205; 602 | /** 603 | * Key code constant: 3D Mode key. Toggles the display between 2D and 3D mode. 604 | */ 605 | public static final int KEYCODE_3D_MODE = 206; 606 | /** 607 | * Key code constant: Contacts special function key. Used to launch an address book application. 608 | */ 609 | public static final int KEYCODE_CONTACTS = 207; 610 | /** 611 | * Key code constant: Calendar special function key. Used to launch a calendar application. 612 | */ 613 | public static final int KEYCODE_CALENDAR = 208; 614 | /** 615 | * Key code constant: Music special function key. Used to launch a music player application. 616 | */ 617 | public static final int KEYCODE_MUSIC = 209; 618 | /** 619 | * Key code constant: Calculator special function key. Used to launch a calculator application. 620 | */ 621 | public static final int KEYCODE_CALCULATOR = 210; 622 | } 623 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/devices/AndroidDeviceProperties.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.devices; 2 | 3 | import java.util.Map; 4 | 5 | import com.google.common.collect.ImmutableMap; 6 | 7 | /** 8 | * Android has a set of properties about the device that can be read using the getprop command line 9 | * utility. This class is a place to collect theses properties. 10 | */ 11 | public class AndroidDeviceProperties { 12 | 13 | public static final Map DALVIK_VM = ImmutableMap.builder() 14 | .put("Heap Growth Limit", "dalvik.vm.heapgrowthlimit") 15 | .put("Heap Max Free", "dalvik.vm.heapmaxfree").put("Heap Min Free", "dalvik.vm.heapminfree") 16 | .put("Heap Size", "dalvik.vm.heapsize").put("Heap Start Size", "dalvik.vm.heapstartsize") 17 | .put("Stack Trace File", "dalvik.vm.stack-trace-file").build(); 18 | 19 | public static final Map DHCP = 20 | ImmutableMap.builder().put("DNS 1", "dhcp.wlan0.dns1") 21 | .put("DNS 2", "dhcp.wlan0.dns2").put("DNS 3", "dhcp.wlan0.dns3") 22 | .put("DNS 4", "dhcp.wlan0.dns4").put("Domain", "dhcp.wlan0.domain") 23 | .put("IP Address", "dhcp.wlan0.ipaddress").put("Mask", "dhcp.wlan0.mask") 24 | .put("Roaming", "dhcp.wlan0.roaming").put("Server", "dhcp.wlan0.server") 25 | .put("Gateway", "dhcp.wlan0.gateway").put("Vendor Info", "dhcp.wlan0.vendorInfo").build(); 26 | 27 | public static final Map GSM = ImmutableMap.builder() 28 | .put("STK Setup Menu", "gsm.STK_SETUP_MENU").put("Phone Type", "[gsm.current.phone-type") 29 | .put("Default PDP Context", "gsm.defaultpdpcontext.active") 30 | .put("Network Type", "gsm.network.type").put("Operator Alpha", "gsm.operator.alpha") 31 | .put("ISO-Country", "gsm.operator.iso-country") 32 | .put("ISPS Roaming", "gsm.operator.ispsroaming").put("Is Roaming?", "gsm.operator.isroaming") 33 | .put("SIM State", "gsm.sim.state").put("Version Baseband", "gsm.version.baseband").build(); 34 | 35 | public static final Map NET = 36 | ImmutableMap.builder().put("BT Name", "net.bt.name") 37 | .put("Change", "net.change").put("DNS 1", "net.dns1").put("DNS 2", "net.dns2").build(); 38 | 39 | public static final Map PRODUCT = ImmutableMap.builder() 40 | .put("Board", "ro.product.board").put("Brand", "ro.product.brand") 41 | .put("CPU ABI2", "ro.product.cpu.abi2").put("CPU ABI", "ro.product.cpu.abi") 42 | .put("Device", "ro.product.device").put("Locale Language", "ro.product.locale.language") 43 | .put("Locale Region", "ro.product.locale.region") 44 | .put("Manufacturer", "ro.product.manufacturer").put("Model", "ro.product.model") 45 | .put("Name", "ro.product.name").put("Ship", "ro.product_ship").build(); 46 | 47 | public static final Map WIFI = 48 | ImmutableMap.builder().put("Interface", "wifi.interface") 49 | .put("Driver Status", "wlan.driver.status").put("WFD Status", "wlan.wfd.status").build(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/exceptions/CommandsException.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.exceptions; 2 | 3 | public class CommandsException extends Exception { 4 | 5 | private static final long serialVersionUID = -1822933755277157084L; 6 | 7 | public CommandsException(final String message) { 8 | super(message.replace("\n", "")); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/misc/Utils.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.misc; 2 | 3 | import static com.mobilebox.repl.commands.CommandsDoc.SEPARATOR; 4 | import com.cedarsoftware.util.io.JsonWriter; 5 | 6 | import java.io.IOException; 7 | import java.io.StringWriter; 8 | 9 | import org.dom4j.Document; 10 | import org.dom4j.DocumentException; 11 | import org.dom4j.DocumentHelper; 12 | import org.dom4j.io.OutputFormat; 13 | import org.dom4j.io.XMLWriter; 14 | 15 | 16 | public class Utils { 17 | 18 | /** 19 | * Pretty-Print XML. 20 | * 21 | * @param xml The XML 22 | * @return A beautiful XML 23 | * @throws IOException 24 | * @throws DocumentException 25 | */ 26 | public static String prettyXML(final String xml) throws IOException, DocumentException { 27 | Document doc = DocumentHelper.parseText(xml); 28 | StringWriter sw = new StringWriter(); 29 | OutputFormat format = OutputFormat.createPrettyPrint(); 30 | format.setIndent(true); 31 | format.setIndentSize(3); 32 | XMLWriter xw = new XMLWriter(sw, format); 33 | xw.write(doc); 34 | return sw.toString(); 35 | } 36 | 37 | /** 38 | * Pretty-Print JSON. 39 | * 40 | * @param json The JSON 41 | * @return A beautiful JSON 42 | */ 43 | public static String prettyJson(final Object json) { 44 | return JsonWriter.formatJson(json.toString()); 45 | } 46 | 47 | /** 48 | * Prints a String on console and then terminate the line. 49 | * 50 | * @param msg The object to be printed. 51 | */ 52 | public static void console(final Object msg) { 53 | System.out.println(msg.toString()); 54 | } 55 | 56 | public static void consoleTitle(final String msg) { 57 | System.out.println("------------------------------------"); 58 | System.out.println("::- " + msg + " -::"); 59 | System.out.println("------------------------------------" + SEPARATOR); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/mobilebox/repl/selectors/UISelector.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.selectors; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.openqa.selenium.By; 6 | 7 | import io.appium.java_client.MobileBy; 8 | 9 | /** 10 | * A very simple Android UISelector Expression Builder. Builds some of the most common UISelector 11 | * expressions. See 12 | * UiSelector 13 | */ 14 | public abstract class UISelector { 15 | 16 | /** 17 | * The UISelector String constructor. 18 | */ 19 | private static final String UISELECTOR_CONSTRUCTOR = "new UiSelector().%s"; 20 | 21 | /** 22 | * Set the search criteria to match the class property for a widget (for example, 23 | * "android.widget.Button"). 24 | * 25 | * @param className The class name 26 | * 27 | * @return the UISelector expression. E.g: new UISelector.className("android.widget.Button"). 28 | */ 29 | public static By className(final String className) { 30 | return MobileBy 31 | .AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, "className(\"" + className + "\")")); 32 | } 33 | 34 | /** 35 | * Set the search criteria to match the given resource ID. 36 | * 37 | * @param id Value to match 38 | * 39 | * @return the UISelector expression. E.g: new UISelector.resourceId("android:id/list") 40 | */ 41 | public static By resourceId(final String id) { 42 | return MobileBy 43 | .AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, "resourceId(\"" + id + "\")")); 44 | } 45 | 46 | /** 47 | * Set the search criteria to match the content-description property for a widget. The 48 | * content-description is typically used by the Android Accessibility framework to provide an 49 | * audio prompt for the widget when the widget is selected. The content-description for the widget 50 | * must match exactly with the string in your input argument. Matching is case-sensitive. 51 | * 52 | * @param desc Value to match 53 | * 54 | * @return the UISelector expression. E.g: new UISelector.description("my description") 55 | */ 56 | public static By description(final String desc) { 57 | return MobileBy 58 | .AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, "description(\"" + desc + "\")")); 59 | } 60 | 61 | /** 62 | * Set the search criteria to match the content-description property for a widget. The 63 | * content-description is typically used by the Android Accessibility framework to provide an 64 | * audio prompt for the widget when the widget is selected. The content-description for the widget 65 | * must contain the string in your input argument. Matching is case-insensitive. 66 | * 67 | * @param desc Value to match 68 | * 69 | * @return the UISelector expression. E.g: new UISelector.descriptionContains("my description") 70 | */ 71 | public static By descriptionContains(final String desc) { 72 | return MobileBy.AndroidUIAutomator( 73 | format(UISELECTOR_CONSTRUCTOR, "descriptionContains(\"" + desc + "\")")); 74 | } 75 | 76 | /** 77 | * Set the search criteria to match the visible text displayed in a widget (for example, the text 78 | * label to launch an app). The text for the element must match exactly with the string in your 79 | * input argument. Matching is case-sensitive. 80 | * 81 | * @param text Value to match 82 | * 83 | * @return the UISelector expression. E.g: new UISelector.text("my text") 84 | */ 85 | public static By text(final String text) { 86 | return MobileBy.AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, "text(\"" + text + "\")")); 87 | } 88 | 89 | /** 90 | * Set the search criteria to match the visible text in a widget where the visible text must 91 | * contain the string in your input argument. The matching is case-sensitive. 92 | * 93 | * @param text Value to match 94 | * 95 | * @return the UISelector expression. E.g: new UISelector.textContains("my text") 96 | */ 97 | public static By textContains(final String text) { 98 | return MobileBy 99 | .AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, "textContains(\"" + text + "\")")); 100 | } 101 | 102 | /** 103 | * Chaining the search criteria on "new UiSelector()." Useful for to distinguish similar elements 104 | * based in the hierarchies they're in. 105 | * 106 | * @param locator Value to match 107 | * @return the UISelector expression. E.g: new 108 | * UiSelector().className("android.widget.RelativeLayout" ).enabled(true).instance(0); 109 | */ 110 | public static By selectorChaining(final String locator) { 111 | return MobileBy.AndroidUIAutomator(format(UISELECTOR_CONSTRUCTOR, locator)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/mobilebox/repl/AppiumTest.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | import org.testng.annotations.AfterClass; 7 | import org.testng.annotations.BeforeClass; 8 | import org.testng.annotations.Test; 9 | 10 | import com.mobilebox.repl.Appium; 11 | 12 | import static org.assertj.core.api.Assertions.*; 13 | 14 | public class AppiumTest { 15 | 16 | private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); 17 | 18 | @BeforeClass 19 | public void setUpStreams() { 20 | System.setOut(new PrintStream(outContent)); 21 | } 22 | 23 | @AfterClass 24 | public void cleanUpStreams() { 25 | System.setOut(null); 26 | } 27 | 28 | @Test 29 | public void commandsAppiumTest() { 30 | Appium.appium(); 31 | assertThat(outContent.toString()).contains("Available Commands for Appium"); 32 | } 33 | 34 | @Test 35 | public void commandsAndroidTest() { 36 | Appium.android(); 37 | assertThat(outContent.toString()).contains("Available Commands for Android"); 38 | } 39 | 40 | @Test 41 | public void commandsAndroidDeviceTest() { 42 | Appium.android_device(); 43 | assertThat(outContent.toString()).contains("Available Commands for AndroidDevice"); 44 | } 45 | 46 | @Test 47 | public void commandsIOSTest() { 48 | Appium.ios(); 49 | assertThat(outContent.toString()).contains("Available Commands for IOS"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/mobilebox/repl/commands/CommandsDocTest.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.PrintStream; 7 | 8 | import org.testng.annotations.AfterClass; 9 | import org.testng.annotations.BeforeClass; 10 | import org.testng.annotations.Test; 11 | 12 | import com.mobilebox.repl.commands.CommandsDoc; 13 | 14 | public class CommandsDocTest { 15 | 16 | private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); 17 | 18 | @BeforeClass 19 | public void setUpStreams() { 20 | System.setOut(new PrintStream(outContent)); 21 | } 22 | 23 | @AfterClass 24 | public void cleanUpStreams() { 25 | System.setOut(null); 26 | } 27 | 28 | @Test 29 | public void printCommands() { 30 | String expected = 31 | "------------------------------------\n::- Available Commands for DummyCommand -::\n------------------------------------\n---> Command: command\n---> Description: Command three\n---> Parameters: [param1 - description one}]\n---> Return: void\n"; 32 | CommandsDoc.printCommands(DummyCommand.class); 33 | assertThat(outContent.toString()).isEqualToIgnoringWhitespace(expected); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/mobilebox/repl/commands/DummyCommand.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.commands; 2 | 3 | import com.mobilebox.repl.commands.CommandRef; 4 | 5 | 6 | public class DummyCommand { 7 | 8 | @CommandRef(desc = "Command three", params = {"param1 - description one}"}, ret = "void") 9 | public void command() {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/mobilebox/repl/misc/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.misc; 2 | 3 | import java.io.IOException; 4 | 5 | import org.dom4j.DocumentException; 6 | import org.testng.annotations.Test; 7 | 8 | import com.mobilebox.repl.misc.Utils; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | public class UtilsTest { 13 | 14 | @Test 15 | public void prettyXMLTest() throws IOException, DocumentException { 16 | String xml = 17 | "IreneoFunes"; 18 | String expected = 19 | "\n\n\n \n Ireneo\n Funes\n \n\n"; 20 | assertThat(Utils.prettyXML(xml)).isEqualToIgnoringCase(expected); 21 | } 22 | 23 | @Test 24 | public void prettyXMLTestShouldBeTrowDocumentException() { 25 | String xml = "FrayBentosFunes"; 26 | assertThatThrownBy(() -> { 27 | Utils.prettyXML(xml); 28 | }).isInstanceOf(DocumentException.class).hasMessageContaining("Error on line 1 of document"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/mobilebox/repl/selectors/UISelectorTest.java: -------------------------------------------------------------------------------- 1 | package com.mobilebox.repl.selectors; 2 | 3 | import org.openqa.selenium.By; 4 | import org.testng.annotations.Test; 5 | 6 | import com.mobilebox.repl.selectors.UISelector; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | public class UISelectorTest { 11 | 12 | @Test 13 | public void classNameTest() { 14 | String expected = "className(\"my.classname\")"; 15 | By locator = UISelector.className("my.classname"); 16 | assertThatByIsEqualTo(locator.toString(), expected); 17 | } 18 | 19 | @Test 20 | public void descriptionTest() { 21 | String expected = "description(\"desc\")"; 22 | By locator = UISelector.description("desc"); 23 | assertThatByIsEqualTo(locator.toString(), expected); 24 | } 25 | 26 | @Test 27 | public void descriptionContainsTest() { 28 | String expected = "descriptionContains(\"desc\")"; 29 | By locator = UISelector.descriptionContains("desc"); 30 | assertThatByIsEqualTo(locator.toString(), expected); 31 | } 32 | 33 | @Test 34 | public void resourceIdTest() { 35 | String expected = "resourceId(\"id\")"; 36 | By locator = UISelector.resourceId("id"); 37 | assertThatByIsEqualTo(locator.toString(), expected); 38 | } 39 | 40 | @Test 41 | public void selectorChainingTest() { 42 | String expected = "resourceId(\"id\").enabled(true).instance(0)"; 43 | By locator = UISelector.selectorChaining(expected); 44 | assertThatByIsEqualTo(locator.toString(), expected); 45 | } 46 | 47 | @Test 48 | public void textTest() { 49 | String expected = "text(\"text\")"; 50 | By locator = UISelector.text("text"); 51 | assertThatByIsEqualTo(locator.toString(), expected); 52 | } 53 | 54 | @Test 55 | public void textContainsTest() { 56 | String expected = "textContains(\"text\")"; 57 | By locator = UISelector.textContains("text"); 58 | assertThatByIsEqualTo(locator.toString(), expected); 59 | } 60 | 61 | private void assertThatByIsEqualTo(final String actual, final String expected) { 62 | String msg = "By.AndroidUIAutomator: new UiSelector()."; 63 | assertThat(actual).isEqualToIgnoringCase(msg + expected); 64 | } 65 | } 66 | --------------------------------------------------------------------------------