Lomiri
Loading...
Searching...
No Matches
test_notifications.py
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Lomiri Autopilot Test Suite
4# Copyright (C) 2012, 2013, 2014, 2015 Canonical Ltd.
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"""Tests for Notifications"""
21
22import time
23import os
24import logging
25import signal
26import subprocess
27
28from autopilot.matchers import Eventually
29from gi.repository import Notify
30from testtools.matchers import Equals, NotEquals
31from lomiriuitoolkit import lomiri_scenarios
32
33from lomiri import shell
34from lomiri.process_helpers import unlock_lomiri
35from lomiri.shell.tests import LomiriTestCase
36
37
38logger = logging.getLogger(__name__)
39
40
42 """Base class for all notification tests that provides helper methods."""
43
44 scenarios = lomiri_scenarios.get_device_simulation_scenarios(
45 lomiri_scenarios.NEXUS4_DEVICE)
46
47 def _get_icon_path(self, icon_name):
48 """Given an icons file name returns the full path (either system or
49 source tree.
50
51 Consider the graphics directory as root so for example (running tests
52 from installed lomiri-autopilot package):
53 >>> self.get_icon_path('clock.png')
54 /usr/share/lomiri/graphics/clock.png
55
56 >>> self.get_icon_path('applicationIcons/facebook.png')
57 /usr/share/lomiri/graphics/applicationIcons/facebook.png
58
59 """
60 if os.path.abspath(__file__).startswith('/usr/'):
61 return '/usr/share/lomiri/graphics/' + icon_name
62 else:
63 return os.path.dirname(__file__) + (
64 "/../../../../../tests/graphics/" + icon_name)
65
66 def _get_notifications_list(self):
67 return self.main_window.select_single(
68 "Notifications",
69 objectName='notificationList'
70 )
71
73 self,
74 notification,
75 summary=None,
76 body=None,
77 icon=True,
78 secondary_icon=False,
79 opacity=None
80 ):
81 """Assert that the expected qualities of a notification are as
82 expected.
83
84 """
85
86 if summary is not None:
87 self.assertThat(notification.summary, Eventually(Equals(summary)))
88
89 if body is not None:
90 self.assertThat(notification.body, Eventually(Equals(body)))
91
92 if icon:
93 self.assertThat(notification.iconSource, Eventually(NotEquals("")))
94 else:
95 self.assertThat(notification.iconSource, Eventually(Equals("")))
96
97 if secondary_icon:
98 self.assertThat(
99 notification.secondaryIconSource,
100 Eventually(NotEquals(""))
101 )
102 else:
103 self.assertThat(
104 notification.secondaryIconSource,
105 Eventually(Equals(""))
106 )
107
108 if opacity is not None:
109 self.assertThat(notification.opacity, Eventually(Equals(opacity)))
110
111
113 """Collection of test for Interactive tests including snap decisions."""
114
115 def setUp(self):
116 super().setUp()
117 # Need to keep track when we launch the notification script.
118 self._notify_proc = None
119
121 """Interactive notification must react upon click on itself."""
122 self.launch_lomiri()
123 unlock_lomiri()
124
125 notify_list = self._get_notifications_list()
126
127 summary = "Interactive notification"
128 body = "This notification can be clicked on to trigger an action."
129 icon_path = self._get_icon_path('avatars/anna_olsson.png')
130 actions = [("action_id", "dummy")]
131 hints = [
132 ("x-lomiri-switch-to-application", "true"),
133 ("x-lomiri-secondary-icon", "dialer")
134 ]
135
137 summary,
138 body,
139 icon_path,
140 "NORMAL",
141 actions,
142 hints,
143 )
144
145 get_notification = lambda: notify_list.wait_select_single(
146 'Notification', objectName='notification0')
147 notification = get_notification()
148
149 notification.pointing_device.click_object(
150 notification.select_single(objectName="interactiveArea")
151 )
152
154
156 """Snap-decision with three actions should use
157 one-over two button layout.
158 """
159 self.launch_lomiri()
160 unlock_lomiri()
161
162 summary = "Theatre at Ferria Stadium"
163 body = "at Ferria Stadium in Bilbao, Spain\n07578545317"
164 hints = [
165 ("x-lomiri-snap-decisions", "true"),
166 ("x-lomiri-non-shaped-icon", "true"),
167 ("x-lomiri-private-affirmative-tint", "true")
168 ]
169
170 actions = [
171 ('action_accept', 'Ok'),
172 ('action_decline_1', 'Snooze'),
173 ('action_decline_2', 'View'),
174 ]
175
177 summary,
178 body,
179 None,
180 "NORMAL",
181 actions,
182 hints
183 )
184
185 # verify and interact with the triggered snap-decision notification
186 notify_list = self._get_notifications_list()
187 get_notification = lambda: notify_list.wait_select_single(
188 'Notification', objectName='notification0')
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"))
194 self.assert_notification_action_id_was_called("action_accept")
195
197 """Snap-decision should block input to shell
198 without greeter/lockscreen.
199 """
200 self.launch_lomiri()
201 unlock_lomiri()
202
203 summary = "Incoming file"
204 body = "Frank would like to send you the file: essay.pdf"
205 icon_path = "sync-idle"
206 hints = [
207 ("x-lomiri-snap-decisions", "true"),
208 ("x-lomiri-non-shaped-icon", "true"),
209 ("x-lomiri-private-affirmative-tint", "true"),
210 ("x-lomiri-private-rejection-tint", "true"),
211 ]
212
213 actions = [
214 ('action_accept', 'Accept'),
215 ('action_decline_1', 'Decline'),
216 ]
217
219 summary,
220 body,
221 icon_path,
222 "NORMAL",
223 actions,
224 hints
225 )
226
227 # verify that we cannot reveal the launcher (no longer interact with
228 # the shell)
229 time.sleep(1)
230 self.main_window.show_dash_swiping()
231 self.assertThat(
232 self.main_window.is_launcher_open, Eventually(Equals(False)))
233
234 # verify and interact with the triggered snap-decision notification
235 notify_list = self._get_notifications_list()
236 get_notification = lambda: notify_list.wait_select_single(
237 'Notification', objectName='notification0')
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"))
243 self.assert_notification_action_id_was_called("action_accept")
244
246 """A snap-decision should block input to the
247 greeter/lockscreen beneath it.
248 """
249 self.launch_lomiri()
250
251 summary = "Incoming file"
252 body = "Frank would like to send you the file: essay.pdf"
253 icon_path = "sync-idle"
254 hints = [
255 ("x-lomiri-snap-decisions", "true"),
256 ("x-lomiri-non-shaped-icon", "true"),
257 ("x-lomiri-private-affirmative-tint", "true"),
258 ("x-lomiri-private-rejection-tint", "true"),
259 ]
260
261 actions = [
262 ('action_accept', 'Accept'),
263 ('action_decline_1', 'Decline'),
264 ]
265
267 summary,
268 body,
269 icon_path,
270 "NORMAL",
271 actions,
272 hints
273 )
274
275 # verify that we cannot reveal the launcher (no longer interact with
276 # the shell)
277 time.sleep(1)
278 self.main_window.show_dash_swiping()
279 self.assertThat(
280 self.main_window.is_launcher_open, Eventually(Equals(False)))
281
282 # verify and interact with the triggered snap-decision notification
283 notify_list = self._get_notifications_list()
284 get_notification = lambda: notify_list.wait_select_single(
285 'Notification', objectName='notification0')
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"))
291 self.assert_notification_action_id_was_called("action_accept")
292
294 self,
295 summary="",
296 body="",
297 icon=None,
298 urgency="NORMAL",
299 actions=[],
300 hints=[]
301 ):
302 """Create a interactive notification command.
303
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',
308 'NORMAL', 'CRITICAL'
309 :param actions: List of tuples containing the 'id' and 'label' for all
310 the actions to add
311 :param hint_strings: List of tuples containing the 'name' and value for
312 setting the hint strings for the notification
313
314 """
315
316 logger.info(
317 "Creating snap-decision notification with summary(%s), body(%s) "
318 "and urgency(%r)",
319 summary,
320 body,
321 urgency
322 )
323
324 script_args = [
325 '--summary', summary,
326 '--body', body,
327 '--urgency', urgency
328 ]
329
330 if icon is not None:
331 script_args.extend(['--icon', icon])
332
333 for hint in hints:
334 key, value = hint
335 script_args.extend(['--hint', "%s,%s" % (key, value)])
336
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])
341
342 python_bin = subprocess.check_output(['which', 'python3']).strip()
343 command = [python_bin, self._get_notify_script()] + script_args
344 logger.info("Launching snap-decision notification as: %s", command)
345 self._notify_proc = subprocess.Popen(
346 command,
347 stdin=subprocess.PIPE,
348 stdout=subprocess.PIPE,
349 stderr=subprocess.PIPE,
350 close_fds=True,
351 universal_newlines=True,
352 )
353
355
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)
360
362 """Returns the path to the interactive notification
363 creation script.
364 """
365 file_path = "../../create_interactive_notification.py"
366
367 the_path = os.path.abspath(
368 os.path.join(__file__, file_path))
369
370 return the_path
371
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)
376
377 def assert_notification_action_id_was_called(self, action_id, timeout=10):
378 """Assert that the interactive notification callback of id *action_id*
379 was called.
380
381 :raises AssertionError: If no interactive notification has actually
382 been created.
383 :raises AssertionError: When *action_id* does not match the actual
384 returned.
385 :raises AssertionError: If no callback was called at all.
386 """
387
388 if self._notify_proc is None:
389 raise AssertionError("No interactive notification was created.")
390
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)
400 )
401 else:
402 return
403 time.sleep(1)
404
405 os.killpg(self._notify_proc.pid, signal.SIGTERM)
406 self._notify_proc = None
407 raise AssertionError(
408 "No callback was called, killing interactivenotification script"
409 )
410
411
413 """Collection of tests for Emphemeral notifications (non-interactive.)"""
414
415 def setUp(self):
416 super().setUp()
417 # Because we are using the Notify library we need to init and un-init
418 # otherwise we get crashes.
419 Notify.init("Autopilot Ephemeral Notification Tests")
420 self.addCleanup(Notify.uninit)
421
423 """Notification must display the expected summary and body text."""
424 self.launch_lomiri()
425 unlock_lomiri()
426
427 notify_list = self._get_notifications_list()
428
429 summary = "Icon-Summary-Body"
430 body = "Hey pal, what's up with the party next weekend? Will you " \
431 "join me and Anna?"
432 icon_path = self._get_icon_path('avatars/anna_olsson.png')
433 hints = [
434 ("x-lomiri-secondary-icon", "message")
435 ]
436
437 notification = shell.create_ephemeral_notification(
438 summary,
439 body,
440 icon_path,
441 hints,
442 "NORMAL",
443 )
444
445 notification.show()
446
447 notification = lambda: notify_list.wait_select_single(
448 'Notification', objectName='notification0')
450 notification(),
451 summary,
452 body,
453 True,
454 True,
455 1.0,
456 )
457
459 """Notification must display the expected summary and secondary
460 icon."""
461 self.launch_lomiri()
462 unlock_lomiri()
463
464 notify_list = self._get_notifications_list()
465
466 summary = "Upload of image completed"
467 icon_path = self._get_icon_path('applicationIcons/facebook.png')
468 hints = []
469
470 notification = shell.create_ephemeral_notification(
471 summary,
472 None,
473 icon_path,
474 hints,
475 "NORMAL",
476 )
477
478 notification.show()
479
480 notification = lambda: notify_list.wait_select_single(
481 'Notification', objectName='notification0')
483 notification(),
484 summary,
485 None,
486 True,
487 False,
488 1.0
489 )
490
492 """Notifications must be displayed in order according to their
493 urgency."""
494 self.launch_lomiri()
495 unlock_lomiri()
496
497 notify_list = self._get_notifications_list()
498
499 summary_low = 'Low Urgency'
500 body_low = "No, I'd rather see paint dry, pal *yawn*"
501 icon_path_low = self._get_icon_path('avatars/amanda.png')
502
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?"
506 icon_path_normal = self._get_icon_path('avatars/funky.png')
507
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')
511
512 notification_normal = shell.create_ephemeral_notification(
513 summary_normal,
514 body_normal,
515 icon_path_normal,
516 urgency="NORMAL"
517 )
518 notification_normal.show()
519
520 notification_low = shell.create_ephemeral_notification(
521 summary_low,
522 body_low,
523 icon_path_low,
524 urgency="LOW"
525 )
526 notification_low.show()
527
528 notification_critical = shell.create_ephemeral_notification(
529 summary_critical,
530 body_critical,
531 icon_path_critical,
532 urgency="CRITICAL"
533 )
534 notification_critical.show()
535
536 get_notification = lambda: notify_list.wait_select_single(
537 'Notification',
538 summary=summary_critical
539 )
540
541 notification = get_notification()
543 notification,
544 summary_critical,
545 body_critical,
546 True,
547 False,
548 1.0
549 )
550
551 get_normal_notification = lambda: notify_list.wait_select_single(
552 'Notification',
553 summary=summary_normal
554 )
555 notification = get_normal_notification()
557 notification,
558 summary_normal,
559 body_normal,
560 True,
561 False,
562 1.0
563 )
564
565 get_low_notification = lambda: notify_list.wait_select_single(
566 'Notification',
567 summary=summary_low
568 )
569 notification = get_low_notification()
571 notification,
572 summary_low,
573 body_low,
574 True,
575 False,
576 1.0
577 )
578
580 """Notification must display the expected summary- and body-text."""
581 self.launch_lomiri()
582 unlock_lomiri()
583
584 notify_list = self._get_notifications_list()
585
586 summary = 'Summary-Body'
587 body = 'This is a superfluous notification'
588
589 notification = shell.create_ephemeral_notification(summary, body)
590 notification.show()
591
592 notification = notify_list.wait_select_single(
593 'Notification', objectName='notification0')
594
596 notification,
597 summary,
598 body,
599 False,
600 False,
601 1.0
602 )
603
605 """Notification must display only the expected summary-text."""
606 self.launch_lomiri()
607 unlock_lomiri()
608
609 notify_list = self._get_notifications_list()
610
611 summary = 'Summary-Only'
612
613 notification = shell.create_ephemeral_notification(summary)
614 notification.show()
615
616 notification = notify_list.wait_select_single(
617 'Notification', objectName='notification0')
618
619 self._assert_notification(notification, summary, '', False, False, 1.0)
620
622 """Notification must allow updating its contents while being
623 displayed."""
624 self.launch_lomiri()
625 unlock_lomiri()
626
627 notify_list = self._get_notifications_list()
628
629 summary = 'Initial notification'
630 body = 'This is the original content of this notification-bubble.'
631 icon_path = self._get_icon_path('avatars/funky.png')
632
633 notification = shell.create_ephemeral_notification(
634 summary,
635 body,
636 icon_path
637 )
638 notification.show()
639
640 get_notification = lambda: notify_list.wait_select_single(
641 'Notification', summary=summary)
643 get_notification(),
644 summary,
645 body,
646 True,
647 False,
648 1.0
649 )
650
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.'
654 icon_path = self._get_icon_path('avatars/amanda.png')
655 notification.update(summary, body, icon_path)
656 notification.show()
658 get_notification(), summary, body, True, False, 1.0)
659
661 """Notification must allow updating its contents and layout while
662 being displayed."""
663 self.launch_lomiri()
664 unlock_lomiri()
665
666 notify_list = self._get_notifications_list()
667
668 summary = 'Initial layout'
669 body = 'This bubble uses the icon-title-body layout with a ' \
670 'secondary icon.'
671 icon_path = self._get_icon_path('avatars/anna_olsson.png')
672 hint_icon = 'dialer'
673
674 notification = shell.create_ephemeral_notification(
675 summary,
676 body,
677 icon_path
678 )
679 notification.set_hint_string(
680 'x-lomiri-secondary-icon',
681 hint_icon
682 )
683 notification.show()
684
685 get_notification = lambda: notify_list.wait_select_single(
686 'Notification', objectName='notification0')
687
689 get_notification(),
690 summary,
691 body,
692 True,
693 True,
694 1.0
695 )
696
697 notification.clear_hints()
698 summary = 'Updated layout'
699 body = 'After the update we now have a bubble using the title-body ' \
700 'layout.'
701 notification.update(summary, body, None)
702 notification.show()
703
704 self.assertThat(get_notification, Eventually(NotEquals(None)))
706 get_notification(), summary, body, False, False, 1.0)
707
709 """ use the create notification script to get a notification dialog.
710 Check that the arguments passed to the script match the fields. """
711
712 self.launch_lomiri()
713 unlock_lomiri()
714
715 summary = 'Helper summary'
716 body = 'Helper body'
717
718 notification = shell.create_ephemeral_notification(summary, body)
719 notification.show()
720
721 notification_data = self.main_window.wait_for_notification()
722
723 self.assertThat(notification_data['summary'],
724 Eventually(Equals(summary)))
725 self.assertThat(notification_data['body'], Eventually(Equals(body)))
launch_lomiri(self, mode="full-greeter", *args)
Definition __init__.py:183
_create_interactive_notification(self, summary="", body="", icon=None, urgency="NORMAL", actions=[], hints=[])
_assert_notification(self, notification, summary=None, body=None, icon=True, secondary_icon=False, opacity=None)