20 """Tests for Notifications"""
22 from __future__
import absolute_import
27 from testtools.matchers
import Equals, NotEquals
28 from autopilot.matchers
import Eventually
30 from gi.repository
import Notify
38 logger = logging.getLogger(__name__)
42 if sys.version_info < (3,):
47 """Base class for all notification tests that provides helper methods."""
49 scenarios = _get_device_emulation_scenarios(
'Nexus4')
51 def _get_icon_path(self, icon_name):
52 """Given an icons file name returns the full path (either system or
55 Consider the graphics directory as root so for example (running tests
56 from installed unity8-autopilot package):
57 >>> self.get_icon_path('clock.png')
58 /usr/share/unity8/graphics/clock.png
60 >>> self.get_icon_path('applicationIcons/facebook.png')
61 /usr/share/unity8/graphics/applicationIcons/facebook.png
64 if os.path.abspath(__file__).startswith(
'/usr/'):
65 return '/usr/share/unity8/graphics/' + icon_name
67 return os.path.dirname(__file__) +
"/../../../../../qml/graphics/" + icon_name
69 def _get_notifications_list(self):
70 return self.main_window.select_single(
72 objectName=
'notificationList'
75 def _assert_notification(
84 """Assert that the expected qualities of a notification are as
89 if summary
is not None:
90 self.assertThat(notification.summary, Eventually(Equals(summary)))
93 self.assertThat(notification.body, Eventually(Equals(body)))
96 self.assertThat(notification.iconSource, Eventually(NotEquals(
"")))
98 self.assertThat(notification.iconSource, Eventually(Equals(
"")))
102 notification.secondaryIconSource,
103 Eventually(NotEquals(
""))
107 notification.secondaryIconSource,
108 Eventually(Equals(
""))
111 if opacity
is not None:
112 self.assertThat(notification.opacity, Eventually(Equals(opacity)))
116 """Collection of test for Interactive tests including snap decisions."""
119 super(InteractiveNotificationBase, self).setUp()
124 """Interactive notification must react upon click on itself."""
126 unlock_unity(unity_proxy)
130 summary =
"Interactive notification"
131 body =
"This notification can be clicked on to trigger an action."
133 actions = [(
"action_id",
"dummy")]
135 (
"x-canonical-switch-to-application",
"true"),
137 "x-canonical-secondary-icon",
151 get_notification =
lambda: notify_list.wait_select_single(
152 'Notification', objectName=
'notification1')
153 notification = get_notification()
155 self.touch.tap_object(
156 notification.select_single(objectName=
"interactiveArea")
162 """Rejecting a call should make notification expand and
163 offer more options."""
165 unlock_unity(unity_proxy)
167 summary =
"Incoming call"
168 body =
"Frank Zappa\n+44 (0)7736 027340"
172 "x-canonical-secondary-icon",
175 (
"x-canonical-snap-decisions",
"true"),
179 (
'action_accept',
'Accept'),
180 (
'action_decline_1',
'Decline'),
181 (
'action_decline_2',
'"Can\'t talk now, what\'s up?"'),
182 (
'action_decline_3',
'"I call you back."'),
183 (
'action_decline_4',
'Send custom message...'),
196 get_notification =
lambda: notify_list.wait_select_single(
197 'Notification', objectName=
'notification1')
198 notification = get_notification()
200 initial_height = notification.height
201 self.touch.tap_object(notification.select_single(objectName=
"button1"))
204 Eventually(Equals(initial_height +
205 3 * notification.select_single(
206 objectName=
"buttonColumn").spacing +
207 3 * notification.select_single(
208 objectName=
"button4").height)))
209 self.touch.tap_object(notification.select_single(objectName=
"button4"))
212 def _create_interactive_notification(
221 """Create a interactive notification command.
223 :param summary: Summary text for the notification
224 :param body: Body text to display in the notification
225 :param icon: Path string to the icon to use
226 :param urgency: Urgency string for the noticiation, either: 'LOW',
228 :param actions: List of tuples containing the 'id' and 'label' for all
230 :param hint_strings: List of tuples containing the 'name' and value for
231 setting the hint strings for the notification
236 "Creating snap-decision notification with summary(%s), body(%s) "
244 '--summary', summary,
250 script_args.extend([
'--icon', icon])
254 script_args.extend([
'--hint',
"%s,%s" % (key, value)])
256 for action
in actions:
257 action_id, action_label = action
258 action_string =
"%s,%s" % (action_id, action_label)
259 script_args.extend([
'--action', action_string])
261 python_bin = subprocess.check_output([
'which',
'python']).strip()
263 logger.info(
"Launching snap-decision notification as: %s", command)
266 stdin=subprocess.PIPE,
267 stdout=subprocess.PIPE,
268 stderr=subprocess.PIPE,
270 universal_newlines=
True,
275 poll_result = self._notify_proc.poll()
276 if poll_result
is not None and self._notify_proc.returncode != 0:
277 error_output = self._notify_proc.communicate()[1]
278 raise RuntimeError(
"Call to script failed with: %s" % error_output)
280 def _get_notify_script(self):
281 """Returns the path to the interactive notification creation script."""
282 file_path =
"../../emulators/create_interactive_notification.py"
284 the_path = os.path.abspath(
285 os.path.join(__file__, file_path))
289 def _tidy_up_script_process(self):
290 if self.
_notify_proc is not None and self._notify_proc.poll()
is None:
291 logger.error(
"Notification process wasn't killed, killing now.")
292 os.killpg(self._notify_proc.pid, signal.SIGTERM)
295 """Assert that the interactive notification callback of id *action_id*
298 :raises AssertionError: If no interactive notification has actually
300 :raises AssertionError: When *action_id* does not match the actual
302 :raises AssertionError: If no callback was called at all.
306 raise AssertionError(
"No interactive notification was created.")
308 for i
in range(timeout):
309 self._notify_proc.poll()
310 if self._notify_proc.returncode
is not None:
311 output = self._notify_proc.communicate()
312 actual_action_id = output[0].strip(
"\n")
313 if actual_action_id != action_id:
314 raise AssertionError(
315 "action id '%s' does not match actual returned '%s'"
316 % (action_id, actual_action_id)
322 os.killpg(self._notify_proc.pid, signal.SIGTERM)
324 raise AssertionError(
325 "No callback was called, killing interactivenotification script"
330 """Collection of tests for Emphemeral notifications (non-interactive.)"""
333 super(EphemeralNotificationsTests, self).setUp()
336 Notify.init(
"Autopilot Ephemeral Notification Tests")
337 self.addCleanup(Notify.uninit)
340 """Notification must display the expected summary and body text."""
342 unlock_unity(unity_proxy)
346 summary =
"Icon-Summary-Body"
347 body =
"Hey pal, what's up with the party next weekend? Will you " \
352 "x-canonical-secondary-icon",
367 notification =
lambda: notify_list.wait_select_single(
368 'Notification', objectName=
'notification1')
379 """Notification must display the expected summary and secondary
382 unlock_unity(unity_proxy)
386 summary =
"Upload of image completed"
389 "x-canonical-secondary-icon",
404 notification =
lambda: notify_list.wait_select_single(
405 'Notification', objectName=
'notification1')
416 """Notifications must be displayed in order according to their
419 unlock_unity(unity_proxy)
423 summary_low =
'Low Urgency'
424 body_low =
"No, I'd rather see paint dry, pal *yawn*"
427 summary_normal =
'Normal Urgency'
428 body_normal =
"Hey pal, what's up with the party next weekend? Will " \
429 "you join me and Anna?"
432 summary_critical =
'Critical Urgency'
433 body_critical =
'Dude, this is so urgent you have no idea :)'
434 icon_path_critical = self.
_get_icon_path(
'avatars/anna_olsson.png')
442 notification_normal.show()
450 notification_low.show()
458 notification_critical.show()
460 get_notification =
lambda: notify_list.wait_select_single(
462 summary=summary_critical
465 notification = get_notification()
475 get_normal_notification =
lambda: notify_list.wait_select_single(
477 summary=summary_normal
479 notification = get_normal_notification()
489 get_low_notification =
lambda: notify_list.wait_select_single(
493 notification = get_low_notification()
504 """Notification must display the expected summary- and body-text."""
506 unlock_unity(unity_proxy)
510 summary =
'Summary-Body'
511 body =
'This is a superfluous notification'
516 notification = notify_list.wait_select_single(
517 'Notification', objectName=
'notification1')
529 """Notification must display only the expected summary-text."""
531 unlock_unity(unity_proxy)
535 summary =
'Summary-Only'
540 notification = notify_list.wait_select_single(
541 'Notification', objectName=
'notification1')
546 """Notification must allow updating its contents while being
549 unlock_unity(unity_proxy)
553 summary =
'Initial notification'
554 body =
'This is the original content of this notification-bubble.'
564 get_notification =
lambda: notify_list.wait_select_single(
565 'Notification', summary=summary)
575 summary =
'Updated notification'
576 body =
'Here the same bubble with new title- and body-text, even ' \
577 'the icon can be changed on the update.'
579 notification.update(summary, body, icon_path)
584 """Notification must allow updating its contents and layout while
587 unlock_unity(unity_proxy)
591 summary =
'Initial layout'
592 body =
'This bubble uses the icon-title-body layout with a ' \
602 notification.set_hint_string(
603 'x-canonical-secondary-icon',
608 get_notification =
lambda: notify_list.wait_select_single(
609 'Notification', objectName=
'notification1')
620 notification.clear_hints()
621 summary =
'Updated layout'
622 body =
'After the update we now have a bubble using the title-body ' \
624 notification.update(summary, body,
None)
627 self.assertThat(get_notification, Eventually(NotEquals(
None)))
631 """Notification has to accumulate body-text using append-hint."""
633 unlock_unity(unity_proxy)
637 summary =
'Cole Raby'
638 body =
'Hey Bro Coly!'
645 hints=[(
'x-canonical-append',
'true')]
650 get_notification =
lambda: notify_list.wait_select_single(
651 'Notification', objectName=
'notification1')
653 notification = get_notification()
665 'Did you watch the air-race in Oshkosh last week?',
666 'Phil owned the place like no one before him!',
667 'Did really everything in the race work according to regulations?',
668 'Somehow I think to remember Burt Williams did cut corners and '
669 'was not punished for this.',
670 'Hopefully the referees will watch the videos of the race.',
671 'Burt could get fined with US$ 50000 for that rule-violation :)'
674 for new_body
in bodies:
676 body_sum +=
'\n' + body
681 hints=[(
'x-canonical-append',
'true')]
685 get_notification =
lambda: notify_list.wait_select_single(
687 objectName=
'notification1'
689 notification = get_notification()
699 def _create_ephemeral_notification(
707 """Create an ephemeral (non-interactive) notification
709 :param summary: Summary text for the notification
710 :param body: Body text to display in the notification
711 :param icon: Path string to the icon to use
712 :param hint_strings: List of tuples containing the 'name' and value
713 for setting the hint strings for the notification
714 :param urgency: Urgency string for the noticiation, either: 'LOW',
719 "Creating ephemeral: summary(%s), body(%s), urgency(%r) "
727 n = Notify.Notification.new(summary, body, icon)
731 n.set_hint_string(key, value)
732 logger.info(
"Adding hint to notification: (%s, %s)", key, value)
737 def _get_urgency(self, urgency):
738 """Translates urgency string to enum."""
739 _urgency_enums = {
'LOW': Notify.Urgency.LOW,
740 'NORMAL': Notify.Urgency.NORMAL,
741 'CRITICAL': Notify.Urgency.CRITICAL}
742 return _urgency_enums.get(urgency.upper())
def test_update_notification_same_layout
def _get_notifications_list
def assert_notification_action_id_was_called
def _tidy_up_script_process
def _create_interactive_notification
def test_summary_and_body
def test_sd_incoming_call
def test_icon_summary_body
def _create_ephemeral_notification
def test_update_notification_layout_change