Merge lp:~adam-disc0tech/ubuntu-autopilot-tests/rhythmbox into lp:ubuntu-autopilot-tests

Proposed by Adam Smith
Status: Merged
Approved by: Dan Chapman 
Approved revision: 111
Merged at revision: 66
Proposed branch: lp:~adam-disc0tech/ubuntu-autopilot-tests/rhythmbox
Merge into: lp:ubuntu-autopilot-tests
Diff against target: 1138 lines (+1093/-0)
6 files modified
ubuntu_autopilot_tests/rhythmbox/emulators/gtk.py (+437/-0)
ubuntu_autopilot_tests/rhythmbox/resources/playlists.xml (+34/-0)
ubuntu_autopilot_tests/rhythmbox/resources/rhythmdb.xml (+177/-0)
ubuntu_autopilot_tests/rhythmbox/tests/base.py (+247/-0)
ubuntu_autopilot_tests/rhythmbox/tests/test_import_and_play.py (+107/-0)
ubuntu_autopilot_tests/rhythmbox/tests/test_rbox.py (+91/-0)
To merge this branch: bzr merge lp:~adam-disc0tech/ubuntu-autopilot-tests/rhythmbox
Reviewer Review Type Date Requested Status
Dan Chapman  (community) Approve
Nicholas Skaggs (community) Approve
Review via email: mp+206494@code.launchpad.net

Description of the change

First release of tests for Rhythmbox, including launch test, launching help, playing internet radio, playing WAV and OGG files, play / pause / skip track buttons

To post a comment you must log in.
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

I'll be away next week and won't have proper time to give this a good review. However, it looks like you are missing the resources with this commit :-)

review: Needs Fixing
Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

Once that's fixed I'll give it a run again. A couple quick comments; we might need to change how you are deriving the home directory, jenkins may not like it. Did the emulator for gtk work out ok?

68. By Adam Smith

Added resources

Revision history for this message
Adam Smith (adam-disc0tech) wrote :

> I'll be away next week and won't have proper time to give this a good review.
> However, it looks like you are missing the resources with this commit :-)

Resources added!

Revision history for this message
Adam Smith (adam-disc0tech) wrote :

> Once that's fixed I'll give it a run again. A couple quick comments; we might
> need to change how you are deriving the home directory, jenkins may not like
> it. Did the emulator for gtk work out ok?

Sure, should I use an absolute path running under jenkins?

The emulator - required significant hacking... Mostly because I couldn't rely on pressing HOME to get to the start of a drop down - as that would select my home directory and rhythmbox would immediately start importing everything from there.

Revision history for this message
Dan Chapman  (dpniel) wrote :

I did think the combobox emulator would need a better solution. I ripped those emulators out of ubiquity tests which it works for it's purpose there. This seems to be the tricky part with gtk emulators as it ultimately depends on the developers implementation choices. I will give your modifications a go on the ubiquity tests and a few other apps and see if it can be used as a more general purpose solution rather than just wacking 'Home'.

Revision history for this message
Adam Smith (adam-disc0tech) wrote :

OK - but note that it is very hacky emulator code... needs revisiting.

A couple of key changes I made were to ignore items with an accessible role of 49 (invisible separators), and to check which item is focussed (unreliable).

I also disabled some of the code that checks the item by name rather than by index. With hindsight, the reasons I disabled it may be invalid now I have implemented the above.

Revision history for this message
Dan Chapman  (dpniel) wrote :

This test is looking good, there are a couple of minor errors that are a quick fix.

see http://people.ubuntu.com/~dpniel/docs/rhythmbox.xml

The first is just a case of cleaning up tabs n spaces. As a side note tests ideally should be python3 compliant so I would recommend using that going forward as it will be used when running tests on Jenkins. it's just a case of using the python3-autopilot package and autopilot3 run/list etc

The Second is just a case of fixing imports in general all testsuites are usually run from the ubuntu-autopilot-tests/ubuntu_autopilot_tests/* directory. So 'autopilot3 run rhythmbox' would be the general start of the testsuite

Also to expand on what Nicholas said about deriving the home directory i personally use os.getenv("HOME") to get the current users home.

If you could make these little fixes first then I will give them a good run :-)

Great job Adam

review: Needs Fixing
69. By Adam Smith

Fixes following review

70. By Adam Smith

corrected import statement

71. By Adam Smith

Fixed tabulation issues

72. By Adam Smith

Added init file

73. By Adam Smith

fixed import error

74. By Adam Smith

Fixed properly this time :)

75. By Adam Smith

Fixed path errors to handle remote directory structure for jenkins

76. By Adam Smith

...

77. By Adam Smith

Now checks for existence of Rhythmbox config dir and creates if necessary

78. By Adam Smith

referenced test_dir correctly

79. By Adam Smith

Changed filter() to enumeration as failing with py3

80. By Adam Smith

Split out base class in new structure

81. By Adam Smith

Added main_window as a property

82. By Adam Smith

Adjusted properties

83. By Adam Smith

Modified to work with python 3, refactored. Combo box is now not working in local test environment - pushing for Test Runner test

84. By Adam Smith

Changed assertion type in emulator

85. By Adam Smith

Removed assertion in emulator

86. By Adam Smith

Fixed bug where combo box goes out of range if last item is a separator

87. By Adam Smith

Additional assertions added

88. By Adam Smith

Corrected max_index naming error

89. By Adam Smith

Additional debug statement to investigate out of range issue

90. By Adam Smith

fix

91. By Adam Smith

fix

92. By Adam Smith

Refactored GTK emulator. Changed fallback mode to first assume gail box will be returned in display order, if that fails use combo accessible name to fall back.

Changed import tests to use Pictures directory rather than Downloads, to support local testing.

93. By Adam Smith

Stable on local

94. By Adam Smith

Moved library parser to use xpath directly

95. By Adam Smith

Works locally for single import test

96. By Adam Smith

hacks

97. By Adam Smith

Added additional debug logging around expected track names

98. By Adam Smith

Reversed the order of track play to match that on live image

99. By Adam Smith

Performance improvement, only check the treeview tracklist once

100. By Adam Smith

Tested all positive import scenarios and work locally. Enabled all tests

Revision history for this message
Adam Smith (adam-disc0tech) wrote :

Fixed issues - now runs fine in test-runner

101. By Adam Smith

Fixed loggin convention
Refactored variable scope
Removed hardcoded resource paths

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

First of all, this looks like a really solid base. Lots of cool stuff being explored here; umockdev, emulator tweaks. Kudos to you Adam!

My one comment would be the function names. I would rather we dropped the given, when, and then from the function names and used comments and logger debug messages instead to achieve a similar thing. Going forward, we're attempting to adopt a new model, based upon the page-object model -- all objects are classes. This tends to make the tests more readable in a similar way without having to name utility functions in such a way.

Overall, good work. I support merging this with the naming tweaks I mentioned.

Ohh, one other thing -- this might also be a good candidate to mock the /home env for as well to avoid colliding with user's data.

review: Needs Fixing
Revision history for this message
Dan Chapman  (dpniel) wrote :

Nice job Adam :-)

I agree with what Nicholas mentions above but, i think those kind of refactoring changes could be incorporated into you current working branch lp:~adam-disc0tech/ubuntu-autopilot-tests/rhythmbox2.

The only issue I would want resolved before this merges is in combobox.select_item it needs some kind of condition to fail the test if we didn't manage to select it in 3-4 attempts. A couple of times the combobox lost focus and it ended up in a loop trying to select without focus and needed to keyboard interrupt. Also ensure we still have focus between attempts. Relying on keyboard input is not ideal so it does become quite tricky with comboboxes.

Also the select_item looks like it has become a little over complex so i think we could refactor that as well at some point.

All in all though I think it's great, and once the combox selection has changed I happy for this to merge

review: Needs Fixing
Revision history for this message
Adam Smith (adam-disc0tech) wrote :

OK thanks for the feedback everyone.

So in this branch I will fix the combox/select item issues. For the second branch mentioned above I'll (1) move to a page object model (2) add additional comments / logging (3) look at mocking the home directory

I also need to remove umockdev from this branch, as I'm actually not using it yet...

102. By Adam Smith

Removed unnecessary resources

103. By Adam Smith

Updated the emulator to do extra checks around whether focus is held, and ensure it does not enter an endless loop

104. By Adam Smith

Included minor changes from second branch

105. By Adam Smith

Spelling in comment corrected

106. By Adam Smith

Change to print tree on failure to select combo box

107. By Adam Smith

Added another exception handling situation that generates a print_tree

108. By Adam Smith

Change print tree to look at root object

109. By Adam Smith

Added some attempts to regain focus where necessary

110. By Adam Smith

fixed tab issue

111. By Adam Smith

Added exceptions and stack trace logging

Revision history for this message
Nicholas Skaggs (nskaggs) wrote :

I believe the other work mentioned above is slated for refactoring, so +1 from me.

review: Approve
Revision history for this message
Dan Chapman  (dpniel) wrote :

This is good to merge now since all other changes are being made in the new branch

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'ubuntu_autopilot_tests/rhythmbox'
2=== added file 'ubuntu_autopilot_tests/rhythmbox/__init__.py'
3=== added directory 'ubuntu_autopilot_tests/rhythmbox/emulators'
4=== added file 'ubuntu_autopilot_tests/rhythmbox/emulators/__init__.py'
5=== added file 'ubuntu_autopilot_tests/rhythmbox/emulators/gtk.py'
6--- ubuntu_autopilot_tests/rhythmbox/emulators/gtk.py 1970-01-01 00:00:00 +0000
7+++ ubuntu_autopilot_tests/rhythmbox/emulators/gtk.py 2014-03-05 19:06:22 +0000
8@@ -0,0 +1,437 @@
9+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
10+#
11+# Copyright (C) Canonical Ltd 2013
12+#
13+# Author: Daniel Chapman daniel@chapman-mail.com
14+# Modified by: Adam Smith adam@disc0tech.com
15+#
16+# This program is free software; you can redistribute it and/or modify
17+# it under the terms of the GNU Lesser General Public License as published by
18+# the Free Software Foundation; version 3.
19+#
20+# This program is distributed in the hope that it will be useful,
21+# but WITHOUT ANY WARRANTY; without even the implied warranty of
22+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+# GNU Lesser General Public License for more details.
24+#
25+# You should have received a copy of the GNU Lesser General Public License
26+# along with this program. If not, see <http://www.gnu.org/licenses/>.
27+import logging, time
28+import os, inspect
29+from autopilot.input import Pointer, Mouse, Keyboard
30+from autopilot.introspection.dbus import StateNotFoundError
31+from autopilot.introspection import CustomEmulatorBase
32+from autopilot.testcase import AutopilotTestCase
33+from testtools.matchers import Equals, NotEquals, GreaterThan, FileExists, StartsWith, Not
34+from autopilot.matchers import Eventually
35+from autopilot.testcase import AutopilotTestCase
36+from testtools.matchers import Equals, NotEquals, GreaterThan, FileExists, StartsWith, Not
37+
38+logger = logging.getLogger('__name__')
39+
40+
41+class AutopilotGtkEmulatorBase(CustomEmulatorBase):
42+ """ Base class for the Gtk Autopilot Emulator """
43+
44+
45+class GtkTreeView(AutopilotGtkEmulatorBase):
46+ """ Emulator for a GtkTreeView instance """
47+
48+ def __init__(self, *args):
49+ super(GtkTreeView, self).__init__(*args)
50+ self.pointing_device = Pointer(Mouse.create())
51+
52+ def select_item(self, labelText):
53+ """ Selects an item in a GtkTreeView by its UI label text
54+
55+ :param labelText: The label value of the tree item as seen on the UI
56+ :returns: An object of the requested treeitem
57+
58+ e.g. If you want to click say an item displaying 'Home' in a treeview
59+ then it would be::
60+
61+ >>> treeitem = treeview.select_item('Home')
62+ >>> self.mouse.click_object(treeitem)
63+
64+ """
65+ logger.debug('Selecting "{0}" item'.format(labelText))
66+ try:
67+ #lets try and get the corresponding GailTreeView
68+ # so we can assure we are selecting the item from the correct
69+ # treeview
70+ treeview = self._get_gail_treeview()
71+ treeview_item = treeview.select_item(str(labelText))
72+ except StateNotFoundError:
73+ #lets catch the exception and have one last go at selecting the
74+ # item from a root instance
75+ # This may return more than one though.
76+ logger.warning('Could not get corresponding '
77+ 'GtkTreeViewAccessibleObject \
78+ with globalRect {0}. Trying to select GtkTreeView '
79+ 'item \
80+ from root object instead'.format(self.globalRect))
81+ treeview_item = self._select_item_from_root(labelText)
82+
83+ logger.debug('Corresponding Item Found in treeview. Returning item.')
84+ return treeview_item
85+
86+ def select_item_by_index(self, index):
87+ """ Selects an item in a GtkTreeVIew by its index
88+
89+ :param index: Index of the wanted tree view item
90+ :returns: Object of the tree view item at the given index
91+ """
92+ treeview = self._get_gail_treeview()
93+ treeview_item = treeview.select_item_by_index(index)
94+ if treeview_item is None:
95+ raise ValueError(
96+ "Could not select item with index '{0}'".format(index))
97+ return treeview_item
98+
99+
100+ def get_all_items(self, startWith=None):
101+ """ Gets all items in GtkTreeView
102+
103+ :param startWith: gets a list of items that start with given string
104+ :returns: List of items in tree view
105+
106+ """
107+ treeview = self._get_gail_treeview()
108+ items = treeview.get_all_items(startWith)
109+ if items is None:
110+ raise ValueError("NoneType object: Could not get list of items")
111+ return items
112+
113+ def _select_item_from_root(self, labelText):
114+ root = self.get_root_instance()
115+
116+ treeview_item = root.select_single('GtkTextCellAccessible',
117+ accessible_name=str(labelText))
118+
119+ if treeview_item is None:
120+ raise ValueError(
121+ "Could not select item with label '{0}'".format(labelText))
122+
123+ return treeview_item
124+
125+ def _get_gail_treeview(self, ):
126+ """ Gets the GtkTreeViews corresponding GtkTreeViewAccessible object
127+ """
128+ logger.debug('Getting corresponding GtkTreeViewAccessible object')
129+ # lets get a root instance
130+ root = self.get_root_instance()
131+ assert root is not None
132+ # As the treeview item is in the GAILWindow tree and not our current
133+ # tree
134+ # We want to select the treeviewaccessible with the same globalRect
135+ # as us
136+ logger.debug('Selecting GtkTreeViewAccessible with same globalRect')
137+ treeviews = root.select_many('GtkTreeViewAccessible',
138+ globalRect=self.globalRect)
139+ # if the treeviews are nested they could potentially have the
140+ # same globalRect so lets pick out the one thats visible
141+ for treeview in treeviews:
142+ if treeview.visible:
143+ logger.debug(
144+ 'GtkTreeViewAccessible object found, returning object.')
145+ return treeview
146+ raise ValueError(
147+ "No treeview visible with globalRect {0}".format(self.globalRect)
148+ )
149+
150+
151+class GtkComboBox(AutopilotGtkEmulatorBase):
152+ """ Emulator class for a GtkComboBox instance"""
153+ last_index_selected = -1
154+ lack_of_focus_events = 0
155+ lack_of_focus_max = 5
156+ skip_items = []
157+
158+ def set_last_label_selected(self, label):
159+ combo = self._get_gail_combobox()
160+ items = combo.select_many('GtkMenuItemAccessible')
161+ n = 0
162+ for i in items:
163+ if(i.accessible_name==label):
164+ self.last_index_selected = n
165+ n+=1
166+
167+
168+ def __init__(self, *args):
169+ super(GtkComboBox, self).__init__(*args)
170+ self.pointing_device = Pointer(Mouse.create())
171+ self.kbd = Keyboard.create()
172+
173+ def select_item(self, labelText):
174+ """ Selects an item in a GtkComboBox by its UI label text value
175+
176+ :param labelText: The label value of the list item as seen on the UI
177+
178+ e.g. ::
179+
180+ >>> combobox = self.main_window.select_single('GtkComboBox')
181+ >>> combobox.select_item('ListItem')
182+
183+ """
184+ logger.debug('Selecting "{0}" item'.format(labelText))
185+ #get our gail combo to start
186+ combo = self._get_gail_combobox()
187+ # get total number of items in the combo
188+ items = combo.select_many('GtkMenuItemAccessible')
189+ # lets check that the item is in the list before cycling through
190+ names = []
191+ for i in items:
192+ names.append(i.accessible_name)
193+ if labelText not in names:
194+ raise ValueError(
195+ "Could not find '{0}' in combobox items".format(labelText))
196+
197+ logger.debug("Clicking on the combobox")
198+ self.pointing_device.click_object(self)
199+ logger.debug("Done")
200+ self.kbd.press_and_release('Enter')
201+
202+ #find the focused item, if you can't assume based on the combo accessible name
203+ focused_index = self._find_item_with_focus(items)
204+ if focused_index == -1:
205+ focused_index = next((index for index, value in enumerate(items) if
206+ combo.accessible_name == value.accessible_name), None)
207+ logger.debug('Assuming focus is with: ' + combo.accessible_name + ' and that is at index ' + str(focused_index))
208+
209+ x = focused_index
210+ max_index = len(items)
211+ logger.debug("index has max items: " + str(max_index))
212+ #add a counter to break out and end the test if 100 keypresses does not get us to the right place
213+ count = 0
214+ max_count = 100
215+
216+ while True:
217+ if len(self.skip_items) > 0:
218+ while x in self.skip_items:
219+ logger.debug("Skipping item: " + str(x))
220+ x = self.__get_next_pos(x, max_index)
221+
222+ logger.debug('Inspecting item ' + str(x))
223+ item = items[x]
224+ logger.debug("current index = " + str(x) + " ["+item.accessible_name +"] ")
225+
226+ if labelText == item.accessible_name:
227+ logger.debug("Found MATCHING labels in combobox: labelText="+labelText+", item.accessible_name="+item.accessible_name)
228+ self.kbd.press_and_release('Enter')
229+ self.set_last_label_selected(item.accessible_name)
230+ logger.debug('Item is now selected, combo.accessible is = ' + combo.accessible_name)
231+
232+
233+ #if the displayed item is not as expected
234+ if not combo.accessible_name == labelText:
235+ while True:
236+ logger.debug('Fallback mode')
237+ x = self.__move_to_next(x, max_index)
238+ self.kbd.press_and_release('Enter')
239+ time.sleep(1)
240+ self.set_last_label_selected(item.accessible_name)
241+ logger.debug('New item now selected, combo.accessible is = ' + combo.accessible_name)
242+ if combo.accessible_name == labelText:
243+ logger.debug('Combo.accessible matches')
244+ logger.debug('Combo box selection complete')
245+ return
246+ else:
247+ logger.debug('Combo box selection complete')
248+ time.sleep(1)
249+ return
250+
251+ else:
252+ #this section ensures we do not get stuck in an endless loop
253+ count += 1
254+ if count > max_count:
255+ logger.error('Reached max_count on keypresses to select the right combo box item')
256+ raise Exception("Max number of keypresses in a GtkComboBox exceeded")
257+
258+ x = self.__move_to_next(x, max_index)
259+
260+ def __move_to_next(self, x, max_index):
261+ #check that we have focus then press down key
262+ if self._is_an_item_focussed() == False:
263+ self.lack_of_focus_events += 1
264+ logger.error('Trying to manipulate a combox box which does not have focus, count: '+ str(self.lack_of_focus_events))
265+ if self.lack_of_focus_events > self.lack_of_focus_max:
266+ raise Exception("Lack of focus on the object under test events exceeded maximium")
267+ #if more than one event, this is probably not due to a seperator, try some other things
268+ if self.lack_of_focus_events > 1:
269+ self.pointing_device.click_object(self)
270+ if self._is_an_item_focussed() == False:
271+ self.kbd.press_and_release('Escape')
272+
273+ #move to next item
274+ self.kbd.press_and_release('Down')
275+ return self.__get_next_pos(x, max_index)
276+
277+ def __get_next_pos(self, x, max_index):
278+ logger.debug('Getting next combo pos, x = ' + str(x))
279+ if x==(max_index-1):
280+ x = 0
281+ logger.debug('Going to the start of the list')
282+ else:
283+ x += 1
284+ assert x is not None
285+ assert x < max_index
286+ return x
287+
288+
289+ def _is_an_item_focussed(self, ):
290+ #return true / false depending on whether one of the items holds focus
291+ combo = self._get_gail_combobox()
292+ items = combo.select_many('GtkMenuItemAccessible')
293+ for i in items:
294+ if i.focused:
295+ return True
296+ return False
297+
298+
299+ def _find_item_with_focus(self, items):
300+ logger.debug('Finding combo box focus')
301+
302+ #determine currently selected item, and skip items with a blank label
303+ focused_index = -1
304+ if self.last_index_selected > -1:
305+ focused_index = self.last_index_selected
306+ logger.debug('Last item selected was ' + str(focused_index))
307+ return focused_index
308+
309+ n = 0
310+ for i in items:
311+ logger.debug("----------------")
312+ logger.debug("Index: " + str(n))
313+ logger.debug("Accessible Name: " + i.accessible_name)
314+ logger.debug(" Accessible Role: " + str(i.accessible_role))
315+ logger.debug(" Showing: " + str(i.showing))
316+ logger.debug(" Selected: " + str(i.selected))
317+ logger.debug(" Enabled: " + str(i.enabled))
318+ logger.debug(" Visible: " + str(i.visible))
319+ logger.debug(" Focused: " + str(i.focused))
320+ logger.debug(" Pressed: " + str(i.pressed))
321+ logger.debug(" Active: " + str(i.active))
322+
323+ if(self.last_index_selected < 0 and i.focused):
324+ focused_index = n
325+ logger.debug('Found item ' + i.accessible_name + ' which holds the focus')
326+
327+ if(i.accessible_role == 49):
328+ self.skip_items.append(n)
329+ logger.debug('Adding to skip list index: ' + str(n) + ' [' + i.accessible_name + ']')
330+
331+ n+=1
332+
333+ logger.debug('Focus on: ' + str(focused_index))
334+ logger.debug(str(len(self.skip_items)) + ' items are separators and will be skipped')
335+ return focused_index
336+
337+ def select_item_by_index(self, index):
338+ """ Selects an item by given index"""
339+ combo = self._get_gail_combobox()
340+ # get total number of items in the combo
341+ items = combo.select_many('GtkMenuItemAccessible')
342+
343+ focused_index = self._find_item_with_focus(items)
344+ i = focused_index
345+ max_index = len(items)-1
346+
347+ logger.debug("index has max items: " + str(max_index))
348+
349+ while True:
350+ if i in self.skip_items:
351+ logger.debug("Skipping item: " + str(i))
352+ i+=1
353+
354+ logger.debug("current index = " + str(i) + " ["+items[i].accessible_name +"] "+ "target = " + str(index))
355+ if i != index:
356+ i = self.__move_to_next(i, max_index)
357+ else:
358+ assert i == index
359+ logger.debug("Found correct item, hitting ENTER")
360+ self.kbd.press_and_release('Enter')
361+ self.last_index_selected = i
362+ break
363+
364+ def _get_gail_combobox(self, ):
365+ """ Gets the GtkComBox's corresponding GtkComboBoxAccessible object """
366+ # lets get a root instance
367+ logger.debug('Getting corresponding GtkComboBoxAccessible object')
368+ root = self.get_root_instance()
369+ assert root is not None
370+ combos = root.select_many('GtkComboBoxAccessible',
371+ globalRect=self.globalRect)
372+ for combo in combos:
373+ if combo.visible:
374+ logger.debug('PICTURES found, returning combo')
375+ return combo
376+ raise ValueError(
377+ "No ComboBox visible with globalRect {0}".format(self.globalRect)
378+ )
379+
380+
381+class GtkComboBoxText(GtkComboBox):
382+ """ Emulator class for a GtkComboBoxText instance
383+
384+ .. note:: `GtkComboBoxText` inherits from :func: `GtkComboBox`
385+
386+ """
387+
388+ def __init__(self, *args):
389+ super(GtkComboBoxText, self).__init__(*args)
390+ self.pointing_device = Pointer(Mouse.create())
391+ self.kbd = Keyboard.create()
392+
393+
394+class GtkTreeViewAccessible(AutopilotGtkEmulatorBase):
395+
396+ def __init__(self, *args):
397+ super(GtkTreeViewAccessible, self).__init__(*args)
398+ self.pointing_device = Pointer(Mouse.create())
399+
400+ def select_item(self, label):
401+ """ Selects an item in a GtkTreeViewAccessible object """
402+ logger.debug('Selecting {0} item'.format(label))
403+ item = self._get_item(label)
404+ if item is None:
405+ raise ValueError("Could not select item with label {0}".format(
406+ label))
407+ logger.debug('{0} item found, returning item'.format(label))
408+ return item
409+
410+ def select_item_by_index(self, index):
411+ """ Select an item by its index """
412+ return self._get_treeview_items()[index]
413+
414+ def get_all_items(self, startWith=None):
415+ """ Gets all items in GtkTreeView
416+
417+ :param startWith: gets a list of items that start with given string
418+ :rtype: List of items in treeview
419+
420+ """
421+ item_list = []
422+ items = self._get_treeview_items()
423+ if startWith:
424+ logger.debug(
425+ "Searching for items beginning with '{0}'".format(startWith))
426+ for item in items:
427+ if startWith in item.accessible_name[0:len(startWith)+1]:
428+ item_list.append(item)
429+ return item_list
430+ else:
431+ return items
432+
433+ def _get_treeview_items(self, ):
434+ logger.debug('Getting list of items.....')
435+ items = self.select_many('GtkTextCellAccessible')
436+ if items is None:
437+ raise ValueError(
438+ "Could not get a list of treeview items"
439+ )
440+ return items
441+
442+ def _get_item(self, label):
443+ """ Gets an item in a GtkTreeView """
444+ return self.select_many('GtkTextCellAccessible',
445+ accessible_name=label)[0]
446
447=== added directory 'ubuntu_autopilot_tests/rhythmbox/resources'
448=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/002.ogg'
449Binary files ubuntu_autopilot_tests/rhythmbox/resources/002.ogg 1970-01-01 00:00:00 +0000 and ubuntu_autopilot_tests/rhythmbox/resources/002.ogg 2014-03-05 19:06:22 +0000 differ
450=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/092.ogg'
451Binary files ubuntu_autopilot_tests/rhythmbox/resources/092.ogg 1970-01-01 00:00:00 +0000 and ubuntu_autopilot_tests/rhythmbox/resources/092.ogg 2014-03-05 19:06:22 +0000 differ
452=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/playlists.xml'
453--- ubuntu_autopilot_tests/rhythmbox/resources/playlists.xml 1970-01-01 00:00:00 +0000
454+++ ubuntu_autopilot_tests/rhythmbox/resources/playlists.xml 2014-03-05 19:06:22 +0000
455@@ -0,0 +1,34 @@
456+<?xml version="1.0"?>
457+<rhythmdb-playlists>
458+ <playlist name="My Top Rated" type="automatic" sort-key="Rating" sort-direction="1">
459+ <conjunction>
460+ <equals prop="type">song</equals>
461+ <subquery>
462+ <conjunction>
463+ <greater prop="rating">4.000000</greater>
464+ </conjunction>
465+ </subquery>
466+ </conjunction>
467+ </playlist>
468+ <playlist name="Recently Added" type="automatic" sort-key="FirstSeen" sort-direction="1">
469+ <conjunction>
470+ <equals prop="type">song</equals>
471+ <subquery>
472+ <conjunction>
473+ <current-time-within prop="first-seen">604800</current-time-within>
474+ </conjunction>
475+ </subquery>
476+ </conjunction>
477+ </playlist>
478+ <playlist name="Recently Played" type="automatic" sort-key="LastPlayed" sort-direction="1">
479+ <conjunction>
480+ <equals prop="type">song</equals>
481+ <subquery>
482+ <conjunction>
483+ <current-time-within prop="last-played">604800</current-time-within>
484+ </conjunction>
485+ </subquery>
486+ </conjunction>
487+ </playlist>
488+ <playlist name="Play Queue" type="queue"/>
489+</rhythmdb-playlists>
490
491=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/podcast-timestamp'
492=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/rhythmdb.xml'
493--- ubuntu_autopilot_tests/rhythmbox/resources/rhythmdb.xml 1970-01-01 00:00:00 +0000
494+++ ubuntu_autopilot_tests/rhythmbox/resources/rhythmdb.xml 2014-03-05 19:06:22 +0000
495@@ -0,0 +1,177 @@
496+<?xml version="1.0" standalone="yes"?>
497+<rhythmdb version="1.8">
498+ <entry type="iradio">
499+ <title>WKNC 88.1 FM (NC State) (Low Quality)</title>
500+ <genre>Music</genre>
501+ <artist></artist>
502+ <album></album>
503+ <location>http://wknc.sma.ncsu.edu:8000/wkncmq.ogg.m3u</location>
504+ <date>0</date>
505+ <media-type>application/octet-stream</media-type>
506+ </entry>
507+ <entry type="iradio">
508+ <title>WKNC 88.1 FM (NC State) (High Quality)</title>
509+ <genre>Music</genre>
510+ <artist></artist>
511+ <album></album>
512+ <location>http://wknc.sma.ncsu.edu:8000/wknchq.ogg.m3u</location>
513+ <date>0</date>
514+ <media-type>application/octet-stream</media-type>
515+ </entry>
516+ <entry type="iradio">
517+ <title>Absolute Radio 80s (Modem)</title>
518+ <genre>80's</genre>
519+ <artist></artist>
520+ <album></album>
521+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=a8</location>
522+ <date>0</date>
523+ <media-type>application/octet-stream</media-type>
524+ </entry>
525+ <entry type="iradio">
526+ <title>Absolute Radio 80s (Broadband)</title>
527+ <genre>80's</genre>
528+ <artist></artist>
529+ <album></album>
530+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=a8bb</location>
531+ <date>0</date>
532+ <media-type>application/octet-stream</media-type>
533+ </entry>
534+ <entry type="iradio">
535+ <title>Absolute Classic Rock (Modem)</title>
536+ <genre>Rock'n'Roll</genre>
537+ <artist></artist>
538+ <album></album>
539+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=vc</location>
540+ <date>0</date>
541+ <media-type>application/octet-stream</media-type>
542+ </entry>
543+ <entry type="iradio">
544+ <title>Absolute Classic Rock (Broadband)</title>
545+ <genre>Rock'n'Roll</genre>
546+ <artist></artist>
547+ <album></album>
548+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=vcbb</location>
549+ <date>0</date>
550+ <media-type>application/octet-stream</media-type>
551+ </entry>
552+ <entry type="iradio">
553+ <title>Absolute Radio 90s (Broadband)</title>
554+ <genre>90's</genre>
555+ <artist></artist>
556+ <album></album>
557+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=a9bb</location>
558+ <date>0</date>
559+ <media-type>application/octet-stream</media-type>
560+ </entry>
561+ <entry type="iradio">
562+ <title>Absolute Radio 90s (Modem)</title>
563+ <genre>90's</genre>
564+ <artist></artist>
565+ <album></album>
566+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=a9</location>
567+ <date>0</date>
568+ <media-type>application/octet-stream</media-type>
569+ </entry>
570+ <entry type="iradio">
571+ <title>HBR1.com - Dream Factory</title>
572+ <genre>Ambient</genre>
573+ <artist></artist>
574+ <album></album>
575+ <location>http://ubuntu.hbr1.com:19800/ambient.ogg</location>
576+ <date>0</date>
577+ <media-type>application/octet-stream</media-type>
578+ </entry>
579+ <entry type="iradio">
580+ <title>StartFM - 94.2MHz, Vilnius University, Lithuania</title>
581+ <genre>Underground</genre>
582+ <artist></artist>
583+ <album></album>
584+ <location>http://eteris.startfm.lt/startfm.ogg</location>
585+ <date>0</date>
586+ <media-type>application/octet-stream</media-type>
587+ </entry>
588+ <entry type="iradio">
589+ <title>WSUM 91.7 FM (University of Wisconsin)</title>
590+ <genre>College Radio</genre>
591+ <artist></artist>
592+ <album></album>
593+ <location>http://vu.wsum.wisc.edu/wsumlive.m3u</location>
594+ <date>0</date>
595+ <media-type>application/octet-stream</media-type>
596+ </entry>
597+ <entry type="iradio">
598+ <title>Radio Paradise (Low Quality)</title>
599+ <genre>Eclectic</genre>
600+ <artist></artist>
601+ <album></album>
602+ <location>http://stream-sd.radioparadise.com:9000/rp_96.ogg</location>
603+ <date>0</date>
604+ <media-type>application/octet-stream</media-type>
605+ </entry>
606+ <entry type="iradio">
607+ <title>Radio Paradise (High Quality)</title>
608+ <genre>Eclectic</genre>
609+ <artist></artist>
610+ <album></album>
611+ <location>http://stream-tx1.radioparadise.com:9000/rp_192.ogg</location>
612+ <date>0</date>
613+ <media-type>application/octet-stream</media-type>
614+ </entry>
615+ <entry type="iradio">
616+ <title>KWUR 90.3 FM St. Louis Underground Radio</title>
617+ <genre>Unknown</genre>
618+ <artist></artist>
619+ <album></album>
620+ <location>http://www.kwur.com/icecast/kwurogg.m3u</location>
621+ <date>0</date>
622+ <media-type>application/octet-stream</media-type>
623+ </entry>
624+ <entry type="iradio">
625+ <title>wbur.org - Boston's NPR news source</title>
626+ <genre>News</genre>
627+ <artist></artist>
628+ <album></album>
629+ <location>http://audio.wbur.org/stream/live_ogg.m3u</location>
630+ <date>0</date>
631+ <media-type>application/octet-stream</media-type>
632+ </entry>
633+ <entry type="iradio">
634+ <title>Absolute Radio (Modem)</title>
635+ <genre>Pop</genre>
636+ <artist></artist>
637+ <album></album>
638+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=vr</location>
639+ <date>0</date>
640+ <media-type>application/octet-stream</media-type>
641+ </entry>
642+ <entry type="iradio">
643+ <title>Absolute Radio (Broadband)</title>
644+ <genre>Pop</genre>
645+ <artist></artist>
646+ <album></album>
647+ <location>http://network.absoluteradio.co.uk/core/audio/ogg/live.pls?service=vrbb</location>
648+ <date>0</date>
649+ <media-type>application/octet-stream</media-type>
650+ </entry>
651+ <entry type="iradio">
652+ <title>HBR1.com - I.D.M. Tranceponder</title>
653+ <genre>Trance</genre>
654+ <artist></artist>
655+ <album></album>
656+ <location>http://ubuntu.hbr1.com:19800/trance.ogg</location>
657+ <play-count>7</play-count>
658+ <last-played>1390659227</last-played>
659+ <bitrate>64</bitrate>
660+ <date>0</date>
661+ <media-type>application/octet-stream</media-type>
662+ </entry>
663+ <entry type="iradio">
664+ <title>HBR1.com - Tronic Lounge</title>
665+ <genre>House</genre>
666+ <artist></artist>
667+ <album></album>
668+ <location>http://ubuntu.hbr1.com:19800/tronic.ogg</location>
669+ <date>0</date>
670+ <media-type>application/octet-stream</media-type>
671+ </entry>
672+</rhythmdb>
673
674=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/trk1.wav'
675Binary files ubuntu_autopilot_tests/rhythmbox/resources/trk1.wav 1970-01-01 00:00:00 +0000 and ubuntu_autopilot_tests/rhythmbox/resources/trk1.wav 2014-03-05 19:06:22 +0000 differ
676=== added file 'ubuntu_autopilot_tests/rhythmbox/resources/trk2.wav'
677Binary files ubuntu_autopilot_tests/rhythmbox/resources/trk2.wav 1970-01-01 00:00:00 +0000 and ubuntu_autopilot_tests/rhythmbox/resources/trk2.wav 2014-03-05 19:06:22 +0000 differ
678=== added directory 'ubuntu_autopilot_tests/rhythmbox/tests'
679=== added file 'ubuntu_autopilot_tests/rhythmbox/tests/__init__.py'
680=== added file 'ubuntu_autopilot_tests/rhythmbox/tests/base.py'
681--- ubuntu_autopilot_tests/rhythmbox/tests/base.py 1970-01-01 00:00:00 +0000
682+++ ubuntu_autopilot_tests/rhythmbox/tests/base.py 2014-03-05 19:06:22 +0000
683@@ -0,0 +1,247 @@
684+from autopilot.testcase import AutopilotTestCase
685+from testtools.matchers import Equals, NotEquals, GreaterThan, FileExists, StartsWith, Not
686+from autopilot.matchers import Eventually
687+from autopilot.introspection import get_proxy_object_for_existing_process
688+import subprocess, re, getpass, time, logging, os, sys, shutil, locale, traceback
689+from rhythmbox.emulators import gtk
690+from collections import namedtuple
691+from autopilot.input import Keyboard, Mouse
692+
693+
694+logger = logging.getLogger('__name__')
695+logger.setLevel(10)
696+
697+class RhythmBoxTestCase(AutopilotTestCase):
698+
699+ def setUp(self, ):
700+ super(AutopilotTestCase, self).setUp()
701+ logger.debug('sys.path = ' + str(sys.path))
702+
703+ #walk the full file system to confirm where the resources directory is
704+ res_dir = self._get_path('trk1.wav')
705+
706+ #assume that the rhythmbox config remains in teh same laction
707+ test_dir = os.getenv("HOME") + "/.local/share/rhythmbox/"
708+
709+ #define baselined config
710+ res_rhythmdb = res_dir + "rhythmdb.xml"
711+ test_rhythmdb = test_dir + 'rhythmdb.xml'
712+ res_playlists = res_dir + 'playlists.xml'
713+ test_playlists = test_dir + 'playlists.xml'
714+ res_podcasts = res_dir + 'podcast-timestamp'
715+ test_podcasts = test_dir + '/podcast-timestamp'
716+
717+ #define objects for track view
718+ self.Track = namedtuple('Track', 'name genre artist album time')
719+ self.tracklist = []
720+
721+ #reset local database / playlist files
722+ try:
723+ os.stat(test_dir)
724+ except:
725+ os.mkdir(test_dir)
726+
727+ shutil.copy (res_rhythmdb, test_rhythmdb)
728+ shutil.copy (res_playlists, test_playlists)
729+ shutil.copy (res_podcasts, test_podcasts)
730+ self.addCleanup(os.remove, (test_rhythmdb))
731+ self.addCleanup(os.remove, (test_playlists))
732+ self.addCleanup(os.remove, (test_podcasts))
733+ self._mouse = None
734+ self._kb = None
735+
736+ #we create our root object and since we are going to use the emulator
737+ # we need to inform autopilot when launching the application.
738+ self.root_obj = self.launch_test_application(
739+ 'rhythmbox',
740+ app_type='gtk',
741+ emulator_base=gtk.AutopilotGtkEmulatorBase)
742+ # then to make thing a bit easier create an object of the app window
743+ # to ensure we are always selecting from the correct tree
744+ main_window = self.root_obj.select_single('GtkApplicationWindow')
745+
746+ #lets start with getting the left panel
747+ display_source = main_window.select_single('RBDisplayPageTree')
748+ # 'display_source' now contains all objects in the left panel
749+ # so we want the tree view inside this panel
750+ self.treeview = display_source.select_single('GtkTreeView')
751+ self.treeitems = self.treeview.get_all_items()
752+ #for item in self.treeitems:
753+
754+
755+ def _get_path(self, filename):
756+ # lets walk down all dirs and files of current directory
757+ for root, dirs, files in os.walk(os.getcwd()):
758+ # for each file we come across see if it's the one we want
759+ for name in files:
760+ #if it is then return the path
761+ if name == filename:
762+ logger.debug('Found resources root: ' + root)
763+ return os.path.abspath(os.path.join(root)) + '/'
764+
765+
766+ #PROPERTIES
767+ @property
768+ def main_window(self, ):
769+ return self.root_obj.select_single('GtkApplicationWindow')
770+
771+ @property
772+ def keyboard(self):
773+ if self._kb is None:
774+ self._kb = Keyboard.create()
775+ return self._kb
776+
777+ @property
778+ def mouse(self):
779+ if self._mouse is None:
780+ self._mouse = Mouse.create()
781+ return self._mouse
782+
783+ def givenThisDirectoryIsEmpty(self, d):
784+ d = d.replace('$HOME',os.getenv("HOME"))
785+ for the_file in os.listdir(d):
786+ file_path = os.path.join(d, the_file)
787+ if os.path.isfile(file_path):
788+ os.unlink(file_path)
789+
790+ def givenIHaveCopiedFile_To(self, f, target):
791+ target = target.replace('$HOME',os.getenv("HOME"))
792+ ps = subprocess.Popen("cp rhythmbox/resources/" + f + " " + target, shell=True, stdout=subprocess.PIPE)
793+ ps.wait()
794+ self.assertThat(target, FileExists())
795+ logger.debug(str(f) + ' copied to ' + target)
796+
797+ def whenISelectObjectByFilter(self, object_type, type_name='*', **kwargs):
798+ object = self.main_window.select_single(object_type, **kwargs)
799+ self.mouse.click_object(object)
800+
801+ def thenObjectMustBeFocusable(self, object_type, type_name='*', **kwargs):
802+ object = self.main_window.select_single(object_type, **kwargs)
803+ self.assertThat(object.can_focus, Eventually(Equals(True)))
804+
805+ def thenObjectLabelMustBe(self, text, object_type, type_name='*', **kwargs):
806+ object = self.main_window.select_single(object_type, **kwargs)
807+ logger.debug("Waiting for object " + object_type + " to have a label: " + text)
808+ self.assertThat(object.label, Eventually(Equals(text)))
809+
810+
811+ def whenISelectALeftPanelOption(self, option):
812+ try:
813+ self.assertTrue(self.main_window.visible)
814+ display_source = self.main_window.select_single('RBDisplayPageTree')
815+ # 'display_source' now contains all objects in the left panel
816+ # so we want the tree view inside this panel
817+ treeview = display_source.select_single('GtkTreeView')
818+ treeitems = treeview.get_all_items()
819+ self.assertThat(len(treeitems), GreaterThan(0))
820+ # now we have the correct treeview, we use the emulated select_item()
821+ # to get the 'Radio' item. select_item() will only work on a
822+ # GtkTreeView object and just requires the label value exactly as you
823+ # see it on the UI.
824+ tree_item = treeview.select_item(option)
825+ self.mouse.click_object(tree_item)
826+ tree_item2 = treeview.select_item(option)
827+ #lets quickly assert the tree item is in selected state
828+ self.assertThat(tree_item2.selected, Eventually(Equals(True)))
829+ except:
830+ print(traceback.format_exc())
831+ self.root_obj.print_tree()
832+ raise Exception("Test failed to select the left panel, check print_tree in logs")
833+
834+
835+ #######################
836+ # THEN METHODS
837+
838+ def thenStatusBarStartsWith(self, text):
839+ logger.debug("Waiting for status bar to start with: " + text)
840+ self.assertThat(self.getStatusBarText(), Eventually(StartsWith(text), timeout=30))
841+
842+ def thenStatusBarShows(self, text):
843+ logger.debug("Waiting for status bar to show: " + text)
844+ self.assertThat(self.getStatusBarText(), Eventually(Equals(self.getStatusBarText()), timeout=20))
845+
846+
847+ def thenFindThisProcess(self, process_name ):
848+ ps = subprocess.Popen("ps aux | grep "+process_name, shell=True, stdout=subprocess.PIPE)
849+ output = ps.stdout.read()
850+ ps.stdout.close()
851+ ps.wait()
852+ return output
853+
854+ def thenGetThisProcessPID(self, process_name):
855+ output = self.thenFindThisProcess(process_name)
856+ logger.debug('Raw process list obtained')
857+ user = getpass.getuser()
858+ self.assertThat(len(user), GreaterThan(0))
859+ process_list = output.decode(locale.getdefaultlocale()[1]).split(user)
860+ self.assertThat(len(process_list), GreaterThan(1))
861+ logger.debug('DECODED:')
862+ logger.debug(process_list)
863+
864+ for x in range(0, len(process_list)):
865+ logger.debug('Enumerating the process list, item: ' + str(x) + ' [' + str(process_list[x] + ']'))
866+ #this ignores the grep process
867+ if not "grep" in process_list[x] and len(process_list[x]) > 0:
868+ target_process = list(filter(None, process_list[x].split(' ')))
869+ self.assertThat(len(target_process), GreaterThan(0))
870+ logger.debug('Found process: ' + str(target_process))
871+ pid = target_process[0]
872+ logger.debug("Target process is: " + pid)
873+ return int(pid)
874+ return -1
875+
876+
877+ def thenGetTheLibraryView(self):
878+ self.tracklist = []
879+ time.sleep(1)
880+ state_dicts = self.root_obj.get_state_by_path('/Root/GtkApplicationWindow/GtkBox/GtkPaned/GtkPaned/GtkBox/GtkNotebook/RBLibrarySource/GtkNotebook/GtkGrid/GtkPaned/RBEntryView/GtkTreeView')
881+ tracks_treeview = [self.root_obj.make_introspection_object(i) for i in state_dicts][0]
882+
883+ #build the track list view
884+ x=1
885+ while(True):
886+ try:
887+ logger.debug("Adding track to tracklist collection: " + tracks_treeview.select_item_by_index(x).accessible_name)
888+ track = self.Track(tracks_treeview.select_item_by_index(x).accessible_name, tracks_treeview.select_item_by_index(x+1).accessible_name, tracks_treeview.select_item_by_index(x+2).accessible_name, tracks_treeview.select_item_by_index(x+3).accessible_name, tracks_treeview.select_item_by_index(x+4).accessible_name)
889+ self.tracklist.append(track)
890+ logger.debug(str(x) + " index: " + track.name)
891+ x += 6
892+ except:
893+ logger.exception("Caught exception parsing treeview")
894+ logger.debug("Finished parsing tracklist collection, objects added " + str(len(self.tracklist)))
895+ logger.debug("Exception thrown accessing treeview index " + str(x))
896+ break
897+
898+ def thenGetTrackNameFromLibraryPosition(self, x):
899+ try:
900+ if len(self.tracklist) < 1:
901+ self.thenGetTheLibraryView()
902+ except:
903+ self.thenGetTheLibraryView()
904+ self.assertThat(len(self.tracklist), GreaterThan(0))
905+ return self.tracklist[x].name
906+
907+
908+ def thenCheckThatThisTrackIsInTheLibraryView(self, track):
909+ self.thenGetTheLibraryView()
910+ self.assertIn(track,self.tracklist)
911+ logger.debug('Copied ' + file + ' to ' + target)
912+ time.sleep(1)
913+ self.assertThat(target, FileExists())
914+ self.addCleanup(os.remove, target)
915+
916+ def getStatusBarText(self, ):
917+ tmp = self.main_window.select_single('RBStatusbar')
918+ status_bar = tmp.select_single('GtkLabel')
919+ status_bar_text = status_bar.label
920+ logger.debug('Status bar: ' + status_bar_text)
921+ return status_bar_text
922+
923+ def TearDown(self, ):
924+ super(RhythmBoxTestCase, self).tearDown()
925+ shutil.copy (self.test_rhythmdb, '/tmp/last_rhythmdb.xml')
926+ shutil.copy (self.test_playlists, '/tmp/last_playlists.xml')
927+ shutil.copy (self.test_podcasts, '/tmp/last_podcast')
928+
929+
930+
931
932=== added file 'ubuntu_autopilot_tests/rhythmbox/tests/test_import_and_play.py'
933--- ubuntu_autopilot_tests/rhythmbox/tests/test_import_and_play.py 1970-01-01 00:00:00 +0000
934+++ ubuntu_autopilot_tests/rhythmbox/tests/test_import_and_play.py 2014-03-05 19:06:22 +0000
935@@ -0,0 +1,107 @@
936+from autopilot.testcase import AutopilotTestCase
937+from testtools.matchers import Equals, NotEquals, GreaterThan, FileExists, StartsWith, Not
938+from autopilot.matchers import Eventually
939+from autopilot.introspection import get_proxy_object_for_existing_process
940+import subprocess, re, getpass, time, logging, os, sys, shutil
941+from rhythmbox.emulators import gtk
942+from rhythmbox.tests.test_rbox import RhythmBoxTestCase
943+from collections import namedtuple
944+
945+logger = logging.getLogger('__name__')
946+logger.setLevel(10)
947+
948+class ImportAndPlayTestCase(RhythmBoxTestCase):
949+
950+ #TODO: Support file types that need extra plugins
951+ scenarios = [
952+ ('wav/wav', {'file1': "trk1.wav", 'file2': "trk2.wav"}),
953+ ('ogg/ogg', {'file1': "002.ogg", 'file2': "092.ogg"}),
954+ ('ogg/wav', {'file1': "002.ogg", 'file2': "trk1.wav"}),
955+ ('wav/ogg', {'file1': "092.ogg", 'file2': "trk2.wav"}),
956+
957+ #('2 mp3', {'file1': "001.mp3", 'file2': "091.mp3"}),
958+ # ('2 wmv', {'file1': "003.wmv", 'file2': "093.wmv"}),
959+ # ('2 ac3', {'file1': "004.ac3", 'file2': "094.ac3"}),
960+ # ('2 mp4', {'file1': "005.mp4", 'file2': "095.mp4"}),
961+ ]
962+
963+ def test_import_and_play_music_and_skip_tracks(self, ):
964+
965+ #check the environment is clean
966+ self.tracklist = None
967+ self.givenThisDirectoryIsEmpty('$HOME/Pictures')
968+ self.givenThisDirectoryIsEmpty('$HOME/Videos')
969+
970+ logger.debug("TEST IMPORT AND PLAY MUSIC AND SKIP TRACKS. SCENARIO START")
971+ #setup the test data
972+ self.givenIHaveCopiedFile_To(self.file1, '$HOME/Videos/' + self.file1)
973+ self.givenIHaveCopiedFile_To(self.file2, '$HOME/Pictures/' + self.file2)
974+
975+ #Select the music library
976+ self.whenISelectALeftPanelOption('Music')
977+ self.thenStatusBarStartsWith(u'0 songs')
978+
979+ #import a song from the VIDEOS directory
980+ logger.debug("WHEN I IMPORT A SONG FROM THE VIDEOS DIRECTORY")
981+ self.whenISelectObjectByFilter('GtkButton',action_name=u'app.library-import')
982+ self.whenISelectObjectByFilter('GtkFileChooserButton', BuilderName='file-chooser-button')
983+ comboBox = self.main_window.select_single('GtkComboBox')
984+ try:
985+ comboBox.select_item('Videos')
986+ except:
987+ self.root_obj.print_tree()
988+ raise Exception("Failed to select the combobox item, check print_tree in logs")
989+
990+ self.thenObjectMustBeFocusable('GtkButton', BuilderName=u'import-button')
991+ self.thenObjectLabelMustBe(u'Import 1 listed track', 'GtkButton', BuilderName=u'import-button')
992+ self.whenISelectObjectByFilter('GtkButton', BuilderName=u'import-button')
993+ self.thenStatusBarStartsWith(u'1 song, 0 minutes')
994+
995+ #import a song from the PICTURES directory
996+ logger.debug("WHEN I IMPORT A SONG FROM THE PICTURES DIRECTORY")
997+ self.whenISelectObjectByFilter('GtkButton',action_name=u'app.library-import')
998+ self.whenISelectObjectByFilter('GtkFileChooserButton', BuilderName='file-chooser-button')
999+ comboBox = self.main_window.select_single('GtkComboBox')
1000+ comboBox.set_last_label_selected('Videos')
1001+
1002+ try:
1003+ comboBox.select_item('Pictures')
1004+ except:
1005+ self.root_obj.print_tree()
1006+ raise Exception("Failed to select the combobox item, check print_tree in logs")
1007+
1008+ self.thenObjectMustBeFocusable('GtkButton', BuilderName=u'import-button')
1009+ self.thenObjectLabelMustBe(u'Import 1 listed track', 'GtkButton', BuilderName=u'import-button')
1010+ self.whenISelectObjectByFilter('GtkButton', BuilderName=u'import-button')
1011+
1012+ self.thenStatusBarStartsWith(u'2 songs, 0 minutes')
1013+
1014+ # Below is reversed in order, deliberately, to match reverse alpha on track name, which is the default startup
1015+ track_one_name = (self.thenGetTrackNameFromLibraryPosition(1))
1016+ track_two_name = (self.thenGetTrackNameFromLibraryPosition(0))
1017+
1018+ #When I play the first track
1019+ logger.debug("WHEN I PLAY THE FIRST TRACK")
1020+ self.whenISelectObjectByFilter('GtkButton',BuilderName=u'play-button')
1021+
1022+ #Then the tile of the song should be in the window title bar
1023+ logger.debug("THEN THE TITLE OF THE SONG " + track_one_name + " SHOULD BE IN THE WINDOW TITLE BAR")
1024+ self.assertThat(self.main_window.title, Eventually(Equals(u'Unknown - '+ track_one_name)))
1025+
1026+ #And then the second file should play after len track_1_length
1027+ logger.debug("AND THE SECOND TRACK " + track_two_name + " SHOULD PLAY AFTERWARDS")
1028+ self.assertThat(self.main_window.title, Eventually(Equals(u'Unknown - ' + track_two_name), timeout=20))
1029+ #when I pause the track and press back
1030+ logger.debug("WHEN I PAUSE THE TRACK AND SKIP BACK")
1031+ self.whenISelectObjectByFilter('GtkButton',BuilderName=u'play-button')
1032+ self.whenISelectObjectByFilter('GtkButton',BuilderName=u'previous-button')
1033+
1034+ #then track one should play again
1035+ logger.debug("THEN TRACK ONE" + track_one_name+ " SHOULD PLAY AGAIN")
1036+ self.assertThat(self.main_window.title, Eventually(Equals(u'Unknown - ' + track_one_name)))
1037+
1038+ #shutdown cleanly so that the database is updated
1039+ logger.debug("Quitting the application - successful test")
1040+ self.whenISelectObjectByFilter('GtkButton',BuilderName=u'play-button')
1041+ self.keyboard.press_and_release('Ctrl+Q')
1042+
1043
1044=== added file 'ubuntu_autopilot_tests/rhythmbox/tests/test_rbox.py'
1045--- ubuntu_autopilot_tests/rhythmbox/tests/test_rbox.py 1970-01-01 00:00:00 +0000
1046+++ ubuntu_autopilot_tests/rhythmbox/tests/test_rbox.py 2014-03-05 19:06:22 +0000
1047@@ -0,0 +1,91 @@
1048+from autopilot.testcase import AutopilotTestCase
1049+from testtools.matchers import Equals, NotEquals, GreaterThan, FileExists, StartsWith, Not
1050+from autopilot.matchers import Eventually
1051+from autopilot.introspection import get_proxy_object_for_existing_process
1052+import subprocess, re, getpass, time, logging, os, sys, shutil
1053+from rhythmbox.emulators import gtk
1054+from rhythmbox.tests.base import RhythmBoxTestCase
1055+from collections import namedtuple
1056+
1057+logger = logging.getLogger('__name__')
1058+logger.setLevel(10)
1059+class RadioTestCase(RhythmBoxTestCase):
1060+
1061+
1062+ ###############
1063+ #TEST CASES
1064+
1065+ def test_window_visible(self, ):
1066+ #lets start with asserting we get a visible window
1067+ self.assertThat(self.main_window.visible, Equals(True))
1068+
1069+ def test_window_title_is_visible(self, ):
1070+ #lets quickly assert window is visible before testing title
1071+ self.assertTrue(self.main_window.visible)
1072+ self.assertThat(self.main_window.title, Eventually(Equals('Rhythmbox')))
1073+
1074+
1075+ def test_select_radio_station(self, ):
1076+ #Test-case name: rhythmbox/rhy-001
1077+
1078+ genre = "Trance (1)"
1079+ station = 'HBR1.com - I.D.M. Tranceponder'
1080+ self.whenISelectALeftPanelOption('Radio')
1081+
1082+ # so now we the radio options come under RBIRadioSource
1083+ # so first we select that as our root object for the next part
1084+ # to make sure we are only selecting in that part of the tree
1085+ radio_source = self.main_window.select_single('RBIRadioSource')
1086+ # looking in vis the radio section consists of a radio toolbar and two
1087+ # treeviews. lets just focus on the two treeviews for selecting
1088+ # channels.
1089+ # Using select_many we can get both at once
1090+ genre_treeview, title_treeview = radio_source.select_many('GtkTreeView')
1091+
1092+ # so lets pick 'Music' genre to start:
1093+ trance_genre_item = genre_treeview.select_item(genre)
1094+
1095+ try:
1096+ self.mouse.click_object(trance_genre_item)
1097+ except:
1098+ print(traceback.format_exc())
1099+ self.root_obj.print_tree()
1100+ raise Exception("Test failed to select the genre, check print_tree in logs")
1101+
1102+ #assert we selected it
1103+ # for some reason we need to reselect the item :-S I don't get why tbh!
1104+ # maybe its refreshing in the background and object id's are changing
1105+ item = genre_treeview.select_item(genre)
1106+ self.assertTrue(item.selected)
1107+ # we need to sleep here, I haven't implemented a wait_select_item() for
1108+ # treeview items yet, so we need to wait for the title_treeview
1109+ # to update
1110+ time.sleep(5)
1111+
1112+ #now lets select the radio station from the title treeview
1113+ radio_station = title_treeview.select_item(station)
1114+ self.mouse.click_object(radio_station)
1115+ self.assertThat(radio_station.selected, Eventually(Equals(True)))
1116+
1117+ #now press the PLAY button
1118+ self.whenISelectObjectByFilter('GtkButton', BuilderName=u'play-button')
1119+
1120+ self.thenStatusBarStartsWith(u'1 station')
1121+
1122+
1123+ def test_yelp_window_launches(self, ):
1124+ self.assertTrue(self.main_window.visible)
1125+ # launch yelp
1126+ self.keyboard.press_and_release('F1')
1127+ app_pid = self.thenGetThisProcessPID('yelp')
1128+ # now lets get a proy object for yelp
1129+ yelp_proxy = get_proxy_object_for_existing_process(pid=app_pid)
1130+ # now lets check we have an object
1131+ self.assertThat(yelp_proxy, NotEquals(None))
1132+ # now lets get the yelp app window
1133+ yelp = yelp_proxy.select_single('YelpWindow')
1134+ #assert the title
1135+ self.assertThat(yelp.title, Eventually(Equals('Rhythmbox Music Player')))
1136+
1137+
1138+

Subscribers

People subscribed via source and target branches