20 """Tests for Notifications"""
22 from __future__
import absolute_import
24 from unity8
import shell
28 from testtools.matchers
import Equals, NotEquals
29 from autopilot.matchers
import Eventually
31 from gi.repository
import Notify
39 logger = logging.getLogger(__name__)
43 if sys.version_info < (3,):
48 """Base class for all notification tests that provides helper methods."""
50 scenarios = _get_device_emulation_scenarios(
'Nexus4')
52 def _get_icon_path(self, icon_name):
53 """Given an icons file name returns the full path (either system or
56 Consider the graphics directory as root so for example (running tests
57 from installed unity8-autopilot package):
58 >>> self.get_icon_path('clock.png')
59 /usr/share/unity8/graphics/clock.png
61 >>> self.get_icon_path('applicationIcons/facebook.png')
62 /usr/share/unity8/graphics/applicationIcons/facebook.png
65 if os.path.abspath(__file__).startswith(
'/usr/'):
66 return '/usr/share/unity8/graphics/' + icon_name
68 return os.path.dirname(__file__) + (
69 "/../../../../../qml/graphics/" + icon_name)
71 def _get_notifications_list(self):
72 return self.main_window.select_single(
74 objectName=
'notificationList'
77 def _assert_notification(
86 """Assert that the expected qualities of a notification are as
91 if summary
is not None:
92 self.assertThat(notification.summary, Eventually(Equals(summary)))
95 self.assertThat(notification.body, Eventually(Equals(body)))
98 self.assertThat(notification.iconSource, Eventually(NotEquals(
"")))
100 self.assertThat(notification.iconSource, Eventually(Equals(
"")))
104 notification.secondaryIconSource,
105 Eventually(NotEquals(
""))
109 notification.secondaryIconSource,
110 Eventually(Equals(
""))
113 if opacity
is not None:
114 self.assertThat(notification.opacity, Eventually(Equals(opacity)))
118 """Collection of test for Interactive tests including snap decisions."""
121 super(InteractiveNotificationBase, self).setUp()
126 """Interactive notification must react upon click on itself."""
128 unlock_unity(unity_proxy)
132 summary =
"Interactive notification"
133 body =
"This notification can be clicked on to trigger an action."
135 actions = [(
"action_id",
"dummy")]
137 (
"x-canonical-switch-to-application",
"true"),
138 (
"x-canonical-secondary-icon",
"dialer")
150 get_notification =
lambda: notify_list.wait_select_single(
151 'Notification', objectName=
'notification1')
152 notification = get_notification()
154 notification.pointing_device.click_object(
155 notification.select_single(objectName=
"interactiveArea")
161 """Snap-decision with three actions should use one-over two button layout."""
163 unlock_unity(unity_proxy)
165 summary =
"Theatre at Ferria Stadium"
166 body =
"at Ferria Stadium in Bilbao, Spain\n07578545317"
168 (
"x-canonical-snap-decisions",
"true"),
169 (
"x-canonical-non-shaped-icon",
"true"),
170 (
"x-canonical-private-affirmative-tint",
"true")
174 (
'action_accept',
'Ok'),
175 (
'action_decline_1',
'Snooze'),
176 (
'action_decline_2',
'View'),
190 get_notification =
lambda: notify_list.wait_select_single(
191 'Notification', objectName=
'notification1')
192 notification = get_notification()
194 notification, summary, body,
False,
False, 1.0)
195 notification.pointing_device.click_object(
196 notification.select_single(objectName=
"notify_oot_button0"))
200 """Snap-decision should block input to shell without greeter/lockscreen."""
202 unlock_unity(unity_proxy)
204 summary =
"Incoming file"
205 body =
"Frank would like to send you the file: essay.pdf"
206 icon_path =
"sync-idle"
208 (
"x-canonical-snap-decisions",
"true"),
209 (
"x-canonical-non-shaped-icon",
"true"),
210 (
"x-canonical-private-affirmative-tint",
"true"),
211 (
"x-canonical-private-rejection-tint",
"true"),
215 (
'action_accept',
'Accept'),
216 (
'action_decline_1',
'Decline'),
231 self.main_window.show_dash_swiping()
233 self.main_window.is_launcher_open, Eventually(Equals(
False)))
237 get_notification =
lambda: notify_list.wait_select_single(
238 'Notification', objectName=
'notification1')
239 notification = get_notification()
241 notification, summary, body,
True,
False, 1.0)
242 notification.pointing_device.click_object(
243 notification.select_single(objectName=
"notify_button0"))
247 """A snap-decision should block input to the greeter/lockscreen beneath it."""
250 summary =
"Incoming file"
251 body =
"Frank would like to send you the file: essay.pdf"
252 icon_path =
"sync-idle"
254 (
"x-canonical-snap-decisions",
"true"),
255 (
"x-canonical-non-shaped-icon",
"true"),
256 (
"x-canonical-private-affirmative-tint",
"true"),
257 (
"x-canonical-private-rejection-tint",
"true"),
261 (
'action_accept',
'Accept'),
262 (
'action_decline_1',
'Decline'),
277 self.main_window.show_dash_swiping()
279 self.main_window.is_launcher_open, Eventually(Equals(
False)))
283 get_notification =
lambda: notify_list.wait_select_single(
284 'Notification', objectName=
'notification1')
285 notification = get_notification()
287 notification, summary, body,
True,
False, 1.0)
288 notification.pointing_device.click_object(
289 notification.select_single(objectName=
"notify_button0"))
292 def _create_interactive_notification(
301 """Create a interactive notification command.
303 :param summary: Summary text for the notification
304 :param body: Body text to display in the notification
305 :param icon: Path string to the icon to use
306 :param urgency: Urgency string for the noticiation, either: 'LOW',
308 :param actions: List of tuples containing the 'id' and 'label' for all
310 :param hint_strings: List of tuples containing the 'name' and value for
311 setting the hint strings for the notification
316 "Creating snap-decision notification with summary(%s), body(%s) "
324 '--summary', summary,
330 script_args.extend([
'--icon', icon])
334 script_args.extend([
'--hint',
"%s,%s" % (key, value)])
336 for action
in actions:
337 action_id, action_label = action
338 action_string =
"%s,%s" % (action_id, action_label)
339 script_args.extend([
'--action', action_string])
341 python_bin = subprocess.check_output([
'which',
'python']).strip()
343 logger.info(
"Launching snap-decision notification as: %s", command)
346 stdin=subprocess.PIPE,
347 stdout=subprocess.PIPE,
348 stderr=subprocess.PIPE,
350 universal_newlines=
True,
355 poll_result = self._notify_proc.poll()
356 if poll_result
is not None and self._notify_proc.returncode != 0:
357 error_output = self._notify_proc.communicate()[1]
358 raise RuntimeError(
"Call to script failed with: %s" % error_output)
360 def _get_notify_script(self):
361 """Returns the path to the interactive notification creation script."""
362 file_path =
"../../emulators/create_interactive_notification.py"
364 the_path = os.path.abspath(
365 os.path.join(__file__, file_path))
369 def _tidy_up_script_process(self):
370 if self.
_notify_proc is not None and self._notify_proc.poll()
is None:
371 logger.error(
"Notification process wasn't killed, killing now.")
372 os.killpg(self._notify_proc.pid, signal.SIGTERM)
375 """Assert that the interactive notification callback of id *action_id*
378 :raises AssertionError: If no interactive notification has actually
380 :raises AssertionError: When *action_id* does not match the actual
382 :raises AssertionError: If no callback was called at all.
386 raise AssertionError(
"No interactive notification was created.")
388 for i
in range(timeout):
389 self._notify_proc.poll()
390 if self._notify_proc.returncode
is not None:
391 output = self._notify_proc.communicate()
392 actual_action_id = output[0].strip(
"\n")
393 if actual_action_id != action_id:
394 raise AssertionError(
395 "action id '%s' does not match actual returned '%s'"
396 % (action_id, actual_action_id)
402 os.killpg(self._notify_proc.pid, signal.SIGTERM)
404 raise AssertionError(
405 "No callback was called, killing interactivenotification script"
410 """Collection of tests for Emphemeral notifications (non-interactive.)"""
413 super(EphemeralNotificationsTests, self).setUp()
416 Notify.init(
"Autopilot Ephemeral Notification Tests")
417 self.addCleanup(Notify.uninit)
420 """Notification must display the expected summary and body text."""
422 unlock_unity(unity_proxy)
426 summary =
"Icon-Summary-Body"
427 body =
"Hey pal, what's up with the party next weekend? Will you " \
431 (
"x-canonical-secondary-icon",
"message")
434 notification = shell.create_ephemeral_notification(
444 notification =
lambda: notify_list.wait_select_single(
445 'Notification', objectName=
'notification1')
456 """Notification must display the expected summary and secondary
459 unlock_unity(unity_proxy)
463 summary =
"Upload of image completed"
467 notification = shell.create_ephemeral_notification(
477 notification =
lambda: notify_list.wait_select_single(
478 'Notification', objectName=
'notification1')
489 """Notifications must be displayed in order according to their
492 unlock_unity(unity_proxy)
496 summary_low =
'Low Urgency'
497 body_low =
"No, I'd rather see paint dry, pal *yawn*"
500 summary_normal =
'Normal Urgency'
501 body_normal =
"Hey pal, what's up with the party next weekend? Will " \
502 "you join me and Anna?"
505 summary_critical =
'Critical Urgency'
506 body_critical =
'Dude, this is so urgent you have no idea :)'
507 icon_path_critical = self.
_get_icon_path(
'avatars/anna_olsson.png')
509 notification_normal = shell.create_ephemeral_notification(
515 notification_normal.show()
517 notification_low = shell.create_ephemeral_notification(
523 notification_low.show()
525 notification_critical = shell.create_ephemeral_notification(
531 notification_critical.show()
533 get_notification =
lambda: notify_list.wait_select_single(
535 summary=summary_critical
538 notification = get_notification()
548 get_normal_notification =
lambda: notify_list.wait_select_single(
550 summary=summary_normal
552 notification = get_normal_notification()
562 get_low_notification =
lambda: notify_list.wait_select_single(
566 notification = get_low_notification()
577 """Notification must display the expected summary- and body-text."""
579 unlock_unity(unity_proxy)
583 summary =
'Summary-Body'
584 body =
'This is a superfluous notification'
586 notification = shell.create_ephemeral_notification(summary, body)
589 notification = notify_list.wait_select_single(
590 'Notification', objectName=
'notification1')
602 """Notification must display only the expected summary-text."""
604 unlock_unity(unity_proxy)
608 summary =
'Summary-Only'
610 notification = shell.create_ephemeral_notification(summary)
613 notification = notify_list.wait_select_single(
614 'Notification', objectName=
'notification1')
619 """Notification must allow updating its contents while being
622 unlock_unity(unity_proxy)
626 summary =
'Initial notification'
627 body =
'This is the original content of this notification-bubble.'
630 notification = shell.create_ephemeral_notification(
637 get_notification =
lambda: notify_list.wait_select_single(
638 'Notification', summary=summary)
648 summary =
'Updated notification'
649 body =
'Here the same bubble with new title- and body-text, even ' \
650 'the icon can be changed on the update.'
652 notification.update(summary, body, icon_path)
655 get_notification(), summary, body,
True,
False, 1.0)
658 """Notification must allow updating its contents and layout while
661 unlock_unity(unity_proxy)
665 summary =
'Initial layout'
666 body =
'This bubble uses the icon-title-body layout with a ' \
671 notification = shell.create_ephemeral_notification(
676 notification.set_hint_string(
677 'x-canonical-secondary-icon',
682 get_notification =
lambda: notify_list.wait_select_single(
683 'Notification', objectName=
'notification1')
694 notification.clear_hints()
695 summary =
'Updated layout'
696 body =
'After the update we now have a bubble using the title-body ' \
698 notification.update(summary, body,
None)
701 self.assertThat(get_notification, Eventually(NotEquals(
None)))
703 get_notification(), summary, body,
False,
False, 1.0)
def test_sd_one_over_two_layout(self)
def launch_unity(self, kwargs)
def test_update_notification_same_layout(self)
def _get_notify_script(self)
def _get_icon_path(self, icon_name)
def test_icon_summary(self)
def test_modal_sd_with_greeter(self)
def assert_notification_action_id_was_called
def test_icon_summary_body(self)
def _create_interactive_notification
def test_summary_only(self)
def _tidy_up_script_process(self)
def test_modal_sd_without_greeter(self)
def test_update_notification_layout_change(self)
def test_summary_and_body(self)
def test_interactive(self)
def test_urgency_order(self)
def _get_notifications_list(self)