@@ -0,0 +1,550 @@
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_location_page(self):
78+ return self.wait_select_single(
79+ objectName='locationPage', visible='True')
80+
81+ def get_reporting_page(self):
82+ return self.wait_select_single(
83+ objectName='reportingPage', visible='True')
84+
85+ def get_finished_page(self):
86+ return self.wait_select_single(
87+ objectName='finishedPage', visible='True')
88+
89+
90+class WizardLanguagePage(UbuntuUIToolkitCustomProxyObjectBase):
91+ """Helper class to interact with the language welcome page"""
92+
93+ # This class was renamed from LanguagePage to WizardLanguagePage because
94+ # there is a class with the same name in the system settings custom proxy
95+ # objects. Reported in http://pad.lv/1422904. --elopio - 2015-02-17
96+
97+ @classmethod
98+ def validate_dbus_object(cls, path, state):
99+ name = introspection.get_classname_from_path(path)
100+ if name == b'Page':
101+ if state['objectName'][1] == 'languagePage':
102+ return True
103+ return False
104+
105+ def _get_language_button(self):
106+ return self.select_single(
107+ 'ComboButton', objectName='languageCombo')
108+
109+ def _get_continue_button(self):
110+ return self.select_single('StackButton', text='Continue')
111+
112+ def _swipe_to_language(self, list, language):
113+ """Swipe to the chosen language in the given list"""
114+ list.swipe_to_top()
115+ while not list.atYEnd:
116+ try:
117+ item = list.select_single(
118+ 'LabelVisual', text=language, visible='True')
119+ item.swipe_into_view()
120+ return item
121+ except introspection.dbus.StateNotFoundError:
122+ list.swipe_to_show_more_below()
123+ # item could not be found
124+ raise introspection.dbus.StateNotFoundError
125+
126+ @autopilot.logging.log_action(logger.info)
127+ def select_language(self, language):
128+ """Select a different language from the list"""
129+ combo_button = self._get_language_button()
130+ self.pointing_device.click_object(combo_button)
131+ language_list = combo_button.wait_select_single(
132+ 'UbuntuListView11', visible='True')
133+ item = self._swipe_to_language(language_list, language)
134+ self.pointing_device.click_object(item)
135+
136+ def get_selected_language(self):
137+ return self._get_language_button().get_properties()['text']
138+
139+ @autopilot.logging.log_action(logger.info)
140+ def continue_(self):
141+ self.pointing_device.click_object(self._get_continue_button())
142+ wizard = get_wizard(self)
143+ next_page = wizard.get_current_page()
144+ sim_inserted = True
145+ if next_page.objectName == 'simPage':
146+ # no sim is inserted
147+ next_page = wizard.get_sim_page()
148+ sim_inserted = False
149+ else:
150+ # sim is inserted
151+ next_page = wizard.get_password_page()
152+ return sim_inserted, next_page
153+
154+
155+class SimPage(UbuntuUIToolkitCustomProxyObjectBase):
156+ """Helper class to interact with the no sim notification page"""
157+
158+ @classmethod
159+ def validate_dbus_object(cls, path, state):
160+ name = introspection.get_classname_from_path(path)
161+ if name == b'Page':
162+ if state['objectName'][1] == 'simPage':
163+ return True
164+ return False
165+
166+ def _get_back_button(self):
167+ return self.select_single('StackButton', text='Back')
168+
169+ def _get_skip_button(self):
170+ return self.select_single('StackButton', text='Skip')
171+
172+ @autopilot.logging.log_action(logger.info)
173+ def back(self):
174+ self.pointing_device.click_object(self._get_back_button())
175+ return get_wizard(self).get_languge_page()
176+
177+ @autopilot.logging.log_action(logger.info)
178+ def skip(self):
179+ self.pointing_device.click_object(self._get_skip_button())
180+ return get_wizard(self).get_password_page()
181+
182+
183+class PasswordPage(UbuntuUIToolkitCustomProxyObjectBase):
184+ """Helper class to interact with the password security page"""
185+
186+ SECURITY_SWIPE = 'Swipe'
187+ SECURITY_PASSCODE = 'Passcode'
188+ SECURITY_PASSPHRASE = 'Passphrase'
189+
190+ @classmethod
191+ def validate_dbus_object(cls, path, state):
192+ name = introspection.get_classname_from_path(path)
193+ if name == b'Page':
194+ if state['objectName'][1] == 'passwdPage':
195+ return True
196+ return False
197+
198+ def _get_option_name(self, formatted_name):
199+ # the option name is formatted as follows:
200+ # need to read the name between bold tags
201+ # '<b>Passcode</b> (4 digits only)'
202+ formatted_pattern = re.compile(r'<b>(.*?)</b>.*',
203+ re.IGNORECASE | re.DOTALL)
204+ name = None
205+ match = formatted_pattern.search(formatted_name)
206+ if match:
207+ name = match.group(1)
208+ return name
209+
210+ def _get_all_options(self):
211+ return self.select_many('OptionSelectorDelegate')
212+
213+ def _get_continue_button(self):
214+ return self.select_single('StackButton', text='Continue')
215+
216+ def _get_back_button(self):
217+ return self.select_single('StackButton', text='Back')
218+
219+ def _get_selected_option(self):
220+ return self.select_single(
221+ 'OptionSelectorDelegate', visible='True', selected='True')
222+
223+ def get_selected_security_option(self):
224+ name_fmt = self._get_selected_option().get_properties()['subText']
225+ return self._get_option_name(name_fmt)
226+
227+ @autopilot.logging.log_action(logger.info)
228+ def select_security_option(self, selected_option):
229+ options = self._get_all_options()
230+ for option in options:
231+ name = self._get_option_name(option.get_properties()['subText'])
232+ if name == selected_option:
233+ self.pointing_device.click_object(option)
234+
235+ @autopilot.logging.log_action(logger.info)
236+ def back(self, sim_inserted):
237+ self.pointing_device.click_object(self._get_back_button())
238+ wizard = get_wizard(self)
239+ if sim_inserted:
240+ page = wizard.get_languge_page()
241+ else:
242+ page = wizard.get_sim_page()
243+ return page
244+
245+ @autopilot.logging.log_action(logger.info)
246+ def continue_(self):
247+ option = self.get_selected_security_option()
248+ self.pointing_device.click_object(self._get_continue_button())
249+ wizard = get_wizard(self)
250+ if option == self.SECURITY_SWIPE:
251+ next_page = wizard.get_wifi_connect_page()
252+ else:
253+ next_page = wizard.get_password_entry_page()
254+ return next_page
255+
256+
257+class PasswordEntryPage(UbuntuUIToolkitCustomProxyObjectBase):
258+ """Helper class to interact with the pin entry page"""
259+
260+ @classmethod
261+ def validate_dbus_object(cls, path, state):
262+ name = introspection.get_classname_from_path(path)
263+ if name == b'Page':
264+ if state['objectName'][1] == 'passwdSetPage':
265+ return True
266+ return False
267+
268+ def _get_pinpad_button(self, char):
269+ return self.select_single('PinPadButton', text=char)
270+
271+ def _get_continue_button(self):
272+ return self.select_single('StackButton', text='Continue')
273+
274+ def _get_back_button(self):
275+ return self.select_single('StackButton', text='Back')
276+
277+ def _get_text_field(self):
278+ return self.select_single('QQuickTextInput')
279+
280+ @autopilot.logging.log_action(logger.info)
281+ def enter_pin(self, pin):
282+ for char in pin:
283+ self.pointing_device.click_object(self._get_pinpad_button(char))
284+ return get_wizard(self).get_confirm_password_page()
285+
286+ @autopilot.logging.log_action(logger.info)
287+ def enter_text(self, text):
288+ self.pointing_device.click_object(self._get_text_field())
289+ autopilot.input.Keyboard.create().type(text)
290+
291+ @autopilot.logging.log_action(logger.info)
292+ def back(self):
293+ self.pointing_device.click_object(self._get_back_button())
294+ return get_wizard(self).get_password_page()
295+
296+ @autopilot.logging.log_action(logger.info)
297+ def continue_(self):
298+ autopilot.input.Keyboard.create().press_and_release('Enter')
299+ return get_wizard(self).get_confirm_password_page()
300+
301+
302+class ConfirmPasswordPage(UbuntuUIToolkitCustomProxyObjectBase):
303+ """Helper class to interact with the pin confirmation page"""
304+
305+ @classmethod
306+ def validate_dbus_object(cls, path, state):
307+ name = introspection.get_classname_from_path(path)
308+ if name == b'Page':
309+ if state['objectName'][1] == 'passwdConfirmPage':
310+ return True
311+ return False
312+
313+ def _get_pinpad_button(self, char):
314+ return self.select_single('PinPadButton', text=char)
315+
316+ def _get_continue_button(self):
317+ return self.select_single('StackButton', text='Continue')
318+
319+ def _get_back_button(self):
320+ return self.select_single('StackButton', text='Back')
321+
322+ def _get_text_field(self):
323+ return self.select_single('QQuickTextInput')
324+
325+ @autopilot.logging.log_action(logger.info)
326+ def enter_pin(self, pin):
327+ for char in pin:
328+ self.pointing_device.click_object(self._get_pinpad_button(char))
329+ return get_wizard(self).get_wifi_connect_page()
330+
331+ @autopilot.logging.log_action(logger.info)
332+ def enter_text(self, text):
333+ self.pointing_device.click_object(self._get_text_field())
334+ autopilot.input.Keyboard.create().type(text)
335+
336+ @autopilot.logging.log_action(logger.info)
337+ def back(self):
338+ self.pointing_device.click_object(self._get_back_button())
339+ return get_wizard(self).get_password_page()
340+
341+ @autopilot.logging.log_action(logger.info)
342+ def continue_(self):
343+ autopilot.input.Keyboard.create().press_and_release('Enter')
344+ return get_wizard(self).get_wifi_connect_page()
345+
346+
347+class WifiConnectPage(UbuntuUIToolkitCustomProxyObjectBase):
348+ """Helper class to interact with the Wi-Fi network list page"""
349+
350+ @classmethod
351+ def validate_dbus_object(cls, path, state):
352+ name = introspection.get_classname_from_path(path)
353+ if name == b'Page':
354+ if state['objectName'][1] == 'wifiPage':
355+ return True
356+ return False
357+
358+ def _get_all_networks(self):
359+ try:
360+ networks = self.select_many('Standard', objectName='accessPoint',
361+ visible='True')
362+ except:
363+ networks = []
364+ return networks
365+
366+ def _get_network(self, ssid):
367+ return self.wait_select_single(
368+ 'Standard', objectName='accessPoint', text=ssid, visible='True')
369+
370+ def _get_network_checkbox(self, ssid):
371+ return self._get_network(ssid).select_single(
372+ 'CheckBox', visible='True')
373+
374+ def _get_next_page(self):
375+ wizard = get_wizard(self)
376+ next_page = wizard.get_current_page()
377+ locationPageEnabled = True
378+ if next_page.objectName == 'locationPage':
379+ next_page = wizard.get_location_page()
380+ else:
381+ locationPageEnabled = False
382+ next_page = wizard.get_reporting_page()
383+ return locationPageEnabled, next_page
384+
385+ def _get_notification(self, unity):
386+ logger.info('Waiting longer for notification object')
387+ with override_proxy_timeout(unity, 30):
388+ return unity.wait_select_single(
389+ Notification, objectName='notification1', visible='True')
390+
391+ def _get_back_button(self):
392+ return self.wait_select_single(
393+ 'StackButton', text='Back', visible='True')
394+
395+ def _get_continue_button(self):
396+ return self.wait_select_single(
397+ 'StackButton', text='Continue', visible='True')
398+
399+ def _get_skip_button(self):
400+ return self.wait_select_single(
401+ 'StackButton', text='Skip', visible='True')
402+
403+ def is_any_network_checked(self):
404+ networks = self._get_all_networks()
405+ for network in networks:
406+ checkbox = network.select_single('CheckBox', visible='True')
407+ if checkbox.get_properties()['checked']:
408+ return True
409+ return False
410+
411+ def is_any_network_found(self):
412+ num_neworks = len(self._get_all_networks())
413+ return True if num_neworks > 0 else False
414+
415+ def is_network_checked(self, ssid):
416+ return self._get_network_checkbox(ssid).get_properties()['checked']
417+
418+ @autopilot.logging.log_action(logger.info)
419+ def select_network(self, unity, ssid):
420+ self.pointing_device.click_object(self._get_network_checkbox(ssid))
421+ return PasswordNotification(self._get_notification(unity))
422+
423+ @autopilot.logging.log_action(logger.info)
424+ def back(self):
425+ self.pointing_device.click_object(self._get_back_button())
426+ return get_wizard(self).get_password_page()
427+
428+ @autopilot.logging.log_action(logger.info)
429+ def skip(self):
430+ self.pointing_device.click_object(self._get_skip_button())
431+ return self._get_next_page()
432+
433+ @autopilot.logging.log_action(logger.info)
434+ def continue_(self):
435+ self.pointing_device.click_object(self._get_continue_button())
436+ return self._get_next_page()
437+
438+
439+class LocationPage(UbuntuUIToolkitCustomProxyObjectBase):
440+ """Helper class to interact with the LocationPage"""
441+
442+ @classmethod
443+ def validate_dbus_object(cls, path, state):
444+ name = introspection.get_classname_from_path(path)
445+ if name == b'Page':
446+ if state['objectName'][1] == 'locationPage':
447+ return True
448+ return False
449+
450+ def _get_back_button(self):
451+ return self.select_single('StackButton', text='Back')
452+
453+ def _get_continue_button(self):
454+ return self.select_single('StackButton', text='Continue')
455+
456+ @autopilot.logging.log_action(logger.info)
457+ def back(self):
458+ self.pointing_device.click_object(self._get_back_button())
459+ return get_wizard(self).get_wifi_connect_page()
460+
461+ @autopilot.logging.log_action(logger.info)
462+ def continue_(self):
463+ self.pointing_device.click_object(self._get_continue_button())
464+ return get_wizard(self).get_reporting_page()
465+
466+
467+class Notification(UbuntuUIToolkitCustomProxyObjectBase):
468+ """Base class for notification objects"""
469+ pass
470+
471+
472+class PasswordNotification(Notification):
473+ """Helper class to interact with the password entry notification"""
474+
475+ def __init__(self, notification):
476+ self.notification = notification
477+
478+ def _get_connect_button(self):
479+ return self.notification.wait_select_single(
480+ 'Button', text='Connect', objectName='notify_button0')
481+
482+ def _get_cancel_buttoon(self):
483+ return self.notification.wait_select_single(
484+ 'Button', text='Cancel', objectName='notify_button1')
485+
486+ def _get_text_field(self):
487+ return self.notification.wait_select_single('TextField')
488+
489+ @autopilot.logging.log_action(logger.info)
490+ def enter_text(self, text):
491+ self._get_text_field().write(text)
492+
493+ @autopilot.logging.log_action(logger.info)
494+ def connect(self):
495+ self.notification.pointing_device.click_object(
496+ self._get_connect_button())
497+
498+ @autopilot.logging.log_action(logger.info)
499+ def cancel(self):
500+ self.notification.pointing_device.click_object(
501+ self._get_cancel_buttoon())
502+
503+
504+class ReportingPage(UbuntuUIToolkitCustomProxyObjectBase):
505+ """Helper class to interact with the reporting page"""
506+
507+ @classmethod
508+ def validate_dbus_object(cls, path, state):
509+ name = introspection.get_classname_from_path(path)
510+ if name == b'Page':
511+ if state['objectName'][1] == 'reportingPage':
512+ return True
513+ return False
514+
515+ def _get_continue_button(self):
516+ return self.select_single('StackButton', text='Continue')
517+
518+ def _get_back_button(self):
519+ return self.select_single('StackButton', text='Back')
520+
521+ @autopilot.logging.log_action(logger.info)
522+ def back(self):
523+ self.pointing_device.click_object(self._get_back_button())
524+ return get_wizard(self).get_wifi_connect_page()
525+
526+ @autopilot.logging.log_action(logger.info)
527+ def continue_(self):
528+ self.pointing_device.click_object(self._get_continue_button())
529+ return get_wizard(self).get_finished_page()
530+
531+
532+class FinishedPage(UbuntuUIToolkitCustomProxyObjectBase):
533+ """Helper class to interact with the finished page"""
534+
535+ @classmethod
536+ def validate_dbus_object(cls, path, state):
537+ name = introspection.get_classname_from_path(path)
538+ if name == b'Page':
539+ if state['objectName'][1] == 'finishedPage':
540+ return True
541+ return False
542+
543+ def _get_finish_button(self):
544+ return self.wait_select_single(
545+ 'StackButton', text='Finish', visible='True')
546+
547+ @autopilot.logging.log_action(logger.info)
548+ def finish(self):
549+ self.pointing_device.click_object(self._get_finish_button())
550+ self.wait_until_destroyed()