Merge lp:~rhuddie/address-book-app/ux-calling-helpers into lp:address-book-app

Proposed by Richard Huddie
Status: Superseded
Proposed branch: lp:~rhuddie/address-book-app/ux-calling-helpers
Merge into: lp:address-book-app
Diff against target: 322 lines (+246/-5)
5 files modified
debian/control (+2/-0)
tests/autopilot/address_book_app/fake_url_dispatcher.py (+74/-0)
tests/autopilot/address_book_app/pages/_contact_list_page.py (+37/-5)
tests/autopilot/address_book_app/pages/_contact_view.py (+69/-0)
tests/autopilot/address_book_app/tests/test_contactlist.py (+64/-0)
To merge this branch: bzr merge lp:~rhuddie/address-book-app/ux-calling-helpers
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Leo Arias (community) code review Needs Fixing
Ubuntu Phablet Team Pending
Review via email: mp+226473@code.launchpad.net

This proposal has been superseded by a proposal from 2014-07-22.

Commit message

New autopilot helper methods to press the telephone and message icons for a specific phone number in contacts view

Description of the change

New autopilot helper methods to press the telephone and message icons for a specific phone number in contacts view

To post a comment you must log in.
239. By Richard Huddie

flake8

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:238
http://jenkins.qa.ubuntu.com/job/address-book-app-ci/601/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-amd64-ci/54
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/54
        deb: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/54/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-i386-ci/54
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/1872
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1566
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2129
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2969
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2969/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/9705
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1312
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1756
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1756/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/address-book-app-ci/601/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:239
http://jenkins.qa.ubuntu.com/job/address-book-app-ci/602/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-amd64-ci/55
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/55
        deb: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/55/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-i386-ci/55
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/1890
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1576
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2145
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2989
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/2989/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/9723
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1320
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1766
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/1766/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/address-book-app-ci/602/rebuild

review: Needs Fixing (continuous-integration)
240. By Richard Huddie

add dependencies

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Leo Arias (elopio) wrote :

Thanks Richard. This is nice.

125 + def get_contact_index_by_name(self, display_name):

This needs some love, but feel free to leave it for the future because it's not your fault. Many things would be a lot easier if we had a method called get_contacts_info on the contacts list page. That method should return a list of contact tuples composed of all the visible information, ordered by the y coordinate. Reminders has a good example of this.
Then you could look for the contact index on that list, instead of in the QML tree.
I'm just saying it would be nice :)

174 + def _get_contact(self, index, phone_number):
This would be clearer as two methods: _get_contact_by_index and _get_contact_by_phone_number

203 + def call_contact(self, index=0, phone_number=None):
215 + def message_contact(self, index=0, phone_number=None):
Just like this, I would make two methods. And for a high level helper, I think index might not be a good parameter. I would be inclined to make a call_contact_by_number and call_contact_by_name. But in our tests we will generally have only one contact, so if you want to call him, index=0 sounds like the easiest thing to do. Feel free to keep it if you prefer that way.

231 + def get_call_button(self):
234 + def get_message_button(self):

Please make these two private method. And make two additional methods click_call_button and click_message_button. That helps encapsulating the responsibilities in the right custom proxy objects.

273 + self.add_contact("Fulano", "de Tal", [self.PHONE_NUMBER])

This is the old helper, it needs to be removed but I didn't have time to refactor all the tests.
This is the new way:

test_contact = data.Contact(
    first_name='Fulano', last_name='de Tal',
    phones=[self.PHONE_NUMBER])

contact_editor = self.app.main_window.go_to_add_contact()
contact_editor.fill_form(test_contact)

self.app.main_window.save()

You can wrap that in an add_contact method. Please don't use the other one, so it's easier to remove it once we have time.

277 + def convert_phone_number_to_url(self, phone_number):

There's a phone object on the data.py module. This sounds like a good method to have in that object.

review: Needs Fixing (code review)
241. By Richard Huddie

review updates

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:241
http://jenkins.qa.ubuntu.com/job/address-book-app-ci/621/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-amd64-ci/74
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/74
        deb: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-armhf-ci/74/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/address-book-app-utopic-i386-ci/74
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/2240
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1861
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2441
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3424
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3424/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/10135
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1563
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2080
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2080/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/address-book-app-ci/621/rebuild

review: Needs Fixing (continuous-integration)
242. By Richard Huddie

merge trunk

243. By Richard Huddie

fix review comments

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

243. By Richard Huddie

fix review comments

242. By Richard Huddie

merge trunk

241. By Richard Huddie

review updates

240. By Richard Huddie

add dependencies

239. By Richard Huddie

flake8

238. By Richard Huddie

added fake url dispatcher and updated tests

237. By Richard Huddie

Added contact list tests for call and message contact

236. By Richard Huddie

add methods to call/message a specified contact

235. By Richard Huddie

add message_contact method

234. By Richard Huddie

Add new helpers for selecting a contact and calling them

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-07-09 17:22:14 +0000
3+++ debian/control 2014-07-22 08:46:30 +0000
4@@ -81,6 +81,8 @@
5 libqt5test5,
6 libqt5widgets5,
7 python-testscenarios,
8+ python3-dbusmock,
9+ python3-fixtures,
10 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 0.1.49+14.10.20140707),
11 ubuntu-ui-toolkit-autopilot (>= 0.1.46+14.10.20140527),
12 address-book-app (>= ${binary:Version}),
13
14=== added file 'tests/autopilot/address_book_app/fake_url_dispatcher.py'
15--- tests/autopilot/address_book_app/fake_url_dispatcher.py 1970-01-01 00:00:00 +0000
16+++ tests/autopilot/address_book_app/fake_url_dispatcher.py 2014-07-22 08:46:30 +0000
17@@ -0,0 +1,74 @@
18+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
19+# Copyright 2013 Canonical
20+#
21+# This program is free software: you can redistribute it and/or modify it
22+# under the terms of the GNU General Public License version 3, as published
23+# by the Free Software Foundation.
24+
25+import subprocess
26+import dbus
27+import dbusmock
28+import fixtures
29+
30+
31+class ContactEditorException(Exception):
32+ """Exception raised from ContactEditor"""
33+
34+
35+class FakeURLDispatcher(fixtures.Fixture):
36+ """Fake URL Dispatcher interface object"""
37+
38+ def setUp(self):
39+ """Start Fake dispatcher service"""
40+ super(FakeURLDispatcher, self).setUp()
41+ self.fake_service = FakeURLDispatcherService()
42+ self.addCleanup(self.fake_service.stop)
43+ self.fake_service.start()
44+
45+ def get_last_dispatch_url_call_parameter(self):
46+ """Get the last call url from the fake dispatcher service"""
47+ return self.fake_service.get_last_dispatch_url_call_parameter()
48+
49+
50+class FakeURLDispatcherService(object):
51+ """Fake URL Dispatcher service using a dbusmock interface."""
52+
53+ def __init__(self):
54+ super(FakeURLDispatcherService, self).__init__()
55+ self.dbus_connection = dbusmock.DBusTestCase.get_dbus(system_bus=False)
56+
57+ def start(self):
58+ """Start the fake URL Dispatcher service."""
59+ # Stop the real url-dispatcher.
60+ subprocess.call(['/sbin/initctl', 'stop', 'url-dispatcher'])
61+ self.dbus_mock_server = dbusmock.DBusTestCase.spawn_server(
62+ 'com.canonical.URLDispatcher',
63+ '/com/canonical/URLDispatcher',
64+ 'com.canonical.URLDispatcher',
65+ system_bus=False,
66+ stdout=subprocess.PIPE)
67+ self.mock = self._get_mock_interface()
68+ self.mock.AddMethod(
69+ 'com.canonical.URLDispatcher', 'DispatchURL', 's', '', '')
70+
71+ def _get_mock_interface(self):
72+ return dbus.Interface(
73+ self.dbus_connection.get_object(
74+ 'com.canonical.URLDispatcher', '/com/canonical/URLDispatcher'),
75+ dbusmock.MOCK_IFACE)
76+
77+ def stop(self):
78+ """Stop the fake URL Dispatcher service."""
79+ self.dbus_mock_server.terminate()
80+ self.dbus_mock_server.wait()
81+ # re-start the real service
82+ subprocess.call(['/sbin/initctl', 'start', 'url-dispatcher'])
83+
84+ def get_last_dispatch_url_call_parameter(self):
85+ """Return the parameter used in the last call to dispatch URL."""
86+ calls = self.mock.GetCalls()
87+ if len(calls) == 0:
88+ raise ContactEditorException(
89+ 'URL dispatcher has not been called.')
90+ last_call = self.mock.GetCalls()[-1]
91+ return last_call[2][0]
92
93=== modified file 'tests/autopilot/address_book_app/pages/_contact_list_page.py'
94--- tests/autopilot/address_book_app/pages/_contact_list_page.py 2014-06-06 22:40:23 +0000
95+++ tests/autopilot/address_book_app/pages/_contact_list_page.py 2014-07-22 08:46:30 +0000
96@@ -86,7 +86,6 @@
97 self.selected_marks.append(self.selection_marks[idx])
98 self.pointing_device.click_object(self.selection_marks[idx])
99
100-
101 def select_contacts_by_index(self, indices):
102 """ Select contacts corresponding to the list of index in indices
103
104@@ -121,8 +120,8 @@
105 else:
106 obj = self
107 try:
108- buttons = obj.select_many("Button",
109- objectName=objectname)
110+ buttons = obj.select_many(
111+ "Button", objectName=objectname)
112 for button in buttons:
113 if button.visible:
114 self.pointing_device.click_object(button)
115@@ -134,6 +133,39 @@
116
117 def delete(self, main_window):
118 main_window.done_selection()
119- dialog = main_window.wait_select_single("RemoveContactsDialog",
120- objectName="removeContactsDialog")
121+ main_window.wait_select_single(
122+ "RemoveContactsDialog", objectName="removeContactsDialog")
123 self.click_button(main_window, "removeContactsDialog.Yes")
124+
125+ def get_contact_index_by_name(self, display_name):
126+ """
127+ Return the index of the contact with the specified name
128+ :param display_name: Display name of contact
129+ :return: Index of the required contact within the list
130+ None if not found
131+ """
132+ counter = 0
133+ match_index = None
134+ contact_list = self.get_contacts()
135+ for contact in contact_list:
136+ try:
137+ # try to find matching contact
138+ contact.select_single(
139+ 'ContactAvatar', displayName=display_name)
140+ except StateNotFoundError:
141+ # increase counter and carry on
142+ counter += 1
143+ else:
144+ # found a match, record index and exit loop
145+ match_index = counter
146+ break
147+ return match_index
148+
149+ def open_contact_by_name(self, display_name):
150+ """
151+ Open the contact specified by display_name
152+ :param display_name: Name of contact to open
153+ :return: Page with contact information
154+ """
155+ contact_index = self.get_contact_index_by_name(display_name)
156+ return self.open_contact(contact_index)
157
158=== modified file 'tests/autopilot/address_book_app/pages/_contact_view.py'
159--- tests/autopilot/address_book_app/pages/_contact_view.py 2014-06-13 15:05:40 +0000
160+++ tests/autopilot/address_book_app/pages/_contact_view.py 2014-07-22 08:46:30 +0000
161@@ -15,6 +15,7 @@
162 # along with this program. If not, see <http://www.gnu.org/licenses/>.
163
164 from address_book_app.pages import _common, _contact_editor
165+from autopilot.introspection.dbus import StateNotFoundError
166
167
168 class ContactView(_common.PageWithHeader):
169@@ -26,3 +27,71 @@
170 _contact_editor.ContactEditor,
171 objectName='contactEditorPage',
172 active=True)
173+
174+ def _get_contact_by_phone_number(self, phone_number):
175+ """
176+ Find the contact using phone number
177+ :param phone_number: Phone number for required contact
178+ :return: ContactDetailPhoneNumberView matching search requirements
179+ None if match is not found
180+ """
181+ required_contact = None
182+ contacts = self.select_many(ContactDetailPhoneNumberView)
183+ if phone_number:
184+ # search for a specific phone number
185+ for contact in contacts:
186+ try:
187+ # try to find matching phone number
188+ contact.select_single('Label', text=phone_number)
189+ except StateNotFoundError:
190+ # move onto the next one
191+ pass
192+ else:
193+ # found the required contact
194+ required_contact = contact
195+ break
196+ return required_contact
197+
198+ def _get_contact_by_index(self, index):
199+ """
200+ Find the contact using index
201+ :param index: Index offset for required contact
202+ :return: ContactDetailPhoneNumberView matching search requirements
203+ """
204+ return self.select_many(ContactDetailPhoneNumberView)[index]
205+
206+ def call_contact(self, phone_number):
207+ """
208+ Press the call button for the required phone number
209+ :param phone_number: The specified phone number to search for
210+ """
211+ contact_phone_number = self._get_contact_by_phone_number(phone_number)
212+ contact_phone_number.click_call_button()
213+
214+ def message_contact(self, phone_number):
215+ """
216+ Press the message button for the required phone number
217+ :param phone_number: The specified phone number to search for
218+ """
219+ contact_phone_number = self._get_contact_by_phone_number(phone_number)
220+ contact_phone_number.click_message_button()
221+
222+
223+class ContactDetailPhoneNumberView(ContactView):
224+ """Class to represent a phone number view with call and message buttons"""
225+
226+ def _get_call_button(self):
227+ """Return the call button ActionButton object"""
228+ return self.select_single('ActionButton', iconName='call-start')
229+
230+ def _get_message_button(self):
231+ """Return the message button ActionButton object"""
232+ return self.select_single('ActionButton', iconName='message')
233+
234+ def click_call_button(self):
235+ """Click on the call button for this phone number"""
236+ self.pointing_device.click_object(self._get_call_button())
237+
238+ def click_message_button(self):
239+ """Click on the message button for this phone number"""
240+ self.pointing_device.click_object(self._get_message_button())
241
242=== modified file 'tests/autopilot/address_book_app/tests/test_contactlist.py'
243--- tests/autopilot/address_book_app/tests/test_contactlist.py 2014-05-22 13:42:45 +0000
244+++ tests/autopilot/address_book_app/tests/test_contactlist.py 2014-07-22 08:46:30 +0000
245@@ -10,13 +10,77 @@
246 from autopilot.matchers import Eventually
247 from testtools.matchers import Equals
248
249+from address_book_app import data
250 from address_book_app.tests import AddressBookAppTestCase
251+from address_book_app.fake_url_dispatcher import (
252+ FakeURLDispatcher,
253+ ContactEditorException)
254
255
256 class TestContactList(AddressBookAppTestCase):
257 """Tests the contact list features"""
258
259+ TEST_MOBILE_NUMBER = '073331234567'
260+
261+ def setUp(self):
262+ super(TestContactList, self).setUp()
263+ self.fake_url_dispatcher = FakeURLDispatcher()
264+ self.useFixture(self.fake_url_dispatcher)
265+ self.phone_number = data.Phone(
266+ type_='Mobile', number=self.TEST_MOBILE_NUMBER)
267+ self.message_url = 'message:///{}'.format(self.phone_number.number)
268+ self.tel_url = 'tel:///{}'.format(self.phone_number.number)
269+
270+ def add_test_contact(self):
271+ """
272+ Create a contact for testing
273+ """
274+ test_contact = data.Contact(
275+ first_name='Fulano', last_name='de Tal',
276+ phones=[self.phone_number])
277+ contact_editor = self.app.main_window.go_to_add_contact()
278+ contact_editor.fill_form(test_contact)
279+ self.app.main_window.save()
280+
281+ def create_and_open_test_contact(self):
282+ """
283+ Create a contact for testing, return detail page for new contact
284+ :return: Contact detail page
285+ """
286+ self.add_test_contact()
287+ contacts_list = self.app.main_window.get_contact_list_page()
288+ return contacts_list.open_contact(0)
289+
290+ def assert_last_dispatch_url(self, expected_url):
291+ """
292+ Assert that the last dispatch url matches the expected url
293+ :param expected_url: The expected url
294+ """
295+ url_dispatcher = self.fake_url_dispatcher
296+
297+ def get_last_dispatch_url_call_parameter():
298+ # Workaround for http://pad.lv/1312384
299+ try:
300+ return url_dispatcher.get_last_dispatch_url_call_parameter()
301+ except ContactEditorException:
302+ return None
303+ self.assertThat(
304+ get_last_dispatch_url_call_parameter,
305+ Eventually(Equals(expected_url)))
306+
307 def test_contact_list(self):
308 contact_list = self.app.main_window.get_contact_list_page()
309 self.assertThat(contact_list.visible, Eventually(Equals(True)))
310 pass
311+
312+ def test_call_contact(self):
313+ """Press the call button on a contact's phone number"""
314+ contact_page = self.create_and_open_test_contact()
315+ contact_page.call_contact(self.TEST_MOBILE_NUMBER)
316+ self.assert_last_dispatch_url(self.tel_url)
317+
318+ def test_message_contact(self):
319+ """Press the message button on a contact's phone number"""
320+ contact_page = self.create_and_open_test_contact()
321+ contact_page.message_contact(self.TEST_MOBILE_NUMBER)
322+ self.assert_last_dispatch_url(self.message_url)

Subscribers

People subscribed via source and target branches