Appium 启发之作 - 让基于 webdriver 的日常调试方便些

Anonymous · 2017年07月22日 · 最后由 xiaoduv587 回复于 2020年05月01日 · 2949 次阅读
本帖已被设为精华帖!

写在前面

从最初开始接触接触 webdriver 的自动化测试工具一来就一直有个小困扰, 那就是调试的时候不是很方便. 其实我们在编写脚本的时候除了设计阶段, 一定的时间是在调试测试步骤和 element 上. 但是常规的写法在调试的时候每次总是要重启 webdriver 包括 seleium 和 appium, 不是很方便. 其实这个功能在一些收费的工具上是作为基本步骤的, 比如 UFT 或者 RFT 等功能自动化工具. 前几天在论坛上看到一位同行写出了一个方式去实现在不重启 webdriver 的情况下能够方便的调试一些操作基于 androiddriver 的, 受到启发. 看孩子和热蒙了之余做了些改动和加了点自己的想法, 写了一个很小的工具, 希望能给测试的同行们提供一点方便.

参考帖子: https://testerhome.com/topics/9040(使用 java 动态加载机制模拟脚本语言的效果)

重点在这

看似理想点的操作方式是这样的, 你想操作 webdriver, 比如 selenium 的 chrome driver :

  • 启动一个 chromedriver, 可以是空白的, 或者是已经打开一个网址的 (http://www.baidu.com).
  • 写个操作, 比如往搜索框里面输入 testerhome 关键字.
driver.findElementById("kw").sendKeys("Testerhome");
  • 你执行一下,没啥问题. 然后就想在加一个 click 旁边搜索按钮一下.
driver.findElementById("su").click();
  • 但是问题来了, 你需要修改代码在跑一次,才能实现这两步操作.这就是问题所在.

其实我们在常规的操作中只是想简单点, 比如:

  • 启动一个 chromedriver, 可以是空白的, 或者是已经打开一个网址的 (http://www.baidu.com).
  • 写个操作, 比如往搜索框里面输入 testerhome 关键字.
  • 执行一下,没啥问题. 然后修改脚本添加代码 click 旁边搜索按钮一下.
  • 在整个修改和执行期间, 后面开启的浏览器, 一直在哪里, 它会伴随的你不断修改的代码,几乎时时执行,无需重启浏览器.你甚至可以随时注释掉一句代码, 在跑一次而无需重启 webdriver.

给个小结论

我们需要的只是让最开始启动的 webdriver 的 session, 比如浏览器的一直在哪里而不重置或者丢失, 就可以了. 基于此, 就可以随意的执行你的自动化代码, 看起来像动态的方式一样, 所以最开始提到的帖子里面, 同行用的是 java 的 reflect, 随时编译执行而无须重启. 我最早接触这种操作是开始于我做的一个操作数据库的轻量 IDE, 启动和 load 一些驱动和主程序用到反射这个概念, 但是并没有深入的研究它. 这次正好看到这位同行分享的方法, 受到启发, 就简单的扩展了一下.

关于这个不是工具的工具

  • 这个小工具叫 MteSenseWdd, 就是一个 jar 文件,MteSense-Wdd-beta-0.1.1.jar.
  • 你可以把这个 jar 放到任何你需要的自动化的 classpath 里面,前提是基于 webdriver 的测试.
  • 没有 java ide 的限制, eclipse, idea 都可以.
  • 适用于 webdriver 的自动化调试, 比如 chrome, firefox, safari, iosdriver, androidsriver 等.
  • 工具仅供调试只用, 不能替代你真正的业务自动化脚本, 仅是为你调试提供一些方便.
  • 想正确的调用需要你的 classpath 里面要有 selenium standalone, 和 appium java clent 的相关 jar 文件.
  • 操作方式极其简单, 真的.

举个栗子

工具的使用需要你建立两个 java 文件, 一个用来启动 webdriver(没它啥都做不了) 和调用 MteSenseLoader(这个是主要的方法), 一个是用来写你调试代码的 java 文件, 之所以要用 JAVA 文件是为了方便你使用 java ide 那些绚丽的功能, 什么敲点出方法之类的, 方便你操作的.最终你的所有调试 webdriver 的代码都只能写到第二 java 文件的指定方法里.

第一个 java 文件例子 (MteSenseWdadTest)

package com.mte.wdd.test;

import com.mte.wdd.main.MteSenseLoader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.util.concurrent.TimeUnit;

/**
 * Created by java on 20/07/2017.
 */
public class MteSenseWdadTest {

    ChromeDriver driver;

    @Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "./config/chromedriver");
        DesiredCapabilities capabilities = DesiredCapabilities.chrome();
        capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
        driver = new ChromeDriver(capabilities);

        driver.manage().timeouts()
                .pageLoadTimeout(20, TimeUnit.SECONDS);

        driver.manage().timeouts()
                .implicitlyWait(10, TimeUnit.SECONDS);

        driver.manage().timeouts()
                .setScriptTimeout(10, TimeUnit.SECONDS);
        driver.manage().window().maximize();
    }

    @Test
    public void testMain() throws Exception {

        MteSenseLoader loader = new MteSenseLoader();

        loader.senseLoader("chrome","./src/MteSenseInstanceUpdate.java", "MteSenseInstanceUpdate", "http://www.baidu.com", "runDynamicAction", driver);

    }

    @After
    public void tearDown() throws Exception {
        driver.quit();
    }

}

第二个 java 文件例子 (MteSenseInstanceUpdate), 你所有的调试代码都要写到这个文件的 runDynamicAction() 方法里面去.


import org.openqa.selenium.chrome.ChromeDriver;

/**
 * Created by java on 20/07/2017.
 */
public class MteSenseInstanceUpdate {

    public static void runDynamicAction(ChromeDriver driver) {

        driver.findElementById("kw").sendKeys("Testerhome");
        driver.findElementById("su").click();
//        driver.get("http://wwww.baidu.com");

    }

}

必须解释一下

请注意下面的代码

@Test
public void testMain() throws Exception {

    MteSenseLoader loader = new MteSenseLoader();

    loader.senseLoader("chrome","./src/MteSenseInstanceUpdate.java", "MteSenseInstanceUpdate", "http://www.baidu.com", "runDynamicAction", driver);

}

senseLoader 方法有六个参数

  • drivertype : 支持这六个关键字 chrome, firefox, safari, ios, android.
  • 用来调试的 java 文件的路径.
  • 用来调试的 java 文件的类名, 如果你没有放在 package 下那就直接些 class 名字, 如果有 package 的名字, 就需要些完整比如 com.xxx.xxx.MteSenseInstanceUpdate 之类的.
  • 需要预先打开的网址, 调试浏览器之类的需要, 不需要可以吧这个字段传入 null.
  • 用来调试的 java 文件的方法名字.
  • 你在第一个 java 文件中创建的 webdriver 的实例.这个参数我定义的类型是 RemoteWebDriver.

开始折腾把

按照栗子的步骤

  • 执行 MteSenseWdadTest, run as junit.
  • 开始修改 MteSenseInstanceUpdate 的 runDynamicAction 方法的 webdriver 的代码.
  • 完了.

无图无真相

来点注意事项

  • 你需要确认你的一个 java 文件能正常的启动 wendriver, 有不能启动的情况的可以去搜索, 这不是废话嘛.
  • 你只需要修改 runDynamicAction 这个方法里面的代码, 不要停止 MteSenseWdadTest 的运行.
  • 每次当你修改了 runDynamicAction 里面的调试代码并且 save 了, 你的操作代码就会自动执行, 所以如果你不确定就别 save.
  • 如果你使用 idea 作为 java ide, 请把"Use "Safe Write"" 选项取消掉, 在 Preferences-->Appearance&Behavior-->System Settings. 不取消会造成 一次修改 两次保存, 也就是你修改的调试代码会执行两次.
  • runDynamicAction 方法 webdriver 参数必须要和你在一个 java 文件定义的一样.
  • Junit 不是唯一的方式, 其实你怎么写都没关, 只要正确启动个 webdriver 和 MteSenseLoader 就行.
  • 这个小工具只是给大家提供点方便, 不能替代你常规的自动化业务脚本.
  • 后续有了再说.

最后陈述

  • 这个小工具只是 Beta 的 Beta 版, 我仅在 macOS 下测试了 chrome 和 iosdrver, 其他 type 如果出现问题请告诉我.
  • 你能够得到这个 jar 和一些帮助在 这里 (https://github.com/PandaSense/MteSenseWdd).
  • 只实现基本功能, 代码需要修改的, 操作基本不变,暂时先不开放, 但是你基本可以在我开始提到的帖子里面了解到.
  • 问题会有的, 大家轻喷.

最后的最后

如果觉得还对付用, 或者 能给你带来点方便, 请在 github 的项目上 点下 star, 非常感谢.

如果有更好的建议和要求, 可以讨论是否添加, 想法还是有的....

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

您好,您的点击 save 保存文件就可自动执行是怎么实现的呢?

simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08
simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 20:49
虚冰丶夜 回复

reconnect 设置为 True,也手动启动了 appium,然后跑的时候,为什么会提示说 AttributeError: 'WebDriver' object has no attribute 'session_id'呢

使用成功了,很厉害,最好支持 IE 就完美了😀 😁

试用了一下,很赞

如果是 python 的话,直接用 jupyter 即可

Anonymous 回复

没想到楼主看得这么仔细,太感谢了!!!看了你的回复 我的问题已经解决了,我就是用 0.22 版本的 jar,问题就是出在这个参数 “src/com.kdzwy.cases/MteSenseInstanceUpdate.java ”,包名路径没加斜杠,改成 “src/com/kdzwy/cases/MteSenseInstanceUpdate.java ” 就好了。还是由于对路径的写法理解不够。

灵枢 回复

我看你这个文件的路径 :src/com.kdzwy.cases/MteSenseInstanceUpdate.java
应该是 src/com/kdzwy/cases/MteSenseInstanceUpdate.java
fullclassname 应该是 : com.kdzwy.cases.MteSenseInstanceUpdate.java
?

或者尝试下 到 github 拿下最新的 0.2.2 版本的 jar

这个是我的栗子的:
MteSenseWddLoaderTest

MteSenseLoader loader = new MteSenseLoader();

MteSenseLoaderOptions options=new MteSenseLoaderOptions();

options.setLoaderOption("mtesensewdd.webDriverType","chrome");
options.setLoaderOption("mtesensewdd.fullFilePath","./src/test/MteSenseActionUpdate.java");
options.setLoaderOption("mtesensewdd.fullClassName","test.MteSenseActionUpdate");
options.setLoaderOption("mtesensewdd.url","");
options.setLoaderOption("mtesensewdd.methodName","runDynamicAction");


loader.senseLoader(options,driver);

MteSenseActionUpdate

package test;

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.HttpCommandExecutor;

import java.net.URL;

/**
 * Created by java on 20/07/2017.
 */
public class MteSenseActionUpdate {

    public String getValue(){
        return "Testerhome";
    }

    public void runDynamicAction(ChromeDriver driver) {

//        driver.get("http://wwww.baidu.com");

        driver.findElementById("kw").sendKeys(getValue());

        driver.findElementById("su").click();
}

主要的关键是 下面的参数设置

options.setLoaderOption("mtesensewdd.fullFilePath","./src/test/MteSenseActionUpdate.java");
options.setLoaderOption("mtesensewdd.fullClassName","test.MteSenseActionUpdate");

mtesensewdd.fullFilePath 指定你要编辑的操作文件的物理路径
mtesensewdd.fullClassName 代表操作文件的全名 例如栗子里的 test/MteSenseActionUpdate.java 对应 test.MteSenseActionUpdate

这样当你启动之后 就直接编辑 MteSenseActionUpdate 这个 java 文件的 指定方法, 然后 save, 只要这个文件的更新时间更改, 就会执行

灵枢 回复

能把 你的 具体代码贴出来一下吗? 我看下你的实际代码, src/com.kdzwy.cases/MteSenseInstanceUpdate.java last modified time is 0 这个 log 通常表示, 就没发现这个文件或者找这个文件出错, 也就没法在内存中编译.

Anonymous 回复

@suky2000 按照的你的帖子我实现了下,也遇到了这个报空指针的问题,应该不是参数传错了,用的 win7 系统,chrome 浏览器。

<---------------- Webdriver Dynamic Debug Start ---------------->

src/com.kdzwy.cases/MteSenseInstanceUpdate.java last modified time is 0

java.lang.NullPointerException
at com.mte.wdd.main.MteSenseLoader.loadActionMethod(MteSenseLoader.java:132)
at com.mte.wdd.main.MteSenseLoader.senseLoader(MteSenseLoader.java:118)
at com.kdzwy.cases.MteSenseWdadTest.testMain(MteSenseWdadTest.java:40)

qigao 回复

MteSenseInstanceUpdate.java 这个文件 你是在运行之前就创建了过了, 作为参数,你输入的 package+ 类名字 是正确的吗?

qigao 回复

而且即使抛出异常, 你再次修改后,save 一下, last modified time 就会改变, 就会再次编译执行...

qigao 回复

在我这边没发现这个问题, 我看 log 里面给出的 last modified time is 0 这个太奇怪了.. 你用的是什么 driver, MteSenseInstanceUpdate.java 这个文件的 package 的 full 那么之类的 参数输入都校验过吗?

常规的 更新 action 的文件,也就是 MteSenseInstanceUpdate.java, 检查 last modified time 应该是一串数值,不应该是 0

打开百度首页,就不动了,未输入内容,然后就报以下错误,不知道是何原因,请指教。
<---------------- Webdriver Dynamic Debug Start ---------------->
.\Tools\MteSenseInstanceUpdate.java last modified time is 0
java.lang.NullPointerException
<---------------- Webdriver Dynamic Debug End ---------------->
at com.mte.wdd.core.MteSenseDynamicEngine.javaCodeToObject(MteSenseDynamicEngine.java:56)

马克一下!

qigao 回复

同意,我是习惯先写个简单 py 文件,用 ipython 执行该 py,然后就可以愉快的在 ipython 中做调试了

python 交互模式非常适合做测试代码调试,appium 修改个参数保持会话长时间连接就行,网页目前长时间保持会话不断的叁数未找到。参考:基于 webdriver 的日常调试方便些 python 篇。

跟我这个配合在一起就好了:
https://github.com/fudax/selenium_recorder

bauul 回复

你好 我还是碰到了无法使用 KEYEVENT 的问题 使用那 3 种方法都没有解决 可以回复一下我的帖子吗?非常感谢

哇噢,好厉害

补充,在网页端的方法:
1.命令行启动:F:>java -jar selenium-server-standalone-2.42.2.jar

  1. 修改以下方法:
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False, file_detector=None,reconnect = False):

        if reconnect:
            sessionResponse = self.execute(Command.GET_ALL_SESSIONS)
            print sessionResponse
            if sessionResponse.has_key('sessionId') and sessionResponse['sessionId']!=None:
                self.session_id = sessionResponse['sessionId']
                self.capabilities =sessionResponse['value']
            else:
                if len(sessionResponse['value']) > 0:
                    print sessionResponse['value'][0]['id']
                    self.session_id=sessionResponse['value'][0]['id']
                    self.capabilities = sessionResponse['value']
        else:
            self.start_session(desired_capabilities, browser_profile)

3. 执行端代码

PROXY = "localhost:4444"
   # Create a copy of desired capabilities object.
   desired_capabilities = driver.DesiredCapabilities.INTERNETEXPLORER.copy()
   # Change the proxy properties of that copy.
   desired_capabilities['proxy'] = {
       "httpProxy": PROXY,
       "ftpProxy": PROXY,
       "sslProxy": PROXY,
       "noProxy": None,
       "proxyType": "MANUAL",
       "class": "org.openqa.selenium.Proxy",
       "autodetect": False
   }

   # you have to use remote, otherwise you'll have to code it yourself in python to
   # dynamically changing the system proxy preferences
   start_driver = driver.Remote("http://localhost:4444/wd/hub", desired_capabilities,reconnect=True)
虚冰丶夜 回复

不错, 晚上回去看看, 感谢分享

yuan 我的 selenium日常调试 中提及了此贴 07月24日 14:07
Anonymous 回复

appium 客户端中 webdriver 管理的部分实际上就是 selenium,上面这个命令也是 selenium 中默认就有的参考

回复

嗯, 能有不同的方式实现 挺好的, 至少多个思路. 也许以后哪里就用上了

Anonymous 回复

估计是封装的东西

虚冰丶夜 回复

看代码, 这操作应该是在创建 webdriver 之前操作的, 得到已经存在的 session,来 build webdrive.这个 sessionResponse 和 execute(Command.GET_ALL_SESSIONS),是项目自己定义的吗?还是 appium 自带的 api?

Anonymous 回复

pythonappium客户端为例:

webdriver初始化的地方添加reconnect参数

如果reconnecttrue的情况下,直接获取server端的session信息,并直接将这些信息保存至客户端脚本上下文中

调试时,脚本代码中只要将reconnect值设为true就行了

yuan 回复

http://www.cnblogs.com/xmlbw/p/4510153.html 我搜到的, 你看下 里面的 newCommandTimeout

虚冰丶夜 回复

我的方式只是保证在一个 session 期间的调试, 你的这种方式我没有试过,能分享下具体的方式吗?

appium 有个超时设置,是无操作一段时间会自动关闭 session,请问这个时间是在那里设置的?调试的时候有时会比较慢或者什么之类的就容易超时了

有点复杂,介绍下我们之前用 appium 调试的简单思路:

因为 appium 是 C/S 架构,每次测试执行的上下文信息都保存在 appium server 端,其中最主要的就是会话 session,所以,只要重写或者添加个 driver 初始化的方法,先去 server 端获取下已存在的 session 信息,就能实现无需重启的单步调试了

以后调试就方便了

😀 不错,明天试试

思寒_seveniruby 将本帖设为了精华贴 07月23日 01:50
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册