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
=== modified file 'src/sst/actions.py'
--- src/sst/actions.py 2013-04-09 15:25:02 +0000
+++ src/sst/actions.py 2013-04-10 15:27:28 +0000
@@ -453,7 +453,7 @@
453 _make_results_dir()453 _make_results_dir()
454 browsermob_proxy.save_har(_make_useable_har_name())454 browsermob_proxy.save_har(_make_useable_har_name())
455455
456456
457def assert_checkbox(id_or_elem):457def assert_checkbox(id_or_elem):
458 """458 """
459 Assert that the element is a checkbox.459 Assert that the element is a checkbox.
@@ -473,23 +473,54 @@
473 or isn't a checkbox."""473 or isn't a checkbox."""
474 checkbox = assert_checkbox(id_or_elem)474 checkbox = assert_checkbox(id_or_elem)
475 real = checkbox.is_selected()475 real = checkbox.is_selected()
476 msg = 'Checkbox: %r - Has Value: %r' % (_get_text(checkbox), real)476 msg = 'Checkbox: %r - Has Value: %r' % (_element_to_string(checkbox), real)
477 if real != value:477 if real != value:
478 _raise(msg)478 _raise(msg)
479479
480480
481def _element_to_string(element):
482 element_id = element.get_attribute('id')
483 if element_id:
484 return element_id
485 else:
486 element_text = _get_text(element)
487 if element_text:
488 return element_text
489 else:
490 return element.get_attribute('outerHTML')
491
492
493def _get_text(elem):
494 text = None
495 try:
496 text = elem.text
497 except InvalidElementStateException:
498 pass
499 if text:
500 # Note that some elements (like textfields) return empty string
501 # for text and we still need to call value
502 return text
503 try:
504 text = elem.get_attribute('value')
505 except InvalidElementStateException:
506 pass
507 return text
508
509
510
481def toggle_checkbox(id_or_elem):511def toggle_checkbox(id_or_elem):
482 """512 """
483 Toggle the checkbox value. Takes an element id or object. Raises a failure513 Toggle the checkbox value. Takes an element id or object. Raises a failure
484 exception if the element specified doesn't exist or isn't a checkbox."""514 exception if the element specified doesn't exist or isn't a checkbox."""
485 checkbox = assert_checkbox(id_or_elem)515 checkbox = assert_checkbox(id_or_elem)
486 logger.debug('Toggling checkbox: %r' % _get_text(checkbox))516 element_string = _element_to_string(checkbox)
517 logger.debug('Toggling checkbox: %r' % element_string)
487 before = checkbox.is_selected()518 before = checkbox.is_selected()
488 checkbox.click()519 checkbox.click()
489 after = checkbox.is_selected()520 after = checkbox.is_selected()
490 msg = 'Checkbox: %r - was not toggled, value remains: %r' \
491 % (_get_text(checkbox), before)
492 if before == after:521 if before == after:
522 msg = 'Checkbox: %r - was not toggled, value remains: %r' \
523 % (element_string, before)
493 _raise(msg)524 _raise(msg)
494525
495526
@@ -499,7 +530,8 @@
499 exception if the element specified doesn't exist or isn't a checkbox."""530 exception if the element specified doesn't exist or isn't a checkbox."""
500 checkbox = assert_checkbox(id_or_elem)531 checkbox = assert_checkbox(id_or_elem)
501 logger.debug(532 logger.debug(
502 'Setting checkbox %r to %r' % (_get_text(checkbox), new_value))533 'Setting checkbox %r to %r' % (_element_to_string(checkbox),
534 new_value))
503 # There is no method to 'unset' a checkbox in the browser object535 # There is no method to 'unset' a checkbox in the browser object
504 current_value = checkbox.is_selected()536 current_value = checkbox.is_selected()
505 if new_value != current_value:537 if new_value != current_value:
@@ -526,7 +558,7 @@
526 """558 """
527 key_element = _get_elem(id_or_elem)559 key_element = _get_elem(id_or_elem)
528 msg = 'Simulating keypress on %r with %r key' \560 msg = 'Simulating keypress on %r with %r key' \
529 % (_get_text(key_element), key_to_press)561 % (_element_to_string(key_element), key_to_press)
530 logger.debug(msg)562 logger.debug(msg)
531 key_code = _make_keycode(key_to_press)563 key_code = _make_keycode(key_to_press)
532 key_element.send_keys(key_code)564 key_element.send_keys(key_code)
@@ -558,7 +590,7 @@
558 off by passing `clear=False`."""590 off by passing `clear=False`."""
559 textfield = assert_textfield(id_or_elem)591 textfield = assert_textfield(id_or_elem)
560 msg = 'Writing to textfield %r with text %r' \592 msg = 'Writing to textfield %r with text %r' \
561 % (_get_text(textfield), new_text)593 % (_element_to_string(textfield), new_text)
562 logger.debug(msg)594 logger.debug(msg)
563595
564 # clear field like this, don't use clear()596 # clear field like this, don't use clear()
@@ -576,7 +608,7 @@
576 current_text = textfield.get_attribute('value')608 current_text = textfield.get_attribute('value')
577 if current_text != new_text:609 if current_text != new_text:
578 msg = 'Textfield: %r - did not write. Text was: %r' \610 msg = 'Textfield: %r - did not write. Text was: %r' \
579 % (_get_text(textfield), current_text)611 % (_element_to_string(textfield), current_text)
580 _raise(msg)612 _raise(msg)
581613
582614
@@ -589,7 +621,7 @@
589 link = _get_elem(id_or_elem)621 link = _get_elem(id_or_elem)
590 if link.tag_name != 'a':622 if link.tag_name != 'a':
591 msg = 'The text %r is not part of a Link or a Link ID' \623 msg = 'The text %r is not part of a Link or a Link ID' \
592 % _get_text(link)624 % _element_to_string(link)
593 _raise(msg)625 _raise(msg)
594 return link626 return link
595627
@@ -623,7 +655,7 @@
623 _logger.debug('Capturing http traffic...')655 _logger.debug('Capturing http traffic...')
624 browsermob_proxy.new_har()656 browsermob_proxy.new_har()
625657
626 logger.debug('Clicking link %r' % _get_text(link))658 logger.debug('Clicking link %r' % _element_to_string(link))
627 link.click()659 link.click()
628660
629 if wait:661 if wait:
@@ -667,7 +699,7 @@
667 logger.debug('Capturing http traffic...')699 logger.debug('Capturing http traffic...')
668 browsermob_proxy.new_har()700 browsermob_proxy.new_har()
669701
670 logger.debug('Clicking element %r' % _get_text(elem))702 logger.debug('Clicking element %r' % _element_to_string(elem))
671 elem.click()703 elem.click()
672704
673 if wait:705 if wait:
@@ -904,7 +936,8 @@
904 """Set the select drop-list to a text or value specified."""936 """Set the select drop-list to a text or value specified."""
905 elem = assert_dropdown(id_or_elem)937 elem = assert_dropdown(id_or_elem)
906 logger.debug(938 logger.debug(
907 'Setting %r option list to %r' % (_get_text(elem), text or value))939 'Setting %r option list to %r' % (_element_to_string(elem),
940 text or value))
908 if text and not value:941 if text and not value:
909 for element in elem.find_elements_by_tag_name('option'):942 for element in elem.find_elements_by_tag_name('option'):
910 if element.text == text:943 if element.text == text:
@@ -958,7 +991,7 @@
958 a radio button"""991 a radio button"""
959 elem = assert_radio(id_or_elem)992 elem = assert_radio(id_or_elem)
960 selected = elem.is_selected()993 selected = elem.is_selected()
961 msg = 'Radio %r should be set to: %s.' % (_get_text(elem), value)994 msg = 'Radio %r should be set to: %s.' % (_element_to_string(elem), value)
962 if value != selected:995 if value != selected:
963 _raise(msg)996 _raise(msg)
964997
@@ -966,27 +999,10 @@
966def set_radio_value(id_or_elem):999def set_radio_value(id_or_elem):
967 """Select the specified radio button."""1000 """Select the specified radio button."""
968 elem = assert_radio(id_or_elem)1001 elem = assert_radio(id_or_elem)
969 logger.debug('Selecting radio button item %r' % _get_text(elem))1002 logger.debug('Selecting radio button item %r' % _element_to_string(elem))
970 elem.click()1003 elem.click()
9711004
9721005
973def _get_text(elem):
974 text = None
975 try:
976 text = elem.text
977 except InvalidElementStateException:
978 pass
979 if text:
980 # Note that some elements (like textfields) return empty string
981 # for text and we still need to call value
982 return text
983 try:
984 text = elem.get_attribute('value')
985 except InvalidElementStateException:
986 pass
987 return text
988
989
990def assert_text(id_or_elem, text):1006def assert_text(id_or_elem, text):
991 """1007 """
992 Assert the specified element text is as specified.1008 Assert the specified element text is as specified.
@@ -996,11 +1012,7 @@
996 elem = _get_elem(id_or_elem)1012 elem = _get_elem(id_or_elem)
997 real = _get_text(elem)1013 real = _get_text(elem)
998 if real is None:1014 if real is None:
999 if isinstance(id_or_elem, str):1015 msg = 'Element %r has no text attribute' % _element_to_string(elem)
1000 element_string = id_or_elem
1001 else:
1002 element_string = elem.get_attribute('outerHTML')
1003 msg = 'Element %r has no text attribute' % element_string
1004 _raise(msg)1016 _raise(msg)
1005 if real != text:1017 if real != text:
1006 msg = 'Element text should be %r. It is %r.' % (text, real)1018 msg = 'Element text should be %r. It is %r.' % (text, real)
@@ -1015,7 +1027,7 @@
1015 elem = _get_elem(id_or_elem)1027 elem = _get_elem(id_or_elem)
1016 real = _get_text(elem)1028 real = _get_text(elem)
1017 if real is None:1029 if real is None:
1018 msg = 'Element %r has no text attribute' % _get_text(elem)1030 msg = 'Element %r has no text attribute' % _element_to_string(elem)
1019 _raise(msg)1031 _raise(msg)
1020 msg = 'Element text is %r. Does not contain %r' % (real, text)1032 msg = 'Element text is %r. Does not contain %r' % (real, text)
1021 if regex:1033 if regex:
@@ -1173,7 +1185,7 @@
1173 logger.debug('Capturing http traffic...')1185 logger.debug('Capturing http traffic...')
1174 browsermob_proxy.new_har()1186 browsermob_proxy.new_har()
11751187
1176 logger.debug('Clicking button %r' % _get_text(button))1188 logger.debug('Clicking button %r' % _element_to_string(button))
1177 button.click()1189 button.click()
11781190
1179 if wait:1191 if wait:
@@ -1420,7 +1432,8 @@
1420 the attribute using a regular expression search.1432 the attribute using a regular expression search.
1421 """1433 """
1422 elem = _get_elem(id_or_elem)1434 elem = _get_elem(id_or_elem)
1423 logger.debug('Checking attribute %r of %r' % (attribute, _get_text(elem)))1435 logger.debug(
1436 'Checking attribute %r of %r' % (attribute, _element_to_string(elem)))
1424 actual = elem.get_attribute(attribute)1437 actual = elem.get_attribute(attribute)
1425 if not regex:1438 if not regex:
1426 success = value == actual1439 success = value == actual
@@ -1442,7 +1455,7 @@
1442 elem = _get_elem(id_or_elem)1455 elem = _get_elem(id_or_elem)
1443 logger.debug(1456 logger.debug(
1444 'Checking css property %r: %r of %r' % (1457 'Checking css property %r: %r of %r' % (
1445 property, value, _get_text(elem)))1458 property, value, _element_to_string(elem)))
1446 actual = elem.value_of_css_property(property)1459 actual = elem.value_of_css_property(property)
1447 # some browsers return string with space padded commas, some don't.1460 # some browsers return string with space padded commas, some don't.
1448 actual = actual.replace(', ', ',')1461 actual = actual.replace(', ', ',')
14491462
=== modified file 'src/sst/selftests/assert_text.py'
--- src/sst/selftests/assert_text.py 2013-04-09 15:25:02 +0000
+++ src/sst/selftests/assert_text.py 2013-04-10 15:27:28 +0000
@@ -14,22 +14,11 @@
14# Test wrong text.14# Test wrong text.
15fails(assert_text, 'some_id', 'Wrong text')15fails(assert_text, 'some_id', 'Wrong text')
1616
17# Test no text with id.17# Test no text.
18try:18try:
19 assert_text('element_without_text', '')19 assert_text('element_without_text', '')
20except AssertionError as error:20except AssertionError as error:
21 assert_equal(21 assert_equal(
22 "Element 'element_without_text' has no text attribute", error.message)22 "Element u'element_without_text' has no text attribute", error.message)
23else:
24 raise AssertionError('assert_text did not fail.')
25
26# Test no text with element.
27try:
28 assert_text(get_element(id='element_without_text'), '')
29except AssertionError as error:
30 expected_message = ("Element u\'<p id=\"element_without_text\"></p>\' "
31 "has no text attribute")
32 assert_equal(
33 expected_message, error.message)
34else:23else:
35 raise AssertionError('assert_text did not fail.')24 raise AssertionError('assert_text did not fail.')
3625
=== added file 'src/sst/selftests/element_to_string.py'
--- src/sst/selftests/element_to_string.py 1970-01-01 00:00:00 +0000
+++ src/sst/selftests/element_to_string.py 2013-04-10 15:27:28 +0000
@@ -0,0 +1,19 @@
1import sst.actions
2
3
4sst.actions.go_to('/')
5sst.actions.assert_title('The Page Title')
6
7element_with_id = sst.actions.get_element(id='some_id')
8element_string = sst.actions._element_to_string(element_with_id)
9sst.actions.assert_equal('some_id', element_string)
10
11element_without_id_with_text = sst.actions.get_element(css_class='no_id')
12element_string = sst.actions._element_to_string(element_without_id_with_text)
13sst.actions.assert_equal('Element without id, with text.', element_string)
14
15element_without_id_without_text = sst.actions.get_element(
16 css_class='no_id_no_text')
17element_string = sst.actions._element_to_string(
18 element_without_id_without_text)
19sst.actions.assert_equal('<p class="no_id_no_text"></p>', element_string)
020
=== modified file 'src/sst/tests/test_actions.py'
--- src/sst/tests/test_actions.py 2013-02-11 19:03:51 +0000
+++ src/sst/tests/test_actions.py 2013-04-10 15:27:28 +0000
@@ -20,10 +20,9 @@
20from cStringIO import StringIO20from cStringIO import StringIO
21import logging21import logging
2222
2323import mock
24import testtools24import testtools
2525
26
27from sst import actions26from sst import actions
2827
2928
@@ -64,3 +63,46 @@
64 def test_wait_for_retries(self):63 def test_wait_for_retries(self):
65 self.assertRaisesOnlyOnce(None, actions.wait_for,64 self.assertRaisesOnlyOnce(None, actions.wait_for,
66 self.raise_stale_element)65 self.raise_stale_element)
66
67
68class TestElementToString(testtools.TestCase):
69
70 def test_element_with_id(self):
71 element = self._get_element_with_id(element_id='Test id')
72 self.assertEqual('Test id', actions._element_to_string(element))
73
74 def _get_element_with_id(self, element_id):
75 element = mock.Mock()
76 def mock_get_attribute(attribute):
77 if attribute == 'id':
78 return element_id
79 element.get_attribute.side_effect = mock_get_attribute
80 return element
81
82 def test_element_without_id_with_text(self):
83 element = self._get_element_without_id_with_text(text='Test text')
84 self.assertEqual('Test text', actions._element_to_string(element))
85
86 def _get_element_without_id_with_text(self, text):
87 element = mock.Mock()
88 element.get_attribute.return_value = None
89 element.text = text
90 return element
91
92 def test_element_without_id_without_text(self):
93 element = self._get_element_without_id_without_text(tag='p')
94 self.assertEqual(
95 '<p></p>', actions._element_to_string(element))
96
97 def _get_element_without_id_without_text(self, tag):
98 element = mock.Mock()
99 def mock_get_attribute(attribute):
100 values = {
101 'id': None,
102 'value': None,
103 'outerHTML': '<{0}></{0}>'.format(tag)
104 }
105 return values[attribute]
106 element.get_attribute.side_effect = mock_get_attribute
107 element.text = None
108 return element
67109
=== modified file 'src/testproject/templates/index.html'
--- src/testproject/templates/index.html 2013-04-09 02:23:36 +0000
+++ src/testproject/templates/index.html 2013-04-10 15:27:28 +0000
@@ -15,6 +15,8 @@
1515
16 <p class="unique_class" id="some_id">Some text here</p>16 <p class="unique_class" id="some_id">Some text here</p>
17 <p id="element_without_text"></p>17 <p id="element_without_text"></p>
18 <p class="no_id">Element without id, with text.</p>
19 <p class="no_id_no_text"></p>
18 <p class="some_class">More text</p>20 <p class="some_class">More text</p>
19 <p class="some_class">And yet more</p>21 <p class="some_class">And yet more</p>
2022

Subscribers

People subscribed via source and target branches