@@ -0,0 +1,507 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2015 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import logging
21+import re
22+
23+import autopilot
24+from autopilot import introspection
25+from ubuntuuitoolkit import UbuntuUIToolkitCustomProxyObjectBase
26+
27+from contextlib import contextmanager
28+
29+
30+logger = logging.getLogger(__name__)
31+
32+
33+@contextmanager
34+def override_proxy_timeout(proxy, timeout_seconds):
35+ original_timeout = proxy._poll_time
36+ try:
37+ proxy._poll_time = timeout_seconds
38+ yield proxy
39+ finally:
40+ proxy._poll_time = original_timeout
41+
42+
43+def get_wizard(current_page):
44+ return current_page.get_root_instance().select_single(Wizard)
45+
46+
47+class Wizard(UbuntuUIToolkitCustomProxyObjectBase):
48+ """High-level helper to navigate through the pages of the wizard"""
49+
50+ def get_language_page(self):
51+ return self.wait_select_single(
52+ objectName='languagePage', visible='True')
53+
54+ def get_current_page(self):
55+ return self.wait_select_single('Page', visible='True')
56+
57+ def get_sim_page(self):
58+ return self.wait_select_single(
59+ objectName='simPage', visible='True')
60+
61+ def get_password_page(self):
62+ return self.wait_select_single(
63+ objectName='passwdPage', visible='True')
64+
65+ def get_password_entry_page(self):
66+ return self.wait_select_single(
67+ objectName='passwdSetPage', visible='True')
68+
69+ def get_confirm_password_page(self):
70+ return self.wait_select_single(
71+ objectName='passwdConfirmPage', visible='True')
72+
73+ def get_wifi_connect_page(self):
74+ return self.wait_select_single(
75+ objectName='wifiPage', visible='True')
76+
77+ def get_reporting_page(self):
78+ return self.wait_select_single(
79+ objectName='reportingPage', visible='True')
80+
81+ def get_finished_page(self):
82+ return self.wait_select_single(
83+ objectName='finishedPage', visible='True')
84+
85+
86+class WizardLanguagePage(UbuntuUIToolkitCustomProxyObjectBase):
87+ """Helper class to interact with the language welcome page"""
88+
89+ # This class was renamed from LanguagePage to WizardLanguagePage because
90+ # there is a class with the same name in the system settings custom proxy
91+ # objects. Reported in http://pad.lv/1422904. --elopio - 2015-02-17
92+
93+ @classmethod
94+ def validate_dbus_object(cls, path, state):
95+ name = introspection.get_classname_from_path(path)
96+ if name == b'Page':
97+ if state['objectName'][1] == 'languagePage':
98+ return True
99+ return False
100+
101+ def _get_language_button(self):
102+ return self.select_single(
103+ 'ComboButton', objectName='languageCombo')
104+
105+ def _get_continue_button(self):
106+ return self.select_single('StackButton', text='Continue')
107+
108+ def _swipe_to_language(self, list, language):
109+ """Swipe to the chosen language in the given list"""
110+ list.swipe_to_top()
111+ while not list.atYEnd:
112+ try:
113+ item = list.select_single(
114+ 'LabelVisual', text=language, visible='True')
115+ item.swipe_into_view()
116+ return item
117+ except introspection.dbus.StateNotFoundError:
118+ list.swipe_to_show_more_below()
119+ # item could not be found
120+ raise introspection.dbus.StateNotFoundError
121+
122+ @autopilot.logging.log_action(logger.info)
123+ def select_language(self, language):
124+ """Select a different language from the list"""
125+ combo_button = self._get_language_button()
126+ self.pointing_device.click_object(combo_button)
127+ language_list = combo_button.wait_select_single(
128+ 'UbuntuListView11', visible='True')
129+ item = self._swipe_to_language(language_list, language)
130+ self.pointing_device.click_object(item)
131+
132+ def get_selected_language(self):
133+ return self._get_language_button().get_properties()['text']
134+
135+ @autopilot.logging.log_action(logger.info)
136+ def continue_(self):
137+ self.pointing_device.click_object(self._get_continue_button())
138+ wizard = get_wizard(self)
139+ next_page = wizard.get_current_page()
140+ sim_inserted = True
141+ if next_page.objectName == 'simPage':
142+ # no sim is inserted
143+ next_page = wizard.get_sim_page()
144+ sim_inserted = False
145+ else:
146+ # sim is inserted
147+ next_page = wizard.get_password_page()
148+ return sim_inserted, next_page
149+
150+
151+class SimPage(UbuntuUIToolkitCustomProxyObjectBase):
152+ """Helper class to interact with the no sim notification page"""
153+
154+ @classmethod
155+ def validate_dbus_object(cls, path, state):
156+ name = introspection.get_classname_from_path(path)
157+ if name == b'Page':
158+ if state['objectName'][1] == 'simPage':
159+ return True
160+ return False
161+
162+ def _get_back_button(self):
163+ return self.select_single('StackButton', text='Back')
164+
165+ def _get_skip_button(self):
166+ return self.select_single('StackButton', text='Skip')
167+
168+ @autopilot.logging.log_action(logger.info)
169+ def back(self):
170+ self.pointing_device.click_object(self._get_back_button())
171+ return get_wizard(self).get_languge_page()
172+
173+ @autopilot.logging.log_action(logger.info)
174+ def skip(self):
175+ self.pointing_device.click_object(self._get_skip_button())
176+ return get_wizard(self).get_password_page()
177+
178+
179+class PasswordPage(UbuntuUIToolkitCustomProxyObjectBase):
180+ """Helper class to interact with the password security page"""
181+
182+ SECURITY_SWIPE = 'Swipe'
183+ SECURITY_PASSCODE = 'Passcode'
184+ SECURITY_PASSPHRASE = 'Passphrase'
185+
186+ @classmethod
187+ def validate_dbus_object(cls, path, state):
188+ name = introspection.get_classname_from_path(path)
189+ if name == b'Page':
190+ if state['objectName'][1] == 'passwdPage':
191+ return True
192+ return False
193+
194+ def _get_option_name(self, formatted_name):
195+ # the option name is formatted as follows:
196+ # need to read the name between bold tags
197+ # '<b>Passcode</b> (4 digits only)'
198+ formatted_pattern = re.compile(r'<b>(.*?)</b>.*',
199+ re.IGNORECASE | re.DOTALL)
200+ name = None
201+ match = formatted_pattern.search(formatted_name)
202+ if match:
203+ name = match.group(1)
204+ return name
205+
206+ def _get_all_options(self):
207+ return self.select_many('OptionSelectorDelegate')
208+
209+ def _get_continue_button(self):
210+ return self.select_single('StackButton', text='Continue')
211+
212+ def _get_back_button(self):
213+ return self.select_single('StackButton', text='Back')
214+
215+ def _get_selected_option(self):
216+ return self.select_single(
217+ 'OptionSelectorDelegate', visible='True', selected='True')
218+
219+ def get_selected_security_option(self):
220+ name_fmt = self._get_selected_option().get_properties()['subText']
221+ return self._get_option_name(name_fmt)
222+
223+ @autopilot.logging.log_action(logger.info)
224+ def select_security_option(self, selected_option):
225+ options = self._get_all_options()
226+ for option in options:
227+ name = self._get_option_name(option.get_properties()['subText'])
228+ if name == selected_option:
229+ self.pointing_device.click_object(option)
230+
231+ @autopilot.logging.log_action(logger.info)
232+ def back(self, sim_inserted):
233+ self.pointing_device.click_object(self._get_back_button())
234+ wizard = get_wizard(self)
235+ if sim_inserted:
236+ page = wizard.get_languge_page()
237+ else:
238+ page = wizard.get_sim_page()
239+ return page
240+
241+ @autopilot.logging.log_action(logger.info)
242+ def continue_(self):
243+ option = self.get_selected_security_option()
244+ self.pointing_device.click_object(self._get_continue_button())
245+ wizard = get_wizard(self)
246+ if option == self.SECURITY_SWIPE:
247+ next_page = wizard.get_wifi_connect_page()
248+ else:
249+ next_page = wizard.get_password_entry_page()
250+ return next_page
251+
252+
253+class PasswordEntryPage(UbuntuUIToolkitCustomProxyObjectBase):
254+ """Helper class to interact with the pin entry page"""
255+
256+ @classmethod
257+ def validate_dbus_object(cls, path, state):
258+ name = introspection.get_classname_from_path(path)
259+ if name == b'Page':
260+ if state['objectName'][1] == 'passwdSetPage':
261+ return True
262+ return False
263+
264+ def _get_pinpad_button(self, char):
265+ return self.select_single('PinPadButton', text=char)
266+
267+ def _get_continue_button(self):
268+ return self.select_single('StackButton', text='Continue')
269+
270+ def _get_back_button(self):
271+ return self.select_single('StackButton', text='Back')
272+
273+ def _get_text_field(self):
274+ return self.select_single('QQuickTextInput')
275+
276+ @autopilot.logging.log_action(logger.info)
277+ def enter_pin(self, pin):
278+ for char in pin:
279+ self.pointing_device.click_object(self._get_pinpad_button(char))
280+ return get_wizard(self).get_confirm_password_page()
281+
282+ @autopilot.logging.log_action(logger.info)
283+ def enter_text(self, text):
284+ self.pointing_device.click_object(self._get_text_field())
285+ autopilot.input.Keyboard.create().type(text)
286+
287+ @autopilot.logging.log_action(logger.info)
288+ def back(self):
289+ self.pointing_device.click_object(self._get_back_button())
290+ return get_wizard(self).get_password_page()
291+
292+ @autopilot.logging.log_action(logger.info)
293+ def continue_(self):
294+ autopilot.input.Keyboard.create().press_and_release('Enter')
295+ return get_wizard(self).get_confirm_password_page()
296+
297+
298+class ConfirmPasswordPage(UbuntuUIToolkitCustomProxyObjectBase):
299+ """Helper class to interact with the pin confirmation page"""
300+
301+ @classmethod
302+ def validate_dbus_object(cls, path, state):
303+ name = introspection.get_classname_from_path(path)
304+ if name == b'Page':
305+ if state['objectName'][1] == 'passwdConfirmPage':
306+ return True
307+ return False
308+
309+ def _get_pinpad_button(self, char):
310+ return self.select_single('PinPadButton', text=char)
311+
312+ def _get_continue_button(self):
313+ return self.select_single('StackButton', text='Continue')
314+
315+ def _get_back_button(self):
316+ return self.select_single('StackButton', text='Back')
317+
318+ def _get_text_field(self):
319+ return self.select_single('QQuickTextInput')
320+
321+ @autopilot.logging.log_action(logger.info)
322+ def enter_pin(self, pin):
323+ for char in pin:
324+ self.pointing_device.click_object(self._get_pinpad_button(char))
325+ return get_wizard(self).get_wifi_connect_page()
326+
327+ @autopilot.logging.log_action(logger.info)
328+ def enter_text(self, text):
329+ self.pointing_device.click_object(self._get_text_field())
330+ autopilot.input.Keyboard.create().type(text)
331+
332+ @autopilot.logging.log_action(logger.info)
333+ def back(self):
334+ self.pointing_device.click_object(self._get_back_button())
335+ return get_wizard(self).get_password_page()
336+
337+ @autopilot.logging.log_action(logger.info)
338+ def continue_(self):
339+ autopilot.input.Keyboard.create().press_and_release('Enter')
340+ return get_wizard(self).get_wifi_connect_page()
341+
342+
343+class WifiConnectPage(UbuntuUIToolkitCustomProxyObjectBase):
344+ """Helper class to interact with the Wi-Fi network list page"""
345+
346+ @classmethod
347+ def validate_dbus_object(cls, path, state):
348+ name = introspection.get_classname_from_path(path)
349+ if name == b'Page':
350+ if state['objectName'][1] == 'wifiPage':
351+ return True
352+ return False
353+
354+ def _get_all_networks(self):
355+ try:
356+ networks = self.select_many('Standard', objectName='accessPoint',
357+ visible='True')
358+ except:
359+ networks = []
360+ return networks
361+
362+ def _get_network(self, ssid):
363+ return self.wait_select_single(
364+ 'Standard', objectName='accessPoint', text=ssid, visible='True')
365+
366+ def _get_network_checkbox(self, ssid):
367+ return self._get_network(ssid).select_single(
368+ 'CheckBox', visible='True')
369+
370+ def _get_notification(self, unity):
371+ logger.info('Waiting longer for notification object')
372+ with override_proxy_timeout(unity, 30):
373+ return unity.wait_select_single(
374+ Notification, objectName='notification1', visible='True')
375+
376+ def _get_back_button(self):
377+ return self.wait_select_single(
378+ 'StackButton', text='Back', visible='True')
379+
380+ def _get_skip_button(self):
381+ return self.wait_select_single(
382+ 'StackButton', text='Skip', visible='True')
383+
384+ def _get_continue_button(self):
385+ return self.wait_select_single(
386+ 'StackButton', text='Continue', visible='True')
387+
388+ def is_any_network_checked(self):
389+ networks = self._get_all_networks()
390+ for network in networks:
391+ checkbox = network.select_single('CheckBox', visible='True')
392+ if checkbox.get_properties()['checked']:
393+ return True
394+ return False
395+
396+ def is_any_network_found(self):
397+ num_neworks = len(self._get_all_networks())
398+ return True if num_neworks > 0 else False
399+
400+ def is_network_checked(self, ssid):
401+ return self._get_network_checkbox(ssid).get_properties()['checked']
402+
403+ @autopilot.logging.log_action(logger.info)
404+ def select_network(self, unity, ssid):
405+ self.pointing_device.click_object(self._get_network_checkbox(ssid))
406+ return PasswordNotification(self._get_notification(unity))
407+
408+ @autopilot.logging.log_action(logger.info)
409+ def back(self):
410+ self.pointing_device.click_object(self._get_back_button())
411+ return get_wizard(self).get_password_page()
412+
413+ @autopilot.logging.log_action(logger.info)
414+ def skip(self):
415+ self.pointing_device.click_object(self._get_skip_button())
416+ return get_wizard(self).get_reporting_page()
417+
418+ @autopilot.logging.log_action(logger.info)
419+ def continue_(self):
420+ self.pointing_device.click_object(self._get_continue_button())
421+ return get_wizard(self).get_reporting_page()
422+
423+
424+class Notification(UbuntuUIToolkitCustomProxyObjectBase):
425+ """Base class for notification objects"""
426+ pass
427+
428+
429+class PasswordNotification(Notification):
430+ """Helper class to interact with the password entry notification"""
431+
432+ def __init__(self, notification):
433+ self.notification = notification
434+
435+ def _get_connect_button(self):
436+ return self.notification.wait_select_single(
437+ 'Button', text='Connect', objectName='notify_button0')
438+
439+ def _get_cancel_buttoon(self):
440+ return self.notification.wait_select_single(
441+ 'Button', text='Cancel', objectName='notify_button1')
442+
443+ def _get_text_field(self):
444+ return self.notification.wait_select_single('TextField')
445+
446+ @autopilot.logging.log_action(logger.info)
447+ def enter_text(self, text):
448+ self._get_text_field().write(text)
449+
450+ @autopilot.logging.log_action(logger.info)
451+ def connect(self):
452+ self.notification.pointing_device.click_object(
453+ self._get_connect_button())
454+
455+ @autopilot.logging.log_action(logger.info)
456+ def cancel(self):
457+ self.notification.pointing_device.click_object(
458+ self._get_cancel_buttoon())
459+
460+
461+class ReportingPage(UbuntuUIToolkitCustomProxyObjectBase):
462+ """Helper class to interact with the reporting page"""
463+
464+ @classmethod
465+ def validate_dbus_object(cls, path, state):
466+ name = introspection.get_classname_from_path(path)
467+ if name == b'Page':
468+ if state['objectName'][1] == 'reportingPage':
469+ return True
470+ return False
471+
472+ def _get_continue_button(self):
473+ return self.select_single('StackButton', text='Continue')
474+
475+ def _get_back_button(self):
476+ return self.select_single('StackButton', text='Back')
477+
478+ @autopilot.logging.log_action(logger.info)
479+ def back(self):
480+ self.pointing_device.click_object(self._get_back_button())
481+ return get_wizard(self).get_wifi_connect_page()
482+
483+ @autopilot.logging.log_action(logger.info)
484+ def continue_(self):
485+ self.pointing_device.click_object(self._get_continue_button())
486+ return get_wizard(self).get_finished_page()
487+
488+
489+class FinishedPage(UbuntuUIToolkitCustomProxyObjectBase):
490+ """Helper class to interact with the finished page"""
491+
492+ @classmethod
493+ def validate_dbus_object(cls, path, state):
494+ name = introspection.get_classname_from_path(path)
495+ if name == b'Page':
496+ if state['objectName'][1] == 'finishedPage':
497+ return True
498+ return False
499+
500+ def _get_finish_button(self):
501+ return self.wait_select_single(
502+ 'StackButton', text='Finish', visible='True')
503+
504+ @autopilot.logging.log_action(logger.info)
505+ def finish(self):
506+ self.pointing_device.click_object(self._get_finish_button())
507+ self.wait_until_destroyed()