20 """Tests for Notifications"""
28 from autopilot.matchers
import Eventually
29 from gi.repository
import Notify
30 from testtools.matchers
import Equals, NotEquals
31 from ubuntuuitoolkit
import ubuntu_scenarios
33 from unity8
import shell
38 logger = logging.getLogger(__name__)
42 """Base class for all notification tests that provides helper methods."""
44 scenarios = ubuntu_scenarios.get_device_simulation_scenarios(
45 ubuntu_scenarios.NEXUS4_DEVICE)
47 def _get_icon_path(self, icon_name):
48 """Given an icons file name returns the full path (either system or
51 Consider the graphics directory as root so for example (running tests
52 from installed unity8-autopilot package):
53 >>> self.get_icon_path('clock.png')
54 /usr/share/unity8/graphics/clock.png
56 >>> self.get_icon_path('applicationIcons/facebook.png')
57 /usr/share/unity8/graphics/applicationIcons/facebook.png
60 if os.path.abspath(__file__).startswith(
'/usr/'):
61 return '/usr/share/unity8/graphics/' + icon_name
63 return os.path.dirname(__file__) + (
64 "/../../../../../qml/graphics/" + icon_name)
66 def _get_notifications_list(self):
67 return self.main_window.select_single(
69 objectName=
'notificationList'
72 def _assert_notification(
81 """Assert that the expected qualities of a notification are as
86 if summary
is not None:
87 self.assertThat(notification.summary, Eventually(Equals(summary)))
90 self.assertThat(notification.body, Eventually(Equals(body)))
93 self.assertThat(notification.iconSource, Eventually(NotEquals(
"")))
95 self.assertThat(notification.iconSource, Eventually(Equals(
"")))
99 notification.secondaryIconSource,
100 Eventually(NotEquals(
""))
104 notification.secondaryIconSource,
105 Eventually(Equals(
""))
108 if opacity
is not None:
109 self.assertThat(notification.opacity, Eventually(Equals(opacity)))
113 """Collection of test for Interactive tests including snap decisions."""
121 """Interactive notification must react upon click on itself."""
127 summary =
"Interactive notification"
128 body =
"This notification can be clicked on to trigger an action."
130 actions = [(
"action_id",
"dummy")]
132 (
"x-canonical-switch-to-application",
"true"),
133 (
"x-canonical-secondary-icon",
"dialer")
145 get_notification =
lambda: notify_list.wait_select_single(
146 'Notification', objectName=
'notification1')
147 notification = get_notification()
149 notification.pointing_device.click_object(
150 notification.select_single(objectName=
"interactiveArea")
156 """Snap-decision with three actions should use
157 one-over two button layout.
162 summary =
"Theatre at Ferria Stadium"
163 body =
"at Ferria Stadium in Bilbao, Spain\n07578545317"
165 (
"x-canonical-snap-decisions",
"true"),
166 (
"x-canonical-non-shaped-icon",
"true"),
167 (
"x-canonical-private-affirmative-tint",
"true")
171 (
'action_accept',
'Ok'),
172 (
'action_decline_1',
'Snooze'),
173 (
'action_decline_2',
'View'),
187 get_notification =
lambda: notify_list.wait_select_single(
188 'Notification', objectName=
'notification1')
189 notification = get_notification()
191 notification, summary, body,
False,
False, 1.0)
192 notification.pointing_device.click_object(
193 notification.select_single(objectName=
"notify_oot_button0"))
197 """Snap-decision should block input to shell
198 without greeter/lockscreen.
203 summary =
"Incoming file"
204 body =
"Frank would like to send you the file: essay.pdf"
205 icon_path =
"sync-idle"
207 (
"x-canonical-snap-decisions",
"true"),
208 (
"x-canonical-non-shaped-icon",
"true"),
209 (
"x-canonical-private-affirmative-tint",
"true"),
210 (
"x-canonical-private-rejection-tint",
"true"),
214 (
'action_accept',
'Accept'),
215 (
'action_decline_1',
'Decline'),
230 self.main_window.show_dash_swiping()
232 self.main_window.is_launcher_open, Eventually(Equals(
False)))
236 get_notification =
lambda: notify_list.wait_select_single(
237 'Notification', objectName=
'notification1')
238 notification = get_notification()
240 notification, summary, body,
True,
False, 1.0)
241 notification.pointing_device.click_object(
242 notification.select_single(objectName=
"notify_button0"))
246 """A snap-decision should block input to the
247 greeter/lockscreen beneath it.
251 summary =
"Incoming file"
252 body =
"Frank would like to send you the file: essay.pdf"
253 icon_path =
"sync-idle"
255 (
"x-canonical-snap-decisions",
"true"),
256 (
"x-canonical-non-shaped-icon",
"true"),
257 (
"x-canonical-private-affirmative-tint",
"true"),
258 (
"x-canonical-private-rejection-tint",
"true"),
262 (
'action_accept',
'Accept'),
263 (
'action_decline_1',
'Decline'),
278 self.main_window.show_dash_swiping()
280 self.main_window.is_launcher_open, Eventually(Equals(
False)))
284 get_notification =
lambda: notify_list.wait_select_single(
285 'Notification', objectName=
'notification1')
286 notification = get_notification()
288 notification, summary, body,
True,
False, 1.0)
289 notification.pointing_device.click_object(
290 notification.select_single(objectName=
"notify_button0"))
293 def _create_interactive_notification(
302 """Create a interactive notification command.
304 :param summary: Summary text for the notification
305 :param body: Body text to display in the notification
306 :param icon: Path string to the icon to use
307 :param urgency: Urgency string for the noticiation, either: 'LOW',
309 :param actions: List of tuples containing the 'id' and 'label' for all
311 :param hint_strings: List of tuples containing the 'name' and value for
312 setting the hint strings for the notification
317 "Creating snap-decision notification with summary(%s), body(%s) "
325 '--summary', summary,
331 script_args.extend([
'--icon', icon])
335 script_args.extend([
'--hint',
"%s,%s" % (key, value)])
337 for action
in actions:
338 action_id, action_label = action
339 action_string =
"%s,%s" % (action_id, action_label)
340 script_args.extend([
'--action', action_string])
342 python_bin = subprocess.check_output([
'which',
'python3']).strip()
344 logger.info(
"Launching snap-decision notification as: %s", command)
347 stdin=subprocess.PIPE,
348 stdout=subprocess.PIPE,
349 stderr=subprocess.PIPE,
351 universal_newlines=
True,
356 poll_result = self._notify_proc.poll()
357 if poll_result
is not None and self._notify_proc.returncode != 0:
358 error_output = self._notify_proc.communicate()[1]
359 raise RuntimeError(
"Call to script failed with: %s" % error_output)
361 def _get_notify_script(self):
362 """Returns the path to the interactive notification
365 file_path =
"../../create_interactive_notification.py"
367 the_path = os.path.abspath(
368 os.path.join(__file__, file_path))
372 def _tidy_up_script_process(self):
373 if self.
_notify_proc is not None and self._notify_proc.poll()
is None:
374 logger.error(
"Notification process wasn't killed, killing now.")
375 os.killpg(self._notify_proc.pid, signal.SIGTERM)
378 """Assert that the interactive notification callback of id *action_id*
381 :raises AssertionError: If no interactive notification has actually
383 :raises AssertionError: When *action_id* does not match the actual
385 :raises AssertionError: If no callback was called at all.
389 raise AssertionError(
"No interactive notification was created.")
391 for i
in range(timeout):
392 self._notify_proc.poll()
393 if self._notify_proc.returncode
is not None:
394 output = self._notify_proc.communicate()
395 actual_action_id = output[0].strip(
"\n")
396 if actual_action_id != action_id:
397 raise AssertionError(
398 "action id '%s' does not match actual returned '%s'"
399 % (action_id, actual_action_id)
405 os.killpg(self._notify_proc.pid, signal.SIGTERM)
407 raise AssertionError(
408 "No callback was called, killing interactivenotification script"
413 """Collection of tests for Emphemeral notifications (non-interactive.)"""
419 Notify.init(
"Autopilot Ephemeral Notification Tests")
420 self.addCleanup(Notify.uninit)
423 """Notification must display the expected summary and body text."""
429 summary =
"Icon-Summary-Body"
430 body =
"Hey pal, what's up with the party next weekend? Will you " \
434 (
"x-canonical-secondary-icon",
"message")
437 notification = shell.create_ephemeral_notification(
447 notification =
lambda: notify_list.wait_select_single(
448 'Notification', objectName=
'notification1')
459 """Notification must display the expected summary and secondary
466 summary =
"Upload of image completed"
470 notification = shell.create_ephemeral_notification(
480 notification =
lambda: notify_list.wait_select_single(
481 'Notification', objectName=
'notification1')
492 """Notifications must be displayed in order according to their
499 summary_low =
'Low Urgency'
500 body_low =
"No, I'd rather see paint dry, pal *yawn*"
503 summary_normal =
'Normal Urgency'
504 body_normal =
"Hey pal, what's up with the party next weekend? Will " \
505 "you join me and Anna?"
508 summary_critical =
'Critical Urgency'
509 body_critical =
'Dude, this is so urgent you have no idea :)'
510 icon_path_critical = self.
_get_icon_path(
'avatars/anna_olsson.png')
512 notification_normal = shell.create_ephemeral_notification(
518 notification_normal.show()
520 notification_low = shell.create_ephemeral_notification(
526 notification_low.show()
528 notification_critical = shell.create_ephemeral_notification(
534 notification_critical.show()
536 get_notification =
lambda: notify_list.wait_select_single(
538 summary=summary_critical
541 notification = get_notification()
551 get_normal_notification =
lambda: notify_list.wait_select_single(
553 summary=summary_normal
555 notification = get_normal_notification()
565 get_low_notification =
lambda: notify_list.wait_select_single(
569 notification = get_low_notification()
580 """Notification must display the expected summary- and body-text."""
586 summary =
'Summary-Body'
587 body =
'This is a superfluous notification'
589 notification = shell.create_ephemeral_notification(summary, body)
592 notification = notify_list.wait_select_single(
593 'Notification', objectName=
'notification1')
605 """Notification must display only the expected summary-text."""
611 summary =
'Summary-Only'
613 notification = shell.create_ephemeral_notification(summary)
616 notification = notify_list.wait_select_single(
617 'Notification', objectName=
'notification1')
622 """Notification must allow updating its contents while being
629 summary =
'Initial notification'
630 body =
'This is the original content of this notification-bubble.'
633 notification = shell.create_ephemeral_notification(
640 get_notification =
lambda: notify_list.wait_select_single(
641 'Notification', summary=summary)
651 summary =
'Updated notification'
652 body =
'Here the same bubble with new title- and body-text, even ' \
653 'the icon can be changed on the update.'
655 notification.update(summary, body, icon_path)
658 get_notification(), summary, body,
True,
False, 1.0)
661 """Notification must allow updating its contents and layout while
668 summary =
'Initial layout'
669 body =
'This bubble uses the icon-title-body layout with a ' \
674 notification = shell.create_ephemeral_notification(
679 notification.set_hint_string(
680 'x-canonical-secondary-icon',
685 get_notification =
lambda: notify_list.wait_select_single(
686 'Notification', objectName=
'notification1')
697 notification.clear_hints()
698 summary =
'Updated layout'
699 body =
'After the update we now have a bubble using the title-body ' \
701 notification.update(summary, body,
None)
704 self.assertThat(get_notification, Eventually(NotEquals(
None)))
706 get_notification(), summary, body,
False,
False, 1.0)
709 """ use the create notification script to get a notification dialog.
710 Check that the arguments passed to the script match the fields. """
715 summary =
'Helper summary'
718 notification = shell.create_ephemeral_notification(summary, body)
721 notification_data = self.main_window.wait_for_notification()
723 self.assertThat(notification_data[
'summary'],
724 Eventually(Equals(summary)))
725 self.assertThat(notification_data[
'body'], Eventually(Equals(body)))
def test_sd_one_over_two_layout(self)
def test_update_notification_same_layout(self)
def launch_unity(self, mode="full-greeter", args)
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)
def test_notification_helper(self)