bok-choy PO测试框架

本文恢复2018-11-20博客

首先是在简书上看到介个,激起了我的兴趣。

bok-choy(selenium+python+page object)使用介绍

接下来我把这个代码搬移,结果测试失败,原来是因为找不到我firefox的路径,emmmmm…

这个时候更改这个参数就好啦:

1
2
#test_search.py文件
os.environ["SELENIUM_FIREFOX_PATH"]="/usr/local/firefox/firefox"

然后今天我要根据bok-choy的API测试一下百度的其他页面以及功能:

bok-choy Github

bok-choy Doc

先看bok-choy给了一些测试设计准则:

  • 将浏览器交互放在页面对象中,而不是测试中。
  • 将断言放在测试中,而不是页面对象。
  • 永远不要使用time.sleep()
  • 始终让页面等待操作完成。
  • 等待JavaScript加载。
  1. 将浏览器交互放在页面对象中,而不是测试中:

有的时候你需要验证网页到达的正确性,你可能会这么写代码

1
2
3
4
class BarTest(WebAppTest):
def test_bar(self):
bar_text = self.browser.find_elements_by_css_selector('div.bar').text
self.assertEqual(bar_text, "Bar")

然而,这么写的缺点是:

前端CSS改变,你包含CSS的所有测试用例都得改。

如果出现超时问题,selenium不会进行重试,相反,bok-choy用于浏览器交互的高级界面包括强大的错误检查和重试逻辑。(emmmmm,自家产品就是好哈)

所以你得这么写:

1
2
3
4
5
6
7
8
9
10
11
class BarPage(PageObject):
def is_browser_on_page(self):
return self.q(css='section#bar').is_present()

@property
def text(self):
return self.q(css='div.bar').text
if len(text_items) > 0:
return text_items[0]
else:
return ""

然后写测试用例的时候介么写:

1
2
3
4
class BarTest(WebAppTest):
def test_bar(self):
bar_page = BarPage(self.browser)
self.assertEqual(bar_page.text, "Bar")

逻辑就是:在尝试使用这个页面时PO会首先检查浏览器是否在正确页面,例如,如果JavaScript在我们检索它和获取元素文本之间修改

,它也会重试(否则会导致运行时异常)。 最后,如果页面上的CSS选择器发生更改,我们可以修改页面对象,从而更新与页面交互的每个测试。

2.将断言放在测试中,而不是页面对象。

因为页面对象的可重用性较低,所以建议把断言放入测试中。

比如说,如果你把断言放在页面对象中

1
2
3
class BarPage(PageObject):
def check_section_title(self):
assert self.q(css='div.bar').text == ['Test Section']

如果另一个测试期望页面标题不是“Test Section”,则它不能重复使用check_section_title()。

所以你得这么写:

1
2
3
4
5
6
7
class BarPage(PageObject):
def section_title(self):
text_items = self.q(css='div.bar').text
if len(text_items) > 0:
return text_items[0]
else:
return ""

3.永远不要使用time.sleep()

有时候测试失败是因为检查页面太快(反应不过来么哈哈),所以我们测试一般要等待页面上的JavaScript完成对DOM的操作,例如添加元素或甚至附加事件侦听器时。这个时候我们就会加入time.sleep()

但是这种方法会存在两个问题:

一个是即使都加载完成了也要等待你设置得时间,那么测试时间就会延长。

一个是你估计得不对,等待的时间可能不够。

对于这个问题,bok-choy提供了两种解决方案:

一种是在你对页面操作前先判断是否到达了正确页面

1
2
3
4
5
6
class FooPage(PageObject):
def is_browser_on_page(self):
return self.q(css='section.bar').is_present()

def do_foo(self):
self.q(css='button.foo').click()

另一种是你可以采用Promise来检查DOM的某种状态

1
2
3
4
5
6
7
8
9
10
11
class FooPage(PageObject):
def is_browser_on_page(self):
return self.q(css='button.foo').is_present()

def do_foo(self):
ready_promise = EmptyPromise(
lambda: 'Loading...' not in self.q(css='div.msg').text,
"Page finished loading"
).fulfill()

self.q(css='button.foo').click()

4.始终让页面等待操作完成。

页面对象通常提供两种与页面交互的方式:1。查询页面以获取信息。 2.在页面上执行操作。

在第二种情况下,页面对象应该在返回之前等待操作完成。 例如,假设页面对象具有单击“保存”按钮的方法save_document()。然后页面重定向到不同的页面。 在这种情况下,页面对象应该等待下一页加载,然后再将控制权返回给调用者。

1
2
3
4
class FooPage(PageObject):
def save_document():
self.q(css='button.save').click()
return BarPage(self.browser).wait_for_page()

5.等待JavaScript加载。

有时,在页面上的JavaScript完成加载之前,页面尚未就绪。 对于异步加载JavaScript的页面(例如,使用RequireJS时),这尤其成问题。

bok-choy提供了一个等待RequireJS模块加载的简单机制:

1
2
3
4
5
6
@requirejs('foo')
class FooPage(PageObject):

@wait_for_js
def text(self):
return self.q(css='div.foo').text

接着看一下今天的测试场景

测试场景一:百度新闻端

进入百度新闻页->点击新闻链接->查看新闻详情页

进入百度新闻页->搜索框输入要搜索的新闻内容->返回搜索结果页

部分代码如下,完整代码请至Github:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#test_news.py
# -*- coding: utf-8 -*-
import os
import unittest
from bok_choy.web_app_test import WebAppTest
from pages import BaiduNewsPage,BaiduSearchPage,NewsDetailPage,NewsResultPage
class TestBaiduNews(WebAppTest):
"""
Tests for the News site
"""
def setUp(self):
super(TestBaiduNews,self).setUp()
self.baidu_news_page=BaiduNewsPage(self.browser)
def test_page_existence(self):
"""
make sure news page is accessible
"""
self.baidu_news_page.visit()
def test_search(self):
test_key=u"钢铁侠"
self.baidu_news_page.visit().search_news(test_key)
self.news_results_page=NewsResultPage(self.browser)
result=self.news_results_page.search_results
assert test_key in result[0]
def test_detail(self):
self.news_detail_page=NewsDetailPage(self.browser)
if __name__ == '__main__':
os.environ["SELENIUM_FIREFOX_PATH"]="/usr/local/firefox/firefox"
unittest.main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class BaiduNewsPage(PageObject):
"""
Baidu news page
"""
url='http://news.baidu.com/'

def see_news_link(self):
self.q(xpath='//*[@id="pane-news"]/div/ul/li[1]/i').click()
def input_search_key(self,keyword):
return self.q(css='#ww').fill(keyword)
def click_search_btn(self):
self.q(css='#s_btn_wr').click()
def is_browser_on_page(self):
title = self.browser.title
matches = re.match(u'百度新闻——全球最大的中文新闻平台', title)
return matches is not None
def search_news(self,keyword):
self.input_search_key(keyword)
self.click_search_btn()
NewsResultPage(self.browser).wait_for_page()
def see_news(self):
self.see_news_link()
NewsDetailPage(self.browser).wait_for_page()
class NewsResultPage(PageObject):
"""
News search result page
"""
url=None

def is_browser_on_page(self):
return self.q(css='h3').is_present()
@property
def search_results(self):
return self.q(css='h3').first.text
class NewsDetailPage(PageObject):
url=None

def is_browser_on_page(self):
return self.q(xpath='/html/body/div[4]/div[2]/div[4]/div[2]/div/div[1]').is_present()

控制台结果:

1
2
3
4
5
6
[root@localhost shiyan]# python test_news.py
...
----------------------------------------------------------------------
Ran 3 tests in 34.570s

OK

我现在有一个疑问,就是它运行一个测试用例就要重新打开一次浏览器,说明测试用例之间是独立的,那么我如何解决在只打开一次浏览器的情况下一次性完成这些测试用例的执行捏,emmmm,吃饭时间到了,吃完饭以后回来想,顺便也做一下百度页面的链接测试~