Python+appium+yaml移动端自动化测试框架实现详解

结构介绍

之前分享过一篇安卓UI测试,但是没有实现数据与代码分离,后期维护成本较高,所以最近抽空优化了一下。

大致结构如下:

Python+appium+yaml移动端自动化测试框架实现详解_第1张图片

testyaml管理用例,实现数据与代码分离,一个模块一个文件夹

public 存放公共文件,如读取配置文件、启动appium服务、读取Yaml文件、定义日志格式等

page 存放最小测试用例集,一个模块一个文件夹

results 存放测试报告及失败截图

Python+appium+yaml移动端自动化测试框架实现详解_第2张图片

logs 存放日志

Python+appium+yaml移动端自动化测试框架实现详解_第3张图片

testcase 存放测试用例runtest.py 运行所有测试用例

​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

yaml格式介绍

首先看下yaml文件的格式,之前也写过一点关于yaml语法学习的文章
testcase部分是重点,其中:

element_info:定位元素信息

find_type:属性,id、xpath、text、ids

operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就四种

上面三个必填,operate_type必填!!!!!!

send_content:send_keys 时用到

index:ids时用到

times: 返回次数或者上滑次数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

testinfo:

 - id: cm001

  title: 新增终端门店

  execute: 1

testcase:

 -

  element_info: 客户

  find_type: text

  operate_type: click

 -

  element_info: com.fiberhome.waiqin365.client:id/cm_topbar_tv_right

  find_type: id

  operate_type: click

 -

  element_info: com.fiberhome.waiqin365.client:id/custview_id_singletv_inputtext

  find_type: ids

  operate_type: send_keys

  send_content: auto0205

  index: 0

 -

  element_info:

  find_type:

  operate_type: swipe_up

  times: 1

 -

  element_info: 提交

  find_type: text

  operate_type: click

 -

  element_info:

  find_type:

  operate_type: back

  times: 1

代码部分

公共部分

个人觉得核心的就是公共部分,相当于建房子,公共部分搞好了,后面仅仅是调用即可,建房子把架子搭好,后面就添砖加瓦吧。

读取配置文件readconfig.py
设置日志格式logs.py
获取设备GetDevices.py
这几个通用的就不做介绍了

读取yaml文件 GetYaml.py
主要用来读取yaml文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

#coding=utf-8

#author='Shichao-Dong'

  

import sys

reload(sys)

sys.setdefaultencoding('utf8')

import yaml

import codecs

  

class getyaml:

 def __init__(self,path):

  self.path = path

  

 def getYaml(self):

  '''

  读取yaml文件

  :param path: 文件路径

  :return:

  '''

  try:

   f = open(self.path)

   data =yaml.load(f)

   f.close()

   return data

  except Exception:

   print(u"未找到yaml文件")

  

 def alldata(self):

  data =self.getYaml()

  return data

  

 def caselen(self):

  data = self.alldata()

  length = len(data['testcase'])

  return length

  

 def get_elementinfo(self,i):

  data = self.alldata()

  # print data['testcase'][i]['element_info']

  return data['testcase'][i]['element_info']

  

 def get_findtype(self,i):

  data = self.alldata()

  # print data['testcase'][i]['find_type']

  return data['testcase'][i]['find_type']

  

 def get_operate_type(self,i):

  data = self.alldata()

  # print data['testcase'][i]['operate_type']

  return data['testcase'][i]['operate_type']

  

 def get_index(self,i):

  data = self.alldata()

  if self.get_findtype(i)=='ids':

     return data['testcase'][i]['index']

  else:

   pass

  

 def get_send_content(self,i):

  data = self.alldata()

  # print data['testcase'][i]['send_content']

  if self.get_operate_type(i) == 'send_keys':

   return data['testcase'][i]['send_content']

  else:

   pass

  

 def get_backtimes(self,i):

  data = self.alldata()

  if self.get_operate_type(i)=='back' or self.get_operate_type(i)=='swipe_up':

     return data['testcase'][i]['times']

  else:

   pass

  

 def get_title(self):

  data = self.alldata()

  # print data['testinfo'][0]['title']

  return data['testinfo'][0]['title']

启动appium服务 StartAppiumServer.py
主要是启动appium并返回端口port,这个port在下面的driver中需要

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

#coding=utf-8

#author='Shichao-Dong'

  

from logs import log

import random,time

import platform

import os

from GetDevices import devices

  

log = log()

dev = devices().get_deviceName()

  

class Sp:

 def __init__(self, device):

  self.device = device

  

 def __start_driver(self, aport, bpport):

  """

  :return:

  """

  if platform.system() == 'Windows':

   import subprocess

   subprocess.Popen("appium -p %s -bp %s -U %s" %

        (aport, bpport, self.device), shell=True)

  

 def start_appium(self):

  """

  启动appium

  p:appium port

  bp:bootstrap port

  :return: 返回appium端口参数

  """

  aport = random.randint(4700, 4900)

  bpport = random.randint(4700, 4900)

  self.__start_driver(aport, bpport)

  

  log.info(

   'start appium :p %s bp %s device:%s' %

   (aport, bpport, self.device))

  time.sleep(10)

  return aport

  

 def main(self):

  """

  :return: 启动appium

  """

  return self.start_appium()

  

 def stop_appium(self):

  '''

  停止appium

  :return:

  '''

  if platform.system() == 'Windows':

   os.popen("taskkill /f /im node.exe")

  

if __name__ == '__main__':

 s = Sp(dev)

 s.main()

获取driver GetDriver.py
platformName、deviceName、appPackage、appActivity这些卸载配置文件config.ini文件中,可以直接通过readconfig.py文件读取获得。
appium_port有StartAppiumServer.py文件返回

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

s = Sp(deviceName)

appium_port = s.main()

  

def mydriver():

 desired_caps = {

    'platformName':platformName,'deviceName':deviceName, 'platformVersion':platformVersion,

    'appPackage':appPackage,'appActivity':appActivity,

    'unicodeKeyboard':True,'resetKeyboard':True,'noReset':True

    }

 try:

  driver = webdriver.Remote('http://127.0.0.1:%s/wd/hub'%appium_port,desired_caps)

  time.sleep(4)

  log.info('获取driver成功')

  return driver

 except WebDriverException:

  print 'No driver'

  

if __name__ == "__main__":

 mydriver()

重新封装find等命令,BaseOperate.py
里面主要是一些上滑、返回、find等一些基础操作

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

#coding=utf-8

#author='Shichao-Dong'

  

from selenium.webdriver.support.ui import WebDriverWait

from logs import log

import os

import time

  

'''

一些基础操作:滑动、截图、点击页面元素等

'''

  

class BaseOperate:

 def __init__(self,driver):

  self.driver = driver

  

 def back(self):

  '''

  返回键

  :return:

  '''

  os.popen("adb shell input keyevent 4")

  

 def get_window_size(self):

  '''

  获取屏幕大小

  :return: windowsize

  '''

  global windowSize

  windowSize = self.driver.get_window_size()

  return windowSize

  

 def swipe_up(self):

  '''

  向上滑动

  :return:

  '''

  windowsSize = self.get_window_size()

  width = windowsSize.get("width")

  height = windowsSize.get("height")

  self.driver.swipe(width/2, height*3/4, width/2, height/4, 1000)

  

 def screenshot(self):

  now=time.strftime("%y%m%d-%H-%M-%S")

  PATH = lambda p: os.path.abspath(

   os.path.join(os.path.dirname(__file__), p)

  )

  screenshoot_path = PATH('../results/screenshoot/')

  self.driver.get_screenshot_as_file(screenshoot_path+now+'.png')

  

 def find_id(self,id):

  '''

  寻找元素

  :return:

  '''

  exsit = self.driver.find_element_by_id(id)

  if exsit :

   return True

  else:

   return False

  

 def find_name(self,name):

  '''

  判断页面是否存在某个元素

  :param name: text

  :return:

  '''

  findname = "//*[@text='%s']"%(name)

  exsit = self.driver.find_element_by_xpath(findname)

  if exsit :

   return True

  else:

   return False

  

 def get_name(self,name):

  '''

  定位页面text元素

  :param name:

  :return:

  '''

  # element = driver.find_element_by_name(name)

  # return element

  

  findname = "//*[@text='%s']"%(name)

  try:

   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(findname))

   # element = self.driver.find_element_by_xpath(findname)

   self.driver.implicitly_wait(2)

   return element

  except:

   self.screenshot()

   log.error('未定位到元素:'+'%s')%(name)

  

 def get_id(self,id):

  '''

  定位页面resouce-id元素

  :param id:

  :return:

  '''

  try:

   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_id(id))

   # element = self.driver.find_element_by_id(id)

   self.driver.implicitly_wait(2)

   return element

  except:

   self.screenshot()

   log.error('未定位到元素:'+'%s')%(id)

  

 def get_xpath(self,xpath):

  '''

  定位页面xpath元素

  :param id:

  :return:

  '''

  try:

   element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(xpath))

   # element = self.driver.find_element_by_xpath(xpath)

   self.driver.implicitly_wait(2)

   return element

  except:

   self.screenshot()

   log.error('未定位到元素:'+'%s')%(xpath)

  

 def get_ids(self,id):

  '''

  定位页面resouce-id元素组

  :param id:

  :return:列表

  '''

  try:

   # elements = self.driver.find_elements_by_id(id)

   elements = WebDriverWait(self.driver, 10).until(lambda x: x.find_elements_by_id(id))

   self.driver.implicitly_wait(2)

   return elements

  except:

   self.screenshot()

   log.error('未定位到元素:'+'%s')%(id)

  

 def page(self,name):

  '''

  返回至指定页面

  :return:

  '''

  i=0

  while i<10:

   i=i+1

   try:

    findname = "//*[@text='%s']"%(name)

    self.driver.find_element_by_xpath(findname)

    self.driver.implicitly_wait(2)

    break

   except :

    os.popen("adb shell input keyevent 4")

    try:

     findname = "//*[@text='确定']"

     self.driver.find_element_by_xpath(findname).click()

     self.driver.implicitly_wait(2)

    except:

     os.popen("adb shell input keyevent 4")

    try:

     self.driver.find_element_by_xpath("//*[@text='工作台']")

     self.driver.implicitly_wait(2)

     break

    except:

     os.popen("adb shell input keyevent 4")

Operate.py
我认为最关键的一步了,后面没有page都是调用这个文件进行测试,主要是根据读取的yaml文件,然后进行if...else...判断,根据对应的operate_type分别进行对应的click、sendkeys等操作

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

#coding=utf-8

#author='Shichao-Dong'

  

from GetYaml import getyaml

from BaseOperate import BaseOperate

  

class Operate:

 def __init__(self,path,driver):

  self.path = path

  self.driver = driver

  self.yaml = getyaml(self.path)

  self.baseoperate=BaseOperate(driver)

  

 def check_operate_type(self):

  '''

  读取yaml信息并执行

  element_info:定位元素信息

  find_type:属性,id、xpath、text、ids

  operate_type: click、sendkeys、back、swipe_up 为back就是返回,暂时就三种

  上面三个必填,operate_type必填!!!!!!

  send_content:send_keys 时用到

  index:ids时用到

  times:

  :return:

  '''

  

  for i in range(self.yaml.caselen()):

   if self.yaml.get_operate_type(i) == 'click':

    if self.yaml.get_findtype(i) == 'text':

     self.baseoperate.get_name(self.yaml.get_elementinfo(i)).click()

    elif self.yaml.get_findtype(i) == 'id':

     self.baseoperate.get_id(self.yaml.get_elementinfo(i)).click()

    elif self.yaml.get_findtype(i) == 'xpath':

     self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).click()

    elif self.yaml.get_findtype(i) == 'ids':

     self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].click()

  

   elif self.yaml.get_operate_type(i) == 'send_keys':

    if self.yaml.get_findtype(i) == 'text':

     self.baseoperate.get_name(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))

    elif self.yaml.get_findtype(i) == 'id':

     self.baseoperate.get_id(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))

    elif self.yaml.get_findtype(i) == 'xpath':

     self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))

    elif self.yaml.get_findtype(i) == 'ids':

     self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].send_keys(self.yaml.get_send_content(i))

  

   elif self.yaml.get_operate_type(i) == 'back':

    for n in range(self.yaml.get_backtimes(i)):

     self.baseoperate.back()

  

   elif self.yaml.get_operate_type(i) == 'swipe_up':

    for n in range(self.yaml.get_backtimes(i)):

     self.baseoperate.swipe_up()

  

 def back_home(self):

  '''

  返回至工作台

  :return:

  '''

  self.baseoperate.page('工作台')

公共部分的代码就介绍这么多,在编写这个框架的时候,大部分精力都花在这部分,所以个人觉得还是值得好好研究的

Page部分

page部分是最小用例集,一个模块一个文件夹,以客户为例,
目前写了两个用例,一个新增,一个排序,文件如下:

Python+appium+yaml移动端自动化测试框架实现详解_第4张图片

代码如下,非常的简洁,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

import sys

reload(sys)

sys.setdefaultencoding('utf8')

import codecs,os

from public.Operate import Operate

from public.GetYaml import getyaml

  

PATH = lambda p: os.path.abspath(

 os.path.join(os.path.dirname(__file__), p)

)

yamlpath = PATH("../../testyaml/cm/cm-001addcm.yaml")

  

class AddcmPage:

  

 def __init__(self,driver):

  self.path = yamlpath

  self.driver = driver

  self.operate = Operate(self.path,self.driver)

  

 def operateap(self):

  self.operate.check_operate_type()

  

 def home(self):

  self.operate.back_home()

运行用例

这部分用了unittest,运行所有测试用例和生成报告。
一个模块一个用例,以客户为例:CmTest.py

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

from page.cm.CmAddcmPage import AddcmPage

from page.cm.CmSortcmPage import SortcmPage

  

  

from public.GetDriver import mydriver

driver = mydriver()

  

import unittest,time

class Cm(unittest.TestCase):

  

 def test_001addcm(self):

  '''

  新增客户

  :return:

  '''

  add = AddcmPage(driver)

  add.operateap()

  add.home()

 def test_002sortcm(self):

  '''

  客户排序

  :return:

  '''

  sort = SortcmPage(driver)

  sort.sortlist()

  sort.home()

  

 def test_999close(self):

  driver.quit()

  time.sleep(10)

  

if __name__ == "__main__":

 unittest.main()

首先从page层将需要运行的用例都import进来,然后用unittest运行即可。
如果想要运行所有的测试用例,需要用到runtest.py

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

import time,os

import unittest

import HTMLTestRunner

from testcase.CmTest import Cm

  

  

def testsuit():

 suite = unittest.TestSuite()

 suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Cm),

  

  

  

  

])

  

 # runner = unittest.TextTestRunner(verbosity=2)

 # runner.run(suite)

  

 now=time.strftime("%y-%m-%d-%H-%M-%S")

 PATH = lambda p: os.path.abspath(

  os.path.join(os.path.dirname(__file__), p)

 )

 dirpath = PATH("./results/waiqin365-")

  

 filename=dirpath + now +'result.html'

 fp=open(filename,'wb')

 runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title='waiqin365 6.0.6beta test result',description=u'result:')

  

 runner.run(suite)

 fp.close()

  

if __name__ =="__main__":

 testsuit()

这边的思路差不多,也是先导入再装入suite即可

总结

就目前而言,暂时算是实现了数据与用例的分离,但是yaml的编写要求较高,不能格式上出错。
同时也有一些其他可以优化的地方,如:

  • 对弹窗的判断
  • 断开后重连机制
  • 失败后重跑机制

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

你可能感兴趣的:(python,appium,软件测试,程序员,接口测试,自动化测试,测试工程师)