Appium混合APP坑汇总
我司APP大部分页面Android和iOS的客户端只提供了webview的功能,都是由H5处理业务逻辑和用户交互。H5承担了和服务端、和客户端的交互。
因业务需要,需针对部分功能做APP自动化,执行过程中遇到了一些坑,特此记录,用以备忘。
1、前提条件:(以mac系统为例)
1)安装brew
ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
2)安装JDK
3)安装python3、pycharm及相关包
brew install python3 pip install selenium pip install appium-python-client
4)配置cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
5)安装git/nodejs
brew install git brew install nodejs
5)安装appium
sudo cnpm install appium -g
6)安装appium-doctor
sudo cnpm install appium-doctor -g
7)appium-doctor监测后安装相关包
8)安装android sdk并配置环境
9)安装appium-desktop
https://github.com/appium/appium-desktop/releases/
10)安装android-studio并配置
11)下载Genymotion模拟器
2、框架选择
pyapp代码:
from lib.pyse import Pyse from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from appium.webdriver.common.mobileby import MobileBy from appium.webdriver.connectiontype import ConnectionType from lib.logger import logger class Pyapp(Pyse): def __init__(self, driver): self.d = driver @property def driver(self): return self.d def swipe_up(self, t=500, n=1): """向上滑动屏幕""" l = self.d.get_window_size() x1 = l['width'] * 0.5 # x坐标 y1 = l['height'] * 0.85 # 起始y坐标 y2 = l['height'] * 0.25 # 终点y坐标 for i in range(n): self.d.swipe(x1, y1, x1, y2, t) def swipe_down(self, t=500, n=1): """向下滑动屏幕""" l = self.d.get_window_size() x1 = l['width'] * 0.5 # x坐标 y1 = l['height'] * 0.25 # 起始y坐标 y2 = l['height'] * 0.75 # 终点y坐标 for i in range(n): self.d.swipe(x1, y1, x1, y2, t) def swipe_left(self, t=500, n=1): """向左滑动屏幕""" l = self.d.get_window_size() x1 = l['width'] * 0.75 y1 = l['height'] * 0.5 x2 = l['width'] * 0.05 for i in range(n): self.d.swipe(x1, y1, x2, y1, t) def swipe_right(self, t=500, n=1): """向右滑动屏幕""" l = self.d.get_window_size() x1 = l['width'] * 0.05 y1 = l['height'] * 0.5 x2 = l['width'] * 0.75 for i in range(n): self.d.swipe(x1, y1, x2, y1, t) def get_element(self, css): if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") self.element_wait(css) by = css.split("=>")[0] value = css.split("=>")[1] if by == "id": element = self.d.find_element_by_id(value) elif by == "name": element = self.d.find_element_by_name(value) elif by == "class": element = self.d.find_element_by_class_name(value) elif by == "link_text": element = self.d.find_element_by_link_text(value) elif by == "xpath": element = self.d.find_element_by_xpath(value) elif by == "css": element = self.d.find_element_by_css_selector(value) elif by == 'content': element = self.d.find_element_by_accessibility_id(value) elif by == 'android': element = self.d.find_element_by_android_uiautomator(value) elif by == 'ios': element = self.d.find_element_by_ios_predicate(value) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") return element def get_elements(self, css): if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") self.element_wait(css) by = css.split("=>")[0] value = css.split("=>")[1] if by == "id": elements = self.d.find_elements_by_id(value) elif by == "name": elements = self.d.find_elements_by_name(value) elif by == "class": elements = self.d.find_elements_by_class_name(value) elif by == "link_text": elements = self.d.find_elements_by_link_text(value) elif by == "xpath": elements = self.d.find_elements_by_xpath(value) elif by == "css": elements = self.d.find_elements_by_css_selector(value) elif by == 'content': elements = self.d.find_elements_by_accessibility_id(value) elif by == 'android': elements = self.d.find_elements_by_android_uiautomator(value) elif by == 'ios': elements = self.d.find_elements_by_ios_predicate(value) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") return elements def element_wait(self, css, secs=60): if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") by = css.split("=>")[0] value = css.split("=>")[1] if by == "id": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.ID, value))) elif by == "name": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.NAME, value))) elif by == "class": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.CLASS_NAME, value))) elif by == "link_text": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.LINK_TEXT, value))) elif by == "xpath": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.XPATH, value))) elif by == "css": WebDriverWait(self.d, secs, 1).until(EC.presence_of_element_located((MobileBy.CSS_SELECTOR, value))) elif by == "content": WebDriverWait(self.d, secs, 1).until( EC.presence_of_element_located((MobileBy.ACCESSIBILITY_ID, value))) elif by == 'android': WebDriverWait(self.d, secs, 1).until( EC.presence_of_element_located((MobileBy.ANDROID_UIAUTOMATOR, value))) elif by == 'ios': WebDriverWait(self.d, secs, 1).until( EC.presence_of_element_located((MobileBy.IOS_PREDICATE, value))) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") def random_action(self, css): if "=>" not in css: raise NameError("Positioning syntax errors, lack of '=>'.") by = css.split("=>")[0] value = css.split("=>")[1] try: if by == "id": return EC.visibility_of_element_located((MobileBy.ID, value)) elif by == "name": return EC.visibility_of_element_located((MobileBy.NAME, value)) elif by == "class": return EC.visibility_of_element_located((MobileBy.CLASS_NAME, value)) elif by == "link_text": return EC.visibility_of_element_located((MobileBy.LINK_TEXT, value)) elif by == "xpath": return EC.visibility_of_element_located((MobileBy.XPATH, value)) elif by == "css": return EC.visibility_of_element_located((MobileBy.CSS_SELECTOR, value)) elif by == "content": return EC.visibility_of_element_located((MobileBy.ACCESSIBILITY_ID, value)) elif by == 'android': return EC.visibility_of_element_located((MobileBy.ANDROID_UIAUTOMATOR, value)) elif by == 'ios': return EC.visibility_of_element_located((MobileBy.IOS_PREDICATE, value)) else: raise NameError( "Please enter the correct targeting elements,'id','name','class','link_text','xpath','css'.") except Exception as e: pass def key_code(self, code): ''' :param code: 按键码 :return: ''' self.d.press_keycode(code) def hide_keyboard(self): self.d.hide_keyboard() def background_app(self, second): self.d.background_app(second) @property def current_context(self): return self.d.current_context @property def context(self): return self.d.context @property def contexts(self): return self.d.contexts def switch_web_view(self): context_names = self.contexts if len(context_names) > 1: logger.debug('webview context_names is',context_names[-1]) self.d.switch_to.context(context_names[-1]) def switch_native_app(self): context_names = self.contexts self.d.switch_to.context(context_names[0]) def set_network(self, network): ''' 1、WIFI 2、数据流量 3、飞行模式 4、无网 5、全部打开 ''' if network == 1: self.d.set_network_connection(ConnectionType.WIFI_ONLY) elif network == 2: self.d.set_network_connection(ConnectionType.DATA_ONLY) elif network == 3: self.d.set_network_connection(ConnectionType.AIRPLANE_MODE) elif network == 4: self.d.set_network_connection(ConnectionType.NO_CONNECTION) elif network == 5: self.d.set_network_connection(ConnectionType.ALL_NETWORK_ON) return self def launch_app(self): return self.d.launch_app() def close_app(self): return self.d.close_app() def reset(self): return self.d.reset() def is_app_installed(self, package): return self.d.is_app_installed(package) def set_value(self, element, value): self.d.set_value(element, value) def lock(self, s): self.d.lock(s) def wait_and_save_exception(self, css, name): try: self.element_wait(css, secs=5) return True except Exception as e: from lib.path import APPPICTUREPATH import threading self.get_windows_img(APPPICTUREPATH.format(threading.current_thread().getName()) + name + '.jpg') return False
报告:BeautifulReport
3、编写代码-坑汇总
实际执行过程中发现如下问题,依次记录
1)问题:本次测试使用的模拟器,发现无法成功安装APP
原因:打包时开发去除x86支持,无法在模拟器中成功安装,app/src/AndroidManifest.xml代码中加上x86支持即可
2)问题:使用appium中,无法看到h5页面
原因:安装包未打开远程调试,需要代码中加上
3)问题:appium中看不到登录页面
原因:我司为金融类APP,开发对登录做了安全处理,导致无法看到,在代码中注释相关代码即解决
代码注释截图:(找到对应的LoginActivity类)

4)h5定位方式:
①获取webview,切换到h5(调用Pyapp模块中的switch_web_view方法)
②h5元素直接使用selenium定位方式
③之后切换回app(调用Pyapp模块中的switch_native_app方法)
5)图片验证:
原理可参照之前的博客:极验验证破解

浙公网安备 33010602011771号