1
# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Test helpers for common AJAX widgets."""
8
'FormPickerWidgetTest',
9
'InlineEditorWidgetTest',
10
'InlinePickerWidgetButtonTest',
11
'InlinePickerWidgetSearchTest',
13
'search_and_select_picker_widget',
14
'search_picker_widget',
18
from windmill.authoring import WindmillTestClient
20
from lp.testing.windmill import (
27
"""A class that represents and interacts with an on-page JavaScript widget.
29
The widget is assumed to be a YUI widget controlled by yui-X-hidden classes.
32
def __init__(self, client, widget_name):
35
:param client: A WindmillTestClient instance for interacting with pages.
36
:param widget_name: The class name of the YUI widget, like 'yui3-picker'.
39
self.widget_name = widget_name
43
"""The XPath of this widget, not including the hidden or visible state.
45
# We include a space after the widget name because @class matches the
46
# /beginning/ of text strings, not whole words!
47
return u"//div[contains(@class, '%s ')]" % self.widget_name
50
def visible_xpath(self):
51
"""The XPath of the widget when it is visible on page."""
52
subs = dict(name=self.widget_name)
53
# We include a space after the widget name because @class matches the
54
# /beginning/ of text strings, not whole words!
55
return (u"//div[contains(@class, '%(name)s ') "
56
"and not(contains(@class, '%(name)s-hidden'))]" % subs)
59
def hidden_xpath(self):
60
"""The XPath of the widget when it is hidden."""
61
# We include a space after the widget name because @class matches the
62
# /beginning/ of text strings, not whole words!
63
subs = dict(name=self.widget_name)
64
return (u"//div[contains(@class, '%(name)s ') "
65
"and contains(@class, '%(name)s-hidden')]" % subs)
67
def should_be_visible(self):
68
"""Check to see if the widget is visible on screen."""
69
self.client.waits.forElement(xpath=self.visible_xpath,
70
timeout=constants.FOR_ELEMENT)
72
def should_be_hidden(self):
73
"""Check to see if the widget is hidden on screen."""
74
self.client.waits.forElement(xpath=self.hidden_xpath,
75
timeout=constants.FOR_ELEMENT)
78
class SearchPickerWidget(OnPageWidget):
79
"""A proxy for the yui3-picker widget from lazr-js."""
81
def __init__(self, client):
84
:param client: A WindmillTestClient instance.
86
super(SearchPickerWidget, self).__init__(client, 'yui3-picker')
87
self.search_input_xpath = (
88
self.visible_xpath + "//input[@class='yui3-picker-search']")
89
self.search_button_xpath = (
91
"//div[@class='yui3-picker-search-box']/button")
93
def _get_result_xpath_by_number(self, item_number):
94
"""Return the XPath for the given search result number."""
95
item_xpath = "//ul[@class='yui3-picker-results']/li[%d]/span" % item_number
96
return self.visible_xpath + item_xpath
98
def do_search(self, text):
99
"""Enter some text in the search field and click the search button.
101
:param text: The text we want to search for.
103
self.client.waits.forElement(xpath=self.search_input_xpath,
104
timeout=constants.FOR_ELEMENT)
105
self.client.type(xpath=self.search_input_xpath, text=text)
106
self.client.click(xpath=self.search_button_xpath,
107
timeout=constants.FOR_ELEMENT)
109
def click_result_by_number(self, item_number):
110
"""Click on the given result number in the results list.
112
:param item_number: The item in the results list we should click on.
114
item_xpath = self._get_result_xpath_by_number(item_number)
115
self.client.waits.forElement(xpath=item_xpath,
116
timeout=constants.FOR_ELEMENT)
117
self.client.click(xpath=item_xpath)
121
"""A base class to provide logon capability."""
122
def getLoggedInClient(self):
123
"""Return a new client, and the url that it has loaded."""
124
client = WindmillTestClient(self.suite_name)
125
email = self.user.email
126
password = self.user.password
127
client.open(url=lpuser.get_basic_login_url(email, password))
128
client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
129
client.open(url=self.url)
130
client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
134
class InlineEditorWidgetTest(WidgetTest):
135
"""Test that the inline editor widget is working properly on a page."""
137
def __init__(self, url, widget_id, expected_value, new_value, name=None,
138
suite_name='inline_editor', user=lpuser.NO_PRIV,
140
"""Create a new InlineEditorWidgetTest.
142
:param url: The URL to the page on which the widget lives.
143
:param widget_id: The HTML id of the widget.
144
:param expected_value: The current expected value of the widget.
145
:param new_value: The value to change the field to.
146
:param suite: The suite in which this test is part of.
147
:param user: The user who should be logged in.
148
:param widget_tag: Element tag the widget is inside of.
152
self.__name__ = ('test_%s_inline_edit'
153
% widget_id.replace('-', '_'))
156
self.widget_id = widget_id
157
self.expected_value = expected_value
158
self.new_value = new_value
159
self.suite_name = suite_name
161
self.widget_tag = widget_tag
164
"""Tests the widget is hooked and works properly.
168
* asserts that the widget is initialized to the expected value;
169
* uses the inline editor to change to the new value;
170
* asserts that the page was updated with the new value;
171
* reloads and verifies that the new value sticked.
173
client = self.getLoggedInClient()
174
widget_base = u"//%s[@id='%s']" % (self.widget_tag, self.widget_id)
175
client.waits.forElement(
176
xpath=widget_base + '/a', timeout=constants.FOR_ELEMENT)
177
client.asserts.assertText(
178
xpath=widget_base + '/span[1]', validator=self.expected_value)
179
client.click(xpath=widget_base + '/a')
180
client.waits.forElement(
181
xpath=widget_base + '/a', timeout=constants.FOR_ELEMENT)
182
client.waits.forElement(
183
xpath=widget_base + '//textarea', timeout=constants.FOR_ELEMENT)
185
xpath=widget_base + '//textarea', text=self.new_value)
186
client.click(xpath=widget_base + '//button[last()]')
187
client.waits.forElement(
188
xpath=widget_base + '/span[1][text()="' + self.new_value + '"]',
189
timeout=constants.FOR_ELEMENT)
192
def search_picker_widget(client, search_text):
193
"""Search using an on-page picker widget."""
194
picker = SearchPickerWidget(client)
195
picker.should_be_visible()
196
picker.do_search(search_text)
199
def search_and_select_picker_widget(client, search_text, result_index):
200
"""Search using an on-page picker widget and click a search result."""
201
picker = SearchPickerWidget(client)
202
picker.should_be_visible()
203
picker.do_search(search_text)
204
picker.click_result_by_number(result_index)
207
class InlinePickerWidgetSearchTest(WidgetTest):
208
"""Test that the Picker widget edits a value inline."""
210
def __init__(self, url, activator_id, search_text, result_index,
211
new_value, name=None, suite_name='inline_picker_search_test',
212
user=lpuser.FOO_BAR):
213
"""Create a new InlinePickerSearchWidgetTest.
215
:param url: The URL to the page on which the widget lives.
216
:param activator_id: The HTML id of the activator widget.
217
:param search_text: Picker search value.
218
:param result_index: Item in picker result to select.
219
:param new_value: The value to change the field to.
220
:param name: Override the test name, if necessary.
221
:param suite: The suite in which this test is part of.
222
:param user: The user who should be logged in.
226
self.__name__ = 'test_%s_inline_picker' % (
227
activator_id.replace('-', '_'),)
230
self.activator_id = activator_id
231
self.search_text = search_text
232
self.result_index = result_index
233
self.new_value = new_value
234
self.suite_name = suite_name
239
client = self.getLoggedInClient()
241
# Click on edit button.
244
"/button[not(contains(@class, 'yui3-activator-hidden'))]"
246
client.waits.forElement(
248
timeout=constants.FOR_ELEMENT)
249
client.click(xpath=button_xpath)
252
search_and_select_picker_widget(
253
client, self.search_text, self.result_index)
256
client.waits.sleep(milliseconds=u'2000')
257
client.asserts.assertText(
258
xpath=u"//span[@id='%s']//a" % self.activator_id,
259
validator=self.new_value)
261
# Reload the page to verify that the selected value is persisted.
262
client.open(url=self.url)
263
client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
265
# Verify update, again.
266
client.waits.forElement(
267
xpath=u"//span[@id='%s']//a" % self.activator_id,
268
timeout=constants.FOR_ELEMENT)
269
client.asserts.assertText(
270
xpath=u"//span[@id='%s']//a" % self.activator_id,
271
validator=self.new_value)
274
class InlinePickerWidgetButtonTest(WidgetTest):
275
"""Test custom buttons/links added to the Picker."""
277
def __init__(self, url, activator_id, button_class, new_value,
278
name=None, suite_name='inline_picker_button_test',
279
user=lpuser.FOO_BAR):
280
"""Create a new InlinePickerWidgetButtonTest.
282
:param url: The URL to the page on which the widget lives.
283
:param activator_id: The HTML id of the activator widget.
284
:param button_class: The CSS class identifying the button.
285
:param new_value: The value to change the field to.
286
:param name: Override the test name, if necessary.
287
:param suite: The suite in which this test is part of.
288
:param user: The user who should be logged in.
291
self.activator_id = activator_id
292
self.button_class = button_class
293
self.new_value = new_value
294
self.suite_name = suite_name
297
self.__name__ = 'test_%s_inline_picker' % (
298
activator_id.replace('-', '_'),)
304
client = self.getLoggedInClient()
306
# Click on edit button.
309
"/button[not(contains(@class, 'yui3-activator-hidden'))]"
311
client.waits.forElement(xpath=button_xpath, timeout=u'25000')
312
client.click(xpath=button_xpath)
314
# Click on remove button.
315
remove_button_xpath = (
316
u"//div[contains(@class, 'yui3-picker ') "
317
"and not(contains(@class, 'yui3-picker-hidden'))]"
318
"//*[contains(@class, '%s')]" % self.button_class)
319
client.waits.forElement(xpath=remove_button_xpath, timeout=u'25000')
320
client.click(xpath=remove_button_xpath)
321
client.waits.sleep(milliseconds=u'2000')
324
client.asserts.assertText(
325
xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
327
validator=self.new_value)
329
# Reload the page to verify that the selected value is persisted.
330
client.open(url=self.url)
331
client.waits.forPageLoad(timeout=u'25000')
333
# Verify removal, again.
334
client.waits.forElement(
335
xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
338
client.asserts.assertText(
339
xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
341
validator=self.new_value)
344
class FormPickerWidgetTest(WidgetTest):
345
"""Test that the Picker widget edits a form value properly."""
347
def __init__(self, url, short_field_name, search_text, result_index,
348
new_value, name=None, suite_name='form_picker',
349
user=lpuser.FOO_BAR):
350
"""Create a new FormPickerWidgetTest.
352
:param url: The URL to the page on which the widget lives.
353
:param short_field_name: The name of the Zope attribute. For example,
354
'owner' which has the id 'field.owner'.
355
:param search_text: Picker search value.
356
:param result_index: Item in picker result to select.
357
:param new_value: The value to change the field to.
358
:param name: Override the test name, if necessary.
359
:param suite: The suite in which this test is part of.
360
:param user: The user who should be logged in.
364
self.__name__ = 'test_%s_form_picker' % (
365
short_field_name.replace('-', '_'),)
368
self.search_text = search_text
369
self.result_index = result_index
370
self.new_value = new_value
371
self.suite_name = suite_name
373
self.choose_link_id = 'show-widget-field-%s' % short_field_name
374
self.field_id = 'field.%s' % short_field_name
378
client = self.getLoggedInClient()
380
# Click on "Choose" link to show picker for the given field.
381
client.waits.forElement(
382
id=self.choose_link_id, timeout=constants.PAGE_LOAD)
383
client.click(id=self.choose_link_id)
386
search_and_select_picker_widget(
387
client, self.search_text, self.result_index)
390
client.asserts.assertProperty(
391
id=self.field_id, validator=u"value|%s" % self.new_value)