iTesting软件测试知识分享

Appium自动化框架入门实践

如何在Windows上搭建Appium测试框架?

环境安装
  1. Download latest node and npm tools MSI (version >= 6.0). The npm and nodejs paths should be in your PATH environment variable.
  2. Open admin cmd prompt, Run the command npm install -g appium which will install Appium from NPM
  3. To start Appium, you can now simply run appium from the prompt.
    3.1 On Windows run Appium.exe as an administrator, or when running from source you need to run cmd as an administrator.
    3.2 You must supply the –no-reset or –full-reset flags for Android to work on Windows, all other flags are here
  4. Follow the directions below for setup for either Android or Windows app testing.
    4.1 Download the latest Java JDK. Set ‘JAVA_HOME’ to be your JDK path. The bin in that directory should be added to your PATH variable.
    4.2 Install the Android SDK. Set the ANDROID_HOME environment variable to be your Android SDK path and add the tools and platform-tools folders to your PATH variable.
    4.3 To run tests on Windows, you will need to have the Android Emulator booted or an Android Device connected that is running an AVD with API Level 17 or greater. Then run Appium on the command line (via the appium command)
    4.4 Your test script should ensure that the platformVersion capability corresponds to the emulator or device version you are testing, and that the app capability is an absolute path to the .apk file of the Android app.
  5. Install the Python client library pip install Appium-Python-Client
  6. Run a test from any Appium client.
    6.1 real device
    6.2 simulator–Genymotion
元素识别

我们用真机来测试,windows平台上appium只有android配置项,首先打开appium, 配置如下:
Appium配置

解释如下:
Application path: apk路径
Capabilities:
automationName Which automation engine to use Appium (default) or Selendroid
platformName Which mobile OS platform to use iOS, Android, or FirefoxOS
platformVersion Mobile OS version e.g., 7.1, 4.4
deviceName: command line 运行 adb devices 得到的结果。
noReset Don’t reset app state before this session. See here for more details true, false
fullReset Perform a complete reset. See here for more details true, false
注意,在windows平台上, fullreset 和noreset一定要选一个。

点击右上角的launch the appium node启动appium,点击inpect图标,等server起来后,在device上打开要测试的app。
Appium配置

这个就列出来当前页面的各个元素,方便我们识别元素。 对于Android来说,android SDK 有个自带的ui automation viewer也同样可以用,打开来如下(同样要求连接设备)
Appium配置

元素识别

对于mobile的元素识别,参看我之前文章:
UIAutomation Viewer定位Android APP元素
利用Android UiSelector()类来定位元素
iOS自动化测试之元素定位

简单示例

我们以我司的一个app为例,测试下内容:
1.Launch app,输入错误的用户名和密码
2.点击登录
3.验证弹出错误对话框中的text为Username or password incorrect。示例如下:
app截屏

保持上一步appium的配置,启动launch the appium node后, 代码如下:

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
import time
from appium import webdriver
class TestAPP():
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.0'
desired_caps['deviceName'] = 'a92e11aa'
desired_caps['app'] = r"D:/_Automation/Mobile_FrameWork/EvcMobile/apps/evc.apk"
def __init__(self):
self.browser = webdriver.Remote('http://localhost:4723/wd/hub', self.desired_caps)
def set_username_or_email(self, username):
time.sleep(10)
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/username']").click()
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/username']").send_keys(username)
def set_password(self, password):
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/password']").click()
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/password']").send_keys(password)
def click_login_button(self):
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/login']").click()
if __name__ == "__main__":
test = TestAPP()
test.set_username_or_email("iTesting")
test.set_password(1)
test.click_login_button()
time.sleep(1)
print (test.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/notificationText']").get_attribute('text'))
assert test.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/notificationText']").get_attribute('text') == "Username or password incorrect"

正常运行,代码没有问题,appium自动化的第一步就算完成了。

框架优化step 1 ,分离测试代码和被测试app, 并添加测试框架unittest,:
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
import unittest
import time
from appium import webdriver
class EFAPP():
def __init__(self, driver):
self.browser = driver
def set_username_or_email(self, username):
time.sleep(10)
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/username']").click()
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/username']").send_keys(username)
def set_password(self, password):
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/password']").click()
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/password']").send_keys(password)
def click_login_button(self):
self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/login']").click()
time.sleep(2)
assert self.browser.find_element_by_xpath("//*[@resource-id='com.ef.evc2015:id/notificationText']").get_attribute('text') == "Username or password incorrect"
class EFAPPTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.0'
desired_caps['deviceName'] = 'a92e11aa'
desired_caps['app'] = r"D:/_Automation/Mobile_FrameWork/EvcMobile/apps/evc.apk"
self.browser = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def test_login_failure(self):
ef_app = EFAPP(self.browser)
ef_app.set_username_or_email("iTesting")
ef_app.set_password(1)
ef_app.click_login_button()
def tearDown(self):
self.browser.quit()
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(EFAPPTests)
unittest.TextTestRunner(verbosity=2).run(suite)

运行结果如下:
运行结果

看起来像大功告成了,不过还有一个问题,所有的待测元素全都写在方法内部,不利于管理和重用,每次xpath改动,都需要改动每个方法。

框架优化step 2, 加入page_object模式。
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
import unittest
import time
from abc import ABCMeta, abstractmethod
from DateTime import DateTime
from assertpy import assert_that
from page_objects import PageObject, PageElement
from appium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class BasePage(PageObject):
__metaclass__ = ABCMeta
WAIT_FOR_SCROLL_TIME = 2
def __init__(self, driver):
# Don't change the variable name w. it's used by PageObject
self.w = driver
self.driver = driver
assert_that(self.has_loaded()).is_true()
def has_loaded(self):
try:
return self.is_target_page()
except NoSuchElementException:
return False
# This function will be implement by sub class
@abstractmethod
def is_target_page(self):
pass
def is_element_displayed(self, by, value, timeout=60):
return WebDriverWait(self.driver, timeout).until(
lambda driver: driver.find_element(by, value).is_displayed())
class EFAPP(BasePage):
EMAIL_OR_USERNAME_TEXT_FIELD_XPATH ="//*[@resource-id='com.ef.evc2015:id/username']"
PASSWORD_TEXT_FIELD_XPATH = "//*[@resource-id='com.ef.evc2015:id/password']"
LOGIN_BUTTON_XPATH = "//*[@resource-id='com.ef.evc2015:id/login']"
LOGIN_ERROR_XPATH = "//*[@resource-id='com.ef.evc2015:id/notificationText']"
email_or_username_text_field = PageElement(xpath=EMAIL_OR_USERNAME_TEXT_FIELD_XPATH)
password_text_field = PageElement(xpath=PASSWORD_TEXT_FIELD_XPATH)
login_button = PageElement(xpath=LOGIN_BUTTON_XPATH)
error_message = PageElement(xpath=LOGIN_ERROR_XPATH)
def __init__(self, driver):
BasePage.__init__(self, driver)
def is_target_page(self):
return self.is_element_displayed(By.XPATH, self.EMAIL_OR_USERNAME_TEXT_FIELD_XPATH)
def set_username_or_email(self, username):
self.email_or_username_text_field.click()
self.email_or_username_text_field.send_keys(username)
def set_password(self, password):
self.password_text_field.click()
self.password_text_field.send_keys(password)
def click_login_button(self):
self.login_button.click()
time.sleep(2)
assert self.error_message.get_attribute('text') == "Username or password incorrect"
class EFAPPTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.0'
desired_caps['deviceName'] = 'a92e11aa'
desired_caps['app'] = r"D:/_Automation/Mobile_FrameWork/EvcMobile/apps/evc.apk"
self.browser = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
def test_login_failure(self):
ef_app = EFAPP(self.browser)
ef_app.set_username_or_email("iTesting")
ef_app.set_password(1)
ef_app.click_login_button()
def tearDown(self):
self.browser.quit()
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(EFAPPTests)
unittest.TextTestRunner(verbosity=2).run(suite)

等等,是不是少了点什么? 一旦你关闭了appium,所有的测试都会失败,

框架优化step 3, 监测,启动,关闭appium。

在代码里添加如下:

1
2
3
4
5
6
7
8
9
10
11
class AppiumUtils():
def __init__(self):
#start adb
os.system('taskkill /F /IM adb.exe')
os.system('adb devices')
#launch appium
os.system('taskkill /F /IM node.exe')
command = ''' start /b "" "{node_exe_path}" "{appium_js_path}" --address "{server_ip}" --port "{port}" --command-timeout "{command_timeout}" --platform-name "{platform_name}" --platform-version "{platform_version}" --device-name "{device_name}" --app "{app}" --no-reset --log-no-color > "{log_path}" '''
os.system("%s" % command)
time.sleep(60) # wait appium start completely.

这段代码目的是为了每次自动化开始时候,杀掉所有影响启动的appium服务,以确保每次appium都能正确启动,其中,大括号包起来的是你自己需要配置的各个配置项,最后一个sleep语句我简化了,其实可以动态判断appium有没有启动,这个不深入了。

改动EFAPPTests这个类,在setUp里添加如下代码:

1
2
3
4
5
class EFAPPTests(unittest.TestCase):
def setUp(self):
AppiumUtils()
。。。

至此, 一个简单的appium测试框架就搭建成功了, 你还可以优化如下:

  1. 所有配置项单独分开放到config.py 里, 比如device信息, apk信息等。
  2. 为每一个待测的case添加一个page,page里为所有这个页面测试需要用到的元素及方法。
  3. 利用unittest这个框架,添加新的case,skip掉(@unittest.skip)不需要跑的case。
  4. 添加logging模块,记录运行信息。
  5. 添加Test Report模块,记录每次运行的结果,划分饼图,等等。
  6. 可以利用之前我讲过的ptest自动化框架组织你的用例,这样整个android自动化框架都搭建起来了。
  7. 。。。。
    不断优化,最终会生成最合适的移动端测试框架。
🐶 您的支持将鼓励我继续创作 🐶
-------------评论, 吐槽, 学习交流,请关注微信公众号 iTesting-------------
请关注微信公众号 iTesting wechat
扫码关注,跟作者互动