~elopio/ubuntu-sso-client/autopilot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import time
import os
import uuid

import dbus
from dbus.mainloop.glib import DBusGMainLoop

from autopilot import introspection, testcase
from autopilot.emulators import X11
from testtools.matchers import Equals, Contains


# This should be an emulator. But it seems that the only way to make an
# emulator is if the application's bus has well-known name.
# TODO ask on #qa.
class Page(object):

    def __init__(self, application, page_type):
        self.application = application
        self.page = self.application.select_single(page_type)
        self.keyboard = X11.Keyboard()
        self.mouse = X11.Mouse()

    def wait_for_action_to_complete(self):
        """Wait for the last action to complete.

        This method waits for the loading overlay to dissapear.

        """
        # If autopilot executes too fast, we might look for the loading overlay
        # before it is shown. We should wait a little to give it time to
        # appear, but it's not possible to wait for it, because if autopilot
        # executes too slow, we might look for it after it dissapears.
        # Thus, the only solution that comes to mind is a sleep. -- Leo
        time.sleep(3)
        loading_overlay = self.application.select_single('LoadingOverlay')
        loading_overlay.visible.wait_for(False)

    # TODO propose this to autopilot.
    def get_child_by_type(self, desired_type, **kwargs):
        children = self.page.get_children_by_type(desired_type, **kwargs)
        assert len(children) > 0, 'No child found.'
        assert len(children) == 1, 'More than one child found.'
        return children[0]


class SignUpPage(Page):

    def __init__(self, application):
        super(SignUpPage, self).__init__(application, 'SetupAccountPage')

    def go_to_log_in(self):
        sign_in_label = self.get_child_by_type(
            'QLabel', objectName='sign_in_label')
        self.mouse.move_to_object(sign_in_label)
        self.mouse.click()
        self.wait_for_action_to_complete()
        return LogInPage(self.application)


class LogInPage(Page):

    def __init__(self, application):
        super(LogInPage, self).__init__(application, 'CurrentUserSignInPage')

    def log_in_with_successful_authentication(self, email, password):
        self._log_in(email, password)
        assert self.get_form_errors() is None
        return SuccessPage(self.application)

    def _log_in(self, email, password):
        self._fill_log_in_form(email, password)
        sign_in_button = self.get_child_by_type(
            'QPushButton', objectName='sign_in_button')
        self.mouse.move_to_object(sign_in_button)
        self.mouse.click()
        self.wait_for_action_to_complete()

    def log_in_with_failed_authentication(self, email, password):
        self._log_in(email, password)
        return self.page

    def _fill_log_in_form(self, email, password):
        email_edit = self.get_child_by_type(
            'QLineEdit', objectName='email_edit')
        self.mouse.move_to_object(email_edit)
        self.mouse.click()
        self.keyboard.type(email)
        password_edit = self.get_child_by_type(
            'QLineEdit', objectName='password_edit')
        self.mouse.move_to_object(password_edit)
        self.mouse.click()
        self.keyboard.type(password)

    def get_form_errors(self):
        form_errors = self.get_child_by_type(
            'QLabel', objectName='form_errors')
        if form_errors.visible:
            return form_errors.text
        else:
            return None


class SuccessPage(Page):

    def __init__(self, application):
        super(SuccessPage, self).__init__(application, 'SuccessPage')

    def get_message(self):
        label = self.get_child_by_type(
            'QLabel', objectName='success_message_body')
        return label.text

    def finish(self):
        # TODO when we have the emulator, this should be on the wizard, not the
        # page.
        # TODO file a bug to QWizard because the buttons don't have objectName.
        finish_button = self.application.select_single(
            'QPushButton', text='&Finish')
        self.mouse.move_to_object(finish_button)
        self.mouse.click()


class NoEnvironmentIntrospectionTestMixin(
        introspection.ApplicationIntrospectionTestMixin):

    def prepare_environment(self, app_path, arguments):
        return app_path, arguments


class TestUbuntuSSOClient(
        testcase.AutopilotTestCase, NoEnvironmentIntrospectionTestMixin):

    def setUp(self):
        super(TestUbuntuSSOClient, self).setUp()
        os.environ['TESTABILITY'] = '1'
        # TODO we shouldn't start the application this way. We should call
        # the register or login method through dbus.
        # There are two problems with this. First, the TESTABILITY variable
        # is not passed as True. Second, I haven't found an easy way to get
        # the autopilot dbus proxy.
        self.app_name = 'App {0}'.format(str(uuid.uuid1()))
        self.application = self.launch_test_application(
            'bin/ubuntu-sso-login-qt',
            '--app_name={0}'.format(self.app_name))

    def _clear_credentials(self):
        DBusGMainLoop(set_as_default=True)
        session_bus = dbus.SessionBus()
        proxy = session_bus.get_object(
            'com.ubuntu.sso', '/com/ubuntu/sso/credentials')
        interface = dbus.Interface(
            proxy, dbus_interface='com.ubuntu.sso.CredentialsManagement')
        interface.clear_credentials(self.app_name, {})

    def _log_in_with_successful_authentication(self, email, password):
        log_in_page = self._go_to_log_in_page()
        return log_in_page.log_in_with_successful_authentication(
            email, password)

    def _go_to_log_in_page(self):
        sign_up_page = SignUpPage(self.application)
        sign_up_page.wait_for_action_to_complete()
        return sign_up_page.go_to_log_in()

    def _log_in_with_failed_authentication(self, email, password):
        log_in_page = self._go_to_log_in_page()
        log_in_page.log_in_with_failed_authentication(email, password)
        self.assertTrue(log_in_page.page.visible)
        return log_in_page

    def test_authentication_failed(self):
        log_in_page = self._log_in_with_failed_authentication(
            'wrong-email@canonical.com', 'wrong password')
        self.assertThat(
            log_in_page.get_form_errors(),
            Contains('The authentication failed.'))

    def test_successful_log_in(self):
        self.skipTest('This is a public project, so we need a way to store '
                      'real user credentials on a private way.')
        self.addCleanup(self._clear_credentials)
        success_page = self._log_in_with_successful_authentication(
            'u1test+sso@canonical.com', 'some-password')
        self.assertThat(
            success_page.get_message(),
            Equals('You are now logged into {0}.'.format(self.app_name)))
        success_page.finish()
        # TODO assert that the application is closed.