Android平台Robotium UI测试详解
Robotium 框架工作原理及实践
Robotium 是什么
一款面向Android端的开源自动化测试框架,,Robotium是基于Instrumentation的测试框架,器测试用例的编写框架是基于Junit的
- 优势
- 同时支持Native应用和Hybrid(混合)
- 支持黑盒测试和白盒
- 基于Instrumentation,测试用例执行速度快
- 运行时识别的是控件,测试用例更健壮
- 可以通过Maven,Gradle或Ant运行
- 可以没有项目代码,只有APK文件
- 可以截图
- 缺点
- 无法跨应用
- 代码运行在被测进程,会影响性能,无法同时监控性能
Robotium 的Native使用
关于Eclipse的使用在Robotium的GitHub主页中可以找到文档,以下均是基于AndroidStudio的
要完成对手机的模拟操作,应包含以下几个基本操作:
1. 需要知道所要控件的坐标
2. 对要操作的控件进行模拟操作
3. 判断操作完成后的结果是否符合预期
1.使用 Robotium 准备工作
在需要测试modle中添加依赖
androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.6.3'
AndroidStudio 在创建项目的时候,会默认的创建出androidTest路径,如果没有的话,需要在想要测试的module中的src文件夹下创建一个androidTest/java的包,然后配置module的build.gradle来指向它
android { sourceSets { androidTest { java.srcDirs = ['androidTest/java'] } } }
- 然后就可以写测试代码了,在java中建一个和项目根包名相同的包,我们带代码都放到这里面
2.简单的例子
写一个简单的求和功能
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.lanou.chenfengyao.robotiumdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/num1_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="a"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"/>
<EditText
android:id="@+id/num2_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="b"/>
<TextView
android:id="@+id/result_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="=结果"/>
</LinearLayout>
<Button
android:id="@+id/get_result_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="求和"/>
</LinearLayout>
MainActivity代码
package com.lanou.chenfengyao.robotiumdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private EditText num1Et, num2Et;
private Button getResultBtn;
private TextView resultTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
num1Et = (EditText) findViewById(R.id.num1_et);
num2Et = (EditText) findViewById(R.id.num2_et);
getResultBtn = (Button) findViewById(R.id.get_result_btn);
resultTv = (TextView) findViewById(R.id.result_tv);
getResultBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int a = Integer.valueOf(num1Et.getText().toString());
int b = Integer.valueOf(num2Et.getText().toString());
//故意写错了
resultTv.setText("= " + a + b);
}
});
}
}
可以看到点击按钮的时候就会求和,并写回TextView上,可以看到我们的求和结果是故意写错了,直接这么写会是字符串的拼接而不是求和的,我们会通过测试代码来找到这个错误
测试代码-1
在androidTest/java/包名下新建一个Java class,开始写我们的测试代码,测试代码类需要继承ActivityInstrumentationTestCase2,这是测试单独Activity时需要继承的泛型填写我们需要测试的Activity类名,接下来是代码
import android.test.ActivityInstrumentationTestCase2;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.robotium.solo.Solo;
/**
* If there is no bug, then it is created by ChenFengYao on 2017/3/3,
* otherwise, I do not know who create it either.
*/
public class TestFirst extends ActivityInstrumentationTestCase2<MainActivity> {
private Solo solo;
public TestFirst() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation());
getActivity();
}
public void testRun() {
EditText num1Et = (EditText) solo.getView(R.id.num1_et);
EditText num2Et = (EditText) solo.getView(R.id.num2_et);
Button resultBtn = (Button) solo.getView(R.id.get_result_btn);
TextView resultTv = (TextView) solo.getView(R.id.result_tv);
solo.enterText(num1Et,"1");
solo.enterText(num2Et,"2");
solo.clickOnView(resultBtn);
solo.sleep(200);
assertEquals("= 3",resultTv.getText().toString());
}
}
这段代码我们复写了setUp方法,在setUp方法里我们初始化了Robotium中最重要的对象,Solo对象,几乎所有对于UI的操作都是针对这个Solo对象的,然后通过调用getActivity来启动MainActivity
下面 是testRun方法,这个方法并不是重写出来的,而是就写成这个名字,写成testXX的都能被识别,在testRun里,通过solo.getView方法来获得界面中我们所有需要的View元素,然后通过solo.enterText对EditText进行赋值,接下来调用button的点击事件,值得注意的是,测试代码并 不是运行在主线程中 的,所以我们没有办法直接操作view元素来进行更改ui,也正因为这样,我们在点击过后让solo等待一下,方便让点击事件生效,最后开始断言,textview中的文字是否是我们期望的文字,如果是的话,就证明我们的程序是没有问题的.
点击类名左边的绿色箭头就可以直接运行,也可以在运行项目那选择测试用例点击运行按钮
可以看到程序运行后自动的填写了数组,并点击了确定,AndroidStudio的控制台也自动变成到了测试的界面,而测试页面也出现了错误日志
第一句就告诉了我们 我们期待的结果是 = 3,而 出现的结果是 = 12,我们把程序代码改正再来运行一下
这回出现了我们期待的结果,测试就通过了
测试代码-2
Android也提供了通过注解的形式来运行测试代码,要比之前的形式更优雅一点
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.robotium.solo.Solo;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
/**
* If there is no bug, then it is created by ChenFengYao on 2017/3/3,
* othrwise, I do not know who create it either.
*/
@RunWith(AndroidJUnit4.class)
public class TestFirst{
private Solo solo;
@Rule
public ActivityTestRule<MainActivity> activityTestRule =
new ActivityTestRule<>(MainActivity.class);
@Before
public void setUp() throws Exception {
solo = new Solo(InstrumentationRegistry.getInstrumentation(),
activityTestRule.getActivity());
}
@Test
public void testRun() {
EditText num1Et = (EditText) solo.getView(R.id.num1_et);
EditText num2Et = (EditText) solo.getView(R.id.num2_et);
Button resultBtn = (Button) solo.getView(R.id.get_result_btn);
TextView resultTv = (TextView) solo.getView(R.id.result_tv);
solo.enterText(num1Et,"1");
solo.enterText(num2Et,"2");
solo.clickOnView(resultBtn);
solo.sleep(200);
assertEquals("= 3",resultTv.getText().toString());
}
@After
public void tearDown() throws Exception {
solo.finishOpenedActivities();
}
}
效果与之前的测试代码是一样的,就不额外演示了,与之前的代码不同,我们想要获得Activity对象则需要通过@Rule注解来获得,@Before和@After都是junit框架提供的,代表测试代码运行前和运行后,而测试方法则需要是用@Test注解进行修饰
Robotium的Native使用
控件的获取
Robotium中获取控件主要有两大方式:
- 根据被测应用的控件ID来获取
- 先获取当前页面所有的控件,对这些控件进行过滤封装后再提供相应获取控件的API
1. 根据控件的ID来获取
EditText num1Et = (EditText) solo.getView("num1_et");
EditText num2Et = (EditText) solo.getView(R.id.num2_et);
在Android中,所有的控件都会继承View,所以该方法能获取项目中所有的View,如果被测试的应用中控件有唯一ID的话,是一定能通过这种形式来获取所要操作的控件的,获取View之后再向下转型成我们需要的类型即可, 当View拥有唯一ID的时候,优先使用这种方式
2. 根据控件类型的索引,文本来获取
根据索引获取
Robotium会先将当前界面中所有控件全部获取,然后按控件类型,索引进行过滤后获取指定的控件//获取页面中第一个类型是Button的控件 Button button = (Button) solo.getButton(0);
而基本上常用的控件Robotium都会提供这样的API,例如EditText,TextView等都是这样,但是如果是自定义组件就不行了
根据文本获取控件
//返回界面中text属性是"登陆"的Button Button loginBtn = (Button) solo.getButton("登陆");
这种方法则局限性更大,只能找到具有text属性的一些控件
根据控件类型获取控件
Robotium可以获取 页面中/指定View下 所有的控件,或同一类型的控件//获取当前页面或dialog中所有的控件 ArrayList<View> getCurrentViews(); //获取当前页面或Dialog中所有类型为classToFilterBy的控件 ArrayList<T> getCurrentViews(Class<T> classToFilterBy); //获取父控件parent下所有控件类型为classToFilterBy的控件 ArrayList<T> getCurrentViews(class<T> classToFilterBy, View parent);
获取相同id的控件
在遇到ListView这种组件的时候,要想对item内部的View进行操作会发现这些View无论是Id还是文本还是类型都是一致的,而ListView本身是可以很方便的获得这些item的,最简单的可以通过listView.getChildAt(index)的形式,而在获取到Item之后,我们又可以通过调用findViewById来获取该Item下的组件了//先获取ListView ListView listView = (ListView) solo.getView("main_lv"); //获取指定的item View convertView = listView.getChildAt(0); //从指定item中获取指定view TextView itemTv = (TextView) listView.findViewById(R.id.item_tv);
这种通过findViewById的方式来获取控件,也适用于从Activity等页面中来获取指定控件.
注意:
- 当没找到想要的控件时,Robotium就会抛出运行时异常,我们可以手动捕获该异常来防止测试代码崩溃
- 尽量不适用通过索引的方式获得控件,这种方式过于依赖页面的布局结构
控件操作
对Android端控件的操作大概有点击,长按,文本输入,滑动,滚动,截图等,而为了页面真实性,有时还需要加入等待等操作
注意:
- Robotium 对控件的操作是不能够跨进程的,所以它是不能够点击到例如通知栏等区域,如果编写了这样的代码,程序是会崩溃的
- 在调试时建议打开 “指针位置” 功能,这样在编写测试代码的时候,就可以知道点击的位置坐标了
1. 点击,长按
点击和长按操作可以是找到组件然后点击该组件,而如果是自定义View时根据位置去处理手势的话,还可以根据指定的坐标去点击或者长按
//点击/长按指定的View控件
void clickOnView(View view);
void clickLongOnView(View view);
//点击/长按指定的屏幕坐标
void clickOnScreen(float x, float y);
void clickLongOnScreen(float x, float y);
Robotium 还提供了点击文本,图片的api,例如 clickOnButton(String text),它会先根据text找到Button,然后再去点击这个Button,目前Robotium也提供了点击RecyclerView的item的方法clickInRecyclerView(int itemIndex)这样的方法,来方便对ListView,RecyclerView进行操作
2. 输入操作
//在指定EditText中输入text
void enterText(EditText editText, String text);
//在指定EditText中键入文本
void typeText(EditText editText, String text);
//清空指定的输入框
void clearEditText(EditText editText);
clearEditText比较好理解,而enterText和typeText的区别是enter是直接对EditText进行赋值,而type则是像用户一样的一个一个文本的输入
EditText editText = (EditText) solo.getView(R.id.main_et);
solo.enterText(editText, "editText");
3. 滑动,滚动操作
//从起始x,y坐标滑动至终点x,y坐标,通过stepCount参数指定滑动时的步长
void drag(float fromX, float toX, float fromY, float toY, int stepCount);
//滚动到顶部/底部
void scrollToTop();
void scrollToBottom();
//向上滚动屏幕/向下滚动屏幕
void scrollUp();
void scrollDown();
//滚动至ListView第line行
void scrollListToLine(AbsListView absListView, int line);
根据坐标法进行滑动的时候坐标不需要多说,而步长的意思是滑动的速度,例如从A点滑动到B点,如果步长为1,那么将直接产生从A到B的滑动手势,如果步长为100,则会将A到B之间均分成100份,然后依次滑动
滚动到顶部/底部的方法是将当前屏幕滚动到顶部或底部,什么能滚,滚什么,例如:如果是ListView就会直接滚动到ListView的顶部,如果是WebView就会滚动到WebView的顶部
向上滑/向下滑则也是根据当前屏幕控件来走的,基本上是滑动一屏的距离,而与drag不同的是,这些都是调用控件自身的api来进行滚动的,例如drag能触发下拉刷新等操作,但是scroll则不能,所以drag更贴近用户的操作
对新的RecyclerView,同样提供了Api,但是不能指定滑动到第几行,只有向上/向下滑动,和滚动到顶端/底端,并且也只能通过RecyclerView的索引来找到RecyclerView
boolean scrollUpRecyclerView(index); boolean scrollDownRecyclerView(index); boolean scrollRecyclerViewToTop(index); boolean scrollRecyclerViewToBotton(index);
4. 搜索与等待
UI自动化测试常常遇到的问题就是项目快速迭代导致界面经常变更,脚本经常出错,控件的搜索与等待则可以缓解这一问题
//休眠指定时间
void sleep(int time);
//从当前页面搜索指定文本
boolean searchText(String text);
//等待控件/文本的出现
boolean waitForView(int id);
boolean waitForText(String text);
//等待指定Activity的出现
boolean waitForActivity(String name);
//等待指定Log的出现
boolean waitForLogMessage(String logMessage);
//等待对话框的打开/关闭
boolean waitForDialogToOpen();
boolean waitForDialogToClose();
//等待某种加载条件的达成
boolean waitForCondition (Condition condition, int timeout);
//等待Fragment的加载
boolean waitForFragmentById (String id);
boolean waitForFragmentByTag (String tag);
大多数API都是比较好理解的,这个Condition是一个接口,里面只提供了一个isSatisfied()方法,该方法返回boolean类型的值,需要我们自己写的,我们可以设置一些复杂的条件来实现这个接口
solo.waitForCondition(new Condition() {
@Override
public boolean isSatisfied() {
return solo.isCheckBoxChecked(0);
}
},200);
而几乎所有的等待操作都可以设置超时时间,需要注意的是,Robotium中查找控件,点击控件等API都默认使用了搜索与等待机制,所以非必要的情况下一般是不需要我们主动设置等待的
5. 其他操作
剩下的一些操作包括截图,对软键盘的操作,点击物理按键等等
//截图,name为图片的参数,默认路径是/sdcard/Robotium-Screenshots/
void takeScreenshot();
void takeScreenshot(String name);
//截取某段时间内一个序列
void takeScreenshotSequence(String name);
//关闭当前已打开的所有Activity
void finishOpenedActivities();
//点击返回键
void goBack();
//不断点击返回键直至返回到指定Activity
void goBackToActivity(String name);
// 收起键盘
void hideSoftKeyboard();
// 设置Activity转屏方向
void setActivityOrientation(int orientation);
在自动化测试中,当执行失败是,除了Log,最方便解决定位问题的就是运行时的截图,Robotium中提供了棘突功能,
断言
在自动化测试中,我们获取控件,执行操作后,接下来就是要对操作后的场景进行断言了,断言就是我们在程序时的一些假设,就是我们断定在程序运行到某个时候,某个属性一定是多少,我们写程序的时候,大多数都是我们事先知道输入与输出的,断言就是来测试程序的输出与我们的输入是否相符
1.Junit中的断言
//断言传入的condition参数应该为True,否则抛出一个带有message提示的Throwable异常
void assertTrue(String message, boolean condition);
// 断言传入的condition 参数应该为False, 否则抛出message的异常
void assertFalse(String message, boolean condition);
//直接认为断言失败,抛出一个message异常
void fail(String message);
// 断言相等
void assertEquals(String message,Obj o1, Obj o2);
Junit中的断言都在AndroidSdk中的 junit.framework.Assert包下的Assert类中, 常用的只有第一个,在使用的时候应该明确说明message参数的作用,方便我们后期查看
Button loginBtn = (Button) solo.getView("loginBtn");
assertTrue("登录按钮应该显示", loginBtn.isShown());
2. Robotium中的断言
// 断言当前界面是否为name参数指定的Activity,若不是则抛出一个带有message提示的Throwable异常
void assertCurrentActivity(String message, String name);
//断言不处于低内存状态
void assertMemoryNotLow();
Robotium基于Junit中的断言,也封装了几个方便在Android端自动化的时候使用,这些方法都是sole对象的方法
3.Android中的断言
//断言view在屏幕中
void assertOnScreen(View origin, View view);
//断言两个View是否低端对齐
void assertBottomAligned(View first, View second);
这些断言都是Android提供的,可以方便的判断一些Android中的UI信息他们在android.test.ViewAsserts包下,但是目前(API level 24)这个类已经被标注成Deprecated了,目前Android提供了一个ViewMatchers的类来处理View的断言,而处理的思路和以前有了变化,核心方法是
static <T> void assertThat(String message T actual, Matcher<T> matcher);
几乎所有的判断用的都是这个方法,message 是抛出的异常信息,Matcher 是断言规则,之前的在屏幕上显示,底部对齐等都变成了一种断言规则,我们也可以写自己的规则,而T则是需要的View,这里使用的是泛型,也就是该断言框架可以不仅局限于用来断言View了 也可以用来断言任何东西,例如想要判读一个TextView是否显示某种文字
TextView textView = (TextView) solo.getView(R.id.main_tv);
ViewMatchers.assertThat("显示: Welcome",textView,ViewMatchers.withText("Welcome"));
Robotium 原理
1.获取控件
我们来看一看solo对象通过view的id来获取控件的方法
public View getView(int id) {
if(this.config.commandLogging) {
Log.d(this.config.commandLoggingTag, "getView(" + id + ")");
}
return this.getView(id, 0);
}
Log先不用看,这个方法最后又调用了getView(int id, int index)的方法,那么我们再来看一看这个方法
public View getView(int id, int index) {
if(this.config.commandLogging) {
Log.d(this.config.commandLoggingTag, "getView(" + id + ", " + index + ")");
}
View viewToReturn = this.getter.getView(id, index);
if(viewToReturn == null) {
String resourceName = "";
try {
resourceName = this.instrumentation.getTargetContext().getResources().getResourceEntryName(id);
} catch (Exception var6) {
Log.d(this.config.commandLoggingTag, "unable to get resource entry name for (" + id + ")");
}
int match = index + 1;
if(match > 1) {
Assert.fail(match + " Views with id: \'" + id + "\', resource name: \'" + resourceName + "\' are not found!");
} else {
Assert.fail("View with id: \'" + id + "\', resource name: \'" + resourceName + "\' is not found!");
}
}
return viewToReturn;
}
这段代码中重要的是View viewToReturn = this.getter.getView(id,index);这一句,其他的都是生成log,或者报错等,这个getter对象就是Robotium中专门用来获取View的类,那么再来看看这里面的方法
public View getView(int id, int index) {
return this.getView(id, index, 0);
}
public View getView(int id, int index, int timeout) {
return this.waiter.waitForView(id, index, timeout);
}
public View waitForView(int id, int index, int timeout) {
if(timeout == 0) {
timeout = Timeout.getSmallTimeout();
}
return this.waitForView(id, index, timeout, false);
}
在getView(int id, int index)方法里,又调用了 有timeout的方法重载,而在有timeout的方法重在里,又调用了waitForView,这样找到控件的方法,最后,就和等待的方法回合了,他们最后都调用了
waitForView(int id, int index, int timeout, boolean scroll)方法
一起来看一下这个方法
public View waitForView(int id, int index, int timeout, boolean scroll) {
HashSet uniqueViewsMatchingId = new HashSet();
long endTime = SystemClock.uptimeMillis() + (long)timeout;
while(SystemClock.uptimeMillis() <= endTime) {
this.sleeper.sleep();
Iterator i$ = this.viewFetcher.getAllViews(false).iterator();
while(i$.hasNext()) {
View view = (View)i$.next();
Integer idOfView = Integer.valueOf(view.getId());
if(idOfView.equals(Integer.valueOf(id))) {
uniqueViewsMatchingId.add(view);
if(uniqueViewsMatchingId.size() > index) {
return view;
}
}
}
if(scroll) {
this.scroller.scrollDown();
}
}
return null;
}
这段代码就是最后来找到View的代码了,首先会创建出一个HashSet来存放View,接着会通过迭代器,从ViewFetcher中获取所有的View,这里传入的boolean值得意思是是否只获取可见的View,去除所有View之后,拿到他们的ID,再与我们传入的id进行对比,如果匹配成功就返回我们的View.那么拿到View的关键就是Robotium怎么获取的我们所有的View,接着一路看源码下去
static {
try {
String e;
if(VERSION.SDK_INT >= 17) {
e = "android.view.WindowManagerGlobal";
} else {
e = "android.view.WindowManagerImpl";
}
windowManager = Class.forName(e);
} catch (ClassNotFoundException var1) {
throw new RuntimeException(var1);
} catch (SecurityException var2) {
var2.printStackTrace();
}
}
public ArrayList<View> getAllViews(boolean onlySufficientlyVisible) {
View[] views = this.getWindowDecorViews();
ArrayList allViews = new ArrayList();
View[] nonDecorViews = this.getNonDecorViews(views);
View view = null;
if(nonDecorViews != null) {
for(int ignored = 0; ignored < nonDecorViews.length; ++ignored) {
view = nonDecorViews[ignored];
try {
this.addChildren(allViews, (ViewGroup)view, onlySufficientlyVisible);
} catch (Exception var9) {
;
}
if(view != null) {
allViews.add(view);
}
}
}
if(views != null && views.length > 0) {
view = this.getRecentDecorView(views);
try {
this.addChildren(allViews, (ViewGroup)view, onlySufficientlyVisible);
} catch (Exception var8) {
;
}
if(view != null) {
allViews.add(view);
}
}
return allViews;
}
public View[] getWindowDecorViews() {
try {
Field viewsField = windowManager.getDeclaredField("mViews");
Field instanceField = windowManager.getDeclaredField(this.windowManagerString);
viewsField.setAccessible(true);
instanceField.setAccessible(true);
Object e = instanceField.get((Object)null);
View[] result;
if(VERSION.SDK_INT >= 19) {
result = (View[])((ArrayList)viewsField.get(e)).toArray(new View[0]);
} else {
result = (View[])((View[])viewsField.get(e));
}
return result;
} catch (Exception var5) {
var5.printStackTrace();
return null;
}
}
跟踪到最后发现,它通过反射拿到了一个叫WindowManagerGlobal的对象,并从这个对象中获得了顶级ViewDecorView,但是仔细观察它的反射方式,是直接通过类名构建了一个 新的WindowManagerGlobal对象 ,那么是如何从这个全新的WindowManagerGlobal中拿到DecorView的呢,只能从Android的源码入手分析了
在所有的Activity创建的过程中,我们知道都会有一个Window对象,当然真正的使用的是其子类PhoneWindow,PhoneWindow中会为Activity创建出一个DecorView对象,而DecorView是Activity的顶级View,在创建完成之后,还要通过WindowManager对象去将DecorView显示到屏幕上,这一步会调用WindowManager的makeVisible()方法
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
在这一步中,会调用WindowManagerImpl的addView方法,WindowManagerImpl就是WindowManager真正的实现类,
@Override
public void addView(View view, ViewGroup.LayoutParams params{
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
会将DecorView添加到一个叫WindowManagerGlobal的对象中,这个对象中有一个叫sDefaultWindowManager的静态的WindowManagerGlobal,所有的DecorView都会添加到这个sDefaultWindowManager的一个成员变量mViews中,它是一个专门用来存放所有的DecorView的ArrayList,同时又因为sDefaultWindowManager是静态的,所以在全局无论通过什么途径拿到它,拿到的都是同一个对象
2. 控件的操作
Robotium获取控件后,调用clickOnView(View view)方法就可以完成点击操作,这个方法可以分为两步实现
1. 根据View获取控件在屏幕中的位置
2. 根据坐标模拟点击事件
对WebView的支持
Robotium 在 4.0 开始 全面支持WebView,它支持通过ID,className等方式来获取WebElement元素
//获取当前WebView所有的WebElement元素
ArrayList<WebElement> getCurrentWebElements();
//通过By根据指定的元素获取当前WebView的所有WebElement元素
ArrayList<WebElement> getCurrentWebElements(By by);
//通过by根据指定的元素属性点击WebElement
void clickOnWebElement(By by);
//点击指定的WebElement
void clickOnWebElement(WebElement webElement);
//根据by找到WebElement,并输入指定的文本text
void enterTextInWebElement(By by, String text);
//等待根据by获得的WebElement出现
boolean waitForWebElement(By by);
By的其实就是当我们知道了Web元素的某种属性之后来通过该属性来获取控件的,例如
ArrayList<WebElement> webElements = solo.getCurrentWebElements();
ArrayList<WebElement> webElements = solo.getCurrentWebElements(By.id("example_id"));
在使用Robotium操作WebView的时候需要注意的是,它只支持原生的WebView而不支持第三方浏览器内核