~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/testing/windmill/widgets.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-06-27 23:10:34 UTC
  • mfrom: (13299.1.18 windmill-death)
  • Revision ID: launchpad@pqm.canonical.com-20110627231034-rykdadu5vb2uqzpz
[r=sinzui,
 stevenk][no-qa] Remove Windmill test infrastructure and annotate old
 tests for refactoring.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
2
 
# GNU Affero General Public License version 3 (see the file LICENSE).
3
 
 
4
 
"""Test helpers for common AJAX widgets."""
5
 
 
6
 
__metaclass__ = type
7
 
__all__ = [
8
 
    'FormPickerWidgetTest',
9
 
    'InlineEditorWidgetTest',
10
 
    'InlinePickerWidgetButtonTest',
11
 
    'InlinePickerWidgetSearchTest',
12
 
    'OnPageWidget',
13
 
    'search_and_select_picker_widget',
14
 
    'search_picker_widget',
15
 
    ]
16
 
 
17
 
 
18
 
from windmill.authoring import WindmillTestClient
19
 
 
20
 
from lp.testing.windmill import (
21
 
    constants,
22
 
    lpuser,
23
 
    )
24
 
 
25
 
 
26
 
class OnPageWidget:
27
 
    """A class that represents and interacts with an on-page JavaScript widget.
28
 
 
29
 
    The widget is assumed to be a YUI widget controlled by yui-X-hidden classes.
30
 
    """
31
 
 
32
 
    def __init__(self, client, widget_name):
33
 
        """Constructor.
34
 
 
35
 
        :param client: A WindmillTestClient instance for interacting with pages.
36
 
        :param widget_name: The class name of the YUI widget, like 'yui3-picker'.
37
 
        """
38
 
        self.client = client
39
 
        self.widget_name = widget_name
40
 
 
41
 
    @property
42
 
    def xpath(self):
43
 
        """The XPath of this widget, not including the hidden or visible state.
44
 
        """
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
48
 
 
49
 
    @property
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)
57
 
 
58
 
    @property
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)
66
 
 
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)
71
 
 
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)
76
 
 
77
 
 
78
 
class SearchPickerWidget(OnPageWidget):
79
 
    """A proxy for the yui3-picker widget from lazr-js."""
80
 
 
81
 
    def __init__(self, client):
82
 
        """Constructor.
83
 
 
84
 
        :param client: A WindmillTestClient instance.
85
 
        """
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 = (
90
 
            self.visible_xpath +
91
 
            "//div[@class='yui3-picker-search-box']/button")
92
 
 
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
97
 
 
98
 
    def do_search(self, text):
99
 
        """Enter some text in the search field and click the search button.
100
 
 
101
 
        :param text: The text we want to search for.
102
 
        """
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)
108
 
 
109
 
    def click_result_by_number(self, item_number):
110
 
        """Click on the given result number in the results list.
111
 
 
112
 
        :param item_number: The item in the results list we should click on.
113
 
        """
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)
118
 
 
119
 
 
120
 
class WidgetTest:
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)
131
 
        return client
132
 
 
133
 
 
134
 
class InlineEditorWidgetTest(WidgetTest):
135
 
    """Test that the inline editor widget is working properly on a page."""
136
 
 
137
 
    def __init__(self, url, widget_id, expected_value, new_value, name=None,
138
 
                 suite_name='inline_editor', user=lpuser.NO_PRIV,
139
 
                 widget_tag='h1'):
140
 
        """Create a new InlineEditorWidgetTest.
141
 
 
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.
149
 
        """
150
 
        self.url = url
151
 
        if name is None:
152
 
            self.__name__ = ('test_%s_inline_edit'
153
 
                             % widget_id.replace('-', '_'))
154
 
        else:
155
 
            self.__name__ = name
156
 
        self.widget_id = widget_id
157
 
        self.expected_value = expected_value
158
 
        self.new_value = new_value
159
 
        self.suite_name = suite_name
160
 
        self.user = user
161
 
        self.widget_tag = widget_tag
162
 
 
163
 
    def __call__(self):
164
 
        """Tests the widget is hooked and works properly.
165
 
 
166
 
        The test:
167
 
        * opens the url;
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.
172
 
        """
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)
184
 
        client.type(
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)
190
 
 
191
 
 
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)
197
 
 
198
 
 
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)
205
 
 
206
 
 
207
 
class InlinePickerWidgetSearchTest(WidgetTest):
208
 
    """Test that the Picker widget edits a value inline."""
209
 
 
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.
214
 
 
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.
223
 
        """
224
 
        self.url = url
225
 
        if name is None:
226
 
            self.__name__ = 'test_%s_inline_picker' % (
227
 
                activator_id.replace('-', '_'),)
228
 
        else:
229
 
            self.__name__ = name
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
235
 
        self.user = user
236
 
 
237
 
    def __call__(self):
238
 
        # Load page.
239
 
        client = self.getLoggedInClient()
240
 
 
241
 
        # Click on edit button.
242
 
        button_xpath = (
243
 
            u"//span[@id='%s']"
244
 
             "/button[not(contains(@class, 'yui3-activator-hidden'))]"
245
 
             % self.activator_id)
246
 
        client.waits.forElement(
247
 
            xpath=button_xpath,
248
 
            timeout=constants.FOR_ELEMENT)
249
 
        client.click(xpath=button_xpath)
250
 
 
251
 
        # Search picker.
252
 
        search_and_select_picker_widget(
253
 
            client, self.search_text, self.result_index)
254
 
 
255
 
        # Verify update.
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)
260
 
 
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)
264
 
 
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)
272
 
 
273
 
 
274
 
class InlinePickerWidgetButtonTest(WidgetTest):
275
 
    """Test custom buttons/links added to the Picker."""
276
 
 
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.
281
 
 
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.
289
 
        """
290
 
        self.url = url
291
 
        self.activator_id = activator_id
292
 
        self.button_class = button_class
293
 
        self.new_value = new_value
294
 
        self.suite_name = suite_name
295
 
        self.user = user
296
 
        if name is None:
297
 
            self.__name__ = 'test_%s_inline_picker' % (
298
 
                activator_id.replace('-', '_'),)
299
 
        else:
300
 
            self.__name__ = name
301
 
 
302
 
    def __call__(self):
303
 
        # Load page.
304
 
        client = self.getLoggedInClient()
305
 
 
306
 
        # Click on edit button.
307
 
        button_xpath = (
308
 
            u"//span[@id='%s']"
309
 
             "/button[not(contains(@class, 'yui3-activator-hidden'))]"
310
 
             % self.activator_id)
311
 
        client.waits.forElement(xpath=button_xpath, timeout=u'25000')
312
 
        client.click(xpath=button_xpath)
313
 
 
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')
322
 
 
323
 
        # Verify removal.
324
 
        client.asserts.assertText(
325
 
            xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
326
 
                  % self.activator_id,
327
 
            validator=self.new_value)
328
 
 
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')
332
 
 
333
 
        # Verify removal, again.
334
 
        client.waits.forElement(
335
 
            xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
336
 
                  % self.activator_id,
337
 
            timeout=u'25000')
338
 
        client.asserts.assertText(
339
 
            xpath=u"//span[@id='%s']/span[@class='yui3-activator-data-box']"
340
 
                  % self.activator_id,
341
 
            validator=self.new_value)
342
 
 
343
 
 
344
 
class FormPickerWidgetTest(WidgetTest):
345
 
    """Test that the Picker widget edits a form value properly."""
346
 
 
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.
351
 
 
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.
361
 
        """
362
 
        self.url = url
363
 
        if name is None:
364
 
            self.__name__ = 'test_%s_form_picker' % (
365
 
                short_field_name.replace('-', '_'),)
366
 
        else:
367
 
            self.__name__ = name
368
 
        self.search_text = search_text
369
 
        self.result_index = result_index
370
 
        self.new_value = new_value
371
 
        self.suite_name = suite_name
372
 
        self.user = user
373
 
        self.choose_link_id = 'show-widget-field-%s' % short_field_name
374
 
        self.field_id = 'field.%s' % short_field_name
375
 
 
376
 
    def __call__(self):
377
 
        # Load page.
378
 
        client = self.getLoggedInClient()
379
 
 
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)
384
 
 
385
 
        # Search picker.
386
 
        search_and_select_picker_widget(
387
 
            client, self.search_text, self.result_index)
388
 
 
389
 
        # Verify value.
390
 
        client.asserts.assertProperty(
391
 
            id=self.field_id, validator=u"value|%s" % self.new_value)