Merge lp:~allanlesage/address-book-app/abook_navigation_favorites into lp:address-book-app
- abook_navigation_favorites
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~allanlesage/address-book-app/abook_navigation_favorites |
Merge into: | lp:address-book-app |
Prerequisite: | lp:~jibel/address-book-app/abook_delete_contact_pickmode |
Diff against target: |
908 lines (+712/-11) 16 files modified
debian/control (+1/-0) tests/autopilot/address_book_app/emulators/__init__.py (+12/-0) tests/autopilot/address_book_app/emulators/address_book.py (+73/-0) tests/autopilot/address_book_app/emulators/contact_list_page.py (+201/-0) tests/autopilot/address_book_app/emulators/contact_view.py (+97/-0) tests/autopilot/address_book_app/emulators/main_window.py (+3/-5) tests/autopilot/address_book_app/emulators/toolbar.py (+38/-0) tests/autopilot/address_book_app/helpers.py (+55/-0) tests/autopilot/address_book_app/tests/__init__.py (+25/-6) tests/autopilot/address_book_app/tests/test_add_contact.py (+4/-0) tests/autopilot/address_book_app/tests/test_contactlist.py (+4/-0) tests/autopilot/address_book_app/tests/test_delete_contact.py (+72/-0) tests/autopilot/address_book_app/tests/test_edit_contact.py (+4/-0) tests/autopilot/address_book_app/tests/test_favorites.py (+121/-0) tests/autopilot/address_book_app/tests/test_multiple_pick_mode.py (+1/-0) tests/autopilot/address_book_app/tests/test_single_pick_mode.py (+1/-0) |
To merge this branch: | bzr merge lp:~allanlesage/address-book-app/abook_navigation_favorites |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Leo Arias (community) | code review | Approve | |
PS Jenkins bot | continuous-integration | Needs Fixing | |
Jean-Baptiste Lallement (community) | Needs Fixing | ||
Chris Gagnon (community) | Needs Fixing | ||
Review via email: mp+205264@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-03-11.
Commit message
Add favoriting tests.
Description of the change
Add some favoriting tests; meanwhile move the existing contacts db out of the way during tests--this was necessary because favoriting appears broken at the moment using the in-memory QTCONTACTS override (don't know if this is a bug or not). So bounce two services (address-
Note that the new address_book emulator comes mostly from iahmad and om26er but I've modified their method to use a templated vCard using jinja2--I'm open to a better method of generating these cards; a python-vobject module exists but ChrisGagnon notes that it's not Python3 so we'd like not to adopt.
- 132. By Allan LeSage
-
Merged trunk.
- 133. By Allan LeSage
-
PEP8 and pyflakes corrections.
- 134. By Allan LeSage
-
Add a python-psutil dependency.
PS Jenkins bot (ps-jenkins) wrote : | # |
Chris Gagnon (chris.gagnon) wrote : | # |
The emulators all functions all need docstrings lp #137 is good example of how it should be done, but it's missing the raises.
134 + def get_contact_
135 + contact_name,
136 + parent_
137 + """Find a label with text matching contact name.
138 +
139 + :param contact_name: Name of the contact, e.g. 'Fulano de Tal'
140 + :param parent_
141 + :raises StateNotFoundError: if contact_name and parent_
I get this error on all the favorites tests
Traceback (most recent call last):
File "/home/
list_
File "/usr/lib/
(self.
AttributeError: Class 'ContactListPage' has no attribute 'click_
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:134
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 135. By Allan LeSage
-
Resolve env prob which failed some favorites tests on transition from in-memory to faked db.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:135
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Chris Gagnon (chris.gagnon) wrote : | # |
There are a lot of pep8 issues in these tests, the ones that should be fixed in this MP are
test_favorites.
test_favorites.
test_favorites.
Jean-Baptiste Lallement (jibel) wrote : | # |
On an LXC container with all updates applied, I get the following error, maybe because a service is not running:
0:45:55.249 INFO globals:57 - *******
10:45:55.249 INFO globals:58 - Starting test address_
10:45:55.249 ERROR content:49 - Could not add content object 'None' due to IO Error: [Errno 13] Permission denied: '/var/log/syslog'
10:45:55.333 INFO _launcher:152 - Launching process: ['/usr/
10:45:56.409 DEBUG dbus:431 - Selecting objects of type MainWindow with attributes: {}
10:45:56.630 DEBUG dbus:431 - Selecting objects of type MainWindow with attributes: {}
10:45:56.645 DEBUG dbus:431 - Selecting objects of type ContactListPage with attributes: {'objectName': 'contactListPage'}
10:45:56.679 DEBUG dbus:431 - Selecting objects of type Label with attributes: {'text': 'Beth Whittard'}
10:45:56.704 ERROR contact_
10:45:56.720 INFO _launcher:340 - waiting for process to exit.
10:45:56.720 INFO _launcher:266 - Killing process 9593
}}}
Traceback (most recent call last):
File "/home/
list_
File "/home/
contact_name, parent_
File "/home/
raise StateNotFoundEr
StateNotFoundError: State not found for class 'Label'.
=======
About jinja2, I'd remove the dependency on jinja2 because it forces the installation of a new package on the device and the logic is simple enough to be implemented with str.format() and standard string functions.
=======
Data sample: Would it be possible to pre-load vcards instead of creating/updating contacts. contacts.db backup/restore and services restart adds a lot of complexity to the test and also requires an additional dependency on psutil.
- 136. By Allan LeSage
-
A fix for importing ContactList which broke individually-
running tests. - 137. By Allan LeSage
-
Added delete favorite test.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:136
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 138. By Allan LeSage
-
Add assertion for deleted contact not in contacts.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:138
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:138
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Chris Gagnon (chris.gagnon) wrote : | # |
The following should be moved into an emulator, maybe the 2 star is illuminated could moved in to one star_is_
(tests/
+ def get_contact_
+ phone_number_label = self.main_
+ text=phone_number)
+ return get_nearest_
+ phone_number_label,
+ 'ContactDetailP
+
+ def star_is_
+ contact_detail = self.get_
+ phone_number)
+ return contact_
+
+ def star_is_
+ contact_detail = self.get_
+ phone_number)
+ return contact_
+
+ def click_phone_
+ # TODO select_many and raise if > 1?
+ phone_number_label = self.main_
+ 'Label',
+ text=phone_number)
+ self.pointing_
- 139. By Allan LeSage
-
Some pep8 and docstring fixes.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:139
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 140. By Allan LeSage
-
Establish contact_view emulator, some massaging.
Chris Gagnon (chris.gagnon) wrote : | # |
tests/autopilot
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:140
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 141. By Allan LeSage
-
Add missing contact_view emulator.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:141
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 142. By Allan LeSage
-
Remove dependency on psutil.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:142
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 143. By Allan LeSage
-
Remove jinja2 dependency, transform vcard template to string.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:143
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 144. By Allan LeSage
-
Add proper docstrings to address_book emulator.
- 145. By Allan LeSage
-
Temp commit of contacts-db-wiping for ChrisGagnon's perusal.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:144
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:144
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 146. By Allan LeSage
-
New helper uses syncevolution to manipulate contacts db.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:146
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Jean-Baptiste Lallement (jibel) wrote : | # |
1. You need to add syncevolution as a dependency of address-
2. On desktop I always get this failure
11:53:00.053 INFO globals:58 - Starting test address_
11:53:00.054 ERROR content:49 - Could not add content object 'None' due to IO Error: [Errno 13] Permission denied: '/var/log/syslog'
11:53:00.240 INFO _launcher:152 - Launching process: ['/usr/
11:53:01.314 DEBUG dbus:431 - Selecting objects of type MainWindow with attributes: {}
11:53:01.533 DEBUG dbus:431 - Selecting objects of type MainWindow with attributes: {}
11:53:01.548 DEBUG dbus:431 - Selecting objects of type ContactListPage with attributes: {'objectName': 'contactListPage'}
11:53:01.576 DEBUG dbus:431 - Selecting objects of type Label with attributes: {'text': 'Beth Whittard'}
11:53:01.593 ERROR contact_
11:53:02.893 INFO _launcher:340 - waiting for process to exit.
11:53:02.893 INFO _launcher:266 - Killing process 7151
}}}
Traceback (most recent call last):
File "/home/
contact_
File "/home/
contact_name, parent_
File "/home/
raise StateNotFoundEr
StateNotFoundError: State not found for class 'Label'.
Jean-Baptiste Lallement (jibel) wrote : | # |
For the StateNotFoundError, I tried this patch and it seems to be working.
'=== modified file 'tests/
'--- tests/autopilot
'+++ tests/autopilot
'@@ -100,9 +100,18 @@
' :raises StateNotFoundError: if contact_name is not found
' """
' try:
'- contact_name_labels = self.select_many(
'- "Label",
'- text=contact_name)
'+ # Note:
'+ # There is no wait_select_many, this loop simulates it and retries
'+ # 3 times or until the list is populated. If the list is still
'+ # empty after the loop, the exception StateNotFoundEr
'+ # will be raised
'+ for i in range(3):
'+ contact_name_labels = self.select_many(
'+ "Label",
'+ text=contact_name)
'+ if contact_name_labels != []:
'+ break
'+ sleep(.5)
' for contact_name_label in contact_
' # we could have a contact or a favorite
' if get_nearest_
Jean-Baptiste Lallement (jibel) wrote : | # |
nicer version: http://
- 147. By Allan LeSage
-
Use Eventually matcher to assert contacts are returned by name.
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
I do not think this is the right approach, you should populate the vcard contact with some favorite contacts, this will allow you to test the functionality without touch on EDS.
Leo Arias (elopio) wrote : | # |
55 +# Borrowed the following helper class from
56 +# lp:address-book-service/examples/contacts.py
Wouldn't it be better if this is packaged and made available as address-
This is not a blocker for this branch, just part of my ranting about developers not writing anything for testability, and we covering for their faults.
=== added file 'tests/
I'd say this file needs unit test. With the query method we have all we need to check that the other methods are working correctly.
258 + # TODO select_many and raise if > 1?
wait_select_single will raise an exception if there's more than one item matching, so I think that's already covered. Unless I didn't get the comment.
299 + try:
300 + buttons = self.select_
301 + objectName=
302 + for button in buttons:
303 + if button.visible:
304 + self.pointing_
305 + except StateNotFoundError:
306 + LOGGER.error(
307 + 'Button with objectName "{0}" not found.'
308 + )
309 + raise
Please note that you are not breaking out of the for loop, so if there are many visible buttons, all of them will be clicked. That doesn't sound like the goal of this method.
I find this weird, as the purpose of the object name is to identify things uniquely. So if there are many buttons with the same name, we need to change the name.
This code could be simplified like this:
self.select_
That will raise an exception if there's no button with that name, if the button with that name is not visible, and if there are many buttons matching the query.
332 + def star_is_
I think we should keep a higher language, closer to the design user stories. From a user perspective, a illuminated starts means that the phone number is favorite, so I would do this:
def is_phone_
self.
That way we keep as private the implementation detail, and on the tests we call the public higher level method that is more likely to remain stable. If in the future design decides to change the widget of a favorite number from a start to a drop down list, we don't have to change any tests, just the method we call inside is_phone_favorite.
576 + contact_
577 + contact_view = self.main_
Here again, clicking a contact is actually an implementation details and it would be better as a private method in the emulator.
I prefer when the test reads like this:
contact_view = contact_
There's a nice selenium pattern called the page object: https:/
You might find it interesting to read. Not everything in there applies to the desktop, but some things do and I always try to follow them.
I find this as the most concise guide to avoid future pains with...
- 148. By Allan LeSage
-
Merged trunk, resolving conflicts (test passing).
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:147
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
Instead of adding contacts on the address-
We will go back to this MR, as soon as this bug get fixed: https:/
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:148
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Chris Gagnon (chris.gagnon) wrote : | # |
holding back on this MP until a bug is fixed is blocking other apps from landing tests. like the dialer and the messaging-app. When the in-memory backend is fixed we can change the helper to use it instead of using the address-
We want one place to add and remove contacts, so we don't have to update every project when there is a change. the address-book-app is the most logical place to keep the helper.
The tests are working currently, there is no reason to block landing the tests from landing.
- 149. By Allan LeSage
-
Add some unit tests for our address_book emulator, removing any speculative functionality ;) .
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:149
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 150. By Allan LeSage
-
After discussing with thomi and elopio, in the interest of landing, remove unit tests for autopilot emulators.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:149
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:150
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 151. By Allan LeSage
-
Restore address_book emulator, some important quotation-mark changes per elopio.
- 152. By Allan LeSage
-
Actually restore address_book emulator :/ , remove emulators tests to expedite landing.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:152
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
Could you guys try to use this service branch: https:/
And instead of adding the contacts direct in the server could you add it using vcard files? this will make the test more flexible in case of we change the server API.
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
A MR to refactory the contact and favorite list is in progress to get merged, we should adapt the test to this new layout.
https:/
- 153. By Allan LeSage
-
Merge trunk.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:153
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 154. By Allan LeSage
-
Docstring improvements and add autopilot action logger to emulators.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:154
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 155. By Allan LeSage
-
Adopt page pattern per elopio's suggestion.
- 156. By Allan LeSage
-
Simplify emulators' click_button per elopio's request.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:155
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:156
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Allan LeSage (allanlesage) wrote : | # |
Ready for review again, ubuntu-qa.
Concerning quotation marks for strings, I've straightened out for this commit (leaving triple-
Implemented the page pattern as elopio describes, thanks for that advice--formerly I've tried to keep the test-descriptions as stupid as possible, i.e. responding to visual stimulus, however I see the utility of separating the intention of the user from the visual implementation. Stashed the 'concise guide' for future reference :) .
Adjusted the simple selection singled out, removed an obsolete TODO.
elopio and I discussed adding unit tests with Thomi in Oakland, agreed that it'll be a good thing but exempted this MP (if we've changed our minds I can recover the tests I wrote for this emulator).
Lastly I've moved some of the emulator goodness to a 'helpers.py' file (although the packaging of this is opaque to me).
Leo Arias (elopio) wrote : | # |
37 +#!/usr/bin/env python
I think you don't need this, as this file will never be executed as a script.
190 + @autopilot_
This log is unnecessary, as you you are already logging the action when calling cancel or delete.
What I would do is turn this:
191 + def click_button(self, objectname):
into _click_button, so we know it shouldn't be used outside the class. If we need to click another button, we will need to create a method for it.
214 + def get_contact_
I would rename this to _get_contact_
With the current name, it sounds like something that a test would call to get the text inside the label showing the contact name.
238 + raise StateNotFoundEr
239 + except StateNotFoundError:
240 + logger.error('...')
241 + raise
I would prefer here to return our own exception, to differentiate it from something that autopilot throws.
Something like:
raise AddressBookErro
I don't want to delay your branch anymore. It's already really nice. I have some comments that I think would make it a little more readable, but I'll make a note to discuss with you about them next week.
Thanks a lot, nice work.
Allan LeSage (allanlesage) wrote : | # |
Thanks elopio for your review; I'll adapt some of your further suggestions after I've sorted renato's new address-book method, stay tuned. . . .
Unmerged revisions
- 156. By Allan LeSage
-
Simplify emulators' click_button per elopio's request.
- 155. By Allan LeSage
-
Adopt page pattern per elopio's suggestion.
- 154. By Allan LeSage
-
Docstring improvements and add autopilot action logger to emulators.
- 153. By Allan LeSage
-
Merge trunk.
- 152. By Allan LeSage
-
Actually restore address_book emulator :/ , remove emulators tests to expedite landing.
- 151. By Allan LeSage
-
Restore address_book emulator, some important quotation-mark changes per elopio.
- 150. By Allan LeSage
-
After discussing with thomi and elopio, in the interest of landing, remove unit tests for autopilot emulators.
- 149. By Allan LeSage
-
Add some unit tests for our address_book emulator, removing any speculative functionality ;) .
- 148. By Allan LeSage
-
Merged trunk, resolving conflicts (test passing).
- 147. By Allan LeSage
-
Use Eventually matcher to assert contacts are returned by name.
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2013-12-11 15:03:32 +0000 |
3 | +++ debian/control 2014-02-27 22:11:08 +0000 |
4 | @@ -68,6 +68,7 @@ |
5 | libqt5test5, |
6 | libqt5widgets5, |
7 | ubuntu-ui-toolkit-autopilot, |
8 | + syncevolution, |
9 | address-book-app (>= ${binary:Version}), |
10 | Description: Test package for address-book-app |
11 | Autopilot tests for the address-book-app package |
12 | |
13 | === modified file 'tests/autopilot/address_book_app/emulators/__init__.py' |
14 | --- tests/autopilot/address_book_app/emulators/__init__.py 2013-07-09 18:42:30 +0000 |
15 | +++ tests/autopilot/address_book_app/emulators/__init__.py 2014-02-27 22:11:08 +0000 |
16 | @@ -4,3 +4,15 @@ |
17 | # This program is free software: you can redistribute it and/or modify it |
18 | # under the terms of the GNU General Public License version 3, as published |
19 | # by the Free Software Foundation. |
20 | + |
21 | + |
22 | +def get_nearest_parent_matching_type(child, desired_type): |
23 | + """Climb down the widget tree, return first instance of desired_type""" |
24 | + while type(child).__name__ != desired_type: |
25 | + parent = child.get_parent() |
26 | + if type(child).__name__ == 'AddressBookApp': |
27 | + # we reached the root and found nothing |
28 | + return None |
29 | + else: |
30 | + child = parent |
31 | + return child |
32 | |
33 | === added file 'tests/autopilot/address_book_app/emulators/address_book.py' |
34 | --- tests/autopilot/address_book_app/emulators/address_book.py 1970-01-01 00:00:00 +0000 |
35 | +++ tests/autopilot/address_book_app/emulators/address_book.py 2014-02-27 22:11:08 +0000 |
36 | @@ -0,0 +1,73 @@ |
37 | +#!/usr/bin/env python |
38 | +# -*- encoding: utf-8 -*- |
39 | +# |
40 | +# Copyright 2014 Canonical Ltd. |
41 | +# |
42 | +# This program is free software; you can redistribute it and/or modify |
43 | +# it under the terms of the GNU Lesser General Public License as published by |
44 | +# the Free Software Foundation; version 3. |
45 | +# |
46 | +# This program is distributed in the hope that it will be useful, |
47 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
48 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
49 | +# GNU Lesser General Public License for more details. |
50 | +# |
51 | +# You should have received a copy of the GNU Lesser General Public License |
52 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
53 | +# |
54 | + |
55 | +# Borrowed the following helper class from |
56 | +# lp:address-book-service/examples/contacts.py |
57 | + |
58 | +import dbus |
59 | + |
60 | +DBUS_IFACE_ADD_BOOK = 'com.canonical.pim.AddressBook' |
61 | +DBUS_IFACE_ADD_BOOKVIEW = 'com.canonical.pim.AddressBookView' |
62 | + |
63 | +TEL_WORK_TEMPLATE = """ |
64 | +TEL;TYPE=WORK,VOICE;PID=1.1:{tel_work}""" |
65 | + |
66 | +VCARD_TEMPLATE = """BEGIN:VCARD |
67 | +VERSION:3.0 |
68 | +N:{name} |
69 | +FN:{f_name} |
70 | +TEL;TYPE=HOME,VOICE;PID=1.1:{tel_home}{tel_work} |
71 | +END:VCARD |
72 | +""" |
73 | + |
74 | + |
75 | +class AddressBook(object): |
76 | + |
77 | + def __init__(self): |
78 | + self.bus = dbus.SessionBus() |
79 | + self.addr = self.bus.get_object('com.canonical.pim', |
80 | + '/com/canonical/pim/AddressBook') |
81 | + self.addr_iface = dbus.Interface(self.addr, |
82 | + dbus_interface=DBUS_IFACE_ADD_BOOK) |
83 | + |
84 | + def create_contact(self, |
85 | + name, |
86 | + f_name=None, |
87 | + tel_home=None, |
88 | + tel_work=None, |
89 | + email=None): |
90 | + """Create a contact in address-book via dbus interface. |
91 | + |
92 | + NOTE that if only one phone number is specified, it must be tel_home. |
93 | + |
94 | + :param name: name of the contact, e.g. 'Lincoln;Abraham' |
95 | + :param f_name: formatted name of the contact, e.g. 'Abraham Lincoln' |
96 | + :param tel_home: telephone number string, e.g. '92340981' |
97 | + :param tel_work: telephone number string, e.g. '23984719' |
98 | + :param email: e-mail address string, e.g. 'abe@whitehouse.gov' |
99 | + """ |
100 | + contact_dict = { |
101 | + 'name': name, |
102 | + 'f_name': f_name if f_name else '', |
103 | + 'tel_home': tel_home if tel_home else '', |
104 | + 'tel_work': TEL_WORK_TEMPLATE.format( |
105 | + tel_work=tel_work) if tel_work else '', |
106 | + 'email': email if email else '' |
107 | + } |
108 | + vcard = VCARD_TEMPLATE.format(**contact_dict) |
109 | + return self.addr_iface.createContact(vcard, "") |
110 | |
111 | === added file 'tests/autopilot/address_book_app/emulators/contact_list_page.py' |
112 | --- tests/autopilot/address_book_app/emulators/contact_list_page.py 1970-01-01 00:00:00 +0000 |
113 | +++ tests/autopilot/address_book_app/emulators/contact_list_page.py 2014-02-27 22:11:08 +0000 |
114 | @@ -0,0 +1,201 @@ |
115 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
116 | +# Copyright 2014 Canonical |
117 | +# |
118 | +# This program is free software: you can redistribute it and/or modify it |
119 | +# under the terms of the GNU General Public License version 3, as published |
120 | +# by the Free Software Foundation. |
121 | + |
122 | +""" ContactListPage emulator for Addressbook App tests """ |
123 | + |
124 | +import logging |
125 | +from time import sleep |
126 | + |
127 | +from autopilot import logging as autopilot_logging |
128 | +from autopilot.introspection.dbus import StateNotFoundError |
129 | +from autopilot.matchers import Eventually |
130 | +from testtools.matchers import GreaterThan |
131 | +from ubuntuuitoolkit import emulators as uitk |
132 | + |
133 | +from address_book_app.emulators import get_nearest_parent_matching_type |
134 | + |
135 | +logger = logging.getLogger(__name__) |
136 | + |
137 | + |
138 | +class ContactListPage(uitk.UbuntuUIToolkitEmulatorBase): |
139 | + """ ContactListPage emulator class """ |
140 | + |
141 | + def __init__(self, *args): |
142 | + self.contacts = None |
143 | + self.selection_marks = [] |
144 | + self.selected_marks = [] |
145 | + super(ContactListPage, self).__init__(*args) |
146 | + |
147 | + def get_contacts(self): |
148 | + """Returns a list of ContactDelegate objects. |
149 | + |
150 | + Also populate self.selection_marks. |
151 | + |
152 | + :return: The list of ContactDelegate objects. |
153 | + |
154 | + """ |
155 | + sleep(1) |
156 | + self.contacts = self.select_many("ContactDelegate") |
157 | + self.selection_marks = [] |
158 | + for contact in self.contacts: |
159 | + if contact.visible: |
160 | + mark = contact.select_single("QQuickRectangle", |
161 | + objectName="selectionMark") |
162 | + self.selection_marks.append(mark) |
163 | + return self.contacts |
164 | + |
165 | + @autopilot_logging.log_action(logger.info) |
166 | + def select_contacts_by_index(self, indices): |
167 | + """Select contacts corresponding to the list of index in indices. |
168 | + |
169 | + :param indices: List of integer indices of contacts to select. |
170 | + |
171 | + """ |
172 | + self.deselect_all() |
173 | + |
174 | + # Select matching indices |
175 | + for idx in indices: |
176 | + self.selected_marks.append(self.selection_marks[idx]) |
177 | + self.pointing_device.click_object(self.selection_marks[idx]) |
178 | + |
179 | + @autopilot_logging.log_action(logger.info) |
180 | + def deselect_all(self): |
181 | + """Deselect every contact.""" |
182 | + contacts = self.select_many("ContactDelegate") |
183 | + self.selected_marks = [] |
184 | + for contact in contacts: |
185 | + if contact.selected: |
186 | + mark = contact.select_single("QQuickRectangle", |
187 | + objectName="selectionMark") |
188 | + self.pointing_device.click_object(mark) |
189 | + |
190 | + @autopilot_logging.log_action(logger.info) |
191 | + def click_button(self, objectname): |
192 | + """Press a button that matches objectname |
193 | + |
194 | + :param objectname: The name of the object. |
195 | + :raise StateNotFoundError: When a matching button is not found. |
196 | + |
197 | + """ |
198 | + buttons = self.select_single("Button", |
199 | + objectName=objectname, |
200 | + visible=True) |
201 | + self.pointing_device.click_object(button) |
202 | + |
203 | + @autopilot_logging.log_action(logger.info) |
204 | + def cancel(self): |
205 | + """Press the cancel button displayed when pick mode is enabled.""" |
206 | + self.click_button("DialogButtons.rejectButton") |
207 | + |
208 | + @autopilot_logging.log_action(logger.info) |
209 | + def delete(self): |
210 | + """Press the delete button displayed when pick mode is enabled.""" |
211 | + self.click_button("DialogButtons.acceptButton") |
212 | + self.get_contacts() |
213 | + |
214 | + def get_contact_by_name(self, |
215 | + contact_name, |
216 | + parent_delegate_type='ContactDelegate'): |
217 | + """Find a label with text matching contact name. |
218 | + |
219 | + :param contact_name: The name of the contact, e.g. 'Fulano de Tal'. |
220 | + :param parent_delegate_type: 'ContactDelegate' or 'FavoriteDelegate'. |
221 | + :return: The label for a matching contact. |
222 | + :raises StateNotFoundError: If the contact_name is not found. |
223 | + |
224 | + """ |
225 | + try: |
226 | + assert(lambda: len(self.select_many("Label", |
227 | + text=contact_name)), |
228 | + Eventually(GreaterThan(0))) |
229 | + contact_name_labels = self.select_many( |
230 | + "Label", |
231 | + text=contact_name) |
232 | + for contact_name_label in contact_name_labels: |
233 | + # we could have a contact or a favorite |
234 | + if get_nearest_parent_matching_type( |
235 | + contact_name_label, |
236 | + parent_delegate_type): |
237 | + return contact_name_label |
238 | + raise StateNotFoundError('Label') |
239 | + except StateNotFoundError: |
240 | + logger.error("Contact {} not found.".format(contact_name)) |
241 | + raise |
242 | + |
243 | + def get_favorite_by_name(self, contact_name): |
244 | + """Find a label with text matching contact name under Favorites. |
245 | + |
246 | + :param contact_name: Name of the contact, e.g. 'Fulano de Tal'. |
247 | + :return: The label for the metching favorite. |
248 | + :raises StateNotFoundError: If the contact_name is not found. |
249 | + |
250 | + """ |
251 | + return self.get_contact_by_name( |
252 | + contact_name, |
253 | + parent_delegate_type='FavoriteDelegate') |
254 | + |
255 | + @autopilot_logging.log_action(logger.info) |
256 | + def click_contact_by_name(self, |
257 | + contact_name, |
258 | + parent_delegate_type='ContactDelegate'): |
259 | + """Click a contact with label matching the given contact name. |
260 | + |
261 | + :param contact_name: Name of a contact, e.g. 'Fulano de Tal'. |
262 | + :param parent_delegate_type: 'ContactDelegate' or 'FavoriteDelegate'. |
263 | + :raises StateNotFoundError: If the contact_name is not found. |
264 | + |
265 | + """ |
266 | + contact_name_label = self.get_contact_by_name( |
267 | + contact_name, parent_delegate_type) |
268 | + self.pointing_device.click_object(contact_name_label) |
269 | + |
270 | + @autopilot_logging.log_action(logger.info) |
271 | + def open_contact_view_by_contact_name(self, contact_name): |
272 | + """Open the contact view page for a contact with the given name. |
273 | + |
274 | + :param contact_name: The name of the contact, e.g. 'Abe Lincoln'. |
275 | + :return: The ContactView for the contact. |
276 | + :raises StateNotFoundError: If the name is not found. |
277 | + |
278 | + """ |
279 | + self.click_contact_by_name(contact_name) |
280 | + |
281 | + @autopilot_logging.log_action(logger.info) |
282 | + def click_favorite_by_name(self, |
283 | + contact_name): |
284 | + """Click a favorite with label matching the given contact name. |
285 | + |
286 | + :param contact_name: Name of a contact, e.g. 'Fulano de Tal' |
287 | + :raises StateNotFoundError: If the contact_name is not found. |
288 | + |
289 | + """ |
290 | + contact_name_label = self.get_favorite_by_name( |
291 | + contact_name) |
292 | + self.pointing_device.click_object(contact_name_label) |
293 | + |
294 | + @autopilot_logging.log_action(logger.info) |
295 | + def open_contact_view_by_favorite_name(self, favorite_name): |
296 | + """Open the contact view page for a favorite with the given name. |
297 | + |
298 | + :param favorite_name: The name of the favorite, e.g. 'Abe Lincoln'. |
299 | + :return: The ContactView for the contact. |
300 | + :raises StateNotFoundError: If the name is not found. |
301 | + |
302 | + """ |
303 | + self.click_favorite_by_name(favorite_name) |
304 | + |
305 | + @autopilot_logging.log_action(logger.info) |
306 | + def click_phone_number(self, phone_number): |
307 | + """Click a label matching the given phone number. |
308 | + |
309 | + :param phone_number: A phone number, e.g. '3321 1232'. |
310 | + :raises StateNotFoundError: If the phone_number is not found. |
311 | + |
312 | + """ |
313 | + phone_number_label = self.wait_select_single('Label', |
314 | + text=phone_number) |
315 | + self.pointing_device.click_object(phone_number_label) |
316 | |
317 | === added file 'tests/autopilot/address_book_app/emulators/contact_view.py' |
318 | --- tests/autopilot/address_book_app/emulators/contact_view.py 1970-01-01 00:00:00 +0000 |
319 | +++ tests/autopilot/address_book_app/emulators/contact_view.py 2014-02-27 22:11:08 +0000 |
320 | @@ -0,0 +1,97 @@ |
321 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
322 | +# Copyright 2014 Canonical |
323 | +# |
324 | +# This program is free software: you can redistribute it and/or modify it |
325 | +# under the terms of the GNU General Public License version 3, as published |
326 | +# by the Free Software Foundation. |
327 | + |
328 | +""" ContactView emulator for Addressbook App tests """ |
329 | + |
330 | +import logging |
331 | + |
332 | +from autopilot import logging as autopilot_logging |
333 | +from autopilot.introspection.dbus import StateNotFoundError |
334 | +from ubuntuuitoolkit import emulators as uitk |
335 | + |
336 | +from address_book_app.emulators import get_nearest_parent_matching_type |
337 | + |
338 | +logger = logging.getLogger(__name__) |
339 | + |
340 | + |
341 | +class ContactView(uitk.UbuntuUIToolkitEmulatorBase): |
342 | + """ContactView emulator class""" |
343 | + |
344 | + def __init__(self, *args): |
345 | + super(ContactView, self).__init__(*args) |
346 | + |
347 | + @autopilot_logging.log_action(logger.info) |
348 | + def click_button(self, objectname): |
349 | + """Press a button that matches objectname. |
350 | + |
351 | + :param objectname: The name of the object. |
352 | + :raises StateNotFoundError: If the matching object is not found. |
353 | + |
354 | + """ |
355 | + buttons = self.select_single("Button", |
356 | + objectName=objectname, |
357 | + visible=True) |
358 | + self.pointing_device.click_object(button) |
359 | + |
360 | + @autopilot_logging.log_action(logger.info) |
361 | + def cancel(self): |
362 | + """Press the cancel button displayed when pick mode is enabled.""" |
363 | + self.click_button("DialogButtons.rejectButton") |
364 | + |
365 | + @autopilot_logging.log_action(logger.info) |
366 | + def delete(self): |
367 | + """Press the delete button displayed when pick mode is enabled.""" |
368 | + self.click_button("DialogButtons.acceptButton") |
369 | + self.get_contacts() |
370 | + |
371 | + def get_contact_detail_phone_number_view(self, phone_number): |
372 | + """Returns the View associated with the given phone number. |
373 | + |
374 | + :param phone_number: The number listed for contact, e.g. '8675309'. |
375 | + :return: The matching view. |
376 | + :raises StateNotFoundError: If the phone_number is not found. |
377 | + |
378 | + """ |
379 | + phone_number_label = self.select_single('Label', |
380 | + text=phone_number) |
381 | + return get_nearest_parent_matching_type( |
382 | + phone_number_label, |
383 | + 'ContactDetailPhoneNumberView') |
384 | + |
385 | + def _star_is_illuminated_for_phone_number(self, phone_number): |
386 | + """Is the star associated with the given phone number illuminated? |
387 | + |
388 | + :param phone_number: The number listed for contact, e.g. '8675309'. |
389 | + :return: A Boolean. |
390 | + :raises StateNotFoundError: If the phone_number is not found. |
391 | + |
392 | + """ |
393 | + contact_detail = self.get_contact_detail_phone_number_view( |
394 | + phone_number) |
395 | + return contact_detail.iconSource == 'artwork:/favorite-selected.svg' |
396 | + |
397 | + @autopilot_logging.log_action(logger.info) |
398 | + def click_phone_number(self, phone_number): |
399 | + """Tap/click the label with text matching the given phone number. |
400 | + |
401 | + :param phone_number: The number listed for contact, e.g. '8675309'. |
402 | + :raises StateNotFoundError: If the phone_number is not found. |
403 | + |
404 | + """ |
405 | + phone_number_label = self.wait_select_single('Label', |
406 | + text=phone_number) |
407 | + self.pointing_device.click_object(phone_number_label) |
408 | + |
409 | + def phone_number_is_favorite(self, phone_number): |
410 | + """Is the given phone number a favorite? |
411 | + |
412 | + :param phone_number: A phone number, e.g. '8675309'. |
413 | + :raises StateNotFoundError: If the phone number is not found. |
414 | + :returns: Boolean. |
415 | + |
416 | + """ |
417 | + return self._star_is_illuminated_for_phone_number(phone_number) |
418 | |
419 | === modified file 'tests/autopilot/address_book_app/emulators/main_window.py' |
420 | --- tests/autopilot/address_book_app/emulators/main_window.py 2014-01-28 10:20:07 +0000 |
421 | +++ tests/autopilot/address_book_app/emulators/main_window.py 2014-02-27 22:11:08 +0000 |
422 | @@ -1,5 +1,5 @@ |
423 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
424 | -# Copyright 2013 Canonical |
425 | +# Copyright 2014 Canonical |
426 | # |
427 | # This program is free software: you can redistribute it and/or modify it |
428 | # under the terms of the GNU General Public License version 3, as published |
429 | @@ -13,7 +13,7 @@ |
430 | |
431 | def get_contact_list_page(self): |
432 | return self. wait_select_single("ContactListPage", |
433 | - objectName="contactListPage") |
434 | + objectName="contactListPage") |
435 | |
436 | def get_contact_edit_page(self): |
437 | return self.wait_select_single("ContactEditor", |
438 | @@ -35,7 +35,7 @@ |
439 | """ |
440 | Returns a ContactListView iobject for the current window |
441 | """ |
442 | - return self.wait_select_single( "ContactListView", |
443 | + return self.wait_select_single("ContactListView", |
444 | objectName="contactListView") |
445 | |
446 | def get_button(self, name): |
447 | @@ -58,5 +58,3 @@ |
448 | Press the 'Save' button |
449 | """ |
450 | self.pointing_device.click_object(self.get_button("accept")) |
451 | - |
452 | - |
453 | |
454 | === added file 'tests/autopilot/address_book_app/emulators/toolbar.py' |
455 | --- tests/autopilot/address_book_app/emulators/toolbar.py 1970-01-01 00:00:00 +0000 |
456 | +++ tests/autopilot/address_book_app/emulators/toolbar.py 2014-02-27 22:11:08 +0000 |
457 | @@ -0,0 +1,38 @@ |
458 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
459 | + |
460 | +""" Toolbar emulator for Addressbook App tests """ |
461 | + |
462 | +# Copyright 2013 Canonical |
463 | +# |
464 | +# This program is free software: you can redistribute it and/or modify it |
465 | +# under the terms of the GNU General Public License version 3, as published |
466 | +# by the Free Software Foundation. |
467 | +from autopilot.introspection.dbus import StateNotFoundError |
468 | +import logging |
469 | +from ubuntuuitoolkit import emulators as toolkit_emulators |
470 | + |
471 | +logger = logging.getLogger(__name__) |
472 | + |
473 | + |
474 | +class Toolbar(toolkit_emulators.Toolbar): |
475 | + """An emulator class that makes it easy to interact with the tool bar""" |
476 | + def __init__(self, *args): |
477 | + super(Toolbar, self).__init__(*args) |
478 | + |
479 | + def click_action_item_by_text(self, text): |
480 | + """Click an action item in the tool labelled 'text' |
481 | + |
482 | + :param text: label of the ActionItem |
483 | + """ |
484 | + try: |
485 | + action_item = self.select_single('ActionItem', text=text) |
486 | + self.pointing_device.click_object(action_item) |
487 | + except StateNotFoundError: |
488 | + logger.error( |
489 | + 'ActionItem with text "{0}" not found.'.format(text) |
490 | + ) |
491 | + raise |
492 | + |
493 | + def click_select(self): |
494 | + """Press 'Select' button on the toolbar""" |
495 | + self.click_action_item_by_text("Select") |
496 | |
497 | === added file 'tests/autopilot/address_book_app/helpers.py' |
498 | --- tests/autopilot/address_book_app/helpers.py 1970-01-01 00:00:00 +0000 |
499 | +++ tests/autopilot/address_book_app/helpers.py 2014-02-27 22:11:08 +0000 |
500 | @@ -0,0 +1,55 @@ |
501 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
502 | +# Copyright 2014 Canonical |
503 | +# |
504 | +# This program is free software: you can redistribute it and/or modify it |
505 | +# under the terms of the GNU General Public License version 3, as published |
506 | +# by the Free Software Foundation. |
507 | + |
508 | +"""Utilities for the evolution contacts db.""" |
509 | + |
510 | +import os |
511 | +import subprocess |
512 | +import tempfile |
513 | + |
514 | + |
515 | +def backup_evolution_contacts_db(backup_filepath=None): |
516 | + """Back up the existing evolution contacts db to a file. |
517 | + |
518 | + :param backup_filepath: file to which to write, if None make tmpfile. |
519 | + :returns: backup filepath used. |
520 | + """ |
521 | + if backup_filepath is None: |
522 | + backup_filepath = tempfile.mktemp(suffix=".syncevolution") |
523 | + subprocess.call([ |
524 | + 'syncevolution', |
525 | + '--export', |
526 | + backup_filepath, |
527 | + 'backend=evolution-contacts', |
528 | + ]) |
529 | + return backup_filepath |
530 | + |
531 | + |
532 | +def restore_evolution_contacts_db(backup_filepath): |
533 | + """Restore the evolution contacts db from the backup. |
534 | + |
535 | + :param backup_filepath: file from which to restore. |
536 | + """ |
537 | + # NOTE restoring from empty file creates a phantom contact |
538 | + if os.path.getsize(backup_filepath) > 0: |
539 | + subprocess.call([ |
540 | + 'syncevolution', |
541 | + '--import', |
542 | + backup_filepath, |
543 | + 'backend=evolution-contacts' |
544 | + ]) |
545 | + |
546 | + |
547 | +def wipe_evolution_contacts_db(): |
548 | + """Wipe the existing evolution contacts db clean of contacts.""" |
549 | + subprocess.call([ |
550 | + 'syncevolution', |
551 | + '--delete-items', |
552 | + 'backend=evolution-contacts', |
553 | + '--luids', |
554 | + '*', |
555 | + ]) |
556 | |
557 | === modified file 'tests/autopilot/address_book_app/tests/__init__.py' |
558 | --- tests/autopilot/address_book_app/tests/__init__.py 2014-01-30 08:35:04 +0000 |
559 | +++ tests/autopilot/address_book_app/tests/__init__.py 2014-02-27 22:11:08 +0000 |
560 | @@ -18,6 +18,9 @@ |
561 | from testtools.matchers import Equals |
562 | |
563 | from address_book_app.emulators.main_window import MainWindow |
564 | +from address_book_app.helpers import (backup_evolution_contacts_db, |
565 | + restore_evolution_contacts_db, |
566 | + wipe_evolution_contacts_db) |
567 | from ubuntuuitoolkit import emulators as toolkit_emulators |
568 | |
569 | |
570 | @@ -31,6 +34,7 @@ |
571 | VCARD_PATH_DEV = os.path.abspath("../data/vcard.vcf") |
572 | ARGS = [] |
573 | PRELOAD_VCARD = False |
574 | + QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
575 | |
576 | def setUp(self): |
577 | self.pointing_device = toolkit_emulators.get_pointing_device() |
578 | @@ -40,13 +44,25 @@ |
579 | if model() != "Desktop": |
580 | subprocess.check_call(["/sbin/initctl", "stop", "maliit-server"]) |
581 | |
582 | + self.backup_filepath = backup_evolution_contacts_db() |
583 | + wipe_evolution_contacts_db() |
584 | + |
585 | if 'AUTOPILOT_APP' in os.environ: |
586 | self.app_bin = os.environ['AUTOPILOT_APP'] |
587 | else: |
588 | self.app_bin = AddressBookAppTestCase.DEFAULT_DEV_LOCATION |
589 | |
590 | - os.environ['QTCONTACTS_MANAGER_OVERRIDE'] = 'memory' |
591 | + print "Running from: %s" % (self.app_bin) |
592 | + # NOTE defeats favoriting: contacts don't show up in "Favorites" header |
593 | + if AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY: |
594 | + os.environ['QTCONTACTS_MANAGER_OVERRIDE'] = 'memory' |
595 | + else: |
596 | + try: |
597 | + del(os.environ['QTCONTACTS_MANAGER_OVERRIDE']) |
598 | + except KeyError: |
599 | + pass |
600 | vcard_data = "" |
601 | + |
602 | if AddressBookAppTestCase.PRELOAD_VCARD: |
603 | # Use vcard from source tree and fallback on installed vcard (from |
604 | # address-book-app-autopilot package) |
605 | @@ -55,16 +71,16 @@ |
606 | else: |
607 | vcard_data = AddressBookAppTestCase.VCARD_PATH_BIN |
608 | |
609 | - os.environ["ADDRESS_BOOK_TEST_DATA"] = vcard_data |
610 | - if vcard_data != "": print "Using vcard %s" % vcard_data |
611 | + os.environ['ADDRESS_BOOK_TEST_DATA'] = vcard_data |
612 | + if vcard_data != '': print 'Using vcard %s' % vcard_data |
613 | if os.path.exists(self.app_bin): |
614 | - print "Running from: %s" % (self.app_bin) |
615 | + print 'Running from: %s' % (self.app_bin) |
616 | self.launch_test_local() |
617 | elif os.path.exists(self.DEB_LOCALTION): |
618 | - print "Running from: %s" % (self.DEB_LOCALTION) |
619 | + print 'Running from: %s' % (self.DEB_LOCALTION) |
620 | self.launch_test_installed() |
621 | else: |
622 | - print "Running from click package: address-book-app" |
623 | + print 'Running from click package: address-book-app' |
624 | self.launch_click_installed() |
625 | |
626 | AddressBookAppTestCase.ARGS = [] |
627 | @@ -73,6 +89,9 @@ |
628 | |
629 | def tearDown(self): |
630 | super(AddressBookAppTestCase, self).tearDown() |
631 | + wipe_evolution_contacts_db() |
632 | + restore_evolution_contacts_db(self.backup_filepath) |
633 | + os.remove(self.backup_filepath) |
634 | |
635 | # start the vkb |
636 | if model() != "Desktop": |
637 | |
638 | === modified file 'tests/autopilot/address_book_app/tests/test_add_contact.py' |
639 | --- tests/autopilot/address_book_app/tests/test_add_contact.py 2014-01-28 10:20:07 +0000 |
640 | +++ tests/autopilot/address_book_app/tests/test_add_contact.py 2014-02-27 22:11:08 +0000 |
641 | @@ -18,6 +18,10 @@ |
642 | class TestAddContact(AddressBookAppTestCase): |
643 | """ Tests the Add contact """ |
644 | |
645 | + def setUp(self): |
646 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
647 | + super(TestAddContact, self).setUp() |
648 | + |
649 | def test_add_and_cancel_contact(self): |
650 | list_page = self.main_window.get_contact_list_page() |
651 | |
652 | |
653 | === modified file 'tests/autopilot/address_book_app/tests/test_contactlist.py' |
654 | --- tests/autopilot/address_book_app/tests/test_contactlist.py 2013-11-21 18:53:19 +0000 |
655 | +++ tests/autopilot/address_book_app/tests/test_contactlist.py 2014-02-27 22:11:08 +0000 |
656 | @@ -18,6 +18,10 @@ |
657 | class TestContactList(AddressBookAppTestCase): |
658 | """Tests the contact list features""" |
659 | |
660 | + def setUp(self): |
661 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
662 | + super(TestContactList, self).setUp() |
663 | + |
664 | def test_contact_list(self): |
665 | contact_list = self.main_window.get_contact_list_page() |
666 | self.assertThat(contact_list.visible, Eventually(Equals(True))) |
667 | |
668 | === added file 'tests/autopilot/address_book_app/tests/test_delete_contact.py' |
669 | --- tests/autopilot/address_book_app/tests/test_delete_contact.py 1970-01-01 00:00:00 +0000 |
670 | +++ tests/autopilot/address_book_app/tests/test_delete_contact.py 2014-02-27 22:11:08 +0000 |
671 | @@ -0,0 +1,72 @@ |
672 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
673 | + |
674 | +"""Tests for the Addressbook App""" |
675 | + |
676 | +# Copyright 2014 Canonical |
677 | +# |
678 | +# This program is free software: you can redistribute it and/or modify it |
679 | +# under the terms of the GNU General Public License version 3, as published |
680 | +# by the Free Software Foundation. |
681 | +from __future__ import absolute_import |
682 | + |
683 | +from testtools.matchers import Equals |
684 | + |
685 | +from address_book_app.tests import AddressBookAppTestCase |
686 | +from address_book_app.emulators.contact_list_page import ContactListPage |
687 | +from address_book_app.emulators.toolbar import Toolbar |
688 | + |
689 | + |
690 | +class TestDeleteSelectContact(AddressBookAppTestCase): |
691 | + """ |
692 | + Delete a contact using pick mode and verify the behavior of Cancel and |
693 | + Delete actions |
694 | + """ |
695 | + scenarios = [ |
696 | + ("single_cancel", { |
697 | + "select": [1], |
698 | + "action": "cancel"}), |
699 | + ("multiple_cancel", { |
700 | + "select": [1, 2], |
701 | + "action": "cancel"}), |
702 | + ("none_delete", { |
703 | + "select": [], |
704 | + "action": "delete"}), |
705 | + ("single_delete", { |
706 | + "select": [1], |
707 | + "action": "delete"}), |
708 | + ("multiple_delete", { |
709 | + "select": [1, 2], |
710 | + "action": "delete"}), |
711 | + ] |
712 | + |
713 | + def setUp(self): |
714 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
715 | + AddressBookAppTestCase.PRELOAD_VCARD = True |
716 | + super(TestDeleteSelectContact, self).setUp() |
717 | + |
718 | + def test_select(self): |
719 | + """ |
720 | + Delete a contact in pick mode |
721 | + |
722 | + This test switch the contact list view to pick mode and validate the |
723 | + behavior of Cancel and delete actions by comparing the numbers of |
724 | + contact in the list before and after the action. |
725 | + Note that it doesn't check which contact has been deleted. |
726 | + """ |
727 | + self.main_window.open_toolbar().click_select() |
728 | + listpage = self.main_window.get_contact_list_page() |
729 | + contacts_before = listpage.get_contacts() |
730 | + |
731 | + listpage.select_contacts_by_index(self.select) |
732 | + deleted = [] |
733 | + if self.action == "cancel": |
734 | + listpage.cancel() |
735 | + elif self.action == "delete": |
736 | + listpage.delete() |
737 | + deleted = self.select |
738 | + |
739 | + contacts_after = listpage.get_contacts() |
740 | + # TODO: |
741 | + # - Verify which contact have been deleted |
742 | + self.assertThat(len(contacts_after), Equals(len(contacts_before) - |
743 | + len(deleted))) |
744 | |
745 | === modified file 'tests/autopilot/address_book_app/tests/test_edit_contact.py' |
746 | --- tests/autopilot/address_book_app/tests/test_edit_contact.py 2014-01-28 10:20:07 +0000 |
747 | +++ tests/autopilot/address_book_app/tests/test_edit_contact.py 2014-02-27 22:11:08 +0000 |
748 | @@ -17,6 +17,10 @@ |
749 | class TestEditContact(AddressBookAppTestCase): |
750 | """Tests edit a contact""" |
751 | |
752 | + def setUp(self): |
753 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
754 | + super(TestEditContact, self).setUp() |
755 | + |
756 | def test_add_new_phone(self): |
757 | self.add_contact("Fulano", "de Tal", ["3321 2300"]) |
758 | edit_page = self.edit_contact(0) |
759 | |
760 | === added file 'tests/autopilot/address_book_app/tests/test_favorites.py' |
761 | --- tests/autopilot/address_book_app/tests/test_favorites.py 1970-01-01 00:00:00 +0000 |
762 | +++ tests/autopilot/address_book_app/tests/test_favorites.py 2014-02-27 22:11:08 +0000 |
763 | @@ -0,0 +1,121 @@ |
764 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
765 | +# Copyright 2014 Canonical |
766 | +# |
767 | +# This program is free software: you can redistribute it and/or modify it |
768 | +# under the terms of the GNU General Public License version 3, as published |
769 | +# by the Free Software Foundation. |
770 | + |
771 | +"""Tests of favoriting contacts for the Addressbook App""" |
772 | + |
773 | +from __future__ import absolute_import |
774 | + |
775 | +from autopilot.introspection.dbus import StateNotFoundError |
776 | + |
777 | +from address_book_app.tests import AddressBookAppTestCase |
778 | +from address_book_app.emulators import get_nearest_parent_matching_type |
779 | +from address_book_app.emulators.contact_list_page import ContactListPage |
780 | +from address_book_app.emulators.contact_view import ContactView |
781 | +from address_book_app.emulators.address_book import AddressBook |
782 | + |
783 | + |
784 | +class TestFavorite(AddressBookAppTestCase): |
785 | + """Test for favoriting contacts""" |
786 | + |
787 | + def setUp(self): |
788 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = False |
789 | + super(TestFavorite, self).setUp() |
790 | + self.address_book = AddressBook() |
791 | + |
792 | + def test_add_favorite(self): |
793 | + self.address_book.create_contact( |
794 | + name='de Tal;Fulano', |
795 | + tel_home='3321 2300') |
796 | + contact_list_page = self.main_window.get_contact_list_page() |
797 | + contact_list_page.open_contact_view_by_contact_name('Fulano de Tal') |
798 | + contact_view = self.main_window.get_contact_view_page() |
799 | + contact_view.click_phone_number('3321 2300') |
800 | + self.assertTrue( |
801 | + contact_view.phone_number_is_favorite('3321 2300')) |
802 | + self.main_window.go_back() |
803 | + self.assertTrue(contact_list_page.get_favorite_by_name('Fulano de Tal')) |
804 | + |
805 | + def test_save_multiple_favorites(self): |
806 | + self.address_book.create_contact( |
807 | + name=';Andrew', |
808 | + tel_home='3321 2300') |
809 | + self.address_book.create_contact( |
810 | + name='Whittard;Beth', |
811 | + tel_home='8675309') |
812 | + contact_list_page = self.main_window.get_contact_list_page() |
813 | + contact_list_page.click_contact_by_name('Beth Whittard') |
814 | + contact_view = self.main_window.get_contact_view_page() |
815 | + contact_view.click_phone_number('8675309') |
816 | + self.main_window.go_back() |
817 | + self.assertIsNotNone(contact_list_page.get_favorite_by_name('Beth Whittard')) |
818 | + contact_list_page.click_contact_by_name('Andrew') |
819 | + contact_view = self.main_window.get_contact_view_page() |
820 | + contact_view.click_phone_number('3321 2300') |
821 | + self.main_window.go_back() |
822 | + self.assertIsNotNone(contact_list_page.get_favorite_by_name('Andrew')) |
823 | + |
824 | + def test_switch_between_favorites(self): |
825 | + self.address_book.create_contact( |
826 | + name='Whittard;Beth', |
827 | + tel_home='8675309', |
828 | + tel_work='5551212') |
829 | + contact_list_page = self.main_window.get_contact_list_page() |
830 | + contact_list_page.click_contact_by_name('Beth Whittard') |
831 | + contact_view = self.main_window.get_contact_view_page() |
832 | + contact_view.click_phone_number('8675309') |
833 | + self.assertTrue(contact_view.phone_number_is_favorite('8675309')) |
834 | + self.assertFalse(contact_view.phone_number_is_favorite('5551212')) |
835 | + contact_view.click_phone_number('5551212') |
836 | + self.assertFalse(contact_view.phone_number_is_favorite('8675309')) |
837 | + self.assertTrue(contact_view.phone_number_is_favorite('5551212')) |
838 | + self.main_window.go_back() |
839 | + contact_list_page.click_contact_by_name('Beth Whittard') |
840 | + contact_view = self.main_window.get_contact_view_page() |
841 | + # NOTE phones not visible from contact list favorites view |
842 | + self.assertTrue(contact_view.phone_number_is_favorite('5551212')) |
843 | + self.assertFalse(contact_view.phone_number_is_favorite('8675309')) |
844 | + |
845 | + def test_remove_favorite(self): |
846 | + self.address_book.create_contact( |
847 | + name='Whittard;Beth', |
848 | + tel_home='8675309') |
849 | + contact_list_page = self.main_window.get_contact_list_page() |
850 | + contact_list_page.click_contact_by_name('Beth Whittard') |
851 | + contact_view = self.main_window.get_contact_view_page() |
852 | + contact_view.click_phone_number('8675309') |
853 | + self.assertTrue(contact_view.phone_number_is_favorite('8675309')) |
854 | + self.main_window.go_back() |
855 | + contact_list_page.click_contact_by_name('Beth Whittard') |
856 | + contact_view = self.main_window.get_contact_view_page() |
857 | + contact_view.click_phone_number('8675309') |
858 | + self.assertFalse(contact_view.phone_number_is_favorite('8675309')) |
859 | + self.main_window.go_back() |
860 | + self.assertRaises( |
861 | + StateNotFoundError, |
862 | + lambda: contact_list_page.get_favorite_by_name('Beth Wittard')) |
863 | + # FIXME: this passes but appears broken ATM--screen refresh? |
864 | + |
865 | + def test_delete_favorite(self): |
866 | + self.address_book.create_contact( |
867 | + name='Whittard;Beth', |
868 | + tel_home='8675309') |
869 | + contact_list_page = self.main_window.get_contact_list_page() |
870 | + contact_list_page.click_contact_by_name('Beth Whittard') |
871 | + contact_view = self.main_window.get_contact_view_page() |
872 | + contact_view.click_phone_number('8675309') |
873 | + self.assertTrue(contact_view.phone_number_is_favorite('8675309')) |
874 | + self.main_window.go_back() |
875 | + contact_list_page.click_favorite_by_name('Beth Whittard') |
876 | + toolbar = self.main_window.open_toolbar() |
877 | + toolbar.click_button('delete') |
878 | + self.main_window.go_back() |
879 | + self.assertRaises( |
880 | + StateNotFoundError, |
881 | + lambda: contact_list_page.get_favorite_by_name('Beth Wittard')) |
882 | + self.assertRaises( |
883 | + StateNotFoundError, |
884 | + lambda: contact_list_page.get_contact_by_name('Beth Wittard')) |
885 | |
886 | === modified file 'tests/autopilot/address_book_app/tests/test_multiple_pick_mode.py' |
887 | --- tests/autopilot/address_book_app/tests/test_multiple_pick_mode.py 2013-12-13 19:31:33 +0000 |
888 | +++ tests/autopilot/address_book_app/tests/test_multiple_pick_mode.py 2014-02-27 22:11:08 +0000 |
889 | @@ -20,6 +20,7 @@ |
890 | |
891 | def setUp(self): |
892 | self.ARGS.append("addressbook:///pick?single=false") |
893 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
894 | AddressBookAppTestCase.PRELOAD_VCARD = True |
895 | super(TestMultiplePickerMode, self).setUp() |
896 | |
897 | |
898 | === modified file 'tests/autopilot/address_book_app/tests/test_single_pick_mode.py' |
899 | --- tests/autopilot/address_book_app/tests/test_single_pick_mode.py 2013-12-13 19:31:33 +0000 |
900 | +++ tests/autopilot/address_book_app/tests/test_single_pick_mode.py 2014-02-27 22:11:08 +0000 |
901 | @@ -21,6 +21,7 @@ |
902 | def setUp(self): |
903 | AddressBookAppTestCase.ARGS.append("addressbook:///pick?single=true") |
904 | AddressBookAppTestCase.PRELOAD_VCARD = True |
905 | + AddressBookAppTestCase.QTCONTACTS_MANAGER_OVERRIDE_MEMORY = True |
906 | super(TestSinglePickerMode, self).setUp() |
907 | |
908 | def test_select_single_contact(self): |
FAILED: Continuous integration, rev:132 /code.launchpad .net/~allanlesa ge/address- book-app/ abook_navigatio n_favorites/ +merge/ 205264/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http:// jenkins. qa.ubuntu. com/job/ address- book-app- ci/377/ jenkins. qa.ubuntu. com/job/ address- book-app- trusty- amd64-ci/ 87 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 87 jenkins. qa.ubuntu. com/job/ address- book-app- trusty- armhf-ci/ 87/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ address- book-app- trusty- i386-ci/ 87 jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 2990/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty- touch/2723/ console jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-trusty/ 2626/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2992 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2992/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/2724 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/2724/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- runner- mako/5143/ console s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 3722
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/address- book-app- ci/377/ rebuild
http://