Calabash-android是支持android的UI自动化测试框架,前面已经介绍过《中文Win7下成功安装calabash-android步骤》,这篇博文尝试测试一个真实应用:开源中国客户端。目的是和你们一块儿学习calabash测试工具。html
oschina除了有网站,还有三大平台手机客户端:android
客户端已经开源!github
那么开源能够用来作什么呢?web
我正在学用calabash-android,得找到一个合适的待测app,平时手机上开源中国这个app用的蛮顺手了,因此就选它了,在此特别向开源中国的开发工程师致谢!express
能够参考:
中文Win7下成功安装calabash-android步骤api
首先到 http://git.oschina.net/oschina/android-app ,数组
如下两种下载源代码方式的方式均可以:ruby
导入到ADT中后,源代码以下:
问题1: adt-bundle-20140702的API版本是20,因此要修改project.properties: target=android-15
改为 target=android-20
问题2: 源代码是使用了已做废的class: android.webkit.CacheManager oschina-android-app/src/net/oschina/app/AppContext.java中使用了android.webkit.CacheManager 因此要把相关代码禁掉:
61行:
//import android.webkit.CacheManager;
1503到1509行:
// File file = CacheManager.getCacheFileBaseDir(); // if (file != null && file.exists() && file.isDirectory()) { // for (File item : file.listFiles()) { // item.delete(); // } // file.delete(); // }
问题3: Run As Android Application报错:
Installation error: INSTALL_FAILED_VERSION_DOWNGRADE
缘由是:手机已经装了一个开源中国的1.7.7.0版本,而ADT要下载的是1.7.6.9版本,Android系统不容许安装一个比已安装版本更旧的版本,因此从手机上卸载已有的1.7.7.0版本就能够了。
Run As Android Application > 选择链接到电脑Usb的手机 > OK
#calabash测试步骤
oschina/android-app项目的AndroidManifest.xml中应该以下行:
<uses-permission android:name="android.permission.INTERNET" />
在命令行下进入oschina/android-app的源代码根目录:
D:\git\oschina>cd android-app D:\git\oschina\android-app>dir Volume Serial Number is 9823-AB19 Directory of D:\git\oschina\android-app 2014/09/01 20:26 <DIR> . 2014/09/01 20:26 <DIR> .. 2014/09/01 20:21 783 .classpath 2014/09/01 20:21 <DIR> .git 2014/09/01 20:21 64 .gitignore 2014/09/01 20:21 822 .project 2014/09/01 20:21 <DIR> .settings 2014/09/01 20:21 10,829 AndroidManifest.xml 2014/09/01 20:21 <DIR> assets 2014/09/01 20:42 <DIR> bin 2014/09/01 20:26 <DIR> gen 2014/09/01 20:21 <DIR> libs 2014/09/01 20:21 18,092 LICENSE.txt 2014/09/01 20:21 1,424 proguard.cfg 2014/09/01 20:41 563 project.properties 2014/09/01 20:21 4,183 README.md 2014/09/01 20:21 <DIR> res 2014/09/01 20:21 <DIR> src 8 File(s) 36,760 bytes 10 Dir(s) 133,131,993,088 bytes free D:\git\oschina\android-app>
建立calabash目录:
D:\git\oschina\android-app>mkdir calabash
建立cucumber skeleton:
D:\git\oschina\android-app>cd calabash D:\git\oschina\android-app\calabash>calabash-android gen ----------Question---------- I'm about to create a subdirectory called features. features will contain all your calabash tests. Please hit return to confirm that's what you want. --------------------------- ----------Info---------- features subdirectory created. --------------------------- D:\git\oschina\android-app\calabash>dir Volume Serial Number is 9823-AB19 Directory of D:\git\oschina\android-app\calabash 2014/09/01 21:54 <DIR> . 2014/09/01 21:54 <DIR> .. 2014/09/01 21:54 <DIR> features 0 File(s) 0 bytes 3 Dir(s) 133,131,988,992 bytes free D:\git\oschina\android-app\calabash>
编辑 D:\git\oschina\android-app\calabash\features\my_first.feature: 初始内容:
Feature: Login feature Scenario: As a valid user I can log into my app When I press "Login" Then I see "Welcome to coolest app ever"
第一行Feature: XX,第二行 Scenario: YY是给人读的,因此随便填写什么中文英文内容均可以
关键内容是第三行When I press ..和第四行Then I see ..,
这是给Cucumber软件识别的。
When后面跟动做语句,Then后面跟内容检查语句
先尝试以下改动:
Feature: 启动开源中国 Scenario: 启动应用后,能看到软件版本更新信息 Then I see "软件版本更新"
用 %windir%\system32\cmd.exe /k chcp65001&&ansicon
启动ansicon,
运行calabash-andriod run
:
D:\git\oschina\android-app\calabash>calabash-android run D:\git\oschina\android-app\bin\oschina-android-app.apk
测试失败,缘由是Then的默认等待时间只有2秒,开源中国app的启动时间比较长。 ansicon中未能显示中文:"软件版本更新",这个后面补充中有描述。
按照 https://github.com/calabash/calabash-android/blob/master/ruby-gem/lib/calabash-android/canned_steps.md的指导,能够指定等待几秒:
Feature: 启动开源中国 Scenario: 启动应用后,能看到软件版本更新信息 Then I wait for 20 seconds Then I see "软件版本更新"
手机上看到的开源中国启动后画面:
按钮“当即更新”为何是红色的?之后再解决。
到此步为止,calabash测试开源中国Android客户端的环境已经创建完毕,接下去同窗们就能够欢快的尝试canned_steps.md里的各个预约义步骤了。
我也会继续完成这个测试用例,你们一块儿共同进步!
解决方法:
ansicon启动时,不要用chcp 65001
设置为UTF-8编码, 用 %windir%\system32\cmd.exe /k ansicon
启动ansicon,而后运行calabash:
运行前,先把D:\git\oschina\android-app\calabash下的screenshot_*.png都删了,避免搞不清楚旧图和新图。
不解释,看脚本和截图:
Feature: 启动开源中国 Scenario: 启动应用后,能看到软件版本更新信息 Then I wait for 5 seconds Then I take a screenshot Then I see "软件版本更新" Then I see "之后再说" When I press "之后再说" Then I don't see "之后再说" Then I take a screenshot
calabash脚本生成的手机截图:
Feature: 启动开源中国 Scenario: 启动应用后,能看到软件版本更新信息 Then I wait for 5 seconds Then I see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说" Then I see "最新资讯" Then I take a screenshot Then I press the menu key Then I see "系统设置" Then I take a screenshot When I press "系统设置" Then I see "已关闭左右滑动" Then I take a screenshot When I press "已关闭左右滑动" Then I see "已启用左右滑动" Then I take a screenshot Then I go back Then I see "最新资讯" Then I take a screenshot
这个right比较难理解,是否是解释为“翻看右边的内容”比较容易记住。 Feature: 启动开源中国 ... Then I swipe right Then I see "问答" Then I don't see "最新资讯" Then I take a screenshot
Then I scroll up
找不到android.widget.ScrollView元素,看样子oschina的可滚动区域不是用标准控件实现的。
接下去我要搜索包含"calabash"的博客,
首先要点击首页右上角的放大镜图标:
这个图标上没有text能够识别,因此必须到首页布局文件mail.xml中找到搜索图标的id,
ADT中打开文件:D:\git\oschina\android-app\res\layout\main.xml
点击搜索图标,右上角的Outline指示搜索图标在main_header.xml中定义。
双击 include-main_header,ADT打开main_header.xml:
点击搜索图标,右边的Outline和Properties区都指示搜索图标的id是main_head_search,控件类型是:ImageButton
接下去在calabash中能够用id点击搜索图标了:
Feature: 搜索包含calabash的博客 Scenario: 启动应用后,能搜到包含calabash的博客 Then I wait for 5 seconds Then I see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说" Then I see "最新资讯" Then I take a screenshot When I press view with id "main_head_search" Then I see "软件" Then I see "问答" Then I see "博客" Then I see "新闻" Then I take a screenshot
我尝试了把I press view with id "main_head_search"
改为 I press "main_head_search"
,也能找到该搜索图标。
说明:I press "text/id"
是万能语法,同时支持文本和id定位控件。那我就不用再记住复杂语句I press view with id "main_head_search"
了。
Feature: 搜索包含calabash的博客 ... When I enter "calabash测试" into input field number 1 Then I take a screenshot When I press the enter button Then I see "已加载所有" Then I take a screenshot
这里的enter button
就是软键盘上的"搜索"键。
注意: 用index查找控件,是从1开始的,而不是像C/Java数组的元素下标那样从0开始。
Feature: 登陆开源中国 Scenario: 启动应用后,输入帐号和密码能登陆开源中国 Then I wait for 5 seconds Then I see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说" Then I see "最新资讯" Then I take a screenshot Then I press the menu key Then I see "用户登陆" Then I take a screenshot When I press "用户登陆" Then I see "记住个人登陆信息" Then I take a screenshot When I enter "username" into input field number 1 And I enter "123456" into input field number 2 And I press button number 2 Then I see "登陆失败用户名或口令错" Then I take a screenshot
说明:
I press "登陆"
定位登陆按钮失败把 D:\git\oschina\android-app\calabash\features\first.feature
更名为 startup.feature,内容以下:
Feature: 启动开源中国 Scenario: 首次启动应用后,启用左右滑动 When I wait up to 5 seconds to see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说" Then I see "最新资讯" Then I take a screenshot Then I press the menu key Then I see "系统设置" Then I take a screenshot When I press "系统设置" Then I see "已关闭左右滑动" And I see "启动检查更新" Then I take a screenshot When I press "已关闭左右滑动" Then I see "已启用左右滑动" When I press "启动检查更新" Then I take a screenshot Scenario: 第二次启动应用后,往右滑动到"问答" When I wait up to 5 seconds to see "最新资讯" When I swipe right Then I see "问答" Then I take a screenshot
本Feature有两个Scenario:
须要说明的是:
I see "启动检查更新"
只有在1280x720分辨率的手机上才能看到;854x480的手机上看不到,会运行错误。按照 http://www.cnblogs.com/puresoul/archive/2011/12/28/2305160.html 的《Cucumber入门之Gherkin》介绍,
一旦咱们写好了一个feature文件,咱们就可使用 cucumber命令来运行它。若是cucumber命令后不跟任何东西的话,那么它会执行全部的.feature文件。若是咱们只想运行某一个.feature文件,咱们可使用命令cucumber features\feature_name
我如今在目录D:\git\oschina\android-app\calabash\features
下有两个feature文件:
D:\git\oschina\android-app\calabash>dir features\*.feature 驱动器 D 中的卷是 工做 卷的序列号是 9823-AB19 D:\git\oschina\android-app\calabash\features 的目录 2014/09/14 21:17 775 login.feature 2014/09/15 21:20 932 startup.feature 2 个文件 1,707 字节 0 个目录 131,744,169,984 可用字节
login.feature 和 startup.feature,feature的执行顺序可有可无,由于每一个feature运行时都要从新安装一遍app。
D:\git\oschina\android-app\calabash>calabash-android run ..\bin\oschina-android-app.apk
将执行这两个feature文件。
若是只想执行 startup.feature
文件,要在calabash-android命令后面添加 features\startup.feature
:
D:\git\oschina\android-app\calabash>calabash-android run ..\bin\oschina-android-app.apk features\startup.feature
中文Win7环境特别说明:
若是feature文件名是中文的,好比:启动.feature,那么在cmd 中 chcp 65001
后,运行命令:
D:\git\oschina\android-app\calabash>calabash-android run ..\bin\oschina-android-app.apk features\启动.feature
将报错:
invalid byte sequence in UTF-8 (ArgumentError)
中文Win7下中文编码统计:
Ubuntu环境下,feature文件名包含中文没有任何问题。
咱们看一下calabash-android的命令参数格式:
D:\git\oschina\android-app\calabash>calabash-android Usage: calabash-android <command-name> [parameters] [options] <command-name> can be one of help prints more detailed help information. gen generate a features folder structure. setup sets up a non-default keystore to use with this test project. resign <apk> resigns the app with the currently configured keystore. build <apk> builds the test server that will be used when testing the app. run <apk> runs Cucumber in the current folder with the enviroment needed. version prints the gem version <options> can be -v, --verbose Turns on verbose logging
我猜测其中的[parameters]
就是指cucumber参数
先看一下cucumber的帮助信息:
D:\git\oschina\android-app\calabash>cucumber -h Usage: cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+ Examples: cucumber examples/i18n/en/features cucumber @rerun.txt (See --format rerun) cucumber examples/i18n/it/features/somma.feature:6:98:113 cucumber -s -i http://rubyurl.com/eeCl -r, --require LIBRARY|DIR Require files before executing the features. If this option is not specified, all *.rb files that are siblings or below the features will be loaded auto- matically. Automatic loading is disabled when this option is specified, and all loading becomes explicit. Files under directories named "support" are always loaded first. This option can be specified multiple times. --i18n LANG List keywords for in a particular language ...
其中的 -r
参数就是用来指定测试指令定义的rb文件所在目录,若是未指定 -r
参数,就到features目录下查找全部 *.rb 文件,并用require加载。
因此能够在把 feature下的两个feature文件分别放到测试集 login 和 startup 下:
D:\git\oschina\android-app\calabash>dir features 驱动器 D 中的卷是 工做 卷的序列号是 9823-AB19
D:\git\oschina\android-app\calabash\features 的目录
2014/09/22 21:00 <DIR> . 2014/09/22 21:00 <DIR> .. 2014/09/22 20:59 <DIR> login 2014/09/22 21:00 <DIR> startup 2014/09/01 21:54 <DIR> step_definitions 2014/09/01 21:54 <DIR> support 2014/09/15 21:20 932 启动.feature 1 个文件 932 字节 6 个目录 125,725,310,976 可用字节
D:\git\oschina\android-app\calabash>dir features\login 驱动器 D 中的卷是 工做 卷的序列号是 9823-AB19
D:\git\oschina\android-app\calabash\features\login 的目录 2014/09/22 20:59 <DIR> . 2014/09/22 20:59 <DIR> .. 2014/09/14 21:17 775 login.feature 1 个文件 775 字节 2 个目录 125,725,310,976 可用字节 D:\git\oschina\android-app\calabash>dir features\startup 驱动器 D 中的卷是 工做 卷的序列号是 9823-AB19 D:\git\oschina\android-app\calabash\features\startup 的目录 2014/09/22 21:00 <DIR> . 2014/09/22 21:00 <DIR> .. 2014/09/15 21:20 932 startup.feature 1 个文件 932 字节 2 个目录 125,725,310,976 可用字节
而后,使用-r
参数,测试login测试集:
D:\git\oschina\android-app\calabash>calabash-android run ..\bin\oschina-android-app.apk features\startup -r features Feature: 启动开源中国 Scenario: 首次启动应用后,启用左右滑动 # features\startup\startup.feature:3
若是不用-r
参数,是这样的结果:
You can implement step definitions for undefined steps with these snippets: When(/^I wait up to (\d+) seconds to see "(.*?)"$/) do |arg1, arg2| pending # express the regexp above with the code you wish you had end
上述报错信息是:全部测试指令都未定义。
有一个现实的需求:
若是有一个oschina测试账号要在多个Feature中使用,
或者个人calabash测试脚本共享给其余人后,他的oschina测试账号要换成本身的。
若是直接修改Feature文件中的帐号和密码,可能要修改多个地方。
因此,设想是否能扩展calabash预约义指令:
Then /^I enter "([^\"]*)" into input field number (\d+)$/ do |text, index|
扩展为支持从环境变量读取输入文本:
Then /^I enter \$([^\$]*) into input field number (\d+)$/ do |text_ev, index|
从文件 D:\ruby-1.9.3-p545-i386-mingw32\lib\ruby\gems\1.9.1\gems\calabash-android-0.5.1\lib\calabash-android\steps\enter_text_steps.rb
中拷贝:
Then /^I enter "([^\"]*)" into input field number (\d+)$/ do |text, index| enter_text("android.widget.EditText index:#{index.to_i-1}", text) end
到 D:\git\oschina\android-app\calabash\features\step_definitions\calabash_steps.rb
中,改动后,以下:
require 'calabash-android/calabash_steps' Then /^I enter %([^%]*)% into input field number (\d+)$/ do |text_ev, index| text = ENV[text_ev] enter_text("android.widget.EditText index:#{index.to_i-1}", text) end Then /^I enter \$([^\$]*) into input field number (\d+)$/ do |text_ev, index| text = ENV[text_ev] enter_text("android.widget.EditText index:#{index.to_i-1}", text) end
这里环境变量支持两种格式:
%text_ev%
Windows cmd的格式$text_ev
Linux Bash的格式而后运行calabash前,设置环境变量:
D:\git\oschina\android-app\calabash>set td_username_1=username1 D:\git\oschina\android-app\calabash>set td_password_1=123456
上述两条命令也能够放到 testdata.bat 文件中,而后执行testdata.bat:
set td_username_1=username1 set td_password_1=123456
修改 login.feature :
Feature: 登陆开源中国 Scenario: 启动应用后,输入错误的帐号和密码不能登陆 Then I wait for 5 seconds Then I see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说" Then I see "最新资讯" Then I take a screenshot Then I press the menu key Then I see "用户登陆" Then I take a screenshot When I press "用户登陆" Then I see "记住个人登陆信息" Then I take a screenshot When I enter %td_username_1% into input field number 1 And I enter $td_password_1 into input field number 2 And I press button number 2 Then I see "登陆失败用户名或口令错" Then I take a screenshot
运行calabash:
D:\git\oschina\android-app\calabash>calabash-android run ..\bin\oschina-android-app.apk features\login.feature
为了支持跨平台测试数据维护,能够把环境变量设置放入ruby脚本 testdata.rb 中:
ENV["td_username_1"]="username1" ENV["td_password_1"]="123456"
在irb中运行calabash:
D:\git\oschina\android-app\calabash>irb irb(main):001:0> require './testdata.rb' => true irb(main):002:0> ENV["td_username_1"] => "username1" irb(main):003:0>exec('calabash-android run ..\bin\oschina-android-app.apk features\login.feature')
Ruby设置和读取环境变量真是太方便了,执行系统命令也很方便,不须要处处import库,值得深刻掌握。
Linux补充
Linux下能够把测试数据写入testdata.bashrc 中:
export td_username_1="username1" export td_password_1="123456"
enter_text_steps.rb 文件位置:
/var/lib/gems/1.9.1/gems/calabash-android-0.5.2/lib/calabash-android/steps/enter_text_steps.rb
Then /^I enter \$([^\$]*) into input field number (\d+)$/ do |text_ev, index| text = ENV[text_ev] enter_text("android.widget.EditText index:#{index.to_i-1}", text) end
等价于:
Then /^I enter \$([^\$]*) into input field number (\d+)$/ do |text_ev, index| text = ENV[text_ev] steps %{ Then I enter "#{text}" into input field number #{index} } end
说明: steps是cucumber的ruby api,用于封装其余cucumber指令。 %{} 是ruby中表示多行字符串的格式。一对大括号之间的全部换行符和空格符都会原本来本的输出。
%{ 第一行 第二行 }
等价于:
"\n 第一行\n 第二行\n "
每次运行feature的第一个scenario时,总要忽略升级提示,第二个scenario时,又不要忽略升级提示, 因此我准备定义一个指令,判断若是出现升级提示,则点击忽略按钮。 忽略提示的步骤以下:
Then I see "软件版本更新" Then I see "之后再说" Then I take a screenshot When I press "之后再说" Then I don't see "之后再说"
简单封装以下:
Then /^I ignore upgrade prompt$/ do puts "若是出现升级提示,则点击忽略按钮" steps %{ Then I see "软件版本更新" Then I see "之后再说" When I press "之后再说" Then I don't see "之后再说" } end
上面假定必定出现升级提示, 下面,进一步判断升级提示出现后,再点击忽略按钮: 根据 calabash Ruby API https://github.com/calabash/calabash-android/blob/master/documentation/ruby_api.md 使用 element_exists(uiquery)判断"之后再说"是否出现
Then /^I ignore upgrade prompt$/ do if element_exists("* {text CONTAINS[c] '之后再说'}") puts "看到了升级提示,取消升级完成启动" steps %{ When I press "之后再说" Then I don't see "之后再说" } else puts "未看到升级提示,正常启动" end end
uiquery语法是直接从 https://github.com/calabash/calabash-android/blob/master/ruby-gem/lib/calabash-android/steps/press_button_steps.rb 里的 Then /^I touch the "([^"]*)" text$/ do |text| 里拷贝的。
login.feature 应用新定义的指令:
Feature: 登陆开源中国 Scenario: 启动应用后,输入错误的帐号和密码不能登陆 When I wait for 5 seconds And I ignore upgrade prompt Then I see "最新资讯" Then I take a screenshot When I press the menu key Then I see "用户登陆" Then I take a screenshot When I press "用户登陆" Then I see "记住个人登陆信息" Then I take a screenshot When I enter %td_username_1% into input field number 1 And I enter $td_password_1 into input field number 2 And I press button number 2 Then I see "登陆失败用户名或口令错" Then I take a screenshot
具体执行结果就不贴啦,相信你们都会执行测试了。
参考文档:
https://github.com/calabash/calabash-android/wiki/05-Query-Syntax
http://blog.lesspainful.com/2012/12/18/Android-Query/
按照上述文档很容易查询控件的id, 好比启动开源中国后的首页,查询结果:
D:\git\oschina\android-app\calabash>calabash-android console ..\bin\oschina-android-app.apk irb(main):001:0> reinstall_apps 5168 KB/s (2498389 bytes in 0.472s) 3800 KB/s (544875 bytes in 0.140s) nil irb(main):002:0> start_test_server_in_background nil
启动后首页弹出升级提示对话框:
irb(main):003:0> query('button')
看出问题没有,中文编码问题又来捣乱了:
"text" => "\u7ACB\u5373\u66F4\u65B0",
的中文是"当即更新",这里只能看的是4个中文字 "text" => "\u4EE5\u540E\u518D\u8BF4",
的中文是"之后再说"
又要说人家Ubuntu了,Ubuntu下query结果的中文能直接显示。
在console下还能够点击按钮,
https://github.com/calabash/calabash-android/blob/master/ruby-gem/lib/calabash-android/steps/press_button_steps.rb 中定义了按button编号点击的指令 :
Then /^I press button number (\d+)$/ do |index| tap_when_element_exists("android.widget.Button index:#{index.to_i-1}") end
要选择按钮"之后再说",把 #{index.to_i-1}
代成1, 在console下输入:
tap_when_element_exists("android.widget.Button index:1")
而后按钮"之后再说"被点击,升级提示对话框消失了:
因此,能够放心的在feature文件中加入指令:
Then I press button number 2
下面还但愿找到右上角搜索图标的id,在console下输入:
query("*")
总共列出了111个控件,编号从0到110,其中第7个是:
irb(main):008:0> query("*") [ ... [ 7] { "id" => "main_head_search", "enabled" => true, "contentDescription" => nil, "class" => "android.widget.ImageButton", "rect" => { "center_y" => 90, "center_x" => 676, "height" => 80, "y" => 50, "width" => 80, "x" => 636 }, ... ]
根据其坐标(x,y) = (636,50),能够肯定就是右上角的搜索按钮,按钮的id是main_head_search。
还能够查询本页面的button:
query("imagebutton")
只有1个查询结果,就是搜索按钮:
irb(main):008:0> query("imagebutton") [ [0] { "id" => "main_head_search", "enabled" => true, "contentDescription" => nil, "class" => "android.widget.ImageButton", "rect" => { "center_y" => 90, "center_x" => 676, "height" => 80, "y" => 50, "width" => 80, "x" => 636 }, "tag" => nil, "description" => "android.widget.ImageButton{42a889a0 VFED..C. .. ...... 636,0-716,80 #7f0b00a3 app:id/main_head_search}" } ]
接下来能够用两种方式点击搜索按钮,
第一种,对应指令是 Then I press image button number 1
:
tap_when_element_exists("android.widget.ImageButton index:0")
第二种,对应指令是 Then I press "main_head_search"
:
tap_when_element_exists("* marked:'main_head_search'")