Appium WebView控件定位
阅读原文时间:2023年07月09日阅读:1

移动应用可以粗分为三种:原生应用(native app), 网页应用(web app,或HTML5 app),以及它们的混血儿——混合模式移动应用(hybrid app)。

什么是Hybrid Mobile App

Hybrid app从外观上来看是一个native app,实则只有一个UIWebView,里面访问的是一个web app,如新闻类和视频类的应用普遍采取该策略:native的框架加上web的内容。不同于native app需要针对不同的平台使用不同的开发语言(如使用Objective-C、Swift开发iOS应用,使用Java等开发Android应用,使用C#开发Windows Phone应用),hybrid app允许开发者仅使用一套网页语言代码(HTML5+CSS+JavaScript),即可开发能够在不同平台上部署的类原生应用 。由于hybrid app结合了native app良好用户交互体验和web app跨平台开发的优势,能够显著节省移动应用开发的时间和成本,hybrid app得到越来越多公司的青睐。

参考资料:

native APP,hybrid APP和web APP:https://blog.csdn.net/weixin_41646716/article/details/82190822

怎么判断是Webview控件

WebView的测试

主要作用在混合(Hybrid)的应用,一部分是原生界面和代码,而另一部分是内嵌网页,比如微信、支付宝,内嵌了一个浏览器内核,由浏览器内核实现的,安卓应用中的内嵌的展示网页内容的模块,我们称之为webview。

Appium之WebView自动化:https://www.cnblogs.com/peipei-Study/p/12012422.html

  • 模拟器,Android Studio自带的Android 6.0版本模拟器,更高版本不支持

  • genymotion的Android 6.0版本模拟器:https://www.genymotion.com

  • 木木模拟器、bluestacks默认不支持

  • 真机打开webview开关

    app修改编译 ---- 需要添加webview调用 ---- 对webview对象加入setWebContentsDebuggingEnable的调用

    直接让开发人员在下面这段代码中加入一句代码:

    protected void onCrete(Bundle saveInstanceState){

        super.onCreate(savedInstanceState);
    WebView myWebView = (WebView) findViewById(R.id.xxxwebview);
    
    myWebView.setWebContentsDebuggingEnabled(true);    #  加上这句代码</code></pre>

    };

如果不知道怎么说,简单总结一句话:“帮忙加一下webview的debug调用”就好。

在chrome中输入:chrome://inspect

使用chrome62版本,其他版本有bug,观察到webview内部结构混乱

需要代理上网

chrome://inspect/#devices HTTP/1.1 404 Not Found 空白页问题:

http://centphp.com/view/165

点击 inspect 按钮,新窗口HTTP/1.1 404 Not Found,出现空白页,这是因为Chrome 的devtools 需要联网下载对应的WebView的测试环境,appspot.com在国内不能直接访问。三种解决方法:

  • 方法一:下载devtools 的inspect的 离线开发者调试工具包。

  • 方法二: 修改网络连接,修改hosts文件。

  • 方法三:使用第三方的chromium内核的浏览器,如QQ浏览器。

进入inspect后

使用Chrome://inspect调试 Android 设备上Webview

https://www.jianshu.com/p/66896bec620e

查看webview版本

  • chrome://inspect/#devices中可以看到

  • 在手机设置中查看

  • 使用命令查看

运行脚本时报错:

selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: No Chromedriver found that can automate Chrome ‘64.0.3282’. See https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md for more details.

https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/web/chromedriver.md中提到可以在https://raw.githubusercontent.com/appium/appium-chromedriver/master/config/mapping.json中找到Chromedriver版本及其匹配的最低Chrome版本的列表,但是.json这个URL访问不了,这个URL应该可以看到类似下面的版本对应说明。


官方的说明原文

Appium通过管理Chromedriver 实例并在必要时向其代理命令来支持自动化Android网页(在Chrome和内置浏览器中)和受Chrome支持的混合应用程序。它与最新版本的Chromedriver捆绑在一起 ,并通过npm软件包appium-chromedriver (Github:appium-chromedriver)安装。

随着Chromedriver的每次更新,最低支持的Chrome版本都会增加,从而使旧版本的设备通常无法通过捆绑版本自动执行。在Appium服务器日志中,将出现如下错误:

An unknown server-side error occurred while processing the command.

Original error: unknown error: Chrome version must be >= 55.0.2883.0

要解决此问题,必须为Appium提供正确的Chromedriver二进制文件,该二进制文件应与 被测设备上运行的Chrome引擎版本匹配。阅读以下Chromedriver/Chrome compatibility主题,以了解有关查找匹配的Chromedriver可执行文件的更多信息。

有几种方法可以为Appium提供自定义的Chromedriver:

1.安装服务器时

提供--chromedriver_version包含实际版本号的命令行参数

npm install appium --chromedriver_version="2.16"

或在CHROMEDRIVER_VERSION环境变量中指定Chromedriver版本,例如,

CHROMEDRIVER_VERSION=2.20 npm install appium

也可以将其设置为LATEST获取最新版本。

2.启动服务器时

可以在运行时通过指定--chromedriver-executable服务器标志来指定Chromedriver版本 ,以及通过手动下载并放入服务器文件系统的Chromedriver可执行文件的完整路径,例如

appium --chromedriver-executable /path/to/my/chromedriver

3.开始会话时(手动发现)

通过提供chromedriverExecutable上限,可以在会话功能中指定Chromedriver版本,该 上限包含匹配的Chromedriver可执行文件的完整路径,必须手动下载该文件并将其放入服务器文件系统。有关更多详细信息,请参见http://appium.io/docs/zh-CN/writing-running-appium/caps/

4.开始会话时(自动发现)

如果本地文件系统上不存在目标Chrome引擎的版本,则Appium也可以尝试检测目标Chrome引擎的版本并自动为其下载匹配的chromedriver。阅读Automatic discovery of compatible Chromedriver下面的主题以获取更多详细信息。

Chromedriver / Chrome兼容性

您可以在https://raw.githubusercontent.com/appium/appium-chromedriver/master/config/mapping.json中找到Chromedriver版本及其匹配的最低Chrome版本的列表。

从2.46版开始,Google更改了Chromedriver版本控制的规则,因此现在主要的Chromedriver版本对应于可以自动执行的主要Web视图/浏览器版本。按照版本选择文档以手动找到Chromedriver,如果其主要版本等于或高于73,则该浏览器将支持您当前的浏览器/网络视图。

要查找较低版本的Chromedriver(低于73)的最低支持的浏览器,请获取Chromium 源代码,查看发布确认,然后检查kMinimumSupportedChromeVersion 文件中的变量src/chrome/test/chromedriver/chrome/version.cc。(要查找发布承诺,可以使用git log --pretty=format:'%h | %s%d' | grep -i "Release Chromedriver version"。)

可用的Chromedriver版本和发行说明的完整列表在此处。

自动发现兼容的Chromedriver

从Appium 1.8.0开始,Appium可以为测试中的Chrome版本选择正确的Chromedriver。虽然Appium仅在发布Appium版本时与最新发布的Chromedriver捆绑在一起,但可以下载更多Chromedriver版本并将其放置在Appium安装中(不建议这样做,因为升级Appium会删除它们)或放置在自定义位置可以向Appium指示chromedriverExecutableDir所需的功能。此功能是您放置一个或多个Chromedriver可执行文件的目录的绝对路径。

同样,由于可能会发布新版本的Chromedriver,而发布Appium版本时还没有,因此可以通过chromedriverChromeMappingFile 所需功能将自定义的Chromedriver映射到它们支持的最低Chrome版本。这应该是其中包含映射的文件的绝对路径。文件的内容需要可解析为JSON对象,例如:

{

“ 2.42 ”:“ 63.0.3239 ”,

“ 2.41 ”:“ 62.0.3202 ”

}

从Appium 1.15.0开始,可以chromedriverExecutableDir从官方的Google存储中自动下载必要的chromedriver 。该脚本将自动搜索支持给定浏览器/ Web视图的最新chromedriver版本,将其下载(哈希总和也将针对下载的档案进行验证)并添加到chromedriverChromeMappingFile映射中。您需要做的所有事情都是在chromedriver_autodownload启用功能的情况下执行服务器(例如appium --allow-insecure chromedriver_autodownload)。您还可以查看“安全性”文档,以获取有关如何控制潜在的不安全服务器功能的更多详细信息。

解决网络问题

安装Appium后,需要下载Chromedriver,因此可能存在导致安装失败的网络问题。

默认情况下,从中检索Chromedriver https://chromedriver.storage.googleapis.com/。要使用ChromeDriver二进制文件的镜像,请使用npm config属性chromedriver_cdnurl。

npm install appium-chromedriver --chromedriver_cdnurl = http://npm.taobao.org/mirrors/chromedriver

或将属性添加到.npmrc文件中。

chromedriver_cdnurl = http://npm.taobao.org/mirrors/chromedriver

另一种选择是使用PATH变量CHROMEDRIVER_CDNURL。

CHROMEDRIVER_CDNURL = http://npm.taobao.org/mirrors/chromedriver npm install appium-chromedriver

可能还需要调整网络代理和防火墙设置,以允许进行下载。

W3C支持

Chromedriver直到75版才遵循W3C标准。如果遇到类似此问题的Proxy命令错误,请更新您的Chromedriver版本。旧的Android设备不能使用较新的Chrome驱动程序。如果使用“移动JSON有线协议”运行测试,则可以避免该错误。由于主要版本75 W3C模式是Chromedriver的默认模式,尽管它仍可以根据传递的会话功能切换为JSONWP模式。您可以从下载中阅读Chromedriver中的W3C支持历史记录。


chromedriver下载地址:https://chromedriver.storage.googleapis.com/index.html

以下是在Appium测试中与webview对话所需的步骤:

  • 导航到应用程序中web页面
  • 获取上下文列表,它返回一个我们可以访问的上下文列表,例如:NATIVE_APP或WEBVIEW_1
  • 切换到你要操作的webview,原来是在NATIVE_APP,切换到WEBVIEW_1,进行相应操作
  • 操作完后,返回到原来的app view(通常为NATIVE_APP)

以雪球app进行演示

需要进行的操作,点击“交易”,进入一个webview页面,输入手机和验证码后,点击返回按钮

地址:https://github.com:yuruotong1/11period-Selenium-homework

from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

class TestWebview:
    def setup(self):
        caps = {}
        caps["platformName"] = "android"
        caps["deviceName"] = "hogwarts"
        caps["appPackage"] = "com.xueqiu.android"
        caps["appActivity"] = ".view.WelcomeActivityAlias"
        caps['autoGrantPermissions'] = True
        caps['noReset'] = True
        caps['chromedriverExecutable'] = 'D:/develop/chromedriver/2.20.exe'
        self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
        self.driver.implicitly_wait(15)

    def test_webview(self):
        # 点击交易
        self.driver.find_element(By.XPATH, '//*[@text="交易" and contains(@resource-id, "tab_name")]').click()
        # 切换到webview
        self.driver.switch_to.context(self.driver.contexts[-1])
        # 切换到webview后,使用selenium定位,点击港美股开户
        self.driver.find_element(By.CSS_SELECTOR, ".trade_home_xueying_SJY").click()
        # 切换窗口
        WebDriverWait(self.driver, 30).until(lambda x: len(self.driver.window_handles) > 3)
        self.driver.switch_to.window(self.driver.window_handles[-1])
        # 显式等待
        WebDriverWait(self.driver, 30).until(expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, '[placeholder="请输入手机号"]')))
        # 输入手机号/验证码
        self.driver.find_element(By.CSS_SELECTOR, '[placeholder="请输入手机号"]').send_keys("12345")
        self.driver.find_element(By.CSS_SELECTOR, '[placeholder="请输入验证码"]').send_keys("555555")
        # 切换回native窗口
        self.driver.switch_to.context(self.driver.contexts[0])
        # 点击返回按钮
        self.driver.find_element(By.ID, "action_bar_back").click()

思寒老师上课的例子:

def test_webview_context(self):
        self.driver.find_element(By.XPATH, "//*[@text='交易' and contains(@resource-id, 'tab')]").click()

        # 首次做测试的时候,用于分析当前的上下文
        # for i in range(5):
        #     print(self.driver.contexts)
        #     sleep(0.5)
        # print(self.driver.page_source)

        # 坑1:webview上下文出现大概有3s的延迟, android 6.0默认支持,其他的需要打开webview调试开关
        # adb shell cat /proc/net/unix | grep  webview
        WebDriverWait(self.driver, 30).until(lambda x: len(self.driver.contexts) > 1)
        # 坑2:chromedriver的版本与chrome版本必须对应
        # 坑3:chromedriver可能会存在无法对应chrome版本的情况,需要使用caps的mapping file或者直接chromedriverExecutable
        # /Users/seveniruby/projects/chromedriver/all/chromedriver_2.20 --url-base=wd/hub --port=8000 --adb-port=5037 --verbose

        self.driver.switch_to.context(self.driver.contexts[-1])
        # print(self.driver.page_source)
        # print(self.driver.window_handles)

        # 使用chrome inspect分析界面控件,需要代理、需要chrome62及以前的版本都可以
        # Proxying [POST /wd/hub/session/b2fe71d1-3dff-45df-bc2c-52e9195d5b98/element] to [POST http://127.0.0.1:8000/wd/hub/session/790fc7cf4c186545679b24ce5bbd9699/element] with body: {"using":"css selector","value":".trade_home_info_3aI"}
        self.driver.find_element(By.CSS_SELECTOR, ".trade_home_info_3aI").click()

        # 首次做测试的时候,用于分析当前的窗口
        # for i in range(5):
        #     print(self.driver.window_handles)
        #     sleep(0.5)

        # 坑4:可能会出现多窗口,所以要注意切换
        WebDriverWait(self.driver, 30).until(lambda x: len(self.driver.window_handles) > 3)
        self.driver.switch_to.window(self.driver.window_handles[-1])
        phone = (By.ID, 'phone-number')

        # html定位的常见问题,元素可以找到的时候,不代表可以交互,需要用显式等待
        WebDriverWait(self.driver, 60).until(expected_conditions.visibility_of_element_located(phone))
        self.driver.find_element(*phone).send_keys("15600534760")

https://blog.csdn.net/lb245557472/article/details/93590156提到的例子:

from appium import webdriver

mobile_desired_caps = {
    'platformName': 'Android',
    'platformVersion': '7.0',
    'deviceName': 'your device name',
    "app": r"your.apk",
    # 指定Chromedriver存放的地址,或者下边的路径,二者其一即可
    "chromedriverExecutableDir": r"C:\path",
    # "chromedriverExecutable": r"C:\path\chromedriver.exe",
    # 声明中文
    "unicodeKeyboard": 'True',
    # 声明中文,否则不支持中文
    "resetKeyboard": 'True',
    # 执行时不重新安装包
    'noReset': 'True',
    'automationName': 'uiautomator2',
    'appPackage': 'your package',
    'appActivity': 'your App activity'
}

driver = webdriver.Remote('http://localhost:4723/wd/hub', mobile_desired_caps)
# 点击进入到webview页面
driver.find_elements_by_id("id").click()
# 切换到 webview
webview = driver.contexts[-1]
driver.switch_to.context(webview)
print "###########################################"
print driver.contexts
# [u'NATIVE_APP', u'WEBVIEW_xxxxxxxxx']
# 判断webview上的元素是否存在
print driver.find_element_by_id("btnLogoutConfirmation").is_displayed()
# 返回到原生view
driver.switch_to.context(driver.contexts[0])