UiAutomator UiAutomator2 测试 H5 页面,切换 webview 后报没有找到对应的 ChromeDriver 版本

Forkey · 2020年12月24日 · 最后由 Forkey 回复于 2021年05月28日 · 6258 次阅读
测试手机:
华为/vivo手机,webView版本是78.0的(其他手机不会出现下面的问题,如:小米、Honor)
前置描述
  1. APP 已经开启 Debug 模式(用 appium 操作是没问题的)
  2. 用 Chome 浏览器查看'chrome://inspect/#devices', 版本如下
  3. 已经准备了对应的 ChomeDriver 版本
现象
  1. chromedriver 实例化后,发现会拉起本地 Chrome 浏览器
  2. 接着就报 ChromeDriver 版本不对

#### 相关日志如下:

2020-12-24 15:57:40,404 conftest:INFO:mothod:usb
[W 201224 15:57:40 __init__:203] atx-agent has something wrong, auto recovering
[D 201224 15:57:40 __init__:286] device UJKDU20611008827 is online
[I 201224 15:57:40 init:155] uiautomator2 version: 2.11.0
2020-12-24 15:57:41,438 conftest:INFO:连接设备:UJKDU20611008827
2020-12-24 15:57:44,776 conftest:INFO:启动APP成功...
2020-12-24 15:57:47,942 conftest:INFO:切换chromedriver前,先清除chromedriver进程!!
2020-12-24 15:57:49,943 conftest:INFO:start chromedriver instance
2020-12-24 15:57:49,944 conftest:INFO:chrome_driver_path: /Users/leedarson/Documents/codes/python_workplace/Arnoo_ATX2/chromedriver/Darwin/78/chromedriver
Starting ChromeDriver 78.0.3904.105 (60e2d8774a8151efa6a00b1f358371b1e0e07ee2-refs/branch-heads/3904@{#877}) on port 9527
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
FAILED

=================================== FAILURES ===================================
___________ TestDistributionNetwork.test_distribution_network_by_ap ____________

self = <cases.distribution_network.test_distribution_network.TestDistributionNetwork object at 0x104c276d0>
common_driver = <uiautomator2.Device object at 0x104c91e80>
env = <tools.mod_config.ReadConfig object at 0x104cb42e0>
log = <tools.Logger.Log object at 0x104cb41c0>, udid = 'UJKDU20611008827'
device_info = {'device_wifi': '9042', 'phone_model': 'norve7', 'phone_name': 'huawei', 'search_keyword': 'Wi-Fi F', ...}

    @allure.story('AP配网测试')
    def test_distribution_network_by_ap(self, common_driver, env, log, udid, device_info):
        allure.dynamic.title("机型[{}]-设备[{}]".format(device_info['phone_name'], device_info['device_wifi']))

        countdown = 130  # 倒计时时间
        app_package = env.get_device("app_package")
        app_activity = env.get_device("app_activity")

        search_keyword = device_info['search_keyword']
        device_wifi = device_info['device_wifi']
        phone_name = device_info['phone_name']

        enable_wifi = env.get_wifi_device_info('enable_wifi')
        wifi_password = env.get_wifi_device_info('wifi_password')
        ssid_list_wifi = env.get_wifi_device_info('ssid_list_wifi')
        statistical_duration = env.get_wifi_device_info("statistical_duration")  # 配网时长

        u2 = common_driver
        common = CommonMethod(u2, log, udid, app_package)
>       driver = ChromeDriver(u2, log, common, phone_name).driver()

cases/distribution_network/test_distribution_network.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tools/chrome_driver.py:70: in driver
    dr = webdriver.Remote('http://localhost:%d' % self._port, capabilities)
/usr/local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py:157: in __init__
    self.start_session(capabilities, browser_profile)
/usr/local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py:252: in start_session
    response = self.execute(Command.NEW_SESSION, parameters)
/usr/local/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py:321: in execute
    self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.remote.errorhandler.ErrorHandler object at 0x104cd9430>
response = {'status': 500, 'value': '{"value":{"error":"session not created","message":"session not created: This version of Chro...fff6ef68109 _pthread_start + 148\\n18  libsystem_pthread.dylib             0x00007fff6ef63b8b thread_start + 15\\n"}}'}

    def check_response(self, response):
        """
        Checks that a JSON response from the WebDriver does not have an error.

        :Args:
         - response - The JSON response from the WebDriver server as a dictionary
           object.

        :Raises: If the response contains an error message.
        """
        status = response.get('status', None)
        if status is None or status == ErrorCode.SUCCESS:
            return
        value = None
        message = response.get("message", "")
        screen = response.get("screen", "")
        stacktrace = None
        if isinstance(status, int):
            value_json = response.get('value', None)
            if value_json and isinstance(value_json, basestring):
                import json
                try:
                    value = json.loads(value_json)
                    if len(value.keys()) == 1:
                        value = value['value']
                    status = value.get('error', None)
                    if status is None:
                        status = value["status"]
                        message = value["value"]
                        if not isinstance(message, basestring):
                            value = message
                            message = message.get('message')
                    else:
                        message = value.get('message', None)
                except ValueError:
                    pass

        exception_class = ErrorInResponseException
        if status in ErrorCode.NO_SUCH_ELEMENT:
            exception_class = NoSuchElementException
        elif status in ErrorCode.NO_SUCH_FRAME:
            exception_class = NoSuchFrameException
        elif status in ErrorCode.NO_SUCH_WINDOW:
            exception_class = NoSuchWindowException
        elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
            exception_class = StaleElementReferenceException
        elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
            exception_class = ElementNotVisibleException
        elif status in ErrorCode.INVALID_ELEMENT_STATE:
            exception_class = InvalidElementStateException
        elif status in ErrorCode.INVALID_SELECTOR \
                or status in ErrorCode.INVALID_XPATH_SELECTOR \
                or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
            exception_class = InvalidSelectorException
        elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
            exception_class = ElementNotSelectableException
        elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
            exception_class = ElementNotInteractableException
        elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
            exception_class = InvalidCookieDomainException
        elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
            exception_class = UnableToSetCookieException
        elif status in ErrorCode.TIMEOUT:
            exception_class = TimeoutException
        elif status in ErrorCode.SCRIPT_TIMEOUT:
            exception_class = TimeoutException
        elif status in ErrorCode.UNKNOWN_ERROR:
            exception_class = WebDriverException
        elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
            exception_class = UnexpectedAlertPresentException
        elif status in ErrorCode.NO_ALERT_OPEN:
            exception_class = NoAlertPresentException
        elif status in ErrorCode.IME_NOT_AVAILABLE:
            exception_class = ImeNotAvailableException
        elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
            exception_class = ImeActivationFailedException
        elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
            exception_class = MoveTargetOutOfBoundsException
        elif status in ErrorCode.JAVASCRIPT_ERROR:
            exception_class = JavascriptException
        elif status in ErrorCode.SESSION_NOT_CREATED:
            exception_class = SessionNotCreatedException
        elif status in ErrorCode.INVALID_ARGUMENT:
            exception_class = InvalidArgumentException
        elif status in ErrorCode.NO_SUCH_COOKIE:
            exception_class = NoSuchCookieException
        elif status in ErrorCode.UNABLE_TO_CAPTURE_SCREEN:
            exception_class = ScreenshotException
        elif status in ErrorCode.ELEMENT_CLICK_INTERCEPTED:
            exception_class = ElementClickInterceptedException
        elif status in ErrorCode.INSECURE_CERTIFICATE:
            exception_class = InsecureCertificateException
        elif status in ErrorCode.INVALID_COORDINATES:
            exception_class = InvalidCoordinatesException
        elif status in ErrorCode.INVALID_SESSION_ID:
            exception_class = InvalidSessionIdException
        elif status in ErrorCode.UNKNOWN_METHOD:
            exception_class = UnknownMethodException
        else:
            exception_class = WebDriverException
        if value == '' or value is None:
            value = response['value']
        if isinstance(value, basestring):
            if exception_class == ErrorInResponseException:
                raise exception_class(response, value)
            raise exception_class(value)
        if message == "" and 'message' in value:
            message = value['message']

        screen = None
        if 'screen' in value:
            screen = value['screen']

        stacktrace = None
        if 'stackTrace' in value and value['stackTrace']:
            stacktrace = []
            try:
                for frame in value['stackTrace']:
                    line = self._value_or_default(frame, 'lineNumber', '')
                    file = self._value_or_default(frame, 'fileName', '<anonymous>')
                    if line:
                        file = "%s:%s" % (file, line)
                    meth = self._value_or_default(frame, 'methodName', '<anonymous>')
                    if 'className' in frame:
                        meth = "%s.%s" % (frame['className'], meth)
                    msg = "    at %s (%s)"
                    msg = msg % (meth, file)
                    stacktrace.append(msg)
            except TypeError:
                pass
        if exception_class == ErrorInResponseException:
            raise exception_class(response, message)
        elif exception_class == UnexpectedAlertPresentException:
            alert_text = None
            if 'data' in value:
                alert_text = value['data'].get('text')
            elif 'alert' in value:
                alert_text = value['alert'].get('text')
            raise exception_class(message, screen, stacktrace, alert_text)
>       raise exception_class(message, screen, stacktrace)
E       selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 78

/usr/local/lib/python3.8/site-packages/selenium/webdriver/remote/errorhandler.py:242: SessionNotCreatedException
------------------------------ Captured log setup ------------------------------

麻烦帮忙看下

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 11 条回复 时间 点赞

@codeskyblue 可以帮忙看下嘛?

自己顶一下,没人帮忙看下吗

Forkey 回复

代码都不发一下?

chromedriver 实例化后,发现会拉起本地 Chrome 浏览器

这段话很奇怪,你测试的是手机里的浏览器打开 web 页面,还是你的 app 内置 webview 里的 web 页面?如果是后者,我理解只需要实例化 appium driver,用 driver 内部切换 context 的方法来切换到 webview 就好(appium 有内置 chromedriver ),不需要实例化 chromedriver 。

最好还是直接贴一下你的脚本代码吧。

codeskyblue 回复

@codeskyblue , 不好意思,重新发一下代码

这个是 ChromeDriver 类文件,跟您写的应该是一样的

from __future__ import absolute_import

import atexit
import six
import os
import time
import platform
import psutil as pt
from selenium import webdriver

if six.PY3:
    import subprocess
    from urllib.error import URLError
else:
    from urllib3 import URLError
    import subprocess32 as subprocess


class ChromeDriver(object):
    def __init__(self, u2, log, common, phone_name, port=9527):
        self._u2 = u2
        self._port = port
        self.log = log
        self.common = common
        self.phone_name = phone_name

    def _launch_webdriver(self):
        self.log.info("start chromedriver instance")
        chrome_driver_path = self.common.select_chromedriver(self.phone_name)
        p = subprocess.Popen([chrome_driver_path, '--port=' + str(self._port)])
        try:
            p.wait(timeout=2.0)
            return False
        except subprocess.TimeoutExpired:
            return True

    def driver(self, device_ip=None, package=None, attach=True, activity=None, process=None):
        """
        Args:
            - package(string): default current running app
            - attach(bool): default true, Attach to an already-running app instead of launching the app with a clear data directory
            - activity(string): Name of the Activity hosting the WebView.
            - process(string): Process name of the Activity hosting the WebView (as given by ps).
                If not given, the process name is assumed to be the same as androidPackage.
        Returns:
            selenium driver
        """
        app = self._u2.app_current()
        print(app)
        capabilities = {
            'chromeOptions': {
                'androidDeviceSerial': device_ip or self._u2.serial,
                'androidPackage': package or app['package'],
                'androidUseRunningApp': attach,
                'androidProcess': process or app['package'],
                'androidActivity': activity or app['activity'],
            }
        }
        print(capabilities)
        # self.kill_chromedriver()
        time.sleep(2)
        self._launch_webdriver()
        dr = webdriver.Remote('http://localhost:%d' % self._port, capabilities)

        # always quit driver when done
        atexit.register(dr.quit)
        return dr

实际调用的代码如下:

driver = ChromeDriver(u2, log, common, phone_name).driver()

# 通过搜索来选择设备
search_input = common.wait_element(driver, (By.XPATH, Add_Device_Locator.search_classname_locator))
陈恒捷 回复

您好,我用的是 uiautomator2 框架, Appium 没问题😁

Forkey 回复

额,你这个回答和我的问题对不上吧。

另外,官方文档里 options 不是你代码里这个写法传进去的,而是下面这样:

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_experimental_option('androidPackage', 'com.android.chrome')
driver = webdriver.Chrome('./chromedriver', options=options)
driver.get('https://google.com')
driver.quit()

初始化启动 chromedriver 的时候就传入 options 了。
官方文档地址:https://chromedriver.chromium.org/getting-started/getting-started---android

你确定过你现在这种写法(命令行调起 chromedriver 文件,然后用 webdriver.Remote 连上 chromedriver 服务并传入 capabilities )是可用的吗?

陈恒捷 回复

您好,

  1. 我是测试 APP 里面的 webview 页面
  2. 我用 appium 操作 webview(就如您所说的 context),是没问题的
  3. 现在用上述的代码,我在小米及 Honor 是可以跑成功的,但是华为跟 vivo 就不行
  4. 我是参考这篇文章的,ATX ATX-uiautomator2 app 原生 + webview 的实际操作记录 (安卓) https://testerhome.com/topics/15998
  5. 按照您的意思是在 lunch_driver 时就应该传入配置信息吗?
Forkey 回复

建议你可以试试官方文档给的方法?

PS:部分手机可以,这个信息麻烦下次提问的时候就给出吧。。。

Forkey #10 · 2021年01月22日 Author
陈恒捷 回复

好的,谢谢

Forkey #11 · 2021年05月28日 Author

现象再复述一下:
对应 chromedriver 驱动,开启 chrome 远程端口,发现会拉起本地 Chrome 浏览器(部分机型会出现该问题)

经过测试,如下:

  1. 使用小米 CC9,webview 版本 83,会出现该问题
  2. 使用华为手机,自带的 webview,会出现该问题
  3. 使用小米 6,webview 版本 74,不会出现问题
  4. 使用三星 s8,webview 版本 73,不会出现该问题

总结:
不管什么手机的 webview,直接用 72 版本的 chromedriver 驱动(这个驱动版本稳定)就可以用,已经在以上 4 部手机实践过,没问题。

Forkey 关闭了讨论 05月28日 11:23
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册