51Testing软件测试论坛

标题: [基础问题]Appium-移动端自动测试框架,如何入门? [打印本页]

作者: lsekfe    时间: 2017-8-24 11:42
标题: [基础问题]Appium-移动端自动测试框架,如何入门?
做自动化测试研究,发现了Appium这个工具,但看遍资料,却还无法入门,自己在windows下跑android程序,下载windows版本Appium后,点击launch后,下一步操作为何?如何与手机建立连接?自动化脚本需要怎么执行?浏览器中打开localhost没有写脚本的地方啊。

作者: lsekfe    时间: 2017-8-24 11:45
作者:九毫
链接:https://www.zhihu.com/question/21453695/answer/103719176
来源:知乎

-------------------------------------------------------------

背景

最近新加入DJI的某项目组(以下均已M指代),需要从零开始搭建功能自动化测试平台。


简单地说,M是一个典型的移动互联网产品,客户端包括iOS和Android,并在app中通过WebView嵌入了H5,后端基于Ruby on Rails实现。
当前阶段,M项目除了Rails Server端采用Jenkins+RSpec实现了部分的持续集成功能外,客户端部分的部署和测试工作都还是完全依赖于手工操作。

基于当前项目的开发模式,我对整个M项目实现持续集成自动化测试的架构流程进行了规划,初步计划的架构图如下图所示。最终的目标是希望能实现:不管是Rails Server,还是App(iOS/Android),以及H5,当任意部分存在代码提交时,系统能自动拉取最新代码进行部署并执行自动化回归测试,及时地将执行情况反馈给开发人员。

[attach]108428[/attach]
目标确定后,便是分阶段进行实现,需要开发的模块包括:


而本系列教程,《从0到1搭建移动App功能自动化测试平台》,便是对整个实践过程的一个记录。

需要说明的是,之前我个人的工作经历主要在服务端性能测试、Android客户端性能测试(测试开发)方向,对于客户端的自动化测试基本上没有经验积累,特别是iOS系统的测试,以前更是完全没有接触过。因此本系列教程只能算是个人在探索路上的学习总结和记录,可能会存在一些错误的观点,还请前辈们多多指教。

自动化测试框架的选择

在愿景图中,绿色方框(Automated Test Platform)负责移动应用客户端(iOS/Android/H5)自动化测试的调度和执行,是整个自动化测试平台的核心。

因此,在搭建自动化测试平台之前,首先需要选择一个合适的自动化测试框架。

对于移动应用的自动化测试框架,当前市面上已经有很多成熟的开源项目。针对当前项目的实际情况,我主要参考如下选择标准:


经过筛选,Appium无疑是最佳的选择。

Appium简介

对于Appium的详细介绍,大家可参考Appium官方文档,我就不再重复引用。

不过对于Appium,仍然有几点很赞的理念值得强调。





作者: lsekfe    时间: 2017-8-24 11:49
Appium——驱动和常用功能的封装背景

初步了解Appium各个功能之后,应该把这些功能进行一些封装,否则整个代码会比较难看,可用性和重用性也会很差。本文是我这段时间使用Appium的一些想法,仅供参考。


操作系统:Mac OS X EI Caption

Appium: 1.4.16

Java: java version “1.7.0_79”

node.js: v5.3.0

npm: 3.3.12

手机:小米NOTE4

待测应用: 微证券


driver的封装

初始化的driver是Python操作Appium的核心,因此driver在整个代码中重用率是非常高的。

新建一个driver.py文件,专门用来封装driver。代码如下:

  1. # ecoding=utf-8
  2. __author__ = "Sven_Weng"
  3. from appium import webdriver


  4. class AppiumTest:
  5.     def __init__(self):
  6.         desired_caps = {'platformName': 'Android',
  7.                         'platformVersion': '5.0.2',
  8.                         'deviceName': '5136b01e',
  9.                         'appPackage': 'com.weizq',
  10.                         'appActivity': 'com.zztzt.android.simple.app.MainActivity'}
  11.         self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
  12.         self.driver.implicitly_wait(30)

  13.     def get_driver(self):
  14.         return self.driver
复制代码

在AppiumTest这个类中,初始化函数包含了driver的信息,然后在get_driver函数中直接把这个driver返回回去,测试用例中只要在测试类的初始化中调用它,就能获取driver。

按模块划分封装的方法

我是这么划分模块的,一个Element,专门封装操作和对象有关的方法,把find_element_by_id这种很长很长的方法缩短一点,用一些比较简洁的名字封装一下,使用起来代码可读性也会比较强。一个common类,专门封装通用的方法,其他就是按照功能模块来划分,我测试的微证券有4个主要功能,所以会有财讯、行情、发现、我这些类模块。

element对象封装

我自己的代码如下:

  1. class Element:
  2.     """
  3.     封装Appium中关于元素对象的方法
  4.     """

  5.     def __init__(self):
  6.         at = AppiumTest()
  7.         self.driver = at.get_driver()

  8.     def get_id(self, id):
  9.         element = self.driver.find_element_by_id(id)
  10.         return element

  11.     def get_name(self, name):
  12.         element = self.driver.find_element_by_name(name)
  13.         return element

  14.     def over(self):
  15.         element = self.driver.quit()
  16.         return element

  17.     def get_screen(self, path):
  18.         self.driver.get_screenshot_as_file(path)

  19.     def get_size(self):
  20.         size = self.driver.get_window_size()
  21.         return size

  22.     def swipe_to_up(self):
  23.         window_size = self.get_size()
  24.         width = window_size.get("width")
  25.         height = window_size.get("height")
  26.         self.driver.swipe(width / 2, height * 3 / 4, width / 2, height / 4, 500)

  27.     def swipe_to_down(self):
  28.         window_size = self.get_size()
  29.         width = window_size.get("width")
  30.         height = window_size.get("height")
  31.         self.driver.swipe(width / 2, height / 4, width / 2, height * 3 / 4, 500)

  32.     def swipe_to_left(self):
  33.         window_size = self.get_size()
  34.         width = window_size.get("width")
  35.         height = window_size.get("height")
  36.         self.driver.swipe(width / 4, height / 2, width * 3 / 4, height / 2, 500)

  37.     def swipe_to_right(self):
  38.         window_size = self.get_size()
  39.         width = window_size.get("width")
  40.         height = window_size.get("height")
  41.         self.driver.swipe(width * 4 / 5, height / 2, width / 5, height / 2, 500)

  42.     def back(self):
  43.         self.driver.keyevent(4)

  44.     def get_classes(self, classesname):
  45.         elements = self.driver.find_elements_by_class_name(classesname)
  46.         return elements

  47.     def get_ids(self, ids):
  48.         elements = self.driver.find_elements_by_id(ids)
  49.         return elements

  50.     def switch_h5(self):
  51.         self.driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {"name": "WEBVIEW_com.weizq"})

  52.     def switch_app(self):
  53.         self.driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {"name": "NATIVE_APP"})
复制代码

上面封装的很简单,就是一些常用的方法,在初始化函数中初始化driver即可。

common

common就是封装一些通用的方法,比如登录,比大小,获取股票代码,页面标题校验等等。以下是我的代码:

  1. class Common:
  2.     """
  3.     封装通用的方法
  4.     """

  5.     def __init__(self):
  6.         self.cf = ConfigParser.ConfigParser()
  7.         self.cf.read('enable_data.conf')

  8.     def cycle_screen(self, section_name, num, driver):
  9.         """
  10.         循环截图功能
  11.         从json文件中读取信息,用来定位点击的方位和保存截图的路径
  12.         执行成功则返回True,否则返回False
  13.         :param section_name: json文件中一级节点的名称
  14.         :param num: json文件中对应的列表序列位
  15.         :param driver: Appium的驱动
  16.         :return:True or False
  17.         """
  18.         try:
  19.             with open('get_screen.json', 'r') as f:
  20.                 jsondata = f.read()
  21.             cx_info = json.loads(jsondata)
  22.             values = cx_info[section_name][num].values()
  23.             for value in values:
  24.                 driver.get_name(value['name']).click()
  25.                 time.sleep(2)
  26.                 self.check_title(value['name'], driver)
  27.                 driver.get_screen(value['path'])
  28.                 logging.info(value['log'])
  29.             return True
  30.         except Exception as e:
  31.             logging.warning(e)
  32.             return False

  33.     def cycle_screen_and_back(self, section_name, num, driver):
  34.         """
  35.         功能和上一个函数一样,在步骤最后加了一个返回事件,用来处理"行情"-"更多"页面的数据校验
  36.         :param section_name: conf配置文件的节点名称
  37.         """
  38.         try:
  39.             with open('get_screen.json', 'r') as f:
  40.                 jsondata = f.read()
  41.             cx_info = json.loads(jsondata)
  42.             values = cx_info[section_name][num].values()
  43.             for value in values:
  44.                 logging.info(value)
  45.                 driver.get_name(value['name']).click()
  46.                 time.sleep(2)
  47.                 self.check_title(value['name'], driver)
  48.                 # title = driver.get_classes('android.widget.TextView')[0].text
  49.                 # try:
  50.                 #     assert title == value['name']
  51.                 #     logging.info(title + '页面显示正确')
  52.                 # except Exception as e:
  53.                 #     logging.warning(value['name'])
  54.                 #     logging.warning(title + '页面显示不正确')
  55.                 driver.get_screen(value['path'])
  56.                 driver.driver.keyevent(4)
  57.                 time.sleep(1)
  58.                 logging.info(value['log'])
  59.             return True
  60.         except Exception as e:
  61.             logging.warning(e)
  62.             return False

  63.     def check_title(self, title, driver):
  64.         text = driver.get_classes('android.widget.TextView')[0].text
  65.         try:
  66.             assert text == title
  67.             logging.info("标题为: {} 校验通过".format(title))
  68.             return True
  69.         except Exception as e:
  70.             logging.warning(e)
  71.             logging.warning("标题为: {} 校验不通过".format(title))
  72.             return False

  73.     def compare(self, ids, numstar, numend, driver):
  74.         """
  75.         比较传入值得大小, 第一个数比第二个数大返回True,否则返回False
  76.         :param ids:传入的ID名
  77.         :param numstar:传入的开始序号
  78.         :param numend:传入的结束需要
  79.         :param driver:驱动
  80.         :return:True or False
  81.         """
  82.         try:
  83.             startext = float(driver.get_ids(ids)[numstar].text)
  84.             endtext = float(driver.get_ids(ids)[numend].text)
  85.         except ValueError:
  86.             starmark = driver.get_ids(ids)[numstar].text[:1]
  87.             endmark = driver.get_ids(ids)[numend].text[:1]
  88.             if starmark == endmark:
  89.                 startext = float(driver.get_ids(ids)[numstar].text.lstrip(starmark).rstrip('%'))
  90.                 endtext = float(driver.get_ids(ids)[numend].text.lstrip(endmark).rstrip('%'))
  91.             elif starmark == "+":
  92.                 return True
  93.             elif starmark == "-":
  94.                 return False
  95.         if startext > endtext:
  96.             return True
  97.         else:
  98.             return False

  99.     def get_SotckCode(self, ids, driver):
  100.         """
  101.         获取行情-自选页面中的所有股票代码,自选超过50支的时候请修改参数中的range(10)
  102.         返回list
  103.         :param driver:驱动控件
  104.         :return:list, 自选股票代码
  105.         """
  106.         SotckCode = []
  107.         for x in driver.get_ids(ids):
  108.             SotckCode.append(x.text)
  109.         for x in range(5):
  110.             driver.swipe_to_up()
  111.             for x in driver.get_ids(ids):
  112.                 if x.text not in SotckCode:
  113.                     SotckCode.append(x.text)
  114.         return SotckCode

  115.     def zjzh_login(self, driver):
  116.         """
  117.         资金账号登录方法,传入appium-driver
  118.         APP启动时需要资金账号为登录状态时调用
  119.         :param driver: Appium驱动
  120.         :return: True
  121.         """
  122.         driver.get_name('发现').click()
  123.         driver.switch_h5()
  124.         driver.get_classes('list-item')[1].click()
  125.         driver.switch_app()
  126.         driver.get_name('买入').click()
  127.         username = self.cf.get('zjzh_login_info', 'username')
  128.         password = self.cf.get('zjzh_login_info', 'password')
  129.         if self.tra_login(username, password, driver):
  130.             self.check_title(u'委托买入', driver)
  131.             driver.back()
  132.             driver.back()
  133.             self.check_title(u'发现', driver)
  134.             logging.info('资金账号登录成功')
  135.             return True

  136.     def tra_login(self, username, password, driver):
  137.         """
  138.         资金账号登录页面的登录方法
  139.         :param username:账号
  140.         :param password:密码
  141.         :param driver:Appium驱动
  142.         :return:True
  143.         """
  144.         driver.get_id('com.weizq:id/edit_account').clear()
  145.         driver.get_id('com.weizq:id/edit_account').send_keys(username)
  146.         logging.info('输入的账号为: {}'.format(username))
  147.         driver.get_id('com.weizq:id/edit_password').send_keys(password)
  148.         logging.info('输入的密码为: {}'.format(password))
  149.         yzm = driver.get_id('com.weizq:id/text_yanzhengma').text
  150.         logging.info('验证码为:{}'.format(yzm))
  151.         driver.get_id('com.weizq:id/edit_yanzhengma').send_keys(yzm)
  152.         driver.get_id('com.weizq:id/login').click()
  153.         time.sleep(3)
  154.         return True
复制代码
其他功能模块

其他功能模块也是同样的道理,不过其他模块封装的都是特定功能模块才会用到的方法,因为代码可能涉及公司的机密,所以功能模块的代码就暂不放出来,思想还是一样的。

日志记录

关于日志记录的原则,我觉得是这样的,写入日志的必须是一些关键的信息,把日志浏览一遍,基本上就能了解到整个脚本跑的流程,比如我调试一些功能的时候,基本上不用关注手机运行的页面,脚本到底做了什么,只要看日志就懂了。下面是一些我调试功能的时候打出来的日志:

  1. Thu, 24 Mar 2016 14:26:35 test_weizq.py[line:437] INFO ==========结束测试我的内容==========

  2. Thu, 24 Mar 2016 14:26:37 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  3. Thu, 24 Mar 2016 14:26:55 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  4. Thu, 24 Mar 2016 14:27:23 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  5. Thu, 24 Mar 2016 14:27:32 test_weizq.py[line:448] INFO -----开始测试个人资料页面校验-----

  6. Thu, 24 Mar 2016 14:27:36 test_weizq.py[line:456] INFO 旧的昵称是: 6850252

  7. Thu, 24 Mar 2016 14:27:50 test_weizq.py[line:463] INFO 新的昵称是: 5030013

  8. Thu, 24 Mar 2016 14:27:50 test_weizq.py[line:437] INFO ==========结束测试我的内容==========

  9. Thu, 24 Mar 2016 14:31:55 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  10. Thu, 24 Mar 2016 14:32:06 test_weizq.py[line:448] INFO -----开始测试个人资料页面校验-----

  11. Thu, 24 Mar 2016 14:32:09 test_weizq.py[line:468] INFO 女

  12. Thu, 24 Mar 2016 14:32:09 test_weizq.py[line:437] INFO ==========结束测试我的内容==========

  13. Thu, 24 Mar 2016 14:34:38 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  14. Thu, 24 Mar 2016 14:34:50 test_weizq.py[line:448] INFO -----开始测试个人资料页面校验-----

  15. Thu, 24 Mar 2016 14:34:53 test_weizq.py[line:468] INFO 修改前的性别: 女

  16. Thu, 24 Mar 2016 14:34:58 test_weizq.py[line:472] INFO 性别修改为: 男

  17. Thu, 24 Mar 2016 14:34:58 test_weizq.py[line:437] INFO ==========结束测试我的内容==========

  18. Thu, 24 Mar 2016 14:35:14 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  19. Thu, 24 Mar 2016 14:35:35 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  20. Thu, 24 Mar 2016 14:35:47 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  21. Thu, 24 Mar 2016 14:36:01 test_weizq.py[line:429] INFO ==========开始测试我的内容==========

  22. Thu, 24 Mar 2016 14:36:12 test_weizq.py[line:448] INFO -----开始测试个人资料页面校验-----

  23. Thu, 24 Mar 2016 14:36:16 test_weizq.py[line:456] INFO 旧的昵称是: 5030013

  24. Thu, 24 Mar 2016 14:36:28 test_weizq.py[line:463] INFO 新的昵称是: 908602

  25. Thu, 24 Mar 2016 14:36:28 test_weizq.py[line:468] INFO 修改前的性别: 男

  26. Thu, 24 Mar 2016 14:36:33 test_weizq.py[line:475] INFO 性别修改为: 女

  27. Thu, 24 Mar 2016 14:36:33 test_weizq.py[line:437] INFO ==========结束测试我的内容==========
复制代码

基本上看到这些日志就知道我到底在测试什么东西了。

作者:点点寒彬
原文地址: http://www.wybblog.applinzi.com/blog/article/35/



作者: lsekfe    时间: 2017-8-24 11:56
Appium——处理混合APP中H5的操作

自己自学APPIUM测试公司的APP有一段时间的,Android原生的元素定位,包括数据处理和逻辑判断也基本上搞熟了,但是公司的APP很坑爹,开发过程中存在混合开发的情况,这就意味着我必须要处理APP里面的webview,真的是很坑爹的东西。


操作系统:Mac OS X EI Caption

Appium: 1.4.16

Java: java version "1.7.0_79"

node.js: v5.3.0

npm: 3.3.12

手机:小米NOTE4

待测应用: 微证券


常规方法

正常来说,常规的方法应该是使用UI Automator Viewer来探测页面的元素,比如下面这样:

[attach]108429[/attach]

但是,如果是混合开发,就会出现只有一个webview元素的情况,正常的方法无法定位。比如这样:

[attach]108430[/attach]

常规的方法是没办法定位出这部分内容,只有一个webview,无法像原生一样获取内容。

取巧的办法

如果针对H5的元素,只需要点击的话,可以这么做,比如上文我们的开户页面,我们需要做的只是在这个页面上点击立即开户,跳转到下一个页面,我们可以用这种取巧的方法:

  1. self.driver.get_name('立即开户').click()
复制代码

这样的代码,也是可以实现的,当然,上面的get_name我是进行封装了的,原来的代码是:

  1. def get_name(self, name):
  2.         element = self.driver.find_element_by_name(name)
  3.         return element
复制代码
更优化的处理方式

既然是H5页面,如果能够获取HTML代码就好了,那么就可以使用操作DOM的方式来操作这个H5。


谷歌浏览器:版本 49.0.2623.87 (64-bit)


我用的谷歌浏览器是这个版本,把手机连接上电脑,在谷歌浏览器中输入这个代码:

  1. chrome://inspect
复制代码

点击这个按钮:

[attach]108431[/attach]

神奇的一幕出现了,所有的代码都展示在你面前。就像web一样调试混合APP中的H5.

切换webdriver

虽然解决了html代码的问题,但是另一个问题出现了,我们要怎么去操作DOM。常规的方法是没办法了,只能操作原生的,这个时候我们需要把webdriver切换成H5的,而不是原生的。

其实原理很简单,只要把webdriver切换到H5的方式就行了。使用如下代码:

  1. contexts
  2. contexts(self):

  3.     Returns the contexts within the current session.
  4.     返回当前会话中的上下文,使用后可以识别H5页面的控件

  5.     :Usage:
  6.         driver.contexts
  7. 用法 driver.contexts
复制代码

调用这个方法后,把结果打印出来,你会发现是一个列表,列表中的第一个是NATIVE_APP,这个就表示现在的webdriver是调用原生的功能,我们使用这个命令切换一下就行了。

  1. def switch_h5(self):
  2.         self.driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {"name": "WEBVIEW_com.weizq"})

  3.     def switch_app(self):
  4.         self.driver.execute(MobileCommand.SWITCH_TO_CONTEXT, {"name": "NATIVE_APP"})
复制代码

第一个函数就是封装切换到H5的方法,name对应的东西就是通过contexts列表中打印出来的东西,当然你也可以使用其他的方法封装。

切换到H5的webdriver之后,再使用driver.find_element_by_id试试?现在就变成查找DOM相关的功能了,再调用第二个函数,又切回原生的webdriver了。

作者:点点寒彬
原文地址: http://www.wybblog.applinzi.com/blog/article/34/



作者: lsekfe    时间: 2017-8-24 12:01
Appium--无安卓源码的一些准备

准备工作其实是比较简单了,现阶段我测试的是安卓,所以就需要准备一台安卓手机了,在环境准备中已经把电脑的环境变量配置完毕了,所以现在需要的就是一些安卓测试的一些前奏知识。

环境

操作系统:Mac OS X EI Caption

Appium: 1.4.16

Java: java version "1.7.0_79"

node.js: v5.3.0

npm: 3.3.12

一切的前提,需要配置好环境


获取包名

我这里假设是我们都没有源码的情况下,有源码当然比较简单,如果只有一个apk文件,我们要获取包名的方式也一样很简单。在终端中输入下列命令:

  1. adb logcat ActivityManager:I *:s
复制代码

然后打开需要测试的APP,找到topActivity.packageName,里面就是待测应用的包名了。

[attach]108432[/attach]

这条命令的实质就是Android手机的日志打印之后,通过管道符过滤,LInux也同样有效,Windows可能就没办法使用这条命令了,当然,windows的朋友可以使用管道符之前的命令,然后找到topActivity.packageName。也同样可以获取到包名。

当前Activity

同样是无源码,也可以通过命令来获取当前Activity。

  1. Mac/Linux: adb shell dumpsys window windows | grep mFocusedApp
  2. Windows: adb shell dump sys window windows  并且手动查找mFocusedApp行
复制代码

效果如下图:

[attach]108433[/attach]

获取页面元素

Appium和Selenium一样,需要通过一些元素来定位,达到自动化测试的目的,这里我使用的是Android Studio里面的一个工具:Android Device Monitor。

这个工具可以获取屏幕快照,然后点击相关元素就可以获得上面的信息了,如下图所示:

[attach]108434[/attach]

最后

熟悉使用这些东西,我们就可以开始Appium之旅了。

作者:点点寒彬
原文地址: http://www.wybblog.applinzi.com/blog/article/33/



作者: lsekfe    时间: 2017-8-24 13:35
Appium环境搭建Appium学习之路—环境搭建

说实话,Appium第一次接触还是在testerhome看到的,介绍说这个框架可以测试安卓和IOS,并且只要写一个脚本,不需要做任何改动就可以直接在IOS和Android上运行,这真心是一个好消息,说干就干。先吧环境弄起来看看到底牛掰不牛掰。 PS:笔者使用的是Mac OS X系统。

准备工作

Appium提供了两种方式,一个是客户端,另一个是命令行。不过不论哪种方式,以下的东西都必须准备好Xcode Command Line Tools 这个是必须的工具可以百度以下,我是使用Mac系统,干脆直接下了一个完整的Xcode Android SDK 和AVD 这个是测试Android必须的,当然AVD可以使用Android的真机来代替。

Appium客户端

我看到这个框架竟然还是有客户端的,真是有一种莫名的兴奋,有客户端就意味着我可以不用搭建那些操蛋的命令行了?下载一个GUI端我就可以轻轻松松的开搞了。不过下载客户端是需要翻墙的,这个有点蛋疼,我自己的专门翻墙出去下载了一个Appium for Mac的客户端,不过那速度慢得让人无法直视。官网地址:Appium官网。我是不太建议在官网下载。这里推荐一个资源,下载速度比较快。Appium国内下载地址

客户端的使用

我个人折腾完客户端之后,感觉不是很给力,客户端虽然界面挺漂亮的,风格也挺好,不过总体折腾下来不是我喜欢的类型,如果喜欢客户端的朋友可以自己去折腾折腾客户端,配置起来很简单,就不做过多的赘述了。

命令行

命令行是体现逼格高大上的东东,不过折腾起来也很费事,笔者折腾这个命令行折腾了大概一周的时间,其中种种原因很多

Mac自带了这东西,不过说实话一般的电脑也都会把这个环境折腾好吧,毕竟java是最流行的开发语言

Mac自带了这东西,因为笔者是使用Python来写脚本,当然你也可以使用其他语言来写,比如java、ruby、c#等等

brew是一个套件管理器,不过笔者比较懒,直接使用Mac自带的easy_install来安装,比较省事

node也是必须的,如果有了brew或者easy_install,安装是很省事的 sudo easy_install node,输入密码就搞定了

  1. SvenWengdeMBP:~ svenweng$ node -v v0.10.34
复制代码

这样就算安装成功

npm这东西和node一样,也是使用安装工具来安装很方便

  1. SvenWengdeMBP:~ svenweng$ npm -v 1.4.28
复制代码

终于到我们的主角了安装命令是 npm install -g appium 这里要说明一下,Appium的安装不能使用sudo。-g是让npm自动帮我们配置Appium的环境。

  1. SvenWengdeMBP:~ svenweng$ appium -v 1.4.16
复制代码

如果你看到了这个东西,就表示你的Appium已经完全安装完毕了。网上还有教程说需要安装一个wd的东西,我不知道这个是干什么的,当然我也安装了,npm install wd。很简单的安装

检查Appium的配置是否正确

执行命令appium-doctor,你如果看到一下的界面,就表示已经正确的安装完毕了

  1. SvenWengdeMBP:~ svenweng$ appium-doctor Running iOS Checks ✔ Xcode is installed at /Applications/Xcode.app/Contents/Developer ✔ Xcode Command Line Tools are installed. ✔ DevToolsSecurity is enabled. ✔ The Authorization DB is set up properly. ✔ Node binary found at /usr/local/bin/node ✔ iOS Checks were successful.

  2. Running Android Checks ✔ ANDROID_HOME is set to "/Users/svenweng/Desktop/Application/adt-bundle-mac-x86_64-20131030/sdk" ✔ JAVA_HOME is set to "/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home." ✔ ADB exists at /Users/svenweng/Desktop/Application/adt-bundle-mac-x86_64-20131030/sdk/platform-tools/adb ✔ Android exists at /Users/svenweng/Desktop/Application/adt-bundle-mac-x86_64-20131030/sdk/tools/android ✔ Emulator exists at /Users/svenweng/Desktop/Application/adt-bundle-mac-x86_64-20131030/sdk/tools/emulator ✔ Android Checks were successful.

  3. ✔ All Checks were successful
复制代码

如果某一行签名不是打钩,而是一个红叉叉,那就表示那个配置没有安装正确

疑问

理论上我的Appium安装是正确的,但是我运行appium的时候出现了下面的一大串错误

  1. SvenWengdeMBP:~ svenweng$ appium error: uncaughtException: fn must be a function

  2. > See http://goo.gl/916lJJ
  3. date=Sat Nov 21 2015 10:37:25 GMT+0800 (HKT), pid=2504, uid=501, gid=20, cwd=/usr/local/lib/node_modules/appium, execPath=/usr/local/bin/node, version=v0.10.34, argv=[node, /usr/local/bin/appium], rss=103559168, heapTotal=86062080, heapUsed=56309664, loadavg=[1.6328125, 1.86767578125, 1.81103515625], uptime=39552, trace=[column=15, file=/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/node_modules/bluebird/js/main/promisify.js, function=Function.Promise.promisify, line=268, method=Promise.promisify, native=false, column=13, file=lib/fs.js, function=, line=46, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false, column=12, file=module.js, function=Function.Module._load, line=312, method=Module._load, native=false, column=17, file=module.js, function=Module.require, line=364, method=require, native=false, column=17, file=module.js, function=require, line=380, method=null, native=false, column=11, file=/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/build/lib/tempdir.js, function=, line=12, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false, column=12, file=module.js, function=Function.Module._load, line=312, method=Module._load, native=false, column=17, file=module.js, function=Module.require, line=364, method=require, native=false, column=17, file=module.js, function=require, line=380, method=null, native=false, column=19, file=/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/build/index.js, function=, line=11, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false, column=12, file=module.js, function=Function.Module._load, line=312, method=Module._load, native=false, column=17, file=module.js, function=Module.require, line=364, method=require, native=false, column=17, file=module.js, function=require, line=380, method=null, native=false, column=42, file=lib/proxy.js, function=, line=2, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false, column=12, file=module.js, function=Function.Module._load, line=312, method=Module._load, native=false, column=17, file=module.js, function=Module.require, line=364, method=require, native=false, column=17, file=module.js, function=require, line=380, method=null, native=false, column=17, file=/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/build/index.js, function=, line=9, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false, column=12, file=module.js, function=Function.Module._load, line=312, method=Module._load, native=false, column=17, file=module.js, function=Module.require, line=364, method=require, native=false, column=17, file=module.js, function=require, line=380, method=null, native=false, column=28, file=lib/chromedriver.js, function=, line=3, method=null, native=false, column=26, file=module.js, function=Module._compile, line=456, method=_compile, native=false, column=10, file=module.js, function=Object.Module._extensions..js, line=474, method=Module._extensions..js, native=false, column=32, file=module.js, function=Module.load, line=356, method=load, native=false], stack=[TypeError: fn must be a function, , See http://goo.gl/916lJJ, , at Function.Promise.promisify (/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/node_modules/bluebird/js/main/promisify.js:268:15), at Object.[HTML_REMOVED] (lib/fs.js:46:13), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32), at Function.Module._load (module.js:312:12), at Module.require (module.js:364:17), at require (module.js:380:17), at Object.[HTML_REMOVED] (/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/build/lib/tempdir.js:12:11), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32), at Function.Module._load (module.js:312:12), at Module.require (module.js:364:17), at require (module.js:380:17), at Object.[HTML_REMOVED] (/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/node_modules/appium-support/build/index.js:11:19), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32), at Function.Module._load (module.js:312:12), at Module.require (module.js:364:17), at require (module.js:380:17), at Object.[HTML_REMOVED] (lib/proxy.js:2:42), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32), at Function.Module._load (module.js:312:12), at Module.require (module.js:364:17), at require (module.js:380:17), at Object.[HTML_REMOVED] (/usr/local/lib/node_modules/appium/node_modules/appium-chromedriver/node_modules/appium-jsonwp-proxy/build/index.js:9:17), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32), at Function.Module._load (module.js:312:12), at Module.require (module.js:364:17), at require (module.js:380:17), at Object.[HTML_REMOVED] (lib/chromedriver.js:3:28), at Module._compile (module.js:456:26), at Object.Module._extensions..js (module.js:474:10), at Module.load (module.js:356:32)]
复制代码

求大神给一个解答吧。

翻墙工具正常了,我进入了命令行提示的错误网址 网址上显示的是这个 Error: fn must be a function

This page is here for the sake of completeness. There really is nothing much to add on that error. 翻译过来就是: 错误: FN必须是一个函数

这页是这里为了完整性。真的是没有什么补充该错误。


折腾了一天,终于把问题搞定了,问题的原因是因为node的版本太旧了导致的,原因分析在1楼回答的那个链接上,再说说今天自己折腾过程中遇到的问题吧,我在网上搜索帖子的时候说需要吧node卸载了才能升级(我真是天真)。于是按照步骤手贱删除了一些不应该删除的东西,结果搞的很乱,思路都乱了,不知道从何下手。于是我就去睡了一觉,醒来重新折腾,重新去官网下了一个node的安装包,把之前干掉的node环境重新部署好。再使用npm install -g appium的时候,一直会出现这个问题

  1. SvenWengdeMBP:~ svenweng$ npm install -g appium npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-10484-941ff227/registry.npmjs.org/appium-uiauto/-/appium-uiauto-1.10.10.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-10484-941ff227/registry.npmjs.org/appium-instruments/-/appium-instruments-2.0.6.tgz loadRequestedDeps → netwo ▐ ╢███████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╟ ^C SvenWengdeMBP:~ svenweng$ sudo chmod -r 777 /usr/local Password: chmod: 777: No such file or directory SvenWengdeMBP:~ svenweng$ sudo chmod -R 777 /usr/local SvenWengdeMBP:~ svenweng$ npm install -g appium loadRequestedDeps → netwo ▄ ╢███████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░╟ ^C SvenWengdeMBP:~ svenweng$ npm install -g appium npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/hoek/-/hoek-2.16.3.tgz npm ERR! tar.unpack untar error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/hoek/-/hoek-2.16.3.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz npm ERR! tar.unpack untar error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.8.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/core-js/-/core-js-0.9.18.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/appium-adb/-/appium-adb-1.7.5.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/appium-instruments/-/appium-instruments-2.0.6.tgz npm ERR! tar.unpack unzip error /var/folders/hm/04tcnt812v909nysd8jgl0100000gn/T/npm-13599-e762bf07/registry.npmjs.org/appium-uiauto/-/appium-uiauto-1.10.10.tgz npm ERR! Darwin 15.0.0 npm ERR! argv "/usr/local/bin/node" "/usr/local/bin/npm" "install" "-g" "appium" npm ERR! node v5.1.0 npm ERR! npm v3.3.12 npm ERR! code Z_BUF_ERROR npm ERR! errno -5

  2. npm ERR! unexpected end of file npm ERR! npm ERR! If you need help, you may report this error at: npm ERR! https://github.com/npm/npm/issues

  3. npm ERR! Please include the following file with any support request: npm ERR! /Users/svenweng/npm-debug.log
复制代码

每次安装到一半都安装不下去,我的电脑也是翻墙的,按道理应该也都能下载才对,具体详细的原因请恕我是小白,不太懂这个。不过我换了一个下载源就把这个问题搞定了,这里发出来,希望下次有遇到这种问题的朋友不会在这个地方浪费太多时间。命令是这个:npm -g --registry http://registry.cnpmjs.org install appium

前前后后折腾了差不多一周的环境,终于把环境的问题搞定了,希望能够更深入的学习,也给自己一个勉励。

作者:点点寒彬
原文地址: http://www.wybblog.applinzi.com/blog/article/25/







欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2