Merge lp:~elopio/u1-test-utils/smart_scopes_tests into lp:u1-test-utils

Proposed by Leo Arias
Status: Superseded
Proposed branch: lp:~elopio/u1-test-utils/smart_scopes_tests
Merge into: lp:u1-test-utils
Diff against target: 1090 lines (+1004/-0)
17 files modified
bin/vm_session_setup.py (+19/-0)
selftest.py (+20/-0)
tests/__init__.py (+114/-0)
tests/browsers.py (+24/-0)
tests/dash.py (+185/-0)
tests/goods.py (+9/-0)
tests/pay.py (+23/-0)
tests/schema.py (+26/-0)
tests/settings.py (+16/-0)
tests/sso.py (+230/-0)
tests/test_dash.py (+40/-0)
tests/test_dbus.py (+19/-0)
tests/test_purchase_good.py (+84/-0)
tests/test_session.py (+19/-0)
tests/test_smart_scopes.py (+28/-0)
tests/test_users.py (+64/-0)
tests/users.py (+84/-0)
To merge this branch: bzr merge lp:~elopio/u1-test-utils/smart_scopes_tests
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+170968@code.launchpad.net
To post a comment you must log in.
66. By Leo Arias

Preview the result.

67. By Leo Arias

Click the View button.

68. By Leo Arias

Check the URL on the browser.

Unmerged revisions

68. By Leo Arias

Check the URL on the browser.

67. By Leo Arias

Click the View button.

66. By Leo Arias

Preview the result.

65. By Leo Arias

Added the first part of the first scope search test.

64. By Leo Arias

Assert that there is at least one result displayed.

63. By Leo Arias

Added a skip because of bug #1185486.

62. By Leo Arias

Removed the code and comments about the previous workaround.

61. By Leo Arias

Remove the test for the expired card. We have no way to add an expired credit card.

60. By Leo Arias

We need to point to the root of the servers.

59. By Leo Arias

But the one we have to clear is the music scope.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'bin'
2=== added file 'bin/vm_session_setup.py'
3--- bin/vm_session_setup.py 1970-01-01 00:00:00 +0000
4+++ bin/vm_session_setup.py 2013-06-23 05:21:25 +0000
5@@ -0,0 +1,19 @@
6+#!/usr/bin/env python
7+"""
8+Setup the gnome session environment to suit test needs.
9+"""
10+
11+from gi.repository import Gio, Gtk
12+
13+
14+def run(args=None):
15+ session = Gio.Settings('org.gnome.desktop.session')
16+ # Never sleep, we're in a vm
17+ session.set_uint('idle-delay', 0)
18+ # Never lock the screen either
19+ screen_saver = Gio.Settings('org.gnome.desktop.screensaver')
20+ screen_saver.set_boolean('lock-enabled', False)
21+
22+
23+if __name__ == "__main__":
24+ run()
25
26=== added file 'selftest.py'
27--- selftest.py 1970-01-01 00:00:00 +0000
28+++ selftest.py 2013-06-23 05:21:25 +0000
29@@ -0,0 +1,20 @@
30+#!/usr/bin/env python
31+
32+import os
33+import sys
34+
35+import tests
36+
37+# load tests
38+# filter tests
39+# order tests
40+# run tests
41+
42+
43+# We discover tests under './tests', the python 'load_test' protocol can be
44+# used in test modules for more fancy stuff.
45+discover_args = ['discover',
46+ '--start-directory', './tests',
47+ ]
48+os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
49+tests.TestProgram(__name__, argv=[sys.argv[0]] + discover_args + sys.argv[1:])
50
51=== added directory 'tests'
52=== added file 'tests/__init__.py'
53--- tests/__init__.py 1970-01-01 00:00:00 +0000
54+++ tests/__init__.py 2013-06-23 05:21:25 +0000
55@@ -0,0 +1,114 @@
56+import os
57+import selenium
58+from selenium.webdriver.firefox import (
59+ firefox_binary,
60+ webdriver as ff_webdriver,
61+ )
62+
63+from sst import runtests as stests
64+import testtools
65+from testtools import (
66+ run,
67+ testcase,
68+ )
69+import unittest
70+import unity.tests as utests
71+
72+
73+class TestCase(testcase.TestCase):
74+ pass
75+
76+
77+class FirefoxNoRemote(firefox_binary.FirefoxBinary):
78+ """A firefox that can be called by all apps.
79+
80+ Selelnium provides a firefox instance working in isolation. While that's
81+ the most common case, we need a variant here as we don't control how the
82+ dash or any scope will call the web browser.
83+ """
84+
85+ def __init__(self, firefox_path=None):
86+ super(FirefoxNoRemote, self).__init__(firefox_path)
87+ if self._firefox_env.get('MOZ_NO_REMOTE', None) is not None:
88+ del self._firefox_env['MOZ_NO_REMOTE']
89+
90+ if selenium.__version__ < '2.32':
91+ # FIXME: The constructor above is all that is needed for selenium 2.32,
92+ # but it hasn't been pushed in all places so far so the method below
93+ # achieve the same trick for previous selenium releases
94+ def _start_from_profile_path(self, path):
95+ import platform
96+ from subprocess import Popen, PIPE, STDOUT
97+ self._firefox_env["XRE_PROFILE_PATH"] = path
98+ self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1"
99+ self._firefox_env["NO_EM_RESTART"] = "1"
100+
101+ if platform.system().lower() == 'linux':
102+ self._modify_link_library_path()
103+ command = [self._start_cmd, "-silent"]
104+ if self.command_line is not None:
105+ for cli in self.command_line:
106+ command.append(cli)
107+
108+ Popen(command, stdout=PIPE, stderr=STDOUT,
109+ env=self._firefox_env).communicate()
110+ command[1] = '-foreground'
111+ self.process = Popen(
112+ command, stdout=PIPE, stderr=STDOUT,
113+ env=self._firefox_env)
114+
115+
116+class WebDriverNoRemote(ff_webdriver.WebDriver):
117+ """A selenium webdriver using FirefoxNoRemote.
118+
119+ See FirefoxNoRemote for rationale.
120+ """
121+
122+ def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30,
123+ capabilities=None, proxy=None):
124+ super(WebDriverNoRemote, self).__init__(
125+ firefox_profile, FirefoxNoRemote(), timeout, capabilities, proxy)
126+
127+
128+class FirefoxNoRemoteFactory(stests.FirefoxFactory):
129+ """A browser factory using FirefoxNoRemote.
130+
131+ See FirefoxNoRemote for rationale.
132+ """
133+
134+ webdriver_class = WebDriverNoRemote
135+
136+
137+class UnitySSTestcase(utests.UnityTestCase, stests.SSTTestCase):
138+
139+ def setUp(self):
140+ # We need to setup a firefox instance that capture all web connections
141+ self.browser_factory = FirefoxNoRemoteFactory()
142+ utests.UnityTestCase.setUp(self)
143+ # FIXME: Urgh, for an unclear reason, the following occurs in
144+ # autopilot.testcase.LoggedTestCase.setUp when super(LoggedTestCase,
145+ # self).setUp() is called. The joy of multiple inheritance... One more
146+ # reason to avoid it but sst doesn't provide a way to compose instead
147+ # of inheriting in our case. If we leave the following call in place,
148+ # we end up calling SSTTestCase.setUp twice which is utterly wrong. One
149+ # workaround may be to make SSTTestCase detect double calls and avoid
150+ # them but that's even dirtier than the actual behavior and may lead to
151+ # even more obscure failures -- vila 2013-03-27
152+ # stests.SSTTestCase.setUp(self)
153+
154+ # Check that the browser started with a single window opened
155+ self.assertEqual(1, len(self.browser.window_handles))
156+ self.assertEqual('about:blank', self.browser.current_url)
157+
158+ # FIXME: sst overrides testtools definition, that breaks autopilot
159+ # assumption that shortDescription can be used to generate a unique log
160+ # file. -- vila 2013-03-27
161+ shortDescription = testcase.TestCase.shortDescription
162+
163+class TestProgram(testtools.run.TestProgram):
164+
165+ def __init__(self, module, argv, stdout=None, testRunner=None, exit=True):
166+ if testRunner is None:
167+ testRunner = unittest.TextTestRunner
168+ super(TestProgram, self).__init__(module, argv=argv, stdout=stdout,
169+ testRunner=testRunner, exit=exit)
170
171=== added file 'tests/browsers.py'
172--- tests/browsers.py 1970-01-01 00:00:00 +0000
173+++ tests/browsers.py 2013-06-23 05:21:25 +0000
174@@ -0,0 +1,24 @@
175+from sst import actions
176+
177+
178+class Browser(object):
179+
180+ def __init__(self, test):
181+ super(Browser, self).__init__()
182+ self.browser = test.browser
183+ self.nb_windows = len(self.browser.window_handles)
184+
185+ def switch_to_new_window(self, test):
186+ def new_window_appeared():
187+ return len(self.browser.window_handles) > 1
188+ actions.wait_for(new_window_appeared)
189+ test.assertEqual(2, len(self.browser.window_handles))
190+ self.browser.switch_to_window(self.browser.window_handles[1])
191+
192+ @property
193+ def url(self):
194+ return self.browser.current_url
195+
196+ @property
197+ def title(self):
198+ return self.browser.title
199
200=== added file 'tests/dash.py'
201--- tests/dash.py 1970-01-01 00:00:00 +0000
202+++ tests/dash.py 2013-06-23 05:21:25 +0000
203@@ -0,0 +1,185 @@
204+import time
205+
206+from autopilot.matchers import Eventually
207+from testtools.matchers import (
208+ Equals,
209+ GreaterThan,
210+ NotEquals,
211+)
212+
213+
214+def search_home_scope(test, query):
215+ test.addCleanup(test.unity.dash.ensure_hidden)
216+ test.unity.dash.ensure_visible()
217+ test.addCleanup(test.unity.dash.clear_search)
218+ test.keyboard.type(query)
219+ _wait_for_result_settle(test)
220+
221+
222+# Copied from unity tests. TODO move it to the unity emulator.
223+# --elopio -2013-06-22
224+def _wait_for_result_settle(test):
225+ """wait for row count to settle"""
226+ old_row_count = -1
227+ new_row_count = test.unity.dash.get_num_rows()
228+ while(old_row_count != new_row_count):
229+ time.sleep(1)
230+ old_row_count = new_row_count
231+ new_row_count = test.unity.dash.get_num_rows()
232+
233+
234+def search_music_scope(test, query):
235+ test.addCleanup(test.unity.dash.ensure_hidden)
236+ test.unity.dash.reveal_music_scope()
237+ test.addCleanup(clear_music_scope_search, test)
238+ test.keyboard.type(query)
239+ music_scope = test.unity.dash.get_current_scope()
240+ # Wait for a category to appear.
241+ test.assertThat(
242+ music_scope.get_num_visible_categories,
243+ Eventually(GreaterThan(0)))
244+
245+
246+def clear_music_scope_search(test):
247+ dash = test.unity.dash
248+ if (not dash.visible) or (dash.get_current_scope() != 'music.scope'):
249+ dash.reveal_music_scope()
250+ dash.clear_search()
251+
252+
253+def get_first_result_from_category(test, category_name):
254+ current_scope = test.unity.dash.get_current_scope()
255+ category = current_scope.get_category_by_name(category_name)
256+ test.assertTrue(category.is_visible)
257+ get_number_of_results = lambda: len(category.get_results())
258+ test.assertThat(get_number_of_results, Eventually(GreaterThan(0)))
259+ return category.get_results()[0]
260+
261+
262+def preview_result(test, result):
263+ current_scope = test.unity.dash.get_current_scope()
264+ test.assertThat(current_scope.get_num_visible_categories(), GreaterThan(0))
265+ result.preview()
266+ _wait_for_preview_animation(test)
267+
268+
269+def _wait_for_preview_animation(test):
270+ test.assertThat(
271+ test.unity.dash.view.get_preview_container,
272+ Eventually(NotEquals(None)))
273+ preview_container = test.unity.dash.view.get_preview_container()
274+ test.assertThat(
275+ preview_container.animating, Eventually(Equals(False)))
276+
277+
278+def ensure_preview_closed(test):
279+ if test.unity.dash.preview_displaying:
280+ close_preview(test)
281+
282+
283+def close_preview(test):
284+ test.assertTrue(test.unity.dash.preview_displaying)
285+ test.keyboard.press_and_release('Escape')
286+
287+
288+def get_current_preview(test):
289+ test.assertThat(
290+ test.unity.dash.view.get_preview_container,
291+ Eventually(NotEquals(None)))
292+ container = test.unity.dash.view.get_preview_container()
293+ test.assertThat(container.waiting_preview, Eventually(Equals(False)))
294+ return container.current_preview
295+
296+
297+class Window(object):
298+ """A dash window with a content, including buttons and links."""
299+
300+ def __init__(self, user):
301+ self.user = user
302+
303+class Home(Window):
304+ """The first window opened when the dash is activated."""
305+
306+ def get_result(self, test):
307+ # XXX Currently we can't force the Music Store to return a result,
308+ # so for now we will just use the first one. --elopio - 2013-05-19
309+ return get_first_result_from_category(test, 'More suggestions')
310+
311+ def get_preview(self, test, good):
312+ search_music_scope(test, good.search)
313+ result = self.get_result(test)
314+ test.addCleanup(ensure_preview_closed, test)
315+ preview_result(test, result)
316+ # We have the preview now
317+ return Preview(self.user, good)
318+
319+
320+class WindowWithGood(Window):
321+
322+ def __init__(self, user, good):
323+ super(WindowWithGood, self).__init__(user)
324+ self.good = good
325+
326+ def good_displayed(self):
327+ # FIXME: There should be a way to fail ;) -- vila 2013-03-26
328+ return True
329+
330+
331+class Preview(WindowWithGood):
332+
333+ _DOWNLOAD_BUTTON_ID = 'show_purchase_preview'
334+
335+ def __init__(self, user, good):
336+ super(Preview, self).__init__(user, good)
337+
338+ def get_message_replacing_action(self, test):
339+ preview = get_current_preview()
340+ # FIXME: Far from pretty but at least we can get it. There are two
341+ # issues below: [2] may break if more StaticCairoTexts are added and
342+ # the 'get_properties()['text'] may be brittle (but that would mean
343+ # StaticCairoText implementation changed which should have other
344+ # fallouts anyway). Ideally, this helper should be provided by the
345+ # MusicPreview object from the dash. -- vila 2013-05-02
346+ message = preview.text_boxes[2].get_properties()['text']
347+ return message
348+
349+ def get_download_button(self, test):
350+ preview = get_current_preview(test)
351+ download = preview.get_action_by_id(self._DOWNLOAD_BUTTON_ID)
352+ return download
353+
354+ def click_download(self, test):
355+ preview = get_current_preview(test)
356+ preview.execute_action_by_id(self._DOWNLOAD_BUTTON_ID)
357+ # FIXME: The current implementation diverges from the design spec by
358+ # going straight to the web without presenting a window explaining why
359+ # the good can't be bought while also displaying the good. As of today,
360+ # the workaround is to display a message instead of the button. But
361+ # this is only implemented for the music scope, not the home
362+ # scope. -- vila 2013-03-28
363+ return GotoU1(self.user, self.good)
364+
365+
366+class GotoU1(WindowWithGood):
367+
368+ def __init__(self, user, good):
369+ super(GotoU1, self).__init__(user, good)
370+ if self.user.is_logged_in():
371+ if self.user.get_payment_type() == 'expired':
372+ self.message = ('Your card has expired.'
373+ 'To add a new payment method, please visit...')
374+ else:
375+ self.message = 'To add a payment method, please visit...'
376+ else:
377+ self.message = ("You don't have an Ubuntu One account,"
378+ " or you are not logged in.")
379+
380+ def click_goto_u1(self):
381+ from tests import browsers
382+ browser = browsers.get_default()
383+ # Fake the app response by forcing the url in the browser
384+ if self.user.logged_in:
385+ browser.current_url = 'https://pay.ubuntu.com'
386+ else:
387+ browser.current_url = 'https://one.ubuntu.com'
388+ return browser
389
390=== added file 'tests/goods.py'
391--- tests/goods.py 1970-01-01 00:00:00 +0000
392+++ tests/goods.py 2013-06-23 05:21:25 +0000
393@@ -0,0 +1,9 @@
394+class Good(object):
395+
396+ def __init__(self, name, search):
397+ # Name is used to lightly check the result we get from the
398+ # search. Lightly here means that we ensure that the result name
399+ # contains the good name.
400+ self.name = name
401+ # How to search for this good from the dash
402+ self.search = search
403
404=== added file 'tests/pay.py'
405--- tests/pay.py 1970-01-01 00:00:00 +0000
406+++ tests/pay.py 2013-06-23 05:21:25 +0000
407@@ -0,0 +1,23 @@
408+from sst import actions as sst_actions
409+from u1testutils.pay import api, data
410+from u1testutils.sso import sst as sso_website
411+
412+
413+def get_payment_preferences_with_server_api(user, pay_server_url):
414+ api_client = api.APIClient(pay_server_url)
415+ return api_client.client.account_preferences(open_id=user.openid)
416+
417+
418+def add_new_credit_card_with_server_api(
419+ user, unattended, pay_server_url, consumer_id):
420+ credit_card = data.CreditCard.make_test_visa_card()
421+ billing_address = data.Address.make_unique()
422+ api_client = api.APIClient(pay_server_url)
423+ api_client.add_new_credit_card(
424+ consumer_id, user, credit_card, billing_address, unattended)
425+
426+
427+def log_in_to_website(user, pay_server_url):
428+ sst_actions.go_to(pay_server_url)
429+ sst_actions.click_link('login-link')
430+ sso_website.sign_in(user, is_site_recognized=False)
431
432=== added file 'tests/schema.py'
433--- tests/schema.py 1970-01-01 00:00:00 +0000
434+++ tests/schema.py 2013-06-23 05:21:25 +0000
435@@ -0,0 +1,26 @@
436+from configglue import schema
437+
438+
439+class InDashPaymentsSchema(schema.Schema):
440+
441+ class openid(schema.Section):
442+ openid_sso_server_url = schema.StringOption()
443+
444+ class upay(schema.Section):
445+ pay_server_url = schema.StringOption()
446+ consumer_id = schema.StringOption()
447+ pay_api_username = schema.StringOption()
448+ pay_api_password = schema.StringOption()
449+
450+ class ubuntuone(schema.Section):
451+ ubuntuone_server_url = schema.StringOption()
452+
453+ class musicsearch(schema.Section):
454+ musicsearch_server_url = schema.StringOption()
455+
456+ class smartscopes(schema.Section):
457+ smartscopes_server_url = schema.StringOption()
458+ smartscopes_geo_store = schema.StringOption()
459+
460+
461+schema = InDashPaymentsSchema
462
463=== added file 'tests/settings.py'
464--- tests/settings.py 1970-01-01 00:00:00 +0000
465+++ tests/settings.py 2013-06-23 05:21:25 +0000
466@@ -0,0 +1,16 @@
467+import os
468+
469+from django_configglue.utils import configglue
470+
471+from tests.schema import schema
472+
473+
474+# Get location of local cfg files.
475+local_configs = os.environ.get('CONFIGGLUE_LOCAL_CONFIG', ['local.cfg'])
476+
477+# Get absolute path for config files
478+current_dir = os.path.dirname(os.path.abspath(__file__))
479+config_files = map(lambda x: os.path.join(current_dir, x), local_configs)
480+
481+# Glue everything together.
482+configglue(schema, config_files, __name__)
483
484=== added file 'tests/sso.py'
485--- tests/sso.py 1970-01-01 00:00:00 +0000
486+++ tests/sso.py 2013-06-23 05:21:25 +0000
487@@ -0,0 +1,230 @@
488+import os
489+import subprocess
490+import threading
491+import time
492+
493+import dbus
494+import dbus.mainloop.glib
495+import gobject
496+
497+from autopilot.emulators import input
498+from autopilot import introspection
499+from u1testutils.sso import api
500+
501+
502+def create_new_account_with_server_api(user, sso_server_url):
503+ api_client = api.APIClient(sso_server_url)
504+ api_client.create_new_account(
505+ user, captcha_id='Not used', captcha_solution='Not used')
506+
507+
508+def log_in_with_desktop_client(user, sso_server_url, ubuntuone_server_url):
509+ desktop_client = UbuntuSSODesktopClient(
510+ sso_server_url, ubuntuone_server_url)
511+ desktop_client.log_in(user)
512+
513+
514+def _start_ubuntu_sso_service_on_desktop(sso_server_url):
515+ os.environ['USSOC_SERVICE_URL'] = '{0}/api/1.0/'.format(sso_server_url)
516+ os.system('pkill ubuntu-sso')
517+ subprocess.Popen('/usr/lib/ubuntu-sso-client/ubuntu-sso-login')
518+
519+
520+def log_out_with_dbus_api(sso_server_url):
521+ _start_ubuntu_sso_service_on_desktop(sso_server_url)
522+ interface = _get_sso_dbus_interface()
523+ interface.clear_credentials('Ubuntu One', {})
524+
525+
526+def is_logged_in_on_desktop(sso_server_url):
527+ _start_ubuntu_sso_service_on_desktop(sso_server_url)
528+ credentials = _get_credentials()
529+ return credentials is not None
530+
531+
532+def _get_credentials():
533+ signaled = threading.Event()
534+ # XXX is there a way to share a variable without the globals? Without that
535+ # the signal callbacks don't set the value on the right variable.
536+ # -- elopio - 2013-05-15
537+ global _credentials
538+ _credentials = None
539+ global _credentials_error
540+ _credentials_error = None
541+
542+ def credentials_found(app_name, credentials=None):
543+ if app_name == 'Ubuntu One':
544+ global _credentials
545+ _credentials = credentials
546+ loop.quit()
547+ signaled.set()
548+
549+ def credentials_error(app_name, error):
550+ if app_name == 'Ubuntu One':
551+ global _credentials_error
552+ _credentials_error = error
553+ loop.quit()
554+ signaled.set()
555+
556+ interface = _get_sso_dbus_interface()
557+ interface.connect_to_signal('CredentialsFound', credentials_found)
558+ interface.connect_to_signal('CredentialsNotFound', credentials_found)
559+ interface.connect_to_signal('CredentialsError', credentials_error)
560+ interface.find_credentials('Ubuntu One', {})
561+ loop = gobject.MainLoop()
562+ loop.run()
563+ signaled.wait()
564+ if _credentials_error:
565+ raise dbus.DBusException(_credentials_error['message'])
566+ else:
567+ return _credentials
568+
569+
570+def _get_sso_dbus_interface():
571+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
572+ session_bus = dbus.SessionBus()
573+ proxy = session_bus.get_object(
574+ 'com.ubuntu.sso', '/com/ubuntu/sso/credentials')
575+ return dbus.Interface(
576+ proxy, dbus_interface='com.ubuntu.sso.CredentialsManagement')
577+
578+
579+class UbuntuSSODesktopClient(object):
580+
581+ def __init__(self, sso_server_url, ubuntuone_server_url):
582+ self.sso_server_url = sso_server_url
583+ self.ubuntuone_server_url = ubuntuone_server_url
584+
585+ def launch_application(self):
586+ self._prepare_environment()
587+ process = subprocess.Popen(
588+ '/usr/lib/ubuntu-sso-client/ubuntu-sso-login-qt')
589+ self.application = (
590+ introspection.get_autopilot_proxy_object_for_process(process))
591+
592+ def _prepare_environment(self):
593+ os.environ['TESTABILITY'] = '1'
594+ os.environ['SSO_AUTH_BASE_URL'] = self.sso_server_url
595+ os.environ['SSO_UONE_BASE_URL'] = self.ubuntuone_server_url
596+ _start_ubuntu_sso_service_on_desktop(self.sso_server_url)
597+
598+ def log_in(self, user):
599+ self.launch_application()
600+ log_in_page = self._go_to_log_in_page()
601+ success_page = log_in_page.log_in_with_successful_authentication(user)
602+ assert (success_page.get_message() ==
603+ 'You are now logged into Ubuntu One.')
604+ success_page.finish()
605+
606+ def _go_to_log_in_page(self):
607+ sign_up_page = SignUpPage(self.application)
608+ sign_up_page.wait_for_action_to_complete()
609+ return sign_up_page.go_to_log_in()
610+
611+
612+class Page(object):
613+
614+ def __init__(self, application, page_type):
615+ self.application = application
616+ self.page = self.application.select_single(page_type)
617+ self.keyboard = input.get_keyboard()
618+ self.mouse = input.get_mouse()
619+
620+ def wait_for_action_to_complete(self):
621+ """Wait for the last action to complete.
622+
623+ This method waits for the loading overlay to dissapear.
624+
625+ """
626+ # If autopilot executes too fast, we might look for the loading overlay
627+ # before it is shown. We should wait a little to give it time to
628+ # appear, but it's not possible to wait for it, because if autopilot
629+ # executes too slow, we might look for it after it dissapears.
630+ # Thus, the only solution that comes to mind is a sleep.
631+ # -- elopio - 2013-05-03
632+ time.sleep(3)
633+ loading_overlay = self.application.select_single('LoadingOverlay')
634+ loading_overlay.visible.wait_for(False)
635+
636+ # TODO propose this to autopilot. -- elopio - 2013-05-03
637+ def get_child_by_type(self, desired_type, **kwargs):
638+ children = self.page.get_children_by_type(desired_type, **kwargs)
639+ assert len(children) > 0, 'No child found.'
640+ assert len(children) == 1, 'More than one child found.'
641+ return children[0]
642+
643+
644+class SignUpPage(Page):
645+
646+ def __init__(self, application):
647+ super(SignUpPage, self).__init__(application, 'SetupAccountPage')
648+
649+ def go_to_log_in(self):
650+ self.wait_for_action_to_complete()
651+ sign_in_label = self.get_child_by_type(
652+ 'QLabel', objectName='sign_in_label')
653+ self.mouse.move_to_object(sign_in_label)
654+ self.mouse.click()
655+ self.wait_for_action_to_complete()
656+ return LogInPage(self.application)
657+
658+
659+class LogInPage(Page):
660+
661+ def __init__(self, application):
662+ super(LogInPage, self).__init__(application, 'CurrentUserSignInPage')
663+
664+ def log_in_with_successful_authentication(self, user):
665+ self._log_in(user.email, user.password)
666+ assert self.get_form_errors() is None
667+ return SuccessPage(self.application)
668+
669+ def _log_in(self, email, password):
670+ self._fill_log_in_form(email, password)
671+ sign_in_button = self.get_child_by_type(
672+ 'QPushButton', objectName='sign_in_button')
673+ self.mouse.move_to_object(sign_in_button)
674+ self.mouse.click()
675+ self.wait_for_action_to_complete()
676+
677+ def _fill_log_in_form(self, email, password):
678+ email_edit = self.get_child_by_type(
679+ 'QLineEdit', objectName='email_edit')
680+ self.mouse.move_to_object(email_edit)
681+ self.mouse.click()
682+ self.keyboard.type(email)
683+ password_edit = self.get_child_by_type(
684+ 'QLineEdit', objectName='password_edit')
685+ self.mouse.move_to_object(password_edit)
686+ self.mouse.click()
687+ self.keyboard.type(password)
688+
689+ def get_form_errors(self):
690+ form_errors = self.get_child_by_type(
691+ 'QLabel', objectName='form_errors')
692+ if form_errors.visible:
693+ return form_errors.text
694+ else:
695+ return None
696+
697+
698+class SuccessPage(Page):
699+
700+ def __init__(self, application):
701+ super(SuccessPage, self).__init__(application, 'SuccessPage')
702+
703+ def get_message(self):
704+ label = self.get_child_by_type(
705+ 'QLabel', objectName='success_message_body')
706+ return label.text
707+
708+ def finish(self):
709+ # TODO when we have the emulator, this should be on the wizard, not the
710+ # page. -- elopio - 2013-05-03
711+ # Bug reported to QWizard because the buttons don't have objectName:
712+ # https://bugreports.qt-project.org/browse/QTBUG-29924
713+ # TODO update when the bug is fixed. -- elopio - 2013-05-03
714+ finish_button = self.application.select_single(
715+ 'QPushButton', text='&Finish')
716+ self.mouse.move_to_object(finish_button)
717+ self.mouse.click()
718
719=== added file 'tests/test_dash.py'
720--- tests/test_dash.py 1970-01-01 00:00:00 +0000
721+++ tests/test_dash.py 2013-06-23 05:21:25 +0000
722@@ -0,0 +1,40 @@
723+import unity.tests as utests
724+
725+from autopilot.matchers import Eventually
726+from testtools.matchers import Equals
727+
728+from tests import dash
729+
730+
731+class TestDash(utests.UnityTestCase):
732+
733+ def setUp(self):
734+ super(TestDash, self).setUp()
735+ self.addCleanup(self.unity.dash.ensure_hidden)
736+
737+ def test_open_music_scope(self):
738+ self.unity.dash.reveal_music_scope()
739+ current_scope = self.unity.dash.get_current_scope()
740+ self.assertEquals(current_scope.name, 'music.scope')
741+
742+ def test_search_music_scope(self):
743+ dash.search_music_scope(self, 'Hendrix')
744+ first_result = dash.get_first_result_from_category(
745+ self, 'More suggestions')
746+ self.assertIn('Hendrix', first_result.name)
747+
748+ def test_open_and_close_music_result(self):
749+ dash.search_music_scope(self, 'Hendrix')
750+ first_result = dash.get_first_result_from_category(
751+ self, 'More suggestions')
752+ dash.preview_result(self, first_result)
753+ self.assertThat(
754+ self.unity.dash.preview_displaying, Eventually(Equals(True)))
755+ preview = dash.get_current_preview(self)
756+ self.assertIn('Hendrix', preview.text_boxes[0].text)
757+ self.assertEquals(preview.get_num_actions(), 1)
758+ download_button = preview.get_action_by_id('show_purchase_preview')
759+ self.assertEqual(download_button.label, 'Download')
760+ dash.close_preview(self)
761+ self.assertThat(
762+ self.unity.dash.preview_displaying, Eventually(Equals(False)))
763
764=== added file 'tests/test_dbus.py'
765--- tests/test_dbus.py 1970-01-01 00:00:00 +0000
766+++ tests/test_dbus.py 2013-06-23 05:21:25 +0000
767@@ -0,0 +1,19 @@
768+import tests
769+
770+import dbus
771+from gi.repository import Gio, Gtk
772+
773+class TestDBUS(tests.TestCase):
774+
775+ def setUp(self):
776+ super(TestDBUS, self).setUp()
777+ self.bus = dbus.SessionBus()
778+ # Register and clean up our own bus.
779+ self.bus.request_name('canonical.online-services.selftest')
780+ self.addCleanup(self.bus.release_name,
781+ 'canonical.online-services.selftest')
782+
783+ def test_it(self):
784+ # FIXME: We need some real tests :0) But the setUp above will be
785+ # exercised even by an eempty test and represent useful knowledge.
786+ pass
787
788=== added file 'tests/test_purchase_good.py'
789--- tests/test_purchase_good.py 1970-01-01 00:00:00 +0000
790+++ tests/test_purchase_good.py 2013-06-23 05:21:25 +0000
791@@ -0,0 +1,84 @@
792+import os
793+import subprocess
794+
795+from sst import actions as sactions
796+
797+# FIXME do not use django config to get the server settings.
798+# -- elopio - 2013-05-15
799+from django import conf
800+
801+import tests
802+from tests import (
803+ browsers,
804+ dash,
805+ goods,
806+ users,
807+)
808+
809+
810+class TestPurchaseGood(tests.UnitySSTestcase):
811+
812+ def setUp(self):
813+ super(TestPurchaseGood, self).setUp()
814+ # A user will purchase a good
815+ self.user = users.User.make_unique()
816+ # Set the urls of the servers that will be used by the user.
817+ self.user.set_servers_data(
818+ sso_server_url=conf.settings.OPENID_SSO_SERVER_URL,
819+ pay_server_url=conf.settings.PAY_SERVER_URL,
820+ consumer_id=conf.settings.CONSUMER_ID,
821+ ubuntuone_server_url=conf.settings.UBUNTUONE_SERVER_URL)
822+ # Arbitrarily using a good that is known to work
823+ self.good = goods.Good(u'Jimi Hendrix', 'hendrix')
824+ self._run_music_lens()
825+
826+ def _run_music_lens(self):
827+ os.environ['MUSICSTORE_URI'] = (
828+ conf.settings.MUSICSEARCH_SERVER_URL + '/v1/')
829+ os.environ['U1_STAGING_AUTHENTICATION'] = (
830+ conf.settings.OPENID_SSO_SERVER_URL + '/')
831+ os.environ['U1_STAGING_WEBAPI'] = (
832+ conf.settings.UBUNTUONE_SERVER_URL + '/')
833+ os.system('pkill unity-music')
834+ subprocess.Popen(
835+ '/usr/lib/x86_64-linux-gnu/unity-lens-music/'
836+ 'unity-musicstore-daemon')
837+
838+ def preview_good(self):
839+ home = dash.Home(self.user)
840+ self.preview = home.get_preview(self, self.good)
841+ # user get a preview screen and a button to click on
842+ self.assertTrue(self.preview.good_displayed())
843+
844+ def purchase_good(self):
845+ # Attempt to purchase the good
846+ return self.preview.click_download(self)
847+
848+
849+class TestUserCannotPay(TestPurchaseGood):
850+
851+ def test_user_not_logged_in(self):
852+ self.assertFalse(self.user.is_logged_in())
853+ self.preview_good()
854+ self.purchase_good()
855+ browser = browsers.Browser(self)
856+ # We've been redirected to the web
857+ browser.switch_to_new_window(self)
858+ sactions.wait_for(
859+ sactions.assert_url_contains, conf.settings.OPENID_SSO_SERVER_URL)
860+ self.assertEqual('Log in', browser.title)
861+
862+ def test_no_payment_method_stored(self):
863+ # Log in with a newly created user, and set no payment method to him.
864+ self.user.login(self)
865+ self.assertIs(self.user.get_payment_type(), None)
866+ self.preview_good()
867+ goto_u1 = self.purchase_good()
868+ self.assertTrue(goto_u1.good_displayed())
869+ self.assertIn('add a payment method', goto_u1.message)
870+ self.skipTest('We are blocked at this point because of bug #1185486.')
871+ # The default browser is launched to propose paying
872+ #browser = goto_u1.click_goto_u1()
873+ # User is sent to the web
874+ #self.assertTrue(browser.is_running())
875+ #self.assertEqual(conf.settings.PAY_SERVER_URL, browser.current_url)
876
877=== added file 'tests/test_session.py'
878--- tests/test_session.py 1970-01-01 00:00:00 +0000
879+++ tests/test_session.py 2013-06-23 05:21:25 +0000
880@@ -0,0 +1,19 @@
881+import tests
882+
883+from gi.repository import Gio
884+
885+class TestGSettings(tests.TestCase):
886+
887+ def test_session(self):
888+ s = Gio.Settings('org.gnome.desktop.session')
889+ orig = s.get_uint('idle-delay')
890+ self.addCleanup(s.set_uint, 'idle-delay', orig)
891+ s.set_uint('idle-delay', orig + 2)
892+ self.assertEqual(orig + 2, s.get_uint('idle-delay'))
893+
894+ def test_screensaver(self):
895+ s = Gio.Settings('org.gnome.desktop.screensaver')
896+ orig = s.get_boolean('lock-enabled')
897+ self.addCleanup(s.set_boolean, 'lock-enabled', orig)
898+ s.set_boolean('lock-enabled', not orig)
899+ self.assertEqual(not orig, s.get_boolean('lock-enabled'))
900
901=== added file 'tests/test_smart_scopes.py'
902--- tests/test_smart_scopes.py 1970-01-01 00:00:00 +0000
903+++ tests/test_smart_scopes.py 2013-06-23 05:21:25 +0000
904@@ -0,0 +1,28 @@
905+import os
906+import subprocess
907+
908+# FIXME do not use django config to get the server settings.
909+# -- elopio - 2013-05-15
910+from django import conf
911+
912+import tests
913+from tests import dash
914+
915+
916+class SmartScopesSearchTestCase(tests.UnitySSTestcase):
917+
918+ def setUp(self):
919+ super(SmartScopesSearchTestCase, self).setUp()
920+ self._run_home_scope()
921+
922+ def _run_home_scope(self):
923+ os.environ['SMART_SCOPES_SERVER"'] = (
924+ conf.settings.SMARTSCOPES_SERVER_URL)
925+ os.environ['SMART_SCOPES_GEO_STORE'] = (
926+ conf.settings.SMARTSCOPES_GEO_STORE)
927+ os.system('pkill unity-scope-home*')
928+ subprocess.Popen(
929+ '/usr/lib/x86_64-linux-gnu/unity-scope-home/unity-scope-home')
930+
931+ def test_search_wikipedia(self):
932+ dash.search_home_scope(self, 'wikipedia:Test')
933
934=== added file 'tests/test_users.py'
935--- tests/test_users.py 1970-01-01 00:00:00 +0000
936+++ tests/test_users.py 2013-06-23 05:21:25 +0000
937@@ -0,0 +1,64 @@
938+import uuid
939+
940+# FIXME do not use django config to get the server settings.
941+# -- elopio - 2013-05-15
942+from django import conf
943+from sst import runtests
944+
945+
946+import tests
947+from tests import users
948+
949+
950+class UsersTest(runtests.SSTTestCase):
951+
952+ xserver_headless = True
953+
954+ def test_make_unique(self):
955+ unique_id = str(uuid.uuid1())
956+ user = users.User.make_unique(unique_id)
957+ user.set_servers_data(
958+ sso_server_url=conf.settings.OPENID_SSO_SERVER_URL,
959+ pay_server_url=conf.settings.PAY_SERVER_URL)
960+ self.assertEqual(
961+ user.user_data.full_name, 'Test user {0}'.format(unique_id)[:30])
962+ self.assertEqual(
963+ user.user_data.email, 'test+{0}@example.com'.format(unique_id))
964+ self.assertEqual(
965+ user.user_data.password, 'Hola123*')
966+ self.assertFalse(user.is_logged_in())
967+ self.assertIs(user.get_payment_type(), None)
968+ self.assertIs(user.get_allow_unattended_payments(), None)
969+
970+ def test_set_credit_card_payment_method(self):
971+ expected_type = 'Credit Card'
972+ user = users.User.make_unique()
973+ user.set_servers_data(
974+ sso_server_url=conf.settings.OPENID_SSO_SERVER_URL,
975+ pay_server_url=conf.settings.PAY_SERVER_URL, consumer_id = 'TEST')
976+ user.set_payment_method(expected_type)
977+ self.assertEqual(user.get_payment_type(), expected_type)
978+ self.assertIs(user.get_allow_unattended_payments(), None)
979+
980+ def test_set_unattended_credit_card_payment_method(self):
981+ expected_type = 'Credit Card'
982+ user = users.User.make_unique()
983+ user.set_servers_data(
984+ sso_server_url=conf.settings.OPENID_SSO_SERVER_URL,
985+ pay_server_url=conf.settings.PAY_SERVER_URL, consumer_id = 'TEST')
986+ user.set_payment_method(expected_type, unattended=True)
987+ self.assertEqual(user.get_payment_type(), expected_type)
988+ self.assertTrue(user.get_allow_unattended_payments())
989+
990+
991+class SessionTest(tests.TestCase):
992+
993+ def test_log_in_and_out(self):
994+ user = users.User.make_unique()
995+ user.set_servers_data(
996+ sso_server_url=conf.settings.OPENID_SSO_SERVER_URL,
997+ ubuntuone_server_url=conf.settings.UBUNTUONE_SERVER_URL)
998+ user.login(self)
999+ self.assertTrue(user.is_logged_in())
1000+ user.logout()
1001+ self.assertFalse(user.is_logged_in())
1002
1003=== added file 'tests/users.py'
1004--- tests/users.py 1970-01-01 00:00:00 +0000
1005+++ tests/users.py 2013-06-23 05:21:25 +0000
1006@@ -0,0 +1,84 @@
1007+from u1testutils.sso import data
1008+
1009+from tests import pay, sso
1010+
1011+
1012+class User(object):
1013+
1014+ def __init__(self, user_data):
1015+ self.user_data = user_data
1016+ self._user_created_in_sso = False
1017+ self._user_created_in_pay = False
1018+ self.payment_method = None
1019+
1020+ def set_servers_data(
1021+ self, sso_server_url=None, pay_server_url=None, consumer_id=None,
1022+ ubuntuone_server_url=None):
1023+ self.sso_server_url = sso_server_url
1024+ self.pay_server_url = pay_server_url
1025+ self.consumer_id = consumer_id
1026+ self.ubuntuone_server_url = ubuntuone_server_url
1027+
1028+ @classmethod
1029+ def make_unique(cls, unique_id=None):
1030+ user_data = data.User.make_unique(unique_id)
1031+ user_data.full_name = user_data.full_name[:30]
1032+ return cls(user_data)
1033+
1034+ def is_logged_in(self):
1035+ return sso.is_logged_in_on_desktop(self.sso_server_url)
1036+
1037+
1038+ def login(self, test):
1039+ self._ensure_user_created_in_sso()
1040+ test.addCleanup(self.logout)
1041+ sso.log_in_with_desktop_client(
1042+ self.user_data, self.sso_server_url, self.ubuntuone_server_url)
1043+
1044+ def _ensure_user_created_in_sso(self):
1045+ if not self._user_created_in_sso:
1046+ # Currently we are using a local server, so we don't need to delete
1047+ # the user after the test. The same applies to the staging server.
1048+ # When we start testing against the production server, we should
1049+ # either use a stock user, or delete it after the test.
1050+ # -- elopio - 2013-05-15
1051+ sso.create_new_account_with_server_api(
1052+ self.user_data, self.sso_server_url)
1053+ self._user_created_in_sso = True
1054+
1055+ def logout(self):
1056+ sso.log_out_with_dbus_api(self.sso_server_url)
1057+
1058+ def _ensure_user_created_in_pay(self):
1059+ self._ensure_user_created_in_sso()
1060+ # Make sure the user is registered in pay. Otherwise, the API calls
1061+ # will give a 500 error.
1062+ # TODO update this when bug http://pad.lv/1144523 is fixed.
1063+ if not self._user_created_in_pay:
1064+ pay.log_in_to_website(self.user_data, self.pay_server_url)
1065+ self._user_created_in_pay = True
1066+
1067+ def set_payment_method(self, payment_type, unattended=False):
1068+ self._ensure_user_created_in_pay()
1069+ if payment_type == 'Credit Card':
1070+ pay.add_new_credit_card_with_server_api(
1071+ self.user_data, unattended, self.pay_server_url,
1072+ self.consumer_id)
1073+ else:
1074+ raise NotImplementedError
1075+
1076+ def get_payment_type(self):
1077+ self._ensure_user_created_in_pay()
1078+ preferences = pay.get_payment_preferences_with_server_api(
1079+ self.user_data, self.pay_server_url)
1080+ default_payment_method = preferences['default_payment_method']
1081+ if default_payment_method is not None:
1082+ return default_payment_method['type']
1083+ else:
1084+ return None
1085+
1086+ def get_allow_unattended_payments(self):
1087+ self._ensure_user_created_in_pay()
1088+ preferences = pay.get_payment_preferences_with_server_api(
1089+ self.user_data, self.pay_server_url)
1090+ return preferences['allow_unattended_payments']

Subscribers

People subscribed via source and target branches

to all changes: