Merge lp:~elopio/selenium-simple-test/print_elements into lp:selenium-simple-test

Proposed by Leo Arias
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: 376
Merged at revision: 370
Proposed branch: lp:~elopio/selenium-simple-test/print_elements
Merge into: lp:selenium-simple-test
Prerequisite: lp:~elopio/selenium-simple-test/fix1166408-assert_text_with_no_text
Diff against target: 361 lines (+121/-56)
5 files modified
src/sst/actions.py (+54/-41)
src/sst/selftests/assert_text.py (+2/-13)
src/sst/selftests/element_to_string.py (+19/-0)
src/sst/tests/test_actions.py (+44/-2)
src/testproject/templates/index.html (+2/-0)
To merge this branch: bzr merge lp:~elopio/selenium-simple-test/print_elements
Reviewer Review Type Date Requested Status
Vincent Ladeuil (community) Approve
Review via email: mp+157904@code.launchpad.net

Commit message

Added _element_to_string to get a printable version of the element.

To post a comment you must log in.
373. By Leo Arias

Improved the test with id.

Revision history for this message
Vincent Ladeuil (vila) wrote :

I don't think this is a good use of mock. It at least triggers my main reluctance about using it: the tests using mock in this proposal are not doubled with tests using the real code.

Can you try rewriting those tests ?

review: Needs Information
374. By Leo Arias

Reverted testproj.db.

375. By Leo Arias

added acceptance tests for element_to_string.

376. By Leo Arias

Improved the readability of the element_to_string unit tests.

Revision history for this message
Leo Arias (elopio) wrote :
Download full text (11.9 KiB)

<elopio> vila: please take a look at http://bazaar.launchpad.net/~elopio/selenium-simple-test/print_elements/revision/375
<vila> elopio: hehe, even shorter ! So you don't need the mock ones anymore right ? <evil grin>
<elopio> vila: I disagree with you there :)
<vila> elopio: I'm all ears
<elopio> vila: this tests are nice to have, but they don't test a unit of code, they are retesting things that are already tested in selenium, they depend on firefox to run, they are slower, and the set up for the test is done on an external file (index.html), thus are not as readable as the other ones.
<vila> elopio: ha, then don't write them this way
<elopio> vila: now I'm all ears ;)
<elopio> vila: the way to solve all those problems is with a unit test and mock. I agree there are some code paths not tested on the original
<elopio> for example, _get_text
<vila> elopio: your implementation could be more testable but it would mean to not rely as much on actions.py then to be able to test it with a simple hmtl snippet
<vila> elopio: didn't you implemented some helpers to get there without involving firefox ?
<elopio> vila: no, firefox is always needed. I tried with htmlunit, but it sucks.
<elopio> we could do something with phantomjs.
<elopio> that would make it faster than firefox, but still slower than the mocked unit test.
<vila> the mocked one is faster because it does less, but you don't get a clear idea of what 'less' means by just reading the tests, you need to know the implementation to write them which is... bad
<elopio> vila: I wrote them before implementing them.
<vila> elopio: knowing how the implementation will work as opposed to knowing only the API
<vila> elopio: you test actions._element_to_string but mock element.get_attribute
<vila> elopio: how do you know they are related ?
<elopio> knowing what the function does, not how it is implemented.
<elopio> it is a really low level function, so it's implementation is straight-forward.
<elopio> but the implementation could change, what the tests describe is what it does. When there's no id attribute, then get the text.
<elopio> lets say we have a way to test an html snipped without a huge delay
<elopio> then
<elopio> element = mock.Mock()
<elopio> element.get_attribute.return_value = None
<elopio> element.text = 'Test text'
<elopio> would be the same as
<elopio> element = '<p>Test text</p>'
<elopio> the latter is shorter and clearer, I agree.
<vila> elopio: no, the later is shor.. see ?
<vila> elopio: moreover, even after reading the mock tests thrice, I still don't get what they do, if I really want to understand, I need to look at the implementation
<vila> elopio: and I still don't understand why there is a if in test_element_with_id.mock.get_attribute but a single call...
<elopio> vila: maybe I can make them clearer, for example making a function called get_element_without_id, and that creates and return the mock.
<vila> elopio: you'll end up duplicating the implementation which is even worse ;)
<elopio> no, let me try.
<elopio> vila: here: http://bazaar.launchpad.net/~elopio/selenium-simple-test/print_elements/revision/376#src/sst/tests/test_actions.py
<elopio> once we have a beautifulsoup sele...

Revision history for this message
Vincent Ladeuil (vila) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/sst/actions.py'
2--- src/sst/actions.py 2013-04-09 15:25:02 +0000
3+++ src/sst/actions.py 2013-04-10 15:27:28 +0000
4@@ -453,7 +453,7 @@
5 _make_results_dir()
6 browsermob_proxy.save_har(_make_useable_har_name())
7
8-
9+
10 def assert_checkbox(id_or_elem):
11 """
12 Assert that the element is a checkbox.
13@@ -473,23 +473,54 @@
14 or isn't a checkbox."""
15 checkbox = assert_checkbox(id_or_elem)
16 real = checkbox.is_selected()
17- msg = 'Checkbox: %r - Has Value: %r' % (_get_text(checkbox), real)
18+ msg = 'Checkbox: %r - Has Value: %r' % (_element_to_string(checkbox), real)
19 if real != value:
20 _raise(msg)
21
22
23+def _element_to_string(element):
24+ element_id = element.get_attribute('id')
25+ if element_id:
26+ return element_id
27+ else:
28+ element_text = _get_text(element)
29+ if element_text:
30+ return element_text
31+ else:
32+ return element.get_attribute('outerHTML')
33+
34+
35+def _get_text(elem):
36+ text = None
37+ try:
38+ text = elem.text
39+ except InvalidElementStateException:
40+ pass
41+ if text:
42+ # Note that some elements (like textfields) return empty string
43+ # for text and we still need to call value
44+ return text
45+ try:
46+ text = elem.get_attribute('value')
47+ except InvalidElementStateException:
48+ pass
49+ return text
50+
51+
52+
53 def toggle_checkbox(id_or_elem):
54 """
55 Toggle the checkbox value. Takes an element id or object. Raises a failure
56 exception if the element specified doesn't exist or isn't a checkbox."""
57 checkbox = assert_checkbox(id_or_elem)
58- logger.debug('Toggling checkbox: %r' % _get_text(checkbox))
59+ element_string = _element_to_string(checkbox)
60+ logger.debug('Toggling checkbox: %r' % element_string)
61 before = checkbox.is_selected()
62 checkbox.click()
63 after = checkbox.is_selected()
64- msg = 'Checkbox: %r - was not toggled, value remains: %r' \
65- % (_get_text(checkbox), before)
66 if before == after:
67+ msg = 'Checkbox: %r - was not toggled, value remains: %r' \
68+ % (element_string, before)
69 _raise(msg)
70
71
72@@ -499,7 +530,8 @@
73 exception if the element specified doesn't exist or isn't a checkbox."""
74 checkbox = assert_checkbox(id_or_elem)
75 logger.debug(
76- 'Setting checkbox %r to %r' % (_get_text(checkbox), new_value))
77+ 'Setting checkbox %r to %r' % (_element_to_string(checkbox),
78+ new_value))
79 # There is no method to 'unset' a checkbox in the browser object
80 current_value = checkbox.is_selected()
81 if new_value != current_value:
82@@ -526,7 +558,7 @@
83 """
84 key_element = _get_elem(id_or_elem)
85 msg = 'Simulating keypress on %r with %r key' \
86- % (_get_text(key_element), key_to_press)
87+ % (_element_to_string(key_element), key_to_press)
88 logger.debug(msg)
89 key_code = _make_keycode(key_to_press)
90 key_element.send_keys(key_code)
91@@ -558,7 +590,7 @@
92 off by passing `clear=False`."""
93 textfield = assert_textfield(id_or_elem)
94 msg = 'Writing to textfield %r with text %r' \
95- % (_get_text(textfield), new_text)
96+ % (_element_to_string(textfield), new_text)
97 logger.debug(msg)
98
99 # clear field like this, don't use clear()
100@@ -576,7 +608,7 @@
101 current_text = textfield.get_attribute('value')
102 if current_text != new_text:
103 msg = 'Textfield: %r - did not write. Text was: %r' \
104- % (_get_text(textfield), current_text)
105+ % (_element_to_string(textfield), current_text)
106 _raise(msg)
107
108
109@@ -589,7 +621,7 @@
110 link = _get_elem(id_or_elem)
111 if link.tag_name != 'a':
112 msg = 'The text %r is not part of a Link or a Link ID' \
113- % _get_text(link)
114+ % _element_to_string(link)
115 _raise(msg)
116 return link
117
118@@ -623,7 +655,7 @@
119 _logger.debug('Capturing http traffic...')
120 browsermob_proxy.new_har()
121
122- logger.debug('Clicking link %r' % _get_text(link))
123+ logger.debug('Clicking link %r' % _element_to_string(link))
124 link.click()
125
126 if wait:
127@@ -667,7 +699,7 @@
128 logger.debug('Capturing http traffic...')
129 browsermob_proxy.new_har()
130
131- logger.debug('Clicking element %r' % _get_text(elem))
132+ logger.debug('Clicking element %r' % _element_to_string(elem))
133 elem.click()
134
135 if wait:
136@@ -904,7 +936,8 @@
137 """Set the select drop-list to a text or value specified."""
138 elem = assert_dropdown(id_or_elem)
139 logger.debug(
140- 'Setting %r option list to %r' % (_get_text(elem), text or value))
141+ 'Setting %r option list to %r' % (_element_to_string(elem),
142+ text or value))
143 if text and not value:
144 for element in elem.find_elements_by_tag_name('option'):
145 if element.text == text:
146@@ -958,7 +991,7 @@
147 a radio button"""
148 elem = assert_radio(id_or_elem)
149 selected = elem.is_selected()
150- msg = 'Radio %r should be set to: %s.' % (_get_text(elem), value)
151+ msg = 'Radio %r should be set to: %s.' % (_element_to_string(elem), value)
152 if value != selected:
153 _raise(msg)
154
155@@ -966,27 +999,10 @@
156 def set_radio_value(id_or_elem):
157 """Select the specified radio button."""
158 elem = assert_radio(id_or_elem)
159- logger.debug('Selecting radio button item %r' % _get_text(elem))
160+ logger.debug('Selecting radio button item %r' % _element_to_string(elem))
161 elem.click()
162
163
164-def _get_text(elem):
165- text = None
166- try:
167- text = elem.text
168- except InvalidElementStateException:
169- pass
170- if text:
171- # Note that some elements (like textfields) return empty string
172- # for text and we still need to call value
173- return text
174- try:
175- text = elem.get_attribute('value')
176- except InvalidElementStateException:
177- pass
178- return text
179-
180-
181 def assert_text(id_or_elem, text):
182 """
183 Assert the specified element text is as specified.
184@@ -996,11 +1012,7 @@
185 elem = _get_elem(id_or_elem)
186 real = _get_text(elem)
187 if real is None:
188- if isinstance(id_or_elem, str):
189- element_string = id_or_elem
190- else:
191- element_string = elem.get_attribute('outerHTML')
192- msg = 'Element %r has no text attribute' % element_string
193+ msg = 'Element %r has no text attribute' % _element_to_string(elem)
194 _raise(msg)
195 if real != text:
196 msg = 'Element text should be %r. It is %r.' % (text, real)
197@@ -1015,7 +1027,7 @@
198 elem = _get_elem(id_or_elem)
199 real = _get_text(elem)
200 if real is None:
201- msg = 'Element %r has no text attribute' % _get_text(elem)
202+ msg = 'Element %r has no text attribute' % _element_to_string(elem)
203 _raise(msg)
204 msg = 'Element text is %r. Does not contain %r' % (real, text)
205 if regex:
206@@ -1173,7 +1185,7 @@
207 logger.debug('Capturing http traffic...')
208 browsermob_proxy.new_har()
209
210- logger.debug('Clicking button %r' % _get_text(button))
211+ logger.debug('Clicking button %r' % _element_to_string(button))
212 button.click()
213
214 if wait:
215@@ -1420,7 +1432,8 @@
216 the attribute using a regular expression search.
217 """
218 elem = _get_elem(id_or_elem)
219- logger.debug('Checking attribute %r of %r' % (attribute, _get_text(elem)))
220+ logger.debug(
221+ 'Checking attribute %r of %r' % (attribute, _element_to_string(elem)))
222 actual = elem.get_attribute(attribute)
223 if not regex:
224 success = value == actual
225@@ -1442,7 +1455,7 @@
226 elem = _get_elem(id_or_elem)
227 logger.debug(
228 'Checking css property %r: %r of %r' % (
229- property, value, _get_text(elem)))
230+ property, value, _element_to_string(elem)))
231 actual = elem.value_of_css_property(property)
232 # some browsers return string with space padded commas, some don't.
233 actual = actual.replace(', ', ',')
234
235=== modified file 'src/sst/selftests/assert_text.py'
236--- src/sst/selftests/assert_text.py 2013-04-09 15:25:02 +0000
237+++ src/sst/selftests/assert_text.py 2013-04-10 15:27:28 +0000
238@@ -14,22 +14,11 @@
239 # Test wrong text.
240 fails(assert_text, 'some_id', 'Wrong text')
241
242-# Test no text with id.
243+# Test no text.
244 try:
245 assert_text('element_without_text', '')
246 except AssertionError as error:
247 assert_equal(
248- "Element 'element_without_text' has no text attribute", error.message)
249-else:
250- raise AssertionError('assert_text did not fail.')
251-
252-# Test no text with element.
253-try:
254- assert_text(get_element(id='element_without_text'), '')
255-except AssertionError as error:
256- expected_message = ("Element u\'<p id=\"element_without_text\"></p>\' "
257- "has no text attribute")
258- assert_equal(
259- expected_message, error.message)
260+ "Element u'element_without_text' has no text attribute", error.message)
261 else:
262 raise AssertionError('assert_text did not fail.')
263
264=== added file 'src/sst/selftests/element_to_string.py'
265--- src/sst/selftests/element_to_string.py 1970-01-01 00:00:00 +0000
266+++ src/sst/selftests/element_to_string.py 2013-04-10 15:27:28 +0000
267@@ -0,0 +1,19 @@
268+import sst.actions
269+
270+
271+sst.actions.go_to('/')
272+sst.actions.assert_title('The Page Title')
273+
274+element_with_id = sst.actions.get_element(id='some_id')
275+element_string = sst.actions._element_to_string(element_with_id)
276+sst.actions.assert_equal('some_id', element_string)
277+
278+element_without_id_with_text = sst.actions.get_element(css_class='no_id')
279+element_string = sst.actions._element_to_string(element_without_id_with_text)
280+sst.actions.assert_equal('Element without id, with text.', element_string)
281+
282+element_without_id_without_text = sst.actions.get_element(
283+ css_class='no_id_no_text')
284+element_string = sst.actions._element_to_string(
285+ element_without_id_without_text)
286+sst.actions.assert_equal('<p class="no_id_no_text"></p>', element_string)
287
288=== modified file 'src/sst/tests/test_actions.py'
289--- src/sst/tests/test_actions.py 2013-02-11 19:03:51 +0000
290+++ src/sst/tests/test_actions.py 2013-04-10 15:27:28 +0000
291@@ -20,10 +20,9 @@
292 from cStringIO import StringIO
293 import logging
294
295-
296+import mock
297 import testtools
298
299-
300 from sst import actions
301
302
303@@ -64,3 +63,46 @@
304 def test_wait_for_retries(self):
305 self.assertRaisesOnlyOnce(None, actions.wait_for,
306 self.raise_stale_element)
307+
308+
309+class TestElementToString(testtools.TestCase):
310+
311+ def test_element_with_id(self):
312+ element = self._get_element_with_id(element_id='Test id')
313+ self.assertEqual('Test id', actions._element_to_string(element))
314+
315+ def _get_element_with_id(self, element_id):
316+ element = mock.Mock()
317+ def mock_get_attribute(attribute):
318+ if attribute == 'id':
319+ return element_id
320+ element.get_attribute.side_effect = mock_get_attribute
321+ return element
322+
323+ def test_element_without_id_with_text(self):
324+ element = self._get_element_without_id_with_text(text='Test text')
325+ self.assertEqual('Test text', actions._element_to_string(element))
326+
327+ def _get_element_without_id_with_text(self, text):
328+ element = mock.Mock()
329+ element.get_attribute.return_value = None
330+ element.text = text
331+ return element
332+
333+ def test_element_without_id_without_text(self):
334+ element = self._get_element_without_id_without_text(tag='p')
335+ self.assertEqual(
336+ '<p></p>', actions._element_to_string(element))
337+
338+ def _get_element_without_id_without_text(self, tag):
339+ element = mock.Mock()
340+ def mock_get_attribute(attribute):
341+ values = {
342+ 'id': None,
343+ 'value': None,
344+ 'outerHTML': '<{0}></{0}>'.format(tag)
345+ }
346+ return values[attribute]
347+ element.get_attribute.side_effect = mock_get_attribute
348+ element.text = None
349+ return element
350
351=== modified file 'src/testproject/templates/index.html'
352--- src/testproject/templates/index.html 2013-04-09 02:23:36 +0000
353+++ src/testproject/templates/index.html 2013-04-10 15:27:28 +0000
354@@ -15,6 +15,8 @@
355
356 <p class="unique_class" id="some_id">Some text here</p>
357 <p id="element_without_text"></p>
358+ <p class="no_id">Element without id, with text.</p>
359+ <p class="no_id_no_text"></p>
360 <p class="some_class">More text</p>
361 <p class="some_class">And yet more</p>
362

Subscribers

People subscribed via source and target branches