QT4W文档

QT4W简介

QT4W (Quick Test for Web) 是QTA自动化体系内实现支持Web自动化测试能力的支持库,是QTA的Android、iOS、Windows和Mac自动化测试中实现Web自动化测试能力的基础。

原生和Web控件的关系

要理解QT4W和QTA其他的针对原生控件的QT4x(x表示i/A/C/Mac等)的关系,可以先了解一下原生控件和Web控件的关系。

+-------------------------------+
|                               |
|  +-------------------------+  |                     +-------------------------+
|  |                         |  |                     |  +------------+         |
|  |                         |  |                     |  |            |         |
|  |                         |  |                     |  |  Element   |         |
|  |                         |  |                     |  |            |         |
|  |                         |  |                     |  +------------+         |
|  |                         |  |                     |  +------------+         |
|  |                         |  |                     |  |            |         |
|  |                         |  |                     |  |  Element   |         |
|  |                         <------------------------>  |            |         |
|  |                         |  |                     |  +------------+         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  |                         |  |                     |                         |
|  +-------------------------+  |                     +-------------------------+
|       Web View Control        |                             Web Page
|                               |
|                               |
+-------------------------------+
         Native Window

       Layers of Native                                     Layers of Web

如上图所示,我们可以理解为,所有的网页(Web Page)都是在原生控件提供的一个Web View的基础上实现的。

统一的Web控件接口

参考原生控件和Web控件的关系,QT4W也设计了类似的Web控件使用接口:

from qt4w.webcontrols import WebPage

wv = SomeNativeWebView()
page = WebPage(wx)
page.controls("XXX").click()

无论是哪个操作或终端的UI自动化的Web自动化部分,QTA都是统一使用以上模式的接口的,因此,QT4W提供的第一个功能,就是在QTA的自动化测试工具体系中,提供统一的Web控件识别和操作的接口,关于这块的更多内容,请参考《Web控件识别和操作》。

Write Once Run Anywhere

在很多情况下,Web实现的页面,是保持在跨端跨平台上一致的,很多时候也是在一份相同的代码实现的,这个是Web本身的魅力之一。因此,在这种情况下,针对跨平台跨端的Web界面,测试自动化本身也是可以做到一份测试脚本,同时在不同终端和平台上执行,实现Write Once Run Anywhere,这样可以大大的降低测试自动化的维护的成本。为了达到这样的效果,除了统一的Web控件操作接口外,QT4W还提供了统一的Webview实例化框架,也就是Browser框架,更多的详情请参考《跨端跨平台测试》。

可扩展性

在各个QT4x的支持下,QT4W目前支持多种操作系统下,不同浏览器内核和混合客户端的Web自动化。目前支持的情况如下:

WebView 平台或操作系统 说明 相关实现代码
IE Windows IE浏览器和内嵌页面使用,支持IE 7~11 由QT4C提供
Chrome Windows Chrome浏览器和内嵌页面使用 由QT4C提供
TBS Windows QQ浏览器和相关内嵌页面使用 由QT4C提供
CEF Windows Chromium内嵌页面使用 由QT4C提供
Chrome Linux Linux下的Headless模式的Chrome浏览器使用 由chrome-headless-browser提供
AndroidBuildin Android Android系统内置浏览器和内嵌页面使用 QT4A提供
X5 Android QQ移动浏览器和X5内核内嵌页面使用 QT4A提供
XWalk Android XWalk内核内嵌页面使用 AndroidWXMPLib提供
iOSBuildin iOS iOS系统内置浏览器和内嵌页面使用 QT4i提供
微信小程序&微信H5 Android微信 微信小程序或者微信内部的H5页面使用 AndroidWXMPLib提供
Chrome MacOS Mac OS下的Chrome浏览器和内嵌页面使用 由QT4Mac提供

QT4W也支持用户自定义扩展来支持更多的兼容性。更多信息请查阅《扩展QT4W》章节。

使用场景及安装

QT4W可用于各个端上的Web应用或者Native应用内嵌Web页面的自动化,其不能独立使用,需要结合其他Native层的自动化框架一起使用。各端的使用场景及安装如下所示:

  • Android端的使用及安装,请参考QT4A文档
  • iOS端的使用及安装,请参考QT4i文档
  • Windows端的使用及安装,请参考QT4C文档

Web控件的标识与使用

Web自动化的实质就是定位控件和操作控件,控件的定位问题就是如何去标识一个控件,并且根据该标识可以找到该控件。找到该控件后就可以使用QT4W提供的接口进行操作了。本部分详细描述在QT4W中是如何来描述一个控件,并且操纵该控件,进而描述一个Web页面以及封装该页面的行为。

跨端跨平台测试

QT4W的Browser抽象

QT4W将浏览器抽象为一个IBrowser对象,在该对象中定义了两个操作:

  • 打开一个URL指定的页面open_url()
  • 在打开的页面中查找和指定url匹配的页面。

在实现自己的IBrowser对象后,就可以通过该对象来操作浏览器。关于如何封装IBrowser接口参见Browser实现。 为了进一步消除,不同浏览器调用时的差异,QT4W提供了Browser类,该类继承自IBrowser。该类能够根据注册的浏览器名,获取到对应的实现实例。在QT4W中一般都通过,该类来调用具体的IBrowser实现。该类的实现,如下所示:

class Browser(IBrowser):
    '''对外的浏览器类
    '''
    browser_dict = {}  # 存储浏览器类型与浏览器类的对应关系
    def __init__(self, browser_name=None):
        '''创建具体的Browser实例
        :param browser_name: 要创建的浏览器类型
        :type browser_name:
        '''
        self._browser_name = browser_name
        self._browser = self._get_browser_cls()
    
    def _get_browser_cls(self):
        '''获取浏览器类
        '''
        if not self._browser_name:
            # 随机选择浏览器
            self._browser_name = random.choice(self.browser_dict.keys())
        browser_cls_path = self.browser_dict.get(self._browser_name)
        if not browser_cls_path:
            raise TypeError('Browser %s is not registered' % self._browser_name)
        logger.info('[Browser] Current browser type is %s' % self._browser_name)
        module = __import__('.'.join(browser_cls_path.split('.')[:-1]))
        for item in browser_cls_path.split('.')[1:]:
            module = getattr(module, item)
        return module()
    
    @staticmethod
    def register_browser(browser_name, browser_cls_path):
        '''注册浏览器
        
        :param browser_name:     浏览器名称
        :type browser_name:      string
        :param browser_cls_path: 浏览器类路径
        :type browser_cls_path:  string
        '''
        Browser.browser_dict[browser_name] = browser_cls_path
        
    def open_url(self, url, page_cls=None):
        '''打开一个url,返回page_cls类的实例
        :param url: 要打开页面的url
        :type url:  string
        :param page_cls: 要返回的具体WebPage类,为None表示返回WebPage实例
        :type page_cls: Class
        '''
        return self._browser.open_url(url, page_cls)
    
    def find_by_url(self, url, page_cls=None, timeout=10):
        '''在当前打开的页面中查找指定url,返回WebPage实例,如果未找到,返回None
        :param url: 要查找的页面url
        :type url:  string
        :param page_cls: 要返回的具体WebPage类,为None表示返回WebPage实例
        :type page_cls: Class
        :param timeout: 查找超时时间,单位:秒
        :type timeout: int/float
        '''
        page = self._browser.find_by_url(url, page_cls, timeout)
        if page == None: raise RuntimeError('Can\'t find page %s in browser %s' % (url, self._browser_name))
        return page

register_browser方法实现了浏览器名和浏览器封装实现的对应关系,并将该对应关系以字典的形式存储在类变量browser_dict中;_get_browser_cls方法会根据browser_dict变量中的信息来返回浏览器名的对应浏览器实现的Object对象。如果初始化的时候未传入浏览器名,会随机选择一个已注册的浏览器实现。如果未注册任何浏览器则会报错。这里的隐式的要求,在实现自己的IBrowser封装时,初始化时没有必传的参数。在QT4W自动化中,推荐使用该类的实例来打开或者查找页面。

Browser用法示例

在获取Browser类的实例前,先需要使用Browser.register_browser注册浏览器,在注册时,传入了两个参数:浏览器名和XXBrowser类的路径。然后再初始化Browser对象时传入注册的浏览器名,获取指定的浏览器对象,使用方法如下所示:

#注册浏览器并获取浏览器实例
 Browser.register_browser('TestBrowser', 'BrowserPath')
 browser=Browser('TestBrowser')
 
 #打开URL
 browser.open_url(https://qtacore.github.io/qt4w/demo.html,PageClass)

说明: 此处注册时传入的第二个参数指示浏览器实现所在的class,此处传入的TestBrowser给出的是TestBrowser浏览器封装的实现。

实现WORA(Write Once Run Anywhere)示例

下面以DemoPage为示例来说说明,如何使用使用Browser类实现一份QT4W测试用例,仅需做少许修改便可以在多个端上运行。

Android端执行DemoPage测试

在Android端,要想使用QT4W需要QT4A的支持。在Android端我们使用QT4A中封装的系统自带的浏览器来检查Demo页面是否运行正常。因此,我们实现了如下所示的测试用例:

class DemoTest(AndroidTestBase):
    '''QT4W示例测试用例
    '''
    owner = "testowner"
    timeout = 5
    priority = AndroidTestBase.EnumPriority.High
    status = AndroidTestBase.EnumStatus.Ready
    
    def pre_test(self):
        Browser.register_browser('TestBrowser', 'qt4a.browser.QT4ABrowser') 
        
    def run_test(self):
        self.startStep('1.设置信息并提交')
        browser = Browser("TestBrowser")
        page = browser.open_url('https://qtacore.github.io/qt4w/demo.html', DemoPage)
        page.set_name("qta")
        page.set_female()
        page.set_age(str(20))
        page.set_company("tencent")
        page.submit()

这里在pre_test中,将TestBrowser注册为QT4A中的QT4ABrowser。在具体的测试用例中,使用Browser(“TestBrowser”)获取到对应的浏览器实例类似执行DemoPage测试。

IOS端执行DemoPage测试

在IOS端,可以直接复制上述代码,然后修改一下浏览器注册部分的内容,将测试的基类改成使用IOS的测试基类即可运行。修改后如下所示:

class DemoTest(iTestCase):
    '''IOS QT4W示例测试用例
    '''
    owner = "testowner"
    timeout = 5
    priority = iTestCase.EnumPriority.High
    status = iTestCase.EnumStatus.Ready
    
    def pre_test(self):
        Browser.register_browser('TestBrowser', 'qt4i.app.Safari') 
        
    def run_test(self):
        self.startStep('1.设置信息并提交')
        browser = Browser("TestBrowser")
        page = browser.open_url('https://qtacore.github.io/qt4w/demo.html', DemoPage)
        print page.url
        page.set_name("qta")
        page.set_age(str(20))
        page.set_company("tencent")
        page.set_female()
        page.submit()

这里仅仅是将TestBrowser重新注册为了qt4i.app.Safari,同时为了用例能够在IOS平台上顺利执行,需要将DemoTest的测试基类改为继承QT4I提供的iTestCase。即可在IOS上顺利执行。

高级用法

更加方便的用法是在各端分别封装一个测试基类,并在测试基类中注册对应的浏览器。例如Android端可以封装如下测试基类,继承自Android自动化的测试基类AndroidTestBase:

class BrowserTestCase(AndroidTestBase):
    '''Browser测试用例基类
    '''
    def pre_test(self):
        super(BrowserTestCases, self).pre_test()
        # 注册Android浏览器类路径
        Browser.register_browser('TestBrowser', 'qt4a.browser.QT4ABrowser')

IOS端同样也封装一个BrowserTestCase,继承IOS自动化的测试基类iTestCase,如下:

class BrowserTestCase(iTestCase):
    '''Browser测试用例基类
    '''
    def pre_test(self):
        super(BrowserTestCases, self).pre_test()
        # 注册IOS浏览器类路径
        Browser.register_browser('TestBrowser', 'qt4i.app.Safari') 

经过如此封装后,Android和IOS两端的DemoPage自动化便可统一使用下面的测试用例:

class DemoTest(BrowserTestCase):
    '''QT4W示例测试用例
    '''
    owner = "testowner"
    timeout = 5
    priority = BrowserTestCase.EnumPriority.High
    status = BrowserTestCase.EnumStatus.Ready
    
    def run_test(self):
        self.startStep('1.设置信息并提交')
        browser = Browser("TestBrowser")
        page = browser.open_url('https://qtacore.github.io/qt4w/demo.html', DemoPage)
        page.set_name("qta")
        page.set_female()
        page.set_age(str(20))
        page.set_company("tencent")
        page.submit()

在上面的示例中,在不同端上,需要手动的去修改所引入的BrowserTestCase,Android端引入Android端封装的BrowserTestCase,IOS端运行时,需要修改为IOS端的BrowserTestCase封装。如果不想所动修改的话,可以在测试用例前使用__import__方法动态引入,package.moudle指代BrowserTestCase的路径,如果两端的实现,在同一路径下,便不需做任何更改。

BrowserTestCase=__import__(package.moudle)

跨终端的具体用例写法可以参考QT4WDemo项目

内嵌页面如何实现WORA

内嵌页面,实际上也是一个隐形的浏览器,内嵌页面实现WORA,同样必须为内嵌页面封装IBrowser类,然后使用Browser.register_browser(),方法注册该内嵌页面的浏览器封装即可。使用方式和上面相同。如何实现IBrowser封装,Android端可以参考QT4ABrowser的实现,IOS端可以参考Safari的实现。

扩展QT4W

QT4W作为一个中间层的框架,做了分层设计,提供了良好的扩展性。虽然QT4X提供的现成的封装已经能够满足大部分场景下的使用。但是某些特殊场景下,可能需要自己去扩展QT4W。 目前QT4W支持的WebView有:

WebView 平台或操作系统 说明 相关实现代码
IE Windows IE浏览器和内嵌页面使用,支持IE 7~11 由QT4C提供
Chrome Windows Chrome浏览器和内嵌页面使用 由QT4C提供
TBS Windows QQ浏览器和相关内嵌页面使用 由QT4C提供
CEF Windows Chromium内嵌页面使用 由QT4C提供
Chrome Linux Linux下的Headless模式的Chrome浏览器使用 由chrome-headless-browser提供
AndroidBuildin Android Android系统内置浏览器和内嵌页面使用 QT4A提供
X5 Android QQ移动浏览器和X5内核内嵌页面使用 QT4A提供
XWalk Android XWalk内核内嵌页面使用 AndroidWXMPLib提供
iOSBuildin iOS iOS系统内置浏览器和内嵌页面使用 QT4i提供
微信小程序 Android微信 微信小程序使用 AndroidWXMPLib提供
Chrome MacOS Mac OS下的Chrome浏览器和内嵌页面使用 由QT4Mac提供

扩展QT4W可能需要实现WebView、WebDriver或者Browser接口。例如如果需要在MAC系统上进行Chrome浏览器的自动化测试,这里就需要实现Mac上能正常运行的WebView、Browser以及WebDriver接口。下面就详细说说如何去实现这几个接口

接口文档

接口文档

qt4w package

Subpackages
qt4w.browser package
Submodules
qt4w.browser.browser module

IBrowser接口定义

class qt4w.browser.browser.Browser(browser_name=None, clear_data=True)

基类:qt4w.browser.browser.IBrowser

对外的浏览器类

browser_dict = {}
clear_data()

清除浏览器数据

close()

关闭浏览器,并清理数据 :return:

find_by_url(url, page_cls=None, timeout=10)

在当前打开的页面中查找指定url,返回WebPage实例,如果未找到,返回None

参数:
  • url (string) – 要查找的页面url
  • page_cls (Class) – 要返回的具体WebPage类,为None表示返回WebPage实例
  • timeout (int/float) – 查找超时时间,单位:秒
open_url(url, page_cls=None, invisible_mode=False)

打开一个url,返回page_cls类的实例

参数:
  • url (string) – 要打开页面的url
  • page_cls (Class) – 要返回的具体WebPage类,为None表示返回WebPage实例
  • invisible_mode (Bool) – 是否开启隐身模式
static register_browser(browser_name, browser_cls_path)

注册浏览器

参数:
  • browser_name (string) – 浏览器名称
  • browser_cls_path (string) – 浏览器类路径
class qt4w.browser.browser.IBrowser

基类:object

浏览器接口类

clear_data()

清除浏览器数据

close()

关闭浏览器,并清理数据 :return:

find_by_url(url, page_cls=None)

在当前打开的页面中查找指定url,返回page_cls类的实例,如果未找到,返回None

参数:
  • url (string) – 要查找的页面url
  • page_cls (Class) – 要返回的具体WebPage类,为None表示返回WebPage实例
open_url(url, page_cls=None, invisible_mode=False)

打开一个url,返回page_cls类的实例

参数:
  • url (string) – 要打开页面的url
  • page_cls (Class) – 要返回的具体WebPage类,为None表示返回WebPage实例
  • invisible_mode (Bool) – 是否开启隐身模式
Module contents

IBrowser接口定义及Windows实现

qt4w.webdriver package
Submodules
qt4w.webdriver.iewebdriver module

IE的WebDriver实现

class qt4w.webdriver.iewebdriver.IEWebDriver(webview)

基类:qt4w.webdriver.webdriver.WebDriverBase

IE的WebDriver实现

driver_script = '/*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */\n(function(){function N(p,r){function q(a){if(q[a]!==w)return q[a];var c;if("bug-string-char-index"==a)c="a"!="a"[0];else if("json"==a)c=q("json-stringify")&&q("json-parse");else{var e;if("json-stringify"==a){c=r.stringify;var b="function"==typeof c&&s;if(b){(e=function(){return 1}).toJSON=e;try{b="0"===c(0)&&"0"===c(new t)&&\'""\'==c(new A)&&c(u)===w&&c(w)===w&&c()===w&&"1"===c(e)&&"[1]"==c([e])&&"[null]"==c([w])&&"null"==c(null)&&"[null,null,null]"==c([w,u,null])&&\'{"a":[1,true,false,null,"\\\\u0000\\\\b\\\\n\\\\f\\\\r\\\\t"]}\'==\nc({a:[e,!0,!1,null,"\\x00\\b\\n\\f\\r\\t"]})&&"1"===c(null,e)&&"[\\n 1,\\n 2\\n]"==c([1,2],null,1)&&\'"-271821-04-20T00:00:00.000Z"\'==c(new C(-864E13))&&\'"+275760-09-13T00:00:00.000Z"\'==c(new C(864E13))&&\'"-000001-01-01T00:00:00.000Z"\'==c(new C(-621987552E5))&&\'"1969-12-31T23:59:59.999Z"\'==c(new C(-1))}catch(f){b=!1}}c=b}if("json-parse"==a){c=r.parse;if("function"==typeof c)try{if(0===c("0")&&!c(!1)){e=c(\'{"a":[1,true,false,null,"\\\\u0000\\\\b\\\\n\\\\f\\\\r\\\\t"]}\');var n=5==e.a.length&&1===e.a[0];if(n){try{n=!c(\'"\\t"\')}catch(d){}if(n)try{n=\n1!==c("01")}catch(g){}if(n)try{n=1!==c("1.")}catch(m){}}}}catch(X){n=!1}c=n}}return q[a]=!!c}p||(p=k.Object());r||(r=k.Object());var t=p.Number||k.Number,A=p.String||k.String,H=p.Object||k.Object,C=p.Date||k.Date,G=p.SyntaxError||k.SyntaxError,K=p.TypeError||k.TypeError,L=p.Math||k.Math,I=p.JSON||k.JSON;"object"==typeof I&&I&&(r.stringify=I.stringify,r.parse=I.parse);var H=H.prototype,u=H.toString,v,B,w,s=new C(-0xc782b5b800cec);try{s=-109252==s.getUTCFullYear()&&0===s.getUTCMonth()&&1===s.getUTCDate()&&\n10==s.getUTCHours()&&37==s.getUTCMinutes()&&6==s.getUTCSeconds()&&708==s.getUTCMilliseconds()}catch(Q){}if(!q("json")){var D=q("bug-string-char-index");if(!s)var x=L.floor,M=[0,31,59,90,120,151,181,212,243,273,304,334],E=function(a,c){return M[c]+365*(a-1970)+x((a-1969+(c=+(1<c)))/4)-x((a-1901+c)/100)+x((a-1601+c)/400)};(v=H.hasOwnProperty)||(v=function(a){var c={},e;(c.__proto__=null,c.__proto__={toString:1},c).toString!=u?v=function(a){var c=this.__proto__;a=a in(this.__proto__=null,this);this.__proto__=\nc;return a}:(e=c.constructor,v=function(a){var c=(this.constructor||e).prototype;return a in this&&!(a in c&&this[a]===c[a])});c=null;return v.call(this,a)});B=function(a,c){var e=0,b,f,n;(b=function(){this.valueOf=0}).prototype.valueOf=0;f=new b;for(n in f)v.call(f,n)&&e++;b=f=null;e?B=2==e?function(a,c){var e={},b="[object Function]"==u.call(a),f;for(f in a)b&&"prototype"==f||v.call(e,f)||!(e[f]=1)||!v.call(a,f)||c(f)}:function(a,c){var e="[object Function]"==u.call(a),b,f;for(b in a)e&&"prototype"==\nb||!v.call(a,b)||(f="constructor"===b)||c(b);(f||v.call(a,b="constructor"))&&c(b)}:(f="valueOf toString toLocaleString propertyIsEnumerable isPrototypeOf hasOwnProperty constructor".split(" "),B=function(a,c){var e="[object Function]"==u.call(a),b,h=!e&&"function"!=typeof a.constructor&&F[typeof a.hasOwnProperty]&&a.hasOwnProperty||v;for(b in a)e&&"prototype"==b||!h.call(a,b)||c(b);for(e=f.length;b=f[--e];h.call(a,b)&&c(b));});return B(a,c)};if(!q("json-stringify")){var U={92:"\\\\\\\\",34:\'\\\\"\',8:"\\\\b",\n12:"\\\\f",10:"\\\\n",13:"\\\\r",9:"\\\\t"},y=function(a,c){return("000000"+(c||0)).slice(-a)},R=function(a){for(var c=\'"\',b=0,h=a.length,f=!D||10<h,n=f&&(D?a.split(""):a);b<h;b++){var d=a.charCodeAt(b);switch(d){case 8:case 9:case 10:case 12:case 13:case 34:case 92:c+=U[d];break;default:if(32>d){c+="\\\\u00"+y(2,d.toString(16));break}c+=f?n[b]:a.charAt(b)}}return c+\'"\'},O=function(a,c,b,h,f,n,d){var g,m,k,l,p,r,s,t,q;try{g=c[a]}catch(z){}if("object"==typeof g&&g)if(m=u.call(g),"[object Date]"!=m||v.call(g,\n"toJSON"))"function"==typeof g.toJSON&&("[object Number]"!=m&&"[object String]"!=m&&"[object Array]"!=m||v.call(g,"toJSON"))&&(g=g.toJSON(a));else if(g>-1/0&&g<1/0){if(E){l=x(g/864E5);for(m=x(l/365.2425)+1970-1;E(m+1,0)<=l;m++);for(k=x((l-E(m,0))/30.42);E(m,k+1)<=l;k++);l=1+l-E(m,k);p=(g%864E5+864E5)%864E5;r=x(p/36E5)%24;s=x(p/6E4)%60;t=x(p/1E3)%60;p%=1E3}else m=g.getUTCFullYear(),k=g.getUTCMonth(),l=g.getUTCDate(),r=g.getUTCHours(),s=g.getUTCMinutes(),t=g.getUTCSeconds(),p=g.getUTCMilliseconds();\ng=(0>=m||1E4<=m?(0>m?"-":"+")+y(6,0>m?-m:m):y(4,m))+"-"+y(2,k+1)+"-"+y(2,l)+"T"+y(2,r)+":"+y(2,s)+":"+y(2,t)+"."+y(3,p)+"Z"}else g=null;b&&(g=b.call(c,a,g));if(null===g)return"null";m=u.call(g);if("[object Boolean]"==m)return""+g;if("[object Number]"==m)return g>-1/0&&g<1/0?""+g:"null";if("[object String]"==m)return R(""+g);if("object"==typeof g){for(a=d.length;a--;)if(d[a]===g)throw K();d.push(g);q=[];c=n;n+=f;if("[object Array]"==m){k=0;for(a=g.length;k<a;k++)m=O(k,g,b,h,f,n,d),q.push(m===w?"null":\nm);a=q.length?f?"[\\n"+n+q.join(",\\n"+n)+"\\n"+c+"]":"["+q.join(",")+"]":"[]"}else B(h||g,function(a){var c=O(a,g,b,h,f,n,d);c!==w&&q.push(R(a)+":"+(f?" ":"")+c)}),a=q.length?f?"{\\n"+n+q.join(",\\n"+n)+"\\n"+c+"}":"{"+q.join(",")+"}":"{}";d.pop();return a}};r.stringify=function(a,c,b){var h,f,n,d;if(F[typeof c]&&c)if("[object Function]"==(d=u.call(c)))f=c;else if("[object Array]"==d){n={};for(var g=0,k=c.length,l;g<k;l=c[g++],(d=u.call(l),"[object String]"==d||"[object Number]"==d)&&(n[l]=1));}if(b)if("[object Number]"==\n(d=u.call(b))){if(0<(b-=b%1))for(h="",10<b&&(b=10);h.length<b;h+=" ");}else"[object String]"==d&&(h=10>=b.length?b:b.slice(0,10));return O("",(l={},l[""]=a,l),f,n,h,"",[])}}if(!q("json-parse")){var V=A.fromCharCode,W={92:"\\\\",34:\'"\',47:"/",98:"\\b",116:"\\t",110:"\\n",102:"\\f",114:"\\r"},b,J,l=function(){b=J=null;throw G();},z=function(){for(var a=J,c=a.length,e,h,f,k,d;b<c;)switch(d=a.charCodeAt(b),d){case 9:case 10:case 13:case 32:b++;break;case 123:case 125:case 91:case 93:case 58:case 44:return e=\nD?a.charAt(b):a[b],b++,e;case 34:e="@";for(b++;b<c;)if(d=a.charCodeAt(b),32>d)l();else if(92==d)switch(d=a.charCodeAt(++b),d){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:e+=W[d];b++;break;case 117:h=++b;for(f=b+4;b<f;b++)d=a.charCodeAt(b),48<=d&&57>=d||97<=d&&102>=d||65<=d&&70>=d||l();e+=V("0x"+a.slice(h,b));break;default:l()}else{if(34==d)break;d=a.charCodeAt(b);for(h=b;32<=d&&92!=d&&34!=d;)d=a.charCodeAt(++b);e+=a.slice(h,b)}if(34==a.charCodeAt(b))return b++,e;l();default:h=\nb;45==d&&(k=!0,d=a.charCodeAt(++b));if(48<=d&&57>=d){for(48==d&&(d=a.charCodeAt(b+1),48<=d&&57>=d)&&l();b<c&&(d=a.charCodeAt(b),48<=d&&57>=d);b++);if(46==a.charCodeAt(b)){for(f=++b;f<c&&(d=a.charCodeAt(f),48<=d&&57>=d);f++);f==b&&l();b=f}d=a.charCodeAt(b);if(101==d||69==d){d=a.charCodeAt(++b);43!=d&&45!=d||b++;for(f=b;f<c&&(d=a.charCodeAt(f),48<=d&&57>=d);f++);f==b&&l();b=f}return+a.slice(h,b)}k&&l();if("true"==a.slice(b,b+4))return b+=4,!0;if("false"==a.slice(b,b+5))return b+=5,!1;if("null"==a.slice(b,\nb+4))return b+=4,null;l()}return"$"},P=function(a){var c,b;"$"==a&&l();if("string"==typeof a){if("@"==(D?a.charAt(0):a[0]))return a.slice(1);if("["==a){for(c=[];;b||(b=!0)){a=z();if("]"==a)break;b&&(","==a?(a=z(),"]"==a&&l()):l());","==a&&l();c.push(P(a))}return c}if("{"==a){for(c={};;b||(b=!0)){a=z();if("}"==a)break;b&&(","==a?(a=z(),"}"==a&&l()):l());","!=a&&"string"==typeof a&&"@"==(D?a.charAt(0):a[0])&&":"==z()||l();c[a.slice(1)]=P(z())}return c}l()}return a},T=function(a,b,e){e=S(a,b,e);e===\nw?delete a[b]:a[b]=e},S=function(a,b,e){var h=a[b],f;if("object"==typeof h&&h)if("[object Array]"==u.call(h))for(f=h.length;f--;)T(h,f,e);else B(h,function(a){T(h,a,e)});return e.call(a,b,h)};r.parse=function(a,c){var e,h;b=0;J=""+a;e=P(z());"$"!=z()&&l();b=J=null;return c&&"[object Function]"==u.call(c)?S((h={},h[""]=e,h),"",c):e}}}r.runInContext=N;return r}var K=typeof define==="function"&&define.amd,F={"function":!0,object:!0},G=F[typeof exports]&&exports&&!exports.nodeType&&exports,k=F[typeof window]&&\nwindow||this,t=G&&F[typeof module]&&module&&!module.nodeType&&"object"==typeof global&&global;!t||t.global!==t&&t.window!==t&&t.self!==t||(k=t);if(G&&!K)N(k,G);else{var L=k.JSON,Q=k.JSON3,M=!1,A=N(k,k.JSON3={noConflict:function(){M||(M=!0,k.JSON=L,k.JSON3=Q,L=Q=null);return A}});k.JSON={parse:A.parse,stringify:A.stringify}}K&&define(function(){return A})}).call(this);\n\n if ( !window.Element || !Element.prototype || !(document.documentElement instanceof Element)) {\n var __Element = window.Element;\n var __prototype = window.Element ? Element.prototype : null;\n window.Element = function(){};\n if (!__prototype) {\n // copy origin attrs\n for(var key in __Element)\n Element.prototype[key] = __Element[key];\n }\n var _hookElement = function (element) {\n if (!element) return;\n if (!element.hooked) {\n console.log(\'hook \' + element);\n for(var key in Element.prototype) {\n try{\n element[key] = Element.prototype[key];\n }catch(e){\n console.warn(element.outerHTML + \':\' + key + \':\' + e.message);\n }\n }\n element.hooked = true;\n }\n }\n\n var hookAllElements = function () {\n var elements = [document.documentElement];\n while (elements.length > 0) {\n var element = elements.splice(0, 1)[0];\n if (!element) continue;\n _hookElement(element);\n if (element.children) {\n for (var i = 0; i < element.children.length; i++) {\n elements.push(element.children[i]);\n }\n }\n }\n }\n\n var hookCreateElement = function () {\n if (document.__createElement) return;\n document.__createElement = document.createElement;\n document.createElement = function (tagName) {\n console.log(\'create element \' + tagName);\n var element = document.__createElement(tagName);\n for(var key in Element.prototype)\n element[key] = Element.prototype[key];\n element.hooked = true;\n return element;\n }\n }\n\n setTimeout(function () {\n try {\n hookCreateElement();\n hookAllElements();\n } catch (e) {\n alert(\'Hook element error: \' + e.message);\n }\n }, 100);\n \n }\n\n Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {\n centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;\n var rect = this.getBoundingClientRect();\n var clientWidth = document.documentElement.clientWidth - 20;\n var clientHeight = document.documentElement.clientHeight - 20;//\xe9\x81\xbf\xe5\x85\x8d\xe5\xad\x98\xe5\x9c\xa8\xe6\xbb\x9a\xe5\x8a\xa8\xe6\x9d\xa1\xe5\xaf\xbc\xe8\x87\xb4\xe9\x81\xae\xe6\x8c\xa1\xe6\x8e\xa7\xe4\xbb\xb6\n if(rect.left >= clientWidth || rect.right <= 0 || rect.top >= clientHeight || rect.bottom <= 0){\n this.scrollIntoView();\n }\n }\n if (!Element.prototype.dispatchEvent) {\n Element.prototype.dispatchEvent = function (event) {\n if (event.type == \'input\') {\n event.type = \'propertychange\';\n event.propertyName = \'value\';\n }\n event.srcElement = event.target = this;\n this.fireEvent(\'on\' + event.type, event);\n }\n }\n\n Array.prototype.forEach = Array.prototype.forEach || function (callback) {\n if (typeof callback != \'function\') {\n throw TypeError(typeof callback);\n }\n for(var i = 0; i < this.length; i ++) {\n callback.call(arguments[1], this[i], i, this);\n }\n };\n\n if (Function.prototype.bind && window.console && typeof console.log == "object"){\n // \xe8\xa7\xa3\xe5\x86\xb3console.log\xe6\xb2\xa1\xe6\x9c\x89apply/bind\xe6\x96\xb9\xe6\xb3\x95\xe9\x97\xae\xe9\xa2\x98\n ["log","info","warn","error","assert","dir","clear","profile","profileEnd"].forEach(function (method) {\n console[method] = this.bind(console[method], console);\n }, Function.prototype.call);\n }\n // Avoid `console` errors in browsers that lack a console.\n (function() {\n var method;\n var noop = function () {};\n var methods = [\n \'assert\', \'clear\', \'count\', \'debug\', \'dir\', \'dirxml\', \'error\',\n \'exception\', \'group\', \'groupCollapsed\', \'groupEnd\', \'info\', \'log\',\n \'markTimeline\', \'profile\', \'profileEnd\', \'table\', \'time\', \'timeEnd\',\n \'timeStamp\', \'trace\', \'warn\'\n ];\n var length = methods.length;\n var console = (window.console = window.console || {});\n\n while (length--) {\n method = methods[length];\n\n // Only stub undefined methods.\n if (!console[method]) {\n console[method] = noop;\n }\n }\n }());\n\n if (!document.createEvent) {\n document.createEvent = function (event) {\n var evt = document.createEventObject(event);\n evt.initEvent = function (type) {\n evt.type = type;\n }\n return evt;\n }\n }\n \n if (typeof window.qt4w_hook_console == \'undefined\') window.qt4w_hook_console = true;\n window[\'qt4w_driver_lib\'] = {\n getScale : function(){\n return 1;\n },\n \n getScreenSize: function(){\n var result = new Array();\n result.push(screen.availWidth);\n result.push(screen.availHeight);\n return result.toString();\n },\n \n selectNodes : function(xpath){\n var oResult = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);\n var aNodes = new Array(); \n if (oResult != null) { \n var oElement = oResult.iterateNext(); \n while (oElement) { \n aNodes.push(oElement); \n oElement = oResult.iterateNext(); \n } \n }\n return aNodes;\n },\n \n selectNode : function(xpath){\n var nodes = this.selectNodes(xpath);\n if(nodes.length == 0) throw new Error(\'Find element \'+xpath+\' failed\');\n else if(nodes.length > 1) throw new Error(\'Find \'+nodes.length+\' elements match \'+xpath);\n return nodes[0];\n },\n \n getElementZoom: function(node){\n var scale = 1;\n while(node != null && node != document.documentElement){\n var zoom = parseFloat(window.getComputedStyle(node, null).zoom);\n if(zoom != 1){\n scale *= zoom;\n }\n node = node.parentNode;\n }\n return scale;\n },\n \n getElemRect: function(node){\n var result = new Array();\n var rect = node.getBoundingClientRect();\n var scale = this.getScale();\n scale *= this.getElementZoom(node);\n var left = rect.left;\n var top = rect.top;\n var width = rect.width;\n var height = rect.height;\n if (typeof width == \'undefined\') width = rect.right - rect.left;\n if (typeof height == \'undefined\') height = rect.bottom - rect.top;\n result.push(left * scale);\n result.push(top * scale);\n result.push(width * scale);\n result.push(height * scale);\n return result.toString();\n },\n \n initHighlightDiv: function(){\n this.bd0 = document.createElement("div");\n this.bd1 = document.createElement("div");\n this.bd2 = document.createElement("div");\n this.bd3 = document.createElement("div");\n },\n \n showDiv: function(cnt){\n if(cnt % 2 != 0){\n document.body.appendChild(this.bd0);\n document.body.appendChild(this.bd1);\n document.body.appendChild(this.bd2);\n document.body.appendChild(this.bd3);\n } else {\n document.body.removeChild(this.bd0);\n document.body.removeChild(this.bd1);\n document.body.removeChild(this.bd2);\n document.body.removeChild(this.bd3);\n }\n if (cnt){\n cnt--;\n if(window.console) console.log(\'show\' + cnt);\n setTimeout("qt4w_driver_lib.showDiv(" + cnt + ")", 200);\n }\n },\n \n highlight: function(node){\n if (!this.bd0) {\n this.initHighlightDiv();\n }\n var rect = node.getBoundingClientRect();\n var left= rect.left;\n var top = rect.top;\n var width = node.offsetWidth;\n var height = node.offsetHeight;\n if(window.console) console.log(left+\',\'+top+\',\'+width+\',\'+height);\n \n this.bd0.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;background-color: red");\n this.bd1.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd2.setAttribute("style", "left:" + (left + width - 1) + "px;top:" + (top) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd3.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top + height - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;;background-color: red");\n //console.log(\'style\'+this.bd0.getAttribute(\'style\')+\'\');\n this.showDiv(3);\n },\n \n scrollToVisible: function(node){\n if(node.scrollIntoViewIfNeeded){\n node.scrollIntoViewIfNeeded();\n }else if(node.scrollIntoView){\n node.scrollIntoView();\n }\n },\n \n logData: [],\n hookConsole: function(){\n var self = this;\n if(window.console && console.log && console.log.apply && window.JSON){\n var hookedConsoleFunc = function(){\n var timeStr = new Date().toLocaleString();\n for(var i=1;i<arguments.length;i++){\n var data = arguments[i];\n if(data instanceof Array){\n data = JSON.stringify(data);\n }else if(data instanceof Object){\n var jsonData = {};\n for(var key in data){\n jsonData[key] = data[key];\n if(jsonData[key]) jsonData[key] = jsonData[key].toString();\n }\n data = JSON.stringify(jsonData);\n }\n self.logData.push(\'[\' + timeStr + \'][console.\' + arguments[0].name + \'] \' + data);\n }\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 1);\n return arguments[0].apply(this, args);\n }\n\n var hookFunction = function (funcName) {\n if (!console[funcName]) return;\n var origFunc = console[funcName];\n console[funcName] = function(){\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 0, origFunc);\n return hookedConsoleFunc.apply(this, args);\n }\n }\n\n hookFunction(\'log\');\n hookFunction(\'dir\');\n hookFunction(\'info\');\n hookFunction(\'warn\');\n hookFunction(\'error\');\n }\n },\n \n readLogData: function (count) {\n if (count < 0) count = this.logData.length;\n return this.logData.splice(0, count);\n }\n \n };\n\n if (window.qt4w_hook_console) qt4w_driver_lib.hookConsole();\n \n window[\'qt4w_driver_lib\'][\'getScale\'] = function(){return screen.deviceXDPI / screen.logicalXDPI;};\n window[\'qt4w_driver_lib\'][\'getElementZoom\'] = function(node){\n return 1;\n };\n '
eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,怎传入“[]”
  • script (string) – 要执行的JavaScript语句
get_style(elem_xpaths, style_name)

获取元素的某一样式值

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • style_name (string) – 样式名称
qt4w.webdriver.webdriver module

IWebDriver接口

class qt4w.webdriver.webdriver.IWebDriver(webview)

基类:object

IWebDriver接口

eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,怎传入“[]”
  • script (string) – 要执行的JavaScript语句
get_attribute(elem_xpaths, attr_name)

获取元素属性

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • attr_name (string) – 属性名
get_elem_rect(elem_xpaths, rav=True)

获取元素在页面中的坐标

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • rav (bool) – 是否是相对于当前frame
get_property(elem_xpaths, prop_name)

获取元素的特定值,例如:node.innerHTML

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • prop_name (string) – property名
get_style(elem_xpaths, style_name)

获取元素的某一样式值

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • style_name (string) – 样式名称
highlight(elem_xpaths)

使元素高亮

参数:elem_xpaths (list) – 元素的XPATH路径
ready_state

页面状态

set_attribute(elem_xpaths, attr_name, value)

设置元素属性

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • attr_name (string) – 属性名
  • value (string) – 新的值
set_property(elem_xpaths, prop_name, value)

设置元素的特定值,例如:node.innerHTML

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • prop_name (string) – property名
  • value (string) – 新的值
class qt4w.webdriver.webdriver.WebDriverBase(webview)

基类:qt4w.webdriver.webdriver.IWebDriver

WebDriver基类

static create_driver(webview)

根据webview类型创建对应的WebDriver实例

参数:webview (object) – WebView实例
drag_element(elem_xpaths, from_x, from_y, to_x, to_y)

拖拽元素

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • from_x (int/float) – 起点横坐标,相对于WebView左上角
  • from_y (int/float) – 起点纵坐标,相对于WebView左上角
  • to_x (int/float) – 终点横坐标,相对于WebView左上角
  • to_y (int/float) – 终点纵坐标,相对于WebView左上角
driver_script = '\n if (typeof window.qt4w_hook_console == \'undefined\') window.qt4w_hook_console = true;\n window[\'qt4w_driver_lib\'] = {\n getScale : function(){\n return 1;\n },\n \n getScreenSize: function(){\n var result = new Array();\n result.push(screen.availWidth);\n result.push(screen.availHeight);\n return result.toString();\n },\n \n selectNodes : function(xpath){\n var oResult = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);\n var aNodes = new Array(); \n if (oResult != null) { \n var oElement = oResult.iterateNext(); \n while (oElement) { \n aNodes.push(oElement); \n oElement = oResult.iterateNext(); \n } \n }\n return aNodes;\n },\n \n selectNode : function(xpath){\n var nodes = this.selectNodes(xpath);\n if(nodes.length == 0) throw new Error(\'Find element \'+xpath+\' failed\');\n else if(nodes.length > 1) throw new Error(\'Find \'+nodes.length+\' elements match \'+xpath);\n return nodes[0];\n },\n \n getElementZoom: function(node){\n var scale = 1;\n while(node != null && node != document.documentElement){\n var zoom = parseFloat(window.getComputedStyle(node, null).zoom);\n if(zoom != 1){\n scale *= zoom;\n }\n node = node.parentNode;\n }\n return scale;\n },\n \n getElemRect: function(node){\n var result = new Array();\n var rect = node.getBoundingClientRect();\n var scale = this.getScale();\n scale *= this.getElementZoom(node);\n var left = rect.left;\n var top = rect.top;\n var width = rect.width;\n var height = rect.height;\n if (typeof width == \'undefined\') width = rect.right - rect.left;\n if (typeof height == \'undefined\') height = rect.bottom - rect.top;\n result.push(left * scale);\n result.push(top * scale);\n result.push(width * scale);\n result.push(height * scale);\n return result.toString();\n },\n \n initHighlightDiv: function(){\n this.bd0 = document.createElement("div");\n this.bd1 = document.createElement("div");\n this.bd2 = document.createElement("div");\n this.bd3 = document.createElement("div");\n },\n \n showDiv: function(cnt){\n if(cnt % 2 != 0){\n document.body.appendChild(this.bd0);\n document.body.appendChild(this.bd1);\n document.body.appendChild(this.bd2);\n document.body.appendChild(this.bd3);\n } else {\n document.body.removeChild(this.bd0);\n document.body.removeChild(this.bd1);\n document.body.removeChild(this.bd2);\n document.body.removeChild(this.bd3);\n }\n if (cnt){\n cnt--;\n if(window.console) console.log(\'show\' + cnt);\n setTimeout("qt4w_driver_lib.showDiv(" + cnt + ")", 200);\n }\n },\n \n highlight: function(node){\n if (!this.bd0) {\n this.initHighlightDiv();\n }\n var rect = node.getBoundingClientRect();\n var left= rect.left;\n var top = rect.top;\n var width = node.offsetWidth;\n var height = node.offsetHeight;\n if(window.console) console.log(left+\',\'+top+\',\'+width+\',\'+height);\n \n this.bd0.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;background-color: red");\n this.bd1.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd2.setAttribute("style", "left:" + (left + width - 1) + "px;top:" + (top) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd3.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top + height - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;;background-color: red");\n //console.log(\'style\'+this.bd0.getAttribute(\'style\')+\'\');\n this.showDiv(3);\n },\n \n scrollToVisible: function(node){\n if(node.scrollIntoViewIfNeeded){\n node.scrollIntoViewIfNeeded();\n }else if(node.scrollIntoView){\n node.scrollIntoView();\n }\n },\n \n logData: [],\n hookConsole: function(){\n var self = this;\n if(window.console && console.log && console.log.apply && window.JSON){\n var hookedConsoleFunc = function(){\n var timeStr = new Date().toLocaleString();\n for(var i=1;i<arguments.length;i++){\n var data = arguments[i];\n if(data instanceof Array){\n data = JSON.stringify(data);\n }else if(data instanceof Object){\n var jsonData = {};\n for(var key in data){\n jsonData[key] = data[key];\n if(jsonData[key]) jsonData[key] = jsonData[key].toString();\n }\n data = JSON.stringify(jsonData);\n }\n self.logData.push(\'[\' + timeStr + \'][console.\' + arguments[0].name + \'] \' + data);\n }\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 1);\n return arguments[0].apply(this, args);\n }\n\n var hookFunction = function (funcName) {\n if (!console[funcName]) return;\n var origFunc = console[funcName];\n console[funcName] = function(){\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 0, origFunc);\n return hookedConsoleFunc.apply(this, args);\n }\n }\n\n hookFunction(\'log\');\n hookFunction(\'dir\');\n hookFunction(\'info\');\n hookFunction(\'warn\');\n hookFunction(\'error\');\n }\n },\n \n readLogData: function (count) {\n if (count < 0) count = this.logData.length;\n return this.logData.splice(0, count);\n }\n \n };\n\n if (window.qt4w_hook_console) qt4w_driver_lib.hookConsole();\n '
eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果(该实现需要处理js基础库未注入情况的处理)

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,怎传入“[]”
  • script (string) – 要执行的JavaScript语句
fire_event(elem_xpaths, type)

触发事件

参数:
  • elem_xpaths (list) – 要触发事件的元素XPATH路径
  • type (string) – 事件类型
get_attribute(elem_xpaths, attr_name)

获取元素属性

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • attr_name (string) – 属性名
get_elem_rect(elem_xpaths, rav=True)

获取元素在页面中的坐标

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • rav (bool) – 是否是相对于当前frame
get_element(elem_xpaths)

获取控件,如果控件不存在抛出ControlNotFoundError

参数:elem_xpaths (list) – 元素xpath路径
get_element_count(elem_xpaths)

获取满足elem_xpaths的元素个数

参数:elem_xpaths (list) – 元素的XPATH路径
get_property(elem_xpaths, prop_name)

获取元素的特定值,例如:node.innerHTML

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • prop_name (string) – property名
get_ready_state(frame_xpaths)

获取页面状态

get_screen_size()

获取屏幕大小

get_style(elem_xpaths, style_name)

获取元素的某一样式值

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • style_name (string) – 样式名称
highlight(elem_xpaths)

使元素高亮

参数:elem_xpaths (list) – 元素的XPATH路径
is_elem_focused(elem_xpaths)

是否是当前有焦点元素

参数:elem_xpaths
read_console_log(frame_xpaths, count=1)

读取指定条数的日志

参数:
  • frame_xpaths (list) – 当前页面的XPATH路径
  • count (int) – 要读取的日志条数,默认为1
返回:

读取到的日志

scroll_to_visible(elem_xpaths)

将元素滚动到可见区域

参数:elem_xpaths (list) – 元素的XPATH路径
set_attribute(elem_xpaths, attr_name, value)

设置元素属性

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • attr_name (string) – 属性名
  • value (string) – 新的值
set_property(elem_xpaths, prop_name, value)

设置元素的特定值,例如:node.innerHTML

参数:
  • elem_xpaths (list) – 元素的XPATH路径
  • prop_name (string) – property名
  • value (string) – 新的值
qt4w.webdriver.webkitwebdriver module

Webkit WebDrvier实现

class qt4w.webdriver.webkitwebdriver.WebkitWebDriver(webview)

基类:qt4w.webdriver.webdriver.WebDriverBase

Webkit WebDrvier

driver_script = '\n if (typeof window.qt4w_hook_console == \'undefined\') window.qt4w_hook_console = true;\n window[\'qt4w_driver_lib\'] = {\n getScale : function(){\n return 1;\n },\n \n getScreenSize: function(){\n var result = new Array();\n result.push(screen.availWidth);\n result.push(screen.availHeight);\n return result.toString();\n },\n \n selectNodes : function(xpath){\n var oResult = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);\n var aNodes = new Array(); \n if (oResult != null) { \n var oElement = oResult.iterateNext(); \n while (oElement) { \n aNodes.push(oElement); \n oElement = oResult.iterateNext(); \n } \n }\n return aNodes;\n },\n \n selectNode : function(xpath){\n var nodes = this.selectNodes(xpath);\n if(nodes.length == 0) throw new Error(\'Find element \'+xpath+\' failed\');\n else if(nodes.length > 1) throw new Error(\'Find \'+nodes.length+\' elements match \'+xpath);\n return nodes[0];\n },\n \n getElementZoom: function(node){\n var scale = 1;\n while(node != null && node != document.documentElement){\n var zoom = parseFloat(window.getComputedStyle(node, null).zoom);\n if(zoom != 1){\n scale *= zoom;\n }\n node = node.parentNode;\n }\n return scale;\n },\n \n getElemRect: function(node){\n var result = new Array();\n var rect = node.getBoundingClientRect();\n var scale = this.getScale();\n scale *= this.getElementZoom(node);\n var left = rect.left;\n var top = rect.top;\n var width = rect.width;\n var height = rect.height;\n if (typeof width == \'undefined\') width = rect.right - rect.left;\n if (typeof height == \'undefined\') height = rect.bottom - rect.top;\n result.push(left * scale);\n result.push(top * scale);\n result.push(width * scale);\n result.push(height * scale);\n return result.toString();\n },\n \n initHighlightDiv: function(){\n this.bd0 = document.createElement("div");\n this.bd1 = document.createElement("div");\n this.bd2 = document.createElement("div");\n this.bd3 = document.createElement("div");\n },\n \n showDiv: function(cnt){\n if(cnt % 2 != 0){\n document.body.appendChild(this.bd0);\n document.body.appendChild(this.bd1);\n document.body.appendChild(this.bd2);\n document.body.appendChild(this.bd3);\n } else {\n document.body.removeChild(this.bd0);\n document.body.removeChild(this.bd1);\n document.body.removeChild(this.bd2);\n document.body.removeChild(this.bd3);\n }\n if (cnt){\n cnt--;\n if(window.console) console.log(\'show\' + cnt);\n setTimeout("qt4w_driver_lib.showDiv(" + cnt + ")", 200);\n }\n },\n \n highlight: function(node){\n if (!this.bd0) {\n this.initHighlightDiv();\n }\n var rect = node.getBoundingClientRect();\n var left= rect.left;\n var top = rect.top;\n var width = node.offsetWidth;\n var height = node.offsetHeight;\n if(window.console) console.log(left+\',\'+top+\',\'+width+\',\'+height);\n \n this.bd0.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;background-color: red");\n this.bd1.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top - 1) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd2.setAttribute("style", "left:" + (left + width - 1) + "px;top:" + (top) + "px;z-index:32767;"\n + "width:2px;height:" + (height) + "px;position:fixed;;background-color: red");\n this.bd3.setAttribute("style", "left:" + (left - 1) + "px;top:" + (top + height - 1) + "px;z-index:32767;"\n + "width:" + (width + 2) + "px;height:2px;position:fixed;;background-color: red");\n //console.log(\'style\'+this.bd0.getAttribute(\'style\')+\'\');\n this.showDiv(3);\n },\n \n scrollToVisible: function(node){\n if(node.scrollIntoViewIfNeeded){\n node.scrollIntoViewIfNeeded();\n }else if(node.scrollIntoView){\n node.scrollIntoView();\n }\n },\n \n logData: [],\n hookConsole: function(){\n var self = this;\n if(window.console && console.log && console.log.apply && window.JSON){\n var hookedConsoleFunc = function(){\n var timeStr = new Date().toLocaleString();\n for(var i=1;i<arguments.length;i++){\n var data = arguments[i];\n if(data instanceof Array){\n data = JSON.stringify(data);\n }else if(data instanceof Object){\n var jsonData = {};\n for(var key in data){\n jsonData[key] = data[key];\n if(jsonData[key]) jsonData[key] = jsonData[key].toString();\n }\n data = JSON.stringify(jsonData);\n }\n self.logData.push(\'[\' + timeStr + \'][console.\' + arguments[0].name + \'] \' + data);\n }\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 1);\n return arguments[0].apply(this, args);\n }\n\n var hookFunction = function (funcName) {\n if (!console[funcName]) return;\n var origFunc = console[funcName];\n console[funcName] = function(){\n var args = Array.prototype.slice.call(arguments);\n args.splice(0, 0, origFunc);\n return hookedConsoleFunc.apply(this, args);\n }\n }\n\n hookFunction(\'log\');\n hookFunction(\'dir\');\n hookFunction(\'info\');\n hookFunction(\'warn\');\n hookFunction(\'error\');\n }\n },\n \n readLogData: function (count) {\n if (count < 0) count = this.logData.length;\n return this.logData.splice(0, count);\n }\n \n };\n\n if (window.qt4w_hook_console) qt4w_driver_lib.hookConsole();\n \n window.Notification = undefined; // disable Notification\n window[\'qt4w_driver_lib\'][\'getScale\'] = function(){return window.devicePixelRatio;};\n window[\'qt4w_driver_lib\'][\'getElemRect\'] = function(node) {\n var result = new Array();\n var rect = node.getBoundingClientRect();\n var scale = this.getScale();\n scale *= this.getElementZoom(node);\n var left = rect.left;\n var top = rect.top;\n if (document.documentElement.scrollWidth > document.documentElement.clientWidth) {\n // \xe9\xa1\xb5\xe9\x9d\xa2\xe6\x9c\xaa\xe9\x80\x82\xe9\x85\x8d\xe7\xbb\x88\xe7\xab\xaf\n if (window.visualViewport) {\n // above Chrome 61\n left -= window.visualViewport.offsetLeft;\n top -= window.visualViewport.offsetTop;\n } else {\n if (window.scrollX && document.documentElement.getBoundingClientRect().left == 0) {\n // getBoundingClientRect return fix position\n left -= window.scrollX;\n }\n if (window.scrollY && document.documentElement.getBoundingClientRect().top == 0) {\n top -= window.scrollY;\n }\n }\n }\n \n result.push(left * scale);\n result.push(top * scale);\n result.push(rect.width * scale);\n result.push(rect.height * scale);\n return result.toString();\n }\n document.addEventListener(\'click\', function(event){console.log(\'[ClickListener](\' + event.clientX + \', \' + event.clientY + \')\');}, true);\n '
Module contents

IWebDriver接口定义及Windows上的实现

class qt4w.webdriver.EnumWebDriverType

基类:object

WebDriver类型(浏览器内核)

IE = 1
WEBKIT = 2
qt4w.webview package
Submodules
qt4w.webview.webview module

IWebView接口

class qt4w.webview.webview.IWebView

基类:object

IWebView接口

click(x_offset, y_offset)

点击WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
double_click(x_offset, y_offset)

双击WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
drag(x1, y1, x2, y2)

从(x1, y1)点拖动到(x2, y2)点

参数:
  • x1 (int/float) – 起点横坐标
  • y1 (int/float) – 起点纵坐标
  • x2 (int/float) – 终点横坐标
  • y2 (int/float) – 终点纵坐标
eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,则传入“[]”
  • script (string) – 要执行的JavaScript语句
hover(x_offset, y_offset)
参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
long_click(x_offset, y_offset, duration=1)

长按WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
  • duration (int/float) – 按住的持续时间
rect

WebView控件的坐标信息

right_click(x_offset, y_offset)

右键点击WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
screenshot()

当前WebView的截图

返回:PIL.Image
scroll(backward=True)
参数:backward (bool) – 是否向后滚动,默认为True
send_keys(text)

发送可见字符按键

参数:text (string) – 要输入的文本
upload_file(file_path)

上传文件

参数:file_path (str) – 文件路径
visible_rect

WebView控件可见区域的坐标信息

webdriver_class

WebView对应的WebDriver类

Module contents

IWebWiew接口定义

Submodules
qt4w.util module

QT4W公共库

exception qt4w.util.ControlAmbiguousError

基类:qt4w.util.QT4WRuntimeError

找到多个控件

exception qt4w.util.ControlNotFoundError

基类:qt4w.util.QT4WRuntimeError

Web元素未找到

class qt4w.util.Deprecated(new_func)

基类:object

废弃函数包装

class qt4w.util.EnumKeyCode

基类:object

按键定义

A = ('KeyA', 65)
ALT = ('Alt', 18)
B = ('KeyB', 66)
BACKSPACE = ('Backspace', 8)
C = ('KeyC', 67)
CTRL = ('Control', 17)
D = ('KeyD', 68)
DELETE = ('Delete', 46)
DOWN = ('ArrowDown', 40)
E = ('KeyE', 69)
END = ('End', 35)
ENTER = ('Enter', 13)
ESC = ('Escape', 27)
F = ('KeyF', 70)
F1 = ('F1', 112)
F10 = ('F10', 121)
F11 = ('F11', 122)
F12 = ('F12', 123)
F2 = ('F2', 113)
F3 = ('F3', 114)
F4 = ('F4', 115)
F5 = ('F5', 116)
F6 = ('F6', 117)
F7 = ('F7', 118)
F8 = ('F8', 119)
F9 = ('F9', 120)
G = ('KeyG', 71)
H = ('KeyH', 72)
HOME = ('Home', 36)
I = ('KeyI', 73)
INSERT = ('Insert', 45)
J = ('KeyJ', 74)
K = ('KeyK', 75)
L = ('KeyL', 76)
LEFT = ('ArrowLeft', 37)
M = ('KeyM', 77)
N = ('KeyN', 78)
O = ('KeyO', 79)
P = ('KeyP', 80)
PAGEDOWN = ('PageDown', 34)
PAGEUP = ('PageUp', 33)
Q = ('KeyQ', 81)
R = ('KeyR', 82)
RIGHT = ('ArrowRight', 39)
S = ('KeyS', 83)
SHIFT = ('Shift', 16)
T = ('KeyT', 84)
TAB = ('Tab', 9)
U = ('KeyU', 85)
UP = ('ArrowUp', 38)
V = ('KeyV', 86)
W = ('KeyW', 87)
X = ('KeyX', 88)
Y = ('KeyY', 89)
Z = ('KeyZ', 90)
static init()
static parse(text)
class qt4w.util.Frame(_id, name, url)

基类:object

frame element

add_child(frame)

添加子frame

find_child_frame(name, url)

查找子frame

id
name
url
class qt4w.util.FrameSelector(webdriver, root_frame)

基类:object

Frame定位

get_frame_by_xpath(frame_xpaths)

根据XPath对象查找frame

参数:frame_xpaths (list) – frame的xpath数组
返回:Frame object
exception qt4w.util.JavaScriptError(frame, err_msg)

基类:exceptions.RuntimeError

执行JavaScript报错

frame

发生JS异常的frame

message
class qt4w.util.KeyCode(name, code)

基类:object

按键

code
name
class qt4w.util.LazyDict(getter, setter=None, lister=None)

基类:object

类字典容器,本身不存储数据,只在需要时调用相应函数实现读写操作

exception qt4w.util.QT4WRuntimeError

基类:exceptions.RuntimeError

QT4W运行时错误

message

解决python3上没有message属性的问题

class qt4w.util.Rect(rect)

基类:object

控件坐标区域

Height
Left
Top
Width
height

高度

left

左上角横坐标

top

左上角纵坐标

width

宽度

exception qt4w.util.TimeoutError

基类:qt4w.util.QT4WRuntimeError

超时错误

class qt4w.util.WebElementAttributes(getter, setter=None, lister=None)

基类:qt4w.util.LazyDict

供WebElement的Attributes属性使用的类字典容器

class qt4w.util.WebElementStyles(getter, setter=None, lister=None)

基类:qt4w.util.LazyDict

供WebElement的Styles属性使用的类字典容器

qt4w.util.encode_wrap(func)

处理函数返回值编码

qt4w.util.general_encode(s)

字符串通用编码处理 python2 => utf8 python3 => unicode

qt4w.util.lazy_init(func)

懒初始化

qt4w.util.unicode_decode(s)

将字符串解码为unicode编码

qt4w.webcontrols module

WebPage、WebElement接口类

class qt4w.webcontrols.ControlContainer

基类:object

控件容器基类

Controls
control(name)

获取控件实例

get_metis_view()

返回MetisView

ui_control_type = None
ui_map = {}
updateLocator(locators)

更新控件定位参数

参数:locators (dict) – 定位参数,格式是 {‘控件名’:{‘type’:控件类, 控件类的参数dict列表}, …}
update_ui_map(ui_map)

从指定的ui_map中更新控件定义

class qt4w.webcontrols.FrameElement(root, locator)

基类:qt4w.webcontrols.WebElement

frame/iframe元素

FramePage
framepage

frame中包含的WebPage

class qt4w.webcontrols.IWebElement

基类:object

WebElement接口定义

Attributes
BoundingRect

元素位置信息

Displayed
InnerHtml
InnerText
attributes

元素的属性集合

click(x_offset=None, y_offset=None)

点击元素,默认点击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
displayed

元素是否显示

double_click(x_offset=None, y_offset=None)

双击元素,默认双击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
drag(x, y)

拖放元素到指定位置

参数:
  • x (int或float) – 拖放终点距离起点的横向偏移。
  • y (int或float) – 放终点距离起点的纵向偏移。
exist()

元素是否存在

focused

元素是否有焦点

getElement(*args, **kwargs)
getElements(*args, **kwargs)
get_element(locator)

在当前元素的子孙元素中查找元素,返回第一个匹配的元素

参数:locator (string或XPath) – 元素的xpath路径
get_elements(locator)

在当前元素的子孙元素中查找元素,返回包含所有匹配的元素的列表

参数:locator (string或XPath) – 元素的xpath路径
hover(x_offset=None, y_offset=None)

鼠标悬停

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
inner_html

元素所包含的HTML代码

inner_text

元素所包含的文本

long_click(x_offset=None, y_offset=None, duration=1)

长按元素

参数:
  • x_offset (int/float) – 距离控件区域左上角的横向偏移
  • y_offset (int/float) – 距离控件区域左上角的纵向偏移
  • duration (int/float) – 按住的持续时间
page

元素所在WebPage

rect

元素位置信息

right_click(x_offset=None, y_offset=None)

右击元素,默认右击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
screenshot()

设置控件截图

scroll(x, y)

滚动元素

参数:
  • x (int) – 横向滚动的偏移,负值向左,正值向右
  • y (int) – 纵向滚动的偏移,负值向上,正值向下
sendKeys(*args, **kwargs)
send_keys(keys)

发送按键

参数:keys (string) – 发送的按键
set_focus()

设为焦点

styles

元素的样式集合

visible

元素是否视觉可见

wait_for_attribute(name, value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的指定属性变为特定值

参数:
  • name (string) – 要等待的属性名
  • value (string) – 要等待的属性值

:param timeout:超时时间 :type timeout: int或float :param interval:重试间隔时间 :type interval:int或float

wait_for_style(name, value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的指定样式变为特定值

参数:
  • name (string) – 要等待的样式名
  • value (string) – 要等待的样式值

:param timeout:超时时间 :type timeout: int或float :param interval:重试间隔时间 :type interval:int或float

wait_for_text(value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的InnerText变为特定值

参数:
  • value (string) – 要等待的特定值
  • timeout (int或float) – 超时时间

:param interval:重试间隔时间 :type interval: int或float

wait_for_visible(timeout=10, interval=0.5)

等待控件可见

参数:timeout (float) – 超时时间

:param interval:查询间隔时间 :type interval: float

class qt4w.webcontrols.IWebPage

基类:object

WebPage接口定义

ReadyState
Title
Url
accessible_object

accessible对象

activate()

激活承载页面的窗口

browser_type

浏览器类型

close()

关闭承载页面的窗口

cookie

页面cookie

execScript(*args, **kwargs)
exec_script(script)

在页面中执行JavaScript代码,并返回直接结果

参数:script (string) – 要执行的代码
getElement(*args, **kwargs)
getElements(*args, **kwargs)
get_element(locator)

在页面中查找元素,返回第一个匹配的元素

参数:locator (string或XPath) – 元素的xpath路径
get_elements(locator)

在页面中查找元素,返回包含所有匹配的元素的列表

参数:locator (string或XPath) – 元素的xpath路径
pull_down_refresh()

下拉刷新

pull_up_refresh()

上拉刷新

read_console_log(timeout=None)

读取一条console.log输出的日志

参数:timeout (int) – 读取日志的超时时间,为None表示不会超时
ready_state

页面状态

release()

释放占用的资源

screenshot()

获取page页面截图

scroll(x, y)

滚动

参数:
  • x (int) – 横向滚动的偏移,负值向左,正值向右
  • y (int) – 纵向滚动的偏移,负值向上,正值向下
title

页面标题

upload_file(file_path)

上传文件

参数:file_path (str) – 文件路径
url

页面url

waitForReady(*args, **kwargs)
wait_for_ready(timeout=60)

等待页面状态变为ready

参数:timeout (int) – 超时时间
class qt4w.webcontrols.InputElement(root, locator)

基类:qt4w.webcontrols.WebElement

input元素

value

当前的value

class qt4w.webcontrols.MetisView(page_or_elem)

基类:object

实现IMetisView接口

click(offset_x=None, offset_y=None)

点击 :param offset_x: 相对于该控件的坐标offset_x,百分比( 0 -> 1 ),不传入则默认该控件的中央 :type offset_x: float|None :param offset_y: 相对于该控件的坐标offset_y,百分比( 0 -> 1 ),不传入则默认该控件的中央 :type offset_y: float|None

double_click(offset_x=None, offset_y=None)

双击

long_click(offset_x=None, offset_y=None)

长按

os_type

系统类型,例如”android”,”ios”,”pc”

rect

元素相对坐标(x, y, w, h)

screenshot()

当前容器的区域截图 :return: PIL.image

send_keys(text)

发送可见字符按键

参数:text (string) – 要输入的文本
class qt4w.webcontrols.SelectElement(root, locator)

基类:qt4w.webcontrols.WebElement

select元素

options

返回所有选项列表

selection

当前选择项

class qt4w.webcontrols.UIListBase(root, locator)

基类:object

List控件基类

filter(condition)

根据条件过滤,找到满足条件的项即返回,如果找不到则抛出异常

参数:condition (dict) – 过滤条件
ui_control_type = None
class qt4w.webcontrols.WebElement(root, locator)

基类:qt4w.webcontrols.ControlContainer, qt4w.webcontrols.IWebElement

IWebElement实现

BoundingRect

元素位置信息

attributes

元素的属性集合

click(x_offset=None, y_offset=None, highlight=True)

点击元素,默认点击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
  • highlight (bool) – 是否高亮元素
displayed
double_click(x_offset=None, y_offset=None)

双击元素,默认双击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
drag(x, y)

拖放元素到指定位置

参数:
  • x (int或float) – 拖放终点距离起点的横向偏移。
  • y (int或float) – 放终点距离起点的纵向偏移。
exist()

元素是否存在

focused
get_element(locator, elem_cls=None)

在当前元素的子孙元素中查找元素,返回第一个匹配的元素

参数:locator (string或XPath) – 元素的xpath路径
Paran elem_cls:返回的元素类型
get_elements(locator, elem_cls=None)

在当前元素的子孙元素中查找元素,返回包含所有匹配的元素的列表

参数:locator (string或XPath) – 元素的xpath路径
Paran elem_cls:返回的元素类型
highlight(*args, **kwargs)
hover(x_offset=None, y_offset=None)

鼠标悬停

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
inner_html
inner_text
long_click(x_offset=None, y_offset=None, duration=1)

长按元素

参数:
  • x_offset (int/float) – 距离控件区域左上角的横向偏移
  • y_offset (int/float) – 距离控件区域左上角的纵向偏移
  • duration (int/float) – 按住的持续时间
page

元素所在WebPage

post_init()

窗口类自定义的初始化逻辑

rect
right_click(x_offset=None, y_offset=None)

右击元素,默认右击元素中点

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
screenshot()

设置控件截图

scroll(x, y)

滚动元素

参数:
  • x (int) – 横向滚动的偏移,负值向左,正值向右
  • y (int) – 纵向滚动的偏移,负值向上,正值向下
send_keys(keys)

发送按键

参数:keys (string) – 发送的按键
set_focus()

设为焦点

styles

元素的样式集合

ui_control_type

WebElement 的别名

visible

元素是否视觉可见

wait_for_attribute(name, value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的指定属性变为特定值

参数:
  • name (string) – 要等待的属性名
  • value (string) – 要等待的属性值

:param timeout:超时时间 :type timeout: int或float :param interval:重试间隔时间 :type interval:int或float

wait_for_style(name, value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的指定样式变为特定值

参数:
  • name (string) – 要等待的样式名
  • value (string) – 要等待的样式值

:param timeout:超时时间 :type timeout: int或float :param interval:重试间隔时间 :type interval:int或float

wait_for_text(text, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的InnerText变为特定值

参数:
  • text (string) – 要等待的特定值
  • timeout (int或float) – 超时时间

:param interval:重试间隔时间 :type interval: int或float

wait_for_value(value, timeout=10, interval=0.5)

暂停程序执行,直到当前元素的InnerText变为特定值

参数:
  • value (string) – 要等待的特定值
  • timeout (int或float) – 超时时间

:param interval:重试间隔时间 :type interval: int或float

wait_for_visible(timeout=10, interval=0.5)

等待控件可见

参数:timeout (float) – 超时时间

:param interval:查询间隔时间 :type interval: float

class qt4w.webcontrols.WebPage(webview_or_webpage, locator=None, wait_for_ready=True)

基类:qt4w.webcontrols.ControlContainer, qt4w.webcontrols.IWebPage

IWebPage接口实现

activate()

激活承载页面的窗口

browser_type

浏览器类型

close()

关闭承载页面的窗口

cookie

页面cookie

exec_script(**kwargs)
get_element(locator, elem_cls=None)

在页面中查找元素,返回第一个匹配的元素

参数:locator (string或XPath) – 元素在当前页面的xpath路径
Paran elem_cls:返回的元素类型
get_elements(locator, elem_cls=None)

在页面中查找元素,返回包含所有匹配的元素的列表

参数:locator (string或XPath) – 元素的xpath路径
Paran elem_cls:返回的元素类型
pull_down_refresh()

下拉刷新

pull_up_refresh()

上拉刷新

read_console_log(timeout=None)

读取一条console.log输出的日志

参数:timeout (int) – 读取日志的超时时间,为None表示不会超时
ready_state

页面状态

release()

释放占用的资源

screenshot()

获取page页面截图

返回:PIL.Image
scroll(x, y)

滚动

参数:
  • x (int) – 横向滚动的偏移,负值向左,正值向右
  • y (int) – 纵向滚动的偏移,负值向上,正值向下
title

页面标题

ui_control_type

WebElement 的别名

upload_file(file_path)

上传文件

参数:file_path (str) – 文件路径
url

页面url

wait_for_ready(timeout=60)

等待页面状态变为ready

参数:timeout (int) – 超时时间
qt4w.webcontrols.ui_list(control_cls)

列表类型

Module contents

Web自动化测试基础库

class qt4w.XPath(obj)

基类:str

表示XPath

Axis
Nodetest
break_frames()
break_steps()
qt4w.set_logger(logger)

set qt4w default logger