Appium 求助!使用 appium2.0 版本时,driver.close() 和.quit() 关闭应用失效的问题!

zZzwen · 2024年04月11日 · 最后由 zZzwen 回复于 2024年04月23日 · 4054 次阅读

最近在使用 appium2 编写安卓自动化,appium2 的会话配置中有一项 appium:noReset: 默认为 false,即启动应用默认重置应用状态。 为了不每次启动应用都要重新登陆然后点击各种弹窗,所以这一项我一般设置为 true。 那么问题来了,我初始化 driver 写在一个 fixture 里(代码最下面),我想每次结束一个条用例就会执行这个 fixture 的后置处理 即关闭应用,目的是在执行下一条用例时启动就会在首页(统一初始页面); 但是当 appium:noReset: true 时无论是 quit() 还是 close() 都会失败,好像是因为和 appium:noReset: true 产生冲突,即 noReset: true 不让你重置关闭也不行! ; 当然把 noReset: 设置为 false,倒是可以关闭了,但是下次启动应用直接重置应用了,要中登陆开始执行了!

语言组织得有点乱, 总的来说我的目的是:在不重置的应用的情况下关闭应用, 但是 appium2.0 是必须设置为每次重置应用才能让关闭应用方法生效!

这把我搞的好烦,求各位大佬指点迷津!

@pytest.fixture()
def init_driver(request):
    # 前置
    driver = webdriver.Remote(appium_server_url, options=AppiumOptions().load_capabilities(capabilities))

    yield driver
    # 后置
    driver.close()
    # driver.quit()

共收到 13 条回复 时间 点赞

真的没人吗 555

现在用这的人少了,大部分都上云测,录脚本了

不能关闭,执行这条 driver.close() 报错是咋样的?

把你的 capabilities 贴出来看看


这是我的 caps 其实主要就是 appium:noReset: false

恒温 回复

上面发了

恒温 回复

test_case/test_login.py:18 (TestLogin.test_login[RenphoHealth-account_data0])
request = <SubRequest 'init_driver' for <Function test_login[RenphoHealth-account_data0]>>

    @pytest.fixture()
    def init_driver(request):
        # 前置
        driver = webdriver.Remote(appium_server_url, options=AppiumOptions().load_capabilities(capabilities))
        # 使用Allure的attach方法将配置信息作为文本附件添加到报告中
        attach_name = "Appium Configuration"
        attach_content = "\n".join([f"{key}: {value}" for key, value in capabilities.items()])
        allure.attach(attach_content, name=attach_name, attachment_type=allure.attachment_type.TEXT)

        yield driver
        # 后置
>       driver.close()

conftest.py:53: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py:459: in close
    self.execute(Command.CLOSE)
/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py:348: in execute
    self.error_handler.check_response(response)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <appium.webdriver.errorhandler.MobileErrorHandler object at 0x1037ed990>
response = {'status': 404, 'value': '{"value":{"error":"unknown command","message":"The requested resource could not be found, or...tory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)\\n\\tat java.lang.Thread.run(Thread.java:1012)\\n"}}'}

    def check_response(self, response: Dict[str, Any]) -> None:
        """
        https://www.w3.org/TR/webdriver/#errors
        """
        payload = response.get('value', '')
        if isinstance(payload, dict):
            payload_dict = payload
        else:
            try:
                payload_dict = json.loads(payload)
            except (json.JSONDecodeError, TypeError):
                return
            if not isinstance(payload_dict, dict):
                return
        value = payload_dict.get('value')
        if not isinstance(value, dict):
            return
        error = value.get('error')
        if not error:
            return

        message = value.get('message', error)
        stacktrace = value.get('stacktrace', '')
        # In theory, we should also be checking HTTP status codes.
        # Java client, for example, prints a warning if the actual `error`
        # value does not match to the response's HTTP status code.
        exception_class: Type[sel_exceptions.WebDriverException] = ERROR_TO_EXC_MAPPING.get(
            error, sel_exceptions.WebDriverException
        )
        if exception_class is sel_exceptions.WebDriverException and message:
            if message == 'No such context found.':
                exception_class = appium_exceptions.NoSuchContextException
            elif message == 'That command could not be executed in the current context.':
                exception_class = appium_exceptions.InvalidSwitchToTargetException

        if exception_class is sel_exceptions.UnexpectedAlertPresentException:
            raise sel_exceptions.UnexpectedAlertPresentException(
                msg=message,
                stacktrace=format_stacktrace(stacktrace),
                alert_text=value.get('data'),
            )
>       raise exception_class(msg=message, stacktrace=format_stacktrace(stacktrace))
E       selenium.common.exceptions.UnknownMethodException: Message: The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource
E       Stacktrace:
E       io.appium.uiautomator2.common.exceptions.UnknownCommandException: The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource
E           at io.appium.uiautomator2.http.ServerHandler.channelRead(ServerHandler.java:84)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)
E           at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:345)
E           at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)
E           at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:345)
E           at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:435)
E           at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:293)
E           at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:267)
E           at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:250)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)
E           at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:345)
E           at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:266)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)
E           at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:345)
E           at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1294)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:366)
E           at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:352)
E           at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:911)
E           at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
E           at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:611)
E           at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:552)
E           at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:466)
E           at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:438)
E           at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:140)
E           at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
E           at java.lang.Thread.run(Thread.java:1012)

你这里面,明显是 close 这个方法是没有的呀,参考:https://github.com/appium/python-client/blob/master/appium/webdriver/mobilecommand.py

这里面没有 close.

/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py:459: in close
    self.execute(Command.CLOSE)

你用的是的 remote,这里是有 CLOSE 的,参见:https://github.com/SeleniumHQ/selenium/blob/trunk/py/selenium/webdriver/remote/command.py

这里其实是对不上的,比较奇怪。不过 appium python client 里面用 remote 也只是用了 NEW_SESSION、FIND_ELEMENT(s)、GET_ELEMENT_ATTRIBUTE、FIND_CHILD_ELEMENT(s)、SEND_KEYS_TO_ELEMENT。本质上 appium-uiautomator2-server 里面没有实现那就应该没有了。

quit 倒是有,

remote 里面:

Command.QUIT: ("DELETE", "/session/$sessionId"),

uiautomator2 driver 里面 里面:

async deleteSession () {
  this.log.debug('Deleting UiAutomator2 server session');
  // rely on jwproxy's intelligence to know what we're talking about and
  // delete the current session
  try {
    await this.jwproxy.command('/', 'DELETE');
  } catch (err) {
    this.log.warn(`Did not get confirmation UiAutomator2 deleteSession worked; ` +
        `Error was: ${err}`);
  }
}

对应服务端的处理:
https://github.com/appium/appium-uiautomator2-server/blob/master/app/src/main/java/io/appium/uiautomator2/server/AppiumServlet.java


@Override
public void handleHttpRequest(IHttpRequest request, IHttpResponse response) {
    BaseRequestHandler handler = null;
    if ("GET".equals(request.method())) {
        handler = findMatcher(request, getHandler);
    } else if ("POST".equals(request.method())) {
        handler = findMatcher(request, postHandler);
    } else if ("DELETE".equals(request.method())) {
        handler = findMatcher(request, deleteHandler);
    }
    if (handler != null) {
        handleRequest(request, response, handler);
    }
}


driver.close() 是 selenium 的用法
driver.close_app() 才是 appium 的用法, 但是这个方法在 appium2.0 逐渐被废弃
driver.terminate_app("com.xxx.xxx") 这个是官方推荐的终止 APP 方法

zZzwen #10 · 2024年04月23日 Author
恒温 回复

感谢大佬!

zZzwen #11 · 2024年04月23日 Author
恒温 回复

这个问题确实如你所说,我用其他方式处理了, 顺便想问下大佬最近有没有用过 appium2,感觉好容易闪退啊,不知道有没有遇到过

zZzwen #12 · 2024年04月23日 Author
usky 回复

没错,我最后就是用的这个方法,想问下大佬最近有没有用过 appium2,感觉好容易闪退啊,不知道有没有遇到过

zZzwen #13 · 2024年04月23日 Author
usky 回复

话说:driver.terminate_app("com.xxx.xxx") 这个是官方推荐的终止 APP 方法,, 这些信息大佬都是在哪里看到的

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册