Appium Appium 学习 总结 (C#)

Stepthen · 2015年09月11日 · 最后由 Stepthen 回复于 2017年03月22日 · 200 次阅读

Guide

最近看到测试群里小白们经常问的问题,在此写一下自己学习的一点小总结

1. 创建 session 时常用命令

DesiredCapabilities cap = new DesiredCapabilities();
cap.SetCapability("browserName", ""); // web 浏览器名称('Safari' ,'Chrome'等)。如果对应用进行自动化测试,这个关键字的值应为空。
cap.SetCapability("platformName", "Android");//你要测试的手机操作系统
cap.SetCapability("platformVersion", "4.4");//手机操作系统版本
cap.SetCapability("automationName", "selendroid");  //你想使用的自动化测试引擎:Appium (默认) 或 Selendroid
cap.SetCapability("deviceName", " Android Emulator"); //使用的手机类型或模拟器类型,真机时输入Android Emulator或者手机型号
cap.SetCapability("udid", udid); //连接的物理设备的唯一设备标识,Android可以不设置

cap.SetCapability("newCommandTimeout", "300");  //设置收到下一条命令的超时时间,超时appium会自动关闭session ,默认60秒
cap.SetCapability("unicodeKeyboard", "True");//支持中文输入,会自动安装Unicode 输入法。默认值为 false
cap.SetCapability("resetKeyboard", "True"); //在设定了 unicodeKeyboard 关键字的 Unicode 测试结束后,重置输入法到原有状态

cap.SetCapability("'app'", "D:\\AndroidAutomation\\AndroidAutoTest\\app\\zhongchou.apk");  //未安装应用时,设置app的路径

//手机已安装app,直接从手机启动app,上面路径不设置
cap.SetCapability("appPackage", "com.xxx");  //你要启动的Android 应用对应的Activity名称|比如`MainActivity`, `.Settings`|
cap.SetCapability("appActivity", "com.xxx.ui.ActivityShow");  //你想运行的Android应用的包名
cap.SetCapability("appWaitActivity", "com.xxx.ui.ActivityLogo");  //你想要等待启动的Android Activity名称|比如`SplashActivity`|

Uri serverUri = new Uri("http://127.0.0.1:4723/wd/hub");
driver = new AndroidDriver<IWebElement>(serverUri, cap, TimeSpan.FromSeconds(180));

更多详细查看官网:https://github.com/appium/appium/blob/master/docs/cn/writing-running-appium/caps.cn.md

2. driver 常用方法及注意事项

1) 常用方法:

driver.HideKeyboard();//隐藏键盘
driver.BackgroundApp(60);//60秒后把当前应用放到后台去
driver.LockDevice(3); //锁定屏幕

//在当前应用中打开一个 activity 或者启动一个新应用并打开一个 activity
driver.StartActivity("com.iwobanas.screenrecorder.pro", "com.iwobanas.screenrecorder.RecorderActivity");
driver.OpenNotifications();//打开下拉通知栏 只能在 Android 上使用
driver.IsAppInstalled("com.example.android.apis-");//检查应用是否已经安装
driver.InstallApp("path/to/my.apk");//安装应用到设备中去
driver.RemoveApp("com.example.android.apis");//从设备中删除一个应用
driver.ShakeDevice();//模拟设备摇晃
driver.CloseApp();//关闭应用
driver.LaunchApp();//根据服务关键字 (desired capabilities) 启动会话 (session) 。请注意这必须在设定 autoLaunch=false 关键字时才能生效。这不是用于启动指定的 app/activities
driver.ResetApp();//应用重置
driver.GetContexts();//列出所有的可用上下文
driver.GetContext();//列出当前上下文
driver.SetContext("name");//将上下文切换到默认上下文
driver.GetAppStrings();//获取应用的字符串
driver.KeyEvent(176);//给设备发送一个按键事件:keycode
driver.GetCurrentActivity();//获取当前 activity。只能在 Android 上使用
//driver.Pinch(25, 25);//捏屏幕 (双指往内移动来缩小屏幕)
//driver.Zoom(100, 200);//放大屏幕 (双指往外移动来放大屏幕)
driver.PullFile("Library/AddressBook/AddressBook.sqlitedb");//从设备中拉出文件
driver.PushFile("/data/local/tmp/file.txt", "some data for the file");//推送文件到设备中去

driver.FindElement(By.Name(""));
driver.FindElementById("id");
driver.FindElementByName("text");
driver.FindElementByXPath("//*[@name='62']");

2) 注意事项:
使用 driver.Sendkeys(string str) 向文本框输入内容前,最好先 element.Click( ) 一下,否则某些情况下,输入的内容会请不掉,文本框提示的内容也会在 输入的文本前显示出来。sendkey 方法在发送数据之前会清空一下文本框,一般不需要 Clear,如前面的情况 Clear 后仍是存在的,click 后正常

3. 等待页面加载策略

1) 显性等待:调用 selenium 的方法, 需要添加 WebDriver.Support 引用
显性等待是指在代码进行下一步操作之前等待某一个条件的发生。最不好的情况是使用 Thread.sleep() 去设置一段确认的时间去等待。但为什么说最不好呢?因为一个元素的加载时间有长有短,你在设置 sleep 的时间之前要自己把握长短,太短容易超时,太长浪费时间。selenium webdriver 提供了一些方法帮助我们等待正好需要等待的时间

 WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
 element = wait.Until<IWebElement>((d) =>
{
    return driver.FindElement(By.Id("userName"));     
});

2) 隐性等待:设置时间不易过长,设置为 500 或 1000 即可
隐性等待是指当要查找元素,而这个元素没有马上出现时,告诉 WebDriver 查询 Dom 一定时间。默认值是 0,但是设置之后,这个时间将在 WebDriver 对象实例整个生命周期都起作用。

driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(1));

4. drive.KeyEvent(int ) 的使用:

可使用 KeyEvent 发送键盘数据,比如退格,Enter 键等,注意:Androiddriver 使用 driver.PressKeyCode( int keycode)

driver.KeyEvent(3); //KEYCODE_HOME 按键Home 3
driver.KeyEvent(4); //KEYCODE_BACK  返回键   4
driver.KeyEvent(82); //KEYCODE_MENU 菜单键   82
driver.KeyEvent(26);  //KEYCODE_POWER 电源键 26
driver.KeyEvent(24);  //KEYCODE_VOLUME_UP   音量增加键 24
driver.KeyEvent(25);  //KEYCODE_VOLUME_DOWN 音量减小键 25

driver.KeyEvent(67);  //KEYCODE_DEL 退格键 67
driver.KeyEvent(66);  //KEYCODE_ENTER 回车键
driver.KeyEvent(122); //KEYCODE_MOVE_HOME 光标移动到开始
driver.KeyEvent(123); //KEYCODE_MOVE_END 光标移动到末尾

keycode 参考:Android KeyCode 列表

5. 坐标操作

为防止不同手机分辨率不同带来的影响,要避免使用固定的坐标,可以用以下方式获取元素的坐标

double Screen_X = driver.Manage().Window.Size.Width;//获取手机屏幕宽度
double Screen_Y = driver.Manage().Window.Size.Height;//获取手机屏幕高度
double startX = element.Location.X; //获取元素的起点坐标,即元素最左上角点的横坐标
double startY = element.Location.Y; //获取元素的起点坐标,即元素最左上角点的纵坐标
double elementWidth = element.Size.Width;  //获取元素的宽度
double elementHight = element.Size.Height; //获取元素的宽度

在封装 “滑动”、“ TouchAction” 等操作时可以用以上方法来获取坐标进行操作。

示例:分装两个元素之间的滑动

IWebElement elmentA = null;
IWebElement elmentB = null;
int startX = 0, startY = 0, endX = 0, endY = 0;
int duration=0,time=0;
/// <summary>
/// 从元素A的位置滑动到元素B的位置
/// </summary>
/// <param name="A">元素A的名称</param>
/// <param name="B">元素B的名称</param>
/// <param name="sDuration">滑动持续时间</param>
/// <param name="sTime">滑动次数</param>
public void SwipeAToB(string A, string B,string sDuration,string sTime)
{
    startX = elmentA.Location.X + elmentA.Size.Width / 2;  //元素A的中心横坐标
    startY = elmentA.Location.Y + elmentA.Size.Height / 2; //元素A的中心纵坐标
    endX = elmentB.Location.X + elmentB.Size.Width / 2;    //元素B的中心横坐标
    endY = elmentB.Location.Y + elmentB.Size.Height / 2;   //元素B的中心纵坐标

    duration = string.IsNullOrEmpty(sDuration) ? 1500 : int.Parse(sDuration); //持续时间为空时,默认设置为1500毫秒
    time = string.IsNullOrEmpty(sTime) ? 1500 : int.Parse(sTime); //滑动次数为空时,默认设置为滑动1次

    for (int i = 0; i < time; i++)
    {
        driver.Swipe(startX, startY, endX, endY, duration);
    }
}

注意:element.Loaction 和 element.Size,每次获取时都会重新去手机里获取,为节省时间如果有获取相同值的,建议储存成变量。

6. 关闭自动推送 appium bootstrap 和安装 unlock 和 setting

一般测试过一次,不需要再重新安装,重新安装比较浪费时间,且有些安装时会有弹窗
注销如下代码:

Appium\node_modules\appium\lib\devices\android\android.js

async.series([
    this.initJavaVersion.bind(this),
    this.initAdb.bind(this),
    this.packageAndLaunchActivityFromManifest.bind(this),
    this.initUiautomator.bind(this),
    this.prepareDevice.bind(this),
    this.checkApiLevel.bind(this),
    this.pushStrings.bind(this),
    this.processFromManifest.bind(this),
    this.uninstallApp.bind(this),
    this.installAppForTest.bind(this),
    this.forwardPort.bind(this),
    //this.pushAppium.bind(this),
    this.initUnicode.bind(this),
    // DO NOT push settings app and unlock app
    //this.pushSettingsApp.bind(this),
    //this.pushUnlock.bind(this),
    function (cb) {this.uiautomator.start(cb);}.bind(this),
    this.wakeUp.bind(this),
    this.unlock.bind(this),
    this.getDataDir.bind(this),
    this.setupCompressedLayoutHierarchy.bind(this),
    this.startAppUnderTest.bind(this),
    this.initAutoWebview.bind(this),
    this.setActualCapabilities.bind(this)
  ], function (err) {

7. 取消重新安装 UnicodeIME 输入法

修改如下代码:

Appium\node_modules\appium\lib\devices\android\android-common.js


androidCommon.pushUnicodeIME = function (cb) {
  cb()
  /*
  logger.debug("Pushing unicode ime to device...");
  var imePath = path.resolve(__dirname, "..", "..", "..", "build",
      "unicode_ime_apk", "UnicodeIME-debug.apk");
  fs.stat(imePath, function (err) {
    if (err) {
      cb(new Error("Could not find Unicode IME apk; please run " +
                   "'reset.sh --android' to build it."));
    } else {
      this.adb.install(imePath, false, cb);
    }
  }.bind(this));
  */
};

8. Appium 测试微信公众号 demo

AndroidDriver<IWebElement> driver = null;
IWebElement element = null;
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("app", "");
capabilities.SetCapability("deviceName", "android emulator");
capabilities.SetCapability("browserName", "");
capabilities.SetCapability("platformName", "android");
capabilities.SetCapability("platformVersion", "6.0");//手机操作系统版本
capabilities.SetCapability("newCommandTimeout", "300"); ////设置命令超时时间,单位:秒
capabilities.SetCapability("unicodeKeyboard", "True");//使用 Unicode 输入法。默认值 false
capabilities.SetCapability("resetKeyboard", "True"); //在设定了 unicodeKeyboard 关键字的 Unicode 测试结束后,重置输入法到原有状态。
capabilities.SetCapability("appPackage", "com.tencent.mm");
capabilities.SetCapability("appActivity", ".ui.LauncherUI");
capabilities.SetCapability("automationName", "appium");
capabilities.SetCapability("fastReset", "false");
capabilities.SetCapability("fullReset", "false");
capabilities.SetCapability("noReset", "true");

DesiredCapabilities option = new DesiredCapabilities();
option.SetCapability("androidProcess", "com.tencent.mm:tools");
capabilities.SetCapability(ChromeOptions.Capability, option.ToDictionary());

Uri serverUri = new Uri("http://127.0.0.1:4723/wd/hub");

//System.IO.File.AppendAllText("D:\\PageSources.xml",driver.PageSource);
driver = new AndroidDriver<IWebElement>(serverUri, capabilities, TimeSpan.FromSeconds(180));
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(1));
driver.FindElementByXPath("//*[@text='通讯录']").Click();
driver.FindElementByXPath("//*[@text='公众号']").Click();
driver.FindElementByAccessibilityId("搜索").Click();
driver.FindElementByXPath("//*[@text='搜索']").SendKeys("产品测试");
driver.FindElementByXPath("//*[@text='产品测试专用']").Click();
driver.FindElementByXPath("//*[@text='发现']").Click();
driver.FindElementByXPath("//*[@text='微信营业厅']").Click();
driver.FindElementByXPath("//*[contains(@text,'微信营业厅')]").Click();
Thread.Sleep(6000);

((IContextAware)driver).Context = "WEBVIEW_com.tencent.mm:tools"; //切换webview

driver.FindElementByXPath("//*[text()='登录']").Click();
Thread.Sleep(3000);
driver.FindElementByXPath("//*[@name='username']").SendKeys("test");  
driver.FindElementByXPath("//*[@name='password']").SendKeys("123456"); 
driver.FindElementByXPath("//button[text()='登录']").Click();

9. Appium 测试手机 Chrome 浏览器 demo

 AndroidDriver<IWebElement> driver = null;
IWebElement element = null;
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability(MobileCapabilityType.DeviceName, "Android Emulator");
capabilities.SetCapability(MobileCapabilityType.BrowserName, MobileBrowserType.Chrome);
capabilities.SetCapability(MobileCapabilityType.PlatformName, MobilePlatform.Android);
capabilities.SetCapability("platformVersion", "6.0");//手机操作系统版本
capabilities.SetCapability("newCommandTimeout", "300"); ////设置命令超时时间,单位:秒。
capabilities.SetCapability("unicodeKeyboard", "True");//使用 Unicode 输入法。默认值 false
capabilities.SetCapability("resetKeyboard", "True"); //在设定了 unicodeKeyboard 关键字的 Unicode 测试结束后,重置输入法到原有状态
capabilities.SetCapability("fastReset", "false");
capabilities.SetCapability("fullReset", "false");
capabilities.SetCapability("noReset", "true");
Uri serverUri = new Uri("http://127.0.0.1:4723/wd/hub");
driver = new AndroidDriver<IWebElement>(serverUri, capabilities, TimeSpan.FromSeconds(180));

driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(1));
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

try
{
    ((IContextAware)driver).Context = "CHROMIUM"; //CHROMIUM
    driver.Navigate().GoToUrl("http://12.99.105.46/m/#main");

    //点击登录
    element = wait.Until<IWebElement>((d) =>
    {
        return driver.FindElementByXPath("//*[text()='登录']");
    });

    element.Click();


    //输入用户名
    element = wait.Until<IWebElement>((d) =>
    {
        return driver.FindElementByXPath("//*[@name='username']");
    });
    element.SendKeys("test"); 

    //输入密码
    driver.FindElementByXPath("//*[@name='password']").SendKeys("123456"); 

    driver.FindElementByXPath("//button[text()='登录']").Click();
    Thread.Sleep(3000);

}
catch (Exception)
{
    driver.Quit();
}

10. 使用 Chrome 远程调试工具查看浏览器或微信打开的网页控件

可使用谷歌浏览器自带的 chrome://inspect/#devices 工具来查看浏览器页面的控件
优点:

  1. 电脑和手机界面双向实时通讯,即可以用 PC 上的 chrome 来控制手机浏览器上的页面,手机操作页面时 PC Chrome 上监控的页面也会试试刷新。
  2. 工具里中 Console 控制台可以输一些 JS 命令来控制手机,有些控件我们是需要使用 JS 去操作的。

工具的使用请参考: 使用 Chrome 浏览器调试移动端网页 chrome://inspect/#devices

11. Appium JS 操作控件(需要切换到 webview 模式)

以下方式部分采取 angularJS 的方式:
JS 点击:
对于 webview 页面中的某些元素,用 element.Click() 方法操作时会报错,此时我们可以使用 JS 的方式去点击。

((IContextAware)driver).Context = "WEBVIEW_com.tencent.mm:tools";//切换到webview
element = driver.FindElementsByXPath("//*[text()='理财产品']").ElementAt(2);
IJavaScriptExecutor excutor= (IJavaScriptExecutor)driver;
excutor.ExecuteScript(String.Format("arguments[0].{0}", "click()"), element);

文本框输入:
对于 webview 页面中的某些元素,用 element.Click() 方法操作时会报错,此时我们可以使用 JS 的方式去点击。

 //由于很多文本框有JS处理,但有些文本框界面上虽然显示已经输入了,提交时仍会提示为空
driver.ExecuteScript("arguments[0].value=arguments[1]", element, "50000"); 
 
driver.ExecuteScript("angular.element(document.getElementsByName('finanAmt')).scope().finanAmt='50000'");

JS 操作复选框:

((IContextAware)driver).Context = "WEBVIEW_com.tencent.mm:tools";//切换到webview
IJavaScriptExecutor excutor= (IJavaScriptExecutor)driver;
excutor.ExecuteScript(angular.element(document.getElementsByName('checkboxValue')).scope().checkboxValue=true);

JS 操作下拉框:

 IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
 js.ExecuteScript(angular.element(document.getElementsByName('finanAccount')).scope().finanAccount = angular.element(document.getElementsByName('finanAccount')).scope().finanAccountList[1]);
 js.ExecuteScript(angular.element(document.getElementsByName('finanAccount')).scope().$apply());  //同步 Model的变化
js.ExecuteScript(angular.element(document.getElementsByName('finanAccount')).scope().onFinanSelect(angular.element(document.getElementsByName('finanAccount')).scope().finanAccount));  //触发控件操作后调用的方法

12. UiAutomator 获取 webview 里面 ui 元素的方法

Android 系统一般自带一个 TalkeBack 功能(设置 - 辅助功能-TalkBack)。但这里千万要注意:打开之后整个系统的操作都变得不同了!!滑动界面需要两个手指,单击变成双击。
打开之后用 UiAutomatorViewer 获取 webview 界面的内容,你就会发现原来 webview 里面获取不到的 ui 元素,现在已经可以可以获取到了。即使现在你关掉 TalkBack,也能获取到,除非重启手机才会回到不能获取的状态,所以开启后我们可以立即关闭,以方便操作。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 6 条回复 时间 点赞
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
          element = wait.Until<IWebElement>((d) =>
         {
             return driver.FindElement(By.Id("userName"));     
         });

这个是直接封闭在 java 版本的吗?userName 可以是哪些啊?resourceId?or?上班有时间可以试试

学习了,谢谢分享

@darker50
1.上面是 C# 的代码,java 参考如下链接:http://blog.csdn.net/aerchi/article/details/8055913
2.userName 是 resourceId 的值

4楼 已删除

第六条:取消重新安装 unlock 和 setting,我注释后,怎么还需要安装 unlock 呢??还需要注释啥吗

driver.GetContexts();//列出所有的可用上下文
driver.GetContext();//列出当前上下文
driver.SetContext("name");//将上下文切换到默认上下文
好像 C# 没有这几个方法啊。我这里直接提示 “不包含 XXX 的定义”。是缺少什么引用吗。我用的是 AndroidDriver

msz06 回复

有的哈,参考以下代码:

var contexts = ((IContextAware)driver).Contexts;
 string webviewContext = null;
 for (int i = 0; i < contexts.Count; i++)
 {
     Console.WriteLine(contexts[i]);
     if (contexts[i].Contains("WEBVIEW"))
     {
         webviewContext = contexts[i];
         break;
     }
 }
 Assert.IsNotNull(webviewContext);
 ((IContextAware)driver).Context = webviewContext;
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册