• 抱歉,我更新下邮箱地址。也可以加我微信(WillNi001),简历发送到我微信上,回复更及时。

  • 谢谢~ 我会转告他们

  • 好的,我等会把中文版的放在前面

  • 测试设计和执行的时候还是要做的,万一有问题再修补代价就大了。我一般是参考开发的意见,但执行都是从产品使用角度来执行。

  • 求助帖真多呀 at 2017年03月17日

    我觉得很多人都有一个想法/念头闪现过,就是改善测试行业。能够努力让更多的人认识到,测试人员是一个项目的标配。测试人员在项目中承担的责任和对项目的贡献是不低于开发人员的。包括刚进入这个行业的测试人员,可能也会被不重视测试的项目或管理者误导,以至于在没有完全了解测试行业所涉及、需要掌握的各种技术前,对自己的职业规划有了动摇。

    我觉得这个社区有这么多资深的人,有必要整理一个测试领域的可视化的技能树(或许已经有了)。测试人员自己可能的发展方向,评估自己的兴趣方向,沿着某一分支或多个分支深入研究。

    有了测试领域技能树的同时,让更多的人了解到:测试的每一项技能都是给项目的顺利进行增加了信心的;少了测试,项目风险是很大的。

    有很长的路要走。

  • 参数是可选的,你可以不加。直接用这个命令也可以。

    python run.py 你的case
    

    可以从 help 里面列出所有支持的参数,比如设置 loglevel

    python run.py --loglevel DEBUG testcase
    
    # 打印help
    python run.py --help
    
  • 我猜你这个问题是不是能够定位到元素,只是在点击的时候出现错误。你可以简单调试下,验证一下问题具体是出在什么地方。

    # 插入断点调试 或者用python -m pdb 脚本文件
    import pdb; pdb.set_trace()
    dr.find_element_by_id('topLoginItem').click()
    

    如果验证下来,能够定位元素但是点击有问题,十有八九是 Edge Webdriver 的问题。试用最新的 Edge Webdriver 或者用 Javascript 定位元素。先验证一下,再找解决办法。

  • 这样的话,建议调试一下,手动尝试定位那个元素看看现象:

    • 在 Selenium2Library/keywords/element.py 下面这个位置插入断点

      def click_element(self, locator):
          """Click element identified by `locator`.
          Key attributes for arbitrary elements are `id` and `name`. See
          `introduction` for details about locating elements.
          """
          self._info("Clicking element '%s'." % locator)
      
          # debug
          import pdb; pdb.set_trace()
      
          self._element_find(locator, True, True).click()
      
    • 用 robot/run.py 执行 case 进入调试模式

      run.py 参数 case文件
      
    • 在执行到 "Clicking element xpath=//*[@id="VC_SAM_SAMPLE"]"时,手动尝试用定位元素

      pdb> element = self._element_find(locator, True, True)
      pdb> dir(element)
      pdb> element.click()
      # 看看浏览器上元素是否被点击了
      # 其他方式定位元素,多调试下
      
  • 最终会结束吗?如果不是一直卡住而最终会结束的话,是有日志的。

  • 我的思路还是 robotide/__init__.py 文件出错位置前插个断点,看下运行时的搜索路径。因为从 ride 的代码逻辑上看,import wx 时捕获到异常,就是说你单独执行 ride 命令 (相当于/usr/bin/env python ride) 的时候,找不到 “/usr/local/lib/wxPython-unicode-2.8.12.1/lib/python2.7”。我的猜测是只查找了 “/System/Library/Frameworks/Python.framework/Versions/2.7”。但是需要加上断点打印一下。

    你用"python"(或者指定绝对路径的 python) 打开解释器时,sys.path 是有 wxPython 的。你可以试试指定解释器: python ride。但是上面的办法是最快最直接的。

  • 看到这些前瞻性的测试想法并工程化,受益很多。希望整个测试行业都受益。

  • 是比较诡异,是否可以进一步 debug,在 robotide/__init__.py 报错的位置前设置断点,看一下 library 查找路径是否有问题

    # robotide/__init__.py
    # 插入断点 - 调试完删除
    import pdb; pdb.set_trace()
    print sys.path
    
    try:
        import wx
    except ImportError as e:
        if "no appropriate 64-bit architecture" in e.message.lower() and \
           sys.platform == 'darwin':
            print("python should be executed in 32-bit mode with wxPython on OSX.")
        else:
            print(errorMessageTemplate.substitute(reason="wxPython not found."))
        sys.exit(1)
    
  • 性能测试及优化是比较复杂的,我的一些想法,一起讨论。

    要分析罗列所有可能影响性能的业务逻辑和搜索对象的属性。哪些会影响性能,需要在分析完成后,有针对性的构造测试数据。另外还要考虑搜索结果在前端的展示。

    • 一个搜索,可以有很多种实现方式,还有可能提供附加功能
    • 一张汽车票本身的属性和可供查询的属性也有很多
    • 有些属性可能会很大程度地影响某些业务逻辑、也可能不会
    • 构造的数据符合真实业务、非法数据会影响业务逻辑
    • 数据量 考虑到未来业务增长的趋势
    • 生产环境的配置/测试环境的配置最好匹配

  • 放心吧,不是消失,是隐藏了。文件名以"."开头的在操作系统中有特殊用途。

    # windows
    dir /ah
    
    # linux
    ls -a
    
  • 工作地点是上海吧

    工作地点:杭州市西溪园区

  • driver.keyevent(xx)

    def keyevent(self, keycode, metastate=None):
        """Sends a keycode to the device. Android only. Possible keycodes can be
        found in http://developer.android.com/reference/android/view/KeyEvent.html.
        :Args:
         - keycode - the keycode to be sent to the device
         - metastate - meta information about the keycode being sent
        """
    
  • 这个你要看一下你的 appium python client 版本,master 分支 11 月 10 号以后的 python client 加了 TOUCH_ID.

    Added touchId to driver (#143)
    Added touchId to driver
    Wrote a test for it (still need help running Python tests though). Updated capabilities to use iOS 10.1

    commit Added touchId to driver

    @@ -49,6 +49,7 @@ class MobileCommand(object):
         END_TEST_COVERAGE = 'endTestCoverage'
         LOCK = 'lock'
         SHAKE = 'shake'
    +    TOUCH_ID = 'touchId'
         RESET = 'reset'
         HIDE_KEYBOARD = 'hideKeyboard'
         REPLACE_KEYS = 'replaceKeys'
    
  • jmeter beanshell 问题 at 2017年03月14日
    问题一:

    是指 import xxx 之前,setProperty 可以用吗?我一般是这么用

    props.put("test", vars.get("rowNum"));
    
    问题二:
    props.put("test", vars.get("rowNum") + 3);
    
  • // CTRL+V (event_code 50 KEYCODE_V)
    driver.pressKeyCode(50, AndroidKeyMetastate.META_CTRL_ON);
    
    • 如果说原理的话就是
      • 创建一个 proxy,proxy 提供一个接口能够查询所有或最近的一步操作的请求
      • 创建 Webdriver 时指定该代理,webdriver 的每个操作对应的 request 都会经过 proxy
    String PROXY = "localhost";
    int PORT = 8080;
    
    com.google.gson.JsonObject json = new com.google.gson.JsonObject();
    json.addProperty("proxyType", "MANUAL");
    json.addProperty("httpProxy", PROXY);
    json.addProperty("httpProxyPort", PORT);
    json.addProperty("sslProxy", PROXY);
    json.addProperty("sslProxyPort", PORT);
    
    DesiredCapabilities cap = new DesiredCapabilities();
    cap.setCapability("proxy", json);
    
    GeckoDriverService service =new GeckoDriverService.Builder(firefoxBinary)
      .usingDriverExecutable(new File("path to geckodriver"))
      .usingAnyFreePort()
      .usingAnyFreePort()
      .build();
    service.start();
    
    // GeckoDriver currently needs the Proxy set in RequiredCapabilities
    driver = new FirefoxDriver(service, cap, cap);
    
    • 测试脚本中,webdriver 的每个操作后,从 proxy 查询这一步发送的请求和返回,校验 UI 和 response

    可以借鉴的例子

    browsermob-proxy allows you to manipulate HTTP requests and responses, capture HTTP content, and export performance data as a HAR file.

  • 提升文本框输入速度,可以用这种方式

    • adb shell input text|keyevent
    element = driver.findElement(By.id("com.calculator:id/EditText01"));
    element.click();
    Process p = Runtime.getRuntime().exec("/path/of/adb -s emulator-8088 shell input text 123456");
    

    对于你提到的这个办法,需要先从剪贴板读取再赋值

    // 安装clipper,启动服务
    // 发送到剪贴板
    Process p = Runtime.getRuntime().exec("adb shell am broadcast -a clipper.set -e text 123456");
    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
    
    // 确定剪贴板内容是想要输入的
    
    // 赋值 CTRL+V (event_code 50 KEYCODE_V)
    driver.pressKeyCode(50, AndroidKeyMetastate.META_CTRL_ON);
    
  • 没有看出明显的问题。是否可以加个 click 操作试一下。如果不行的话,建议升级 appium 后再试试。

    element = EBC_Emulator_Data.driver.find_element_by_id("com.sdwx.ebochong:id/et_license_plate")
    element.click()
    element.send_keys("a13579")
    
    info: [debug] [BOOTSTRAP] [debug] Attempting to clear using UiObject.clearText().
    info: [debug] [BOOTSTRAP] [debug] Text remains after clearing, but it appears to be hint text.
    info: [debug] [BOOTSTRAP] [debug] Text not cleared. Assuming remainder is hint text.
    info: [debug] [BOOTSTRAP] [debug] Sending plain text to element: a13579
    info: [debug] [BOOTSTRAP] [debug] Returning result: {"status":0,"value":true}
    
  • 方便把日志帖一下吗

  • 分享下自己项目中对 json 处理的一些想法。
    类 xpath 获取对应的值是很方便的,平时写 Selenium 的 case 大多用 xpath,在做 REST API 测试时,处理 json 一般是有两种办法:

    • 按照 path 取值

      ObjectMapper mapper = new ObjectMapper();
      JsonNode root = mapper.readTree(s);
      int id = root.at("/data/0/id").asInt();
      String name = root.at("/data/0/name").asText();
      
    • 定义 Python/Java 对象 mapping 到 json

    在实际使用中,如果 json 结构和深度非常复杂,第一种办法在组装 request body 和解析 response body 时会很繁琐,并且代码不再那么整洁,所以后来大多用第二种方法。

    比如
    http://h17007.www1.hpe.com/docs/enterprise/servers/oneview3.0/cic-api/en/api-docs/current/index.html#rest/server-profiles

    {
        "type": "ServerProfileV6",
        "name": "Profile101",
        "serverHardwareUri": "/rest/server-hardware/{serverUUID}",
        "affinity": "Bay",
        "macType": "Virtual",
        "serialNumberType": "Virtual",
        "wwnType": "Virtual",
        "hideUnusedFlexNics":true,
        "connections": [{
            "id": 1,
            "name":"connection1",
            "functionType": "Ethernet",
            "portId": "Flb 1:1-a",
            "requestedMbps": 2500,
            "networkUri": "/rest/ethernet-networks/{networkUUID}",
            "boot": {
                "priority": "Primary"
            }
        },
        {
            "id": 2,
            "functionType": "Ethernet",
            "portId": "Auto",
            "requestedMbps": 2500,
            "networkUri": "/rest/network-sets/{networkSetUUID}",
            "boot": {
                "priority": "Secondary"
            }
        },
        {
            "id": 3,
            "functionType": "FibreChannel",
            "portId": "Auto",
            "requestedMbps": 2500,
            "networkUri": "/rest/fc-networks/{fcNetworkID}",
            "boot": {
                "priority": "Primary",
                "bootVolumeSource": "UserDefined",
                "targets": [{
                    "arrayWwpn": "{arrayWwpn}",
                    "lun": "{lun}"
                }]
            }
        },
        {
            "id": 4,
            "functionType": "Ethernet",
            "portId": "Auto",
            "requestedMbps": 2500,
            "macType": "UserDefined",
            "mac": "12:11:11:11:00:00",
            "networkUri": "/rest/network-sets/{networkSetUUID}",
            "boot": {
                "priority": "NotBootable"
            }
        },
        {
            "id": 5,
            "functionType": "FibreChannel",
            "portId": "Auto",
            "requestedMbps": 2500,
            "wwpnType":"UserDefined",
            "wwnn":"10:00:1C:11:00:00:00:00",
            "wwpn":"10:00:1C:11:00:00:00:01",
            "macType":"UserDefined",
            "mac":"12:11:11:00:00:00",
            "networkUri": "/rest/fc-networks/{fcNetworkID}",
            "boot": {
                "priority": "Secondary",
                "bootVolumeSource": "UserDefined",
                "targets": [{
                    "arrayWwpn": "{arrayWwpn}",
                    "lun": "{lun}"
                }]
            }
        }],
        "boot": {
            "manageBoot": true,
            "order": ["PXE",
            "HardDisk",
            "CD",
            "Floppy",
            "USB"]
        },
        "bios": {
            "manageBios": true,
            "overriddenSettings": [{
                "id": "91",
                "value": "1"
            },
            {
                "id": "158",
                "value": "2"
            }]
        },
        "localStorage": {
            "sasLogicalJBODs": [
            {
                "id": 1,
                "deviceSlot": "Mezz 1",
                "name": "Data Storage",
                "numPhysicalDrives": 1,
                "driveMinSizeGB": 200,
                "driveMaxSizeGB": 600,
                "driveTechnology": "SasHdd",
                "sasLogicalJBODUri": null
            },
            {
                "id": 2,
                "deviceSlot": "Mezz 1",
                "name": "Recovery Volume",
                "numPhysicalDrives": 2,
                "driveMinSizeGB": 200,
                "driveMaxSizeGB": 600,
                "driveTechnology": "SasHdd",
                "sasLogicalJBODUri": null
            }],
        "controllers": [
            {
                "deviceSlot": "Embedded",
                "mode": "RAID",
                "initialize": false,
                "importConfiguration": false,
                "logicalDrives": [
                {
                    "name": "Operating System",
                    "raidLevel": "RAID1",
                    "bootable": true,
                    "numPhysicalDrives": 2,
                    "driveTechnology": null,
                    "sasLogicalJBODId": null
                }]
          },
          {
                "deviceSlot": "Mezz 1",
                "mode": "RAID",
                "initialize": false,
                "importConfiguration": false,
                "logicalDrives": [
                {
                    "name": null,
                    "raidLevel": "RAID0",
                    "bootable": false,
                    "numPhysicalDrives": null,
                    "driveTechnology": null,
                    "sasLogicalJBODId": 1
                },
                {
                    "name": null,
                    "raidLevel": "RAID1",
                    "bootable": false,
                    "numPhysicalDrives": null,
                    "driveTechnology": null,
                    "sasLogicalJBODId": 2
                }]
          }]
        },
        "firmware": {
            "manageFirmware": true,
            "firmwareBaselineUri": "/rest/firmware-drivers/{fwBaselineId}",
            "forceInstallFirmware": false
        }
    }
    
  • Jenkins 控制台中文乱码。 at 2017年03月12日

    看了下这个插件,master 和 slave encoding 不一致可能会导致类似问题。

    hudson/plugins/maskpasswords/MaskPasswordsOutputStream.java

    // TODO: The logic relies on the default encoding, which may cause issues when master and agent have different encodings
    @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "Open TODO item for wider rework")
    @Override
    protected void eol(byte[] bytes, int len) throws IOException {
        String line = new String(bytes, 0, len);
        if(passwordsAsPattern != null) {
            line = passwordsAsPattern.matcher(line).replaceAll(MASKED_PASSWORD);
        }
        logger.write(line.getBytes());
    }
    

    我推测的解决办法 (手头上没有测试环境模拟你的问题,我的生产环境 Jenkins encoding 为 UTF-8 的情况下没有此问题):

    如果 job 是在 master 上执行, 检查 master 的 encoding 是否为 UTF-8

    在Manage Jenkins - System Information中查看

    • LANG=en_US.UTF-8
      • Linux 上export LANG=en_US.UTF-8 或 Windows 上setx LANG en_US.UTF-8
    • file.encoding=UTF-8
      • java -Dfile.encoding=UTF-8 jenkins.war
    如果 job 是在 slave 上执行,检查 master 和 slave 的 encoding 是否为 UTF-8
    • 同上更改 Master encoding
    • 在 slave 上设置环境变量
      • Linux 上export LANG=en_US.UTF-8 或 Windows 上setx LANG en_US.UTF-8