-
Notifications
You must be signed in to change notification settings - Fork 1
/
pages.py
254 lines (190 loc) · 8.46 KB
/
pages.py
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
from ftw.testing import browser
from plone.app.testing import PLONE_SITE_ID
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from splinter.browser import _DRIVERS
import os
class PageObject(object):
@property
def portal_url(self):
"""Returns the full URL to the plone site under test.
The URL is different depending on the kind of browser which is
used: zope.testbrowser uses a fake-url (e.g. http://nohost/plone),
but when using phantomjs or a real browser we need to use
a proper URL with the right port (e.g. http://localhost:5501/plone).
The URL can be influenced by setting environment variables.
This allows to connect to remote hosts (e.g. for CI servers with VMs)
or to hook a varnish or similar in between.
"""
if self.browser_driver == 'zope.testbrowser':
return 'http://nohost/%s' % PLONE_SITE_ID
host = os.environ.get('ZSERVER_HOST', 'localhost')
port = int(os.environ.get('PLONE_TESTING_PORT',
os.environ.get('ZSERVER_PORT', 55001)))
return 'http://%s:%s/%s' % (host, port, PLONE_SITE_ID)
@property
def browser_driver(self):
"""Returns the name (string) of the browser driver currently in use.
Known splinter drivers:
- firefox
- remote
- chrome
- phantomjs
- zope.testbrowser
"""
return next((name for name, klass in _DRIVERS.items()
if type(browser()) is klass))
@property
def javascript_supported(self):
"""Returns true if the current browser driver supports javascript.
"""
return self.browser_driver != 'zope.testbrowser'
@property
def iframes_supported(self):
"""Returns true if the current browser driver supports iframes.
"""
return self.browser_driver not in ('zope.testbrowser', 'phantomjs')
def find_one_by_xpath(self, selector):
"""Finds one single element by xpath.
If there is no element or more than one matching raise an error.
"""
elements = browser().find_by_xpath(selector)
assert len(elements) != 0, \
'No element found with xpath: %s' % selector
assert len(elements) == 1, \
'More than one element found with xpath: %s\n%s' % (
selector,
str(map(lambda item: item.outer_html, elements)))
return elements.first
def normalize_whitespace(self, text):
return ' '.join(text.split())
class Plone(PageObject):
def visit_portal(self):
browser().visit(self.portal_url)
return self
def login(self, user=TEST_USER_NAME, password=TEST_USER_PASSWORD):
# This should be implemented by setting request headers.
browser().visit(self.portal_url + '/login')
browser().fill('__ac_name', user)
browser().fill('__ac_password', password)
browser().find_by_xpath(
'//input[@type="submit" and @value="Log in"]').first.click()
return self
def get_first_heading(self):
return browser().find_by_css('.documentFirstHeading').text
def get_body_classes(self):
"""Returns the classes of the body node.
"""
body = browser().find_by_xpath('//body').first
assert body, 'No <body> tag found.'
return body['class'].strip().split(' ')
def assert_body_class(self, cssclass):
assert cssclass in self.get_body_classes(), \
'Missing body class "%s" on this page. Body classes are: %s' % (
cssclass,
self.get_body_classes())
def get_template_class(self):
"""Returns the template class of the body node.
"""
template = [cls for cls in self.get_body_classes()
if cls.startswith('template-')]
assert len(template) != 0, \
'No "tempalte-" class on body tag found.'
assert len(template) == 1, \
'Template classes on body ambiguous: %s' % (
str(template))
return template[0]
def portal_messages(self):
return {'info': browser().find_by_css('.portalMessage.info dd'),
'warning': browser().find_by_css('.portalMessage.warning dd'),
'error': browser().find_by_css('.portalMessage.error dd')}
def portal_text_messages(self):
messages = self.portal_messages()
item_to_text = lambda item: self.normalize_whitespace(
item.text.strip())
return {'info': map(item_to_text, messages['info']),
'warning': map(item_to_text, messages['warning']),
'error': map(item_to_text, messages['error'])}
def assert_portal_message(self, kind, message):
message = message.strip()
messages = self.portal_text_messages()
assert message in messages[kind], \
'Portal message "%s" of kind %s is not visible. Got %s' % (
message, kind, str(messages))
def disable_wysiwyg_for_field(self, fieldname):
browser().find_by_css(
'div[data-fieldname=%s] .suppressVisualEditor a' % fieldname).first.click()
assert browser().is_text_not_present('Edit without visual editor'), \
'Failed to disable WYSIWYG field "%s"' % fieldname
def open_add_form(self, type_name):
if self.javascript_supported:
browser().find_by_xpath(
"//span[text() = 'Add new\xe2\x80\xa6']").click()
self.find_one_by_xpath(
'//a/span[normalize-space(text()) = "%s"]/..' % type_name).click()
if 'portal_factory' in browser().url:
return ATFormPage()
elif '++add++' in browser().url:
return DXFormPage()
else:
raise NotImplementedError()
def create_object(self, type_title, fields):
page = self.open_add_form(type_title)
for key, value in fields.items():
page.fill_field(key, value)
return page.save()
def get_button(self, value, type_=None):
if type_ is not None:
xpr = '//input[@type="%s" and @value="%s"]' % (type_, value)
else:
xpr = '//input[(@type="submit" or @type="button") and @value="%s"]' % \
value
elements = browser().find_by_xpath(xpr)
if len(elements) == 0:
return None
assert len(elements) < 2, \
'Ambiguous matches for button "%s".\nXpath: %s\n%s' % (
value, xpr, str(map(lambda item: item.outer_html, elements)))
return elements.first
def click_button(self, value, type_=None):
self.get_button(value, type_=type_).click()
class FormPage(Plone):
def fill_field(self, label, value):
fields = browser().find_by_xpath(
'//*[@*[name()="id" or name()="name"]=//label[normalize-space(text())="%s"]/@for]' % label)
assert len(fields) != 0, \
'No field with label-content "%s" found.' % label
assert len(fields) < 2, \
'Ambiguous matches for fields with labels "%s": %s' % (
label, str(map(lambda item: item.outer_html, fields)))
if 'mce_editable' in fields.first['class'].split(' ') and \
self.iframes_supported:
# oh gosh, its tinymce
with browser().get_iframe('%s_ifr' % fields.first['name']) as frame:
frame.find_by_xpath('//body').first.type(value)
else:
browser().fill_form({fields.first['name']: value})
class ATFormPage(FormPage):
def save(self):
self.click_button('Save', type_='submit')
self.assert_portal_message('info', 'Changes saved.')
return Plone()
class DXFormPage(FormPage):
def save(self):
self.click_button('Save', type_='submit')
self.assert_portal_message('info', 'Item created')
return Plone()
class PloneControlPanel(Plone):
def open(self):
browser().visit('/'.join((
self.portal_url, '@@overview-controlpanel')))
return self
def assert_on_control_panel(self):
self.assert_body_class('template-overview-controlpanel')
self.assert_body_class('portaltype-plone-site')
def get_control_panel_links(self):
self.assert_on_control_panel()
links = browser().find_by_css('ul.configlets li a')
return dict(map(lambda link: (link.text.strip(), link), links))
def get_control_panel_link(self, title):
return self.get_control_panel_links().get(title)