Merge lp:~mardy/ubuntu-system-settings-online-accounts/fix-ap-tests into lp:ubuntu-system-settings-online-accounts

Proposed by Alberto Mardegan
Status: Rejected
Rejected by: Alberto Mardegan
Proposed branch: lp:~mardy/ubuntu-system-settings-online-accounts/fix-ap-tests
Merge into: lp:ubuntu-system-settings-online-accounts
Diff against target: 542 lines (+93/-396)
4 files modified
debian/control (+1/-1)
debian/tests/autopilot (+6/-0)
debian/tests/control (+2/-0)
tests/autopilot/online_accounts_ui/tests/test_online_accounts_ui.py (+84/-395)
To merge this branch: bzr merge lp:~mardy/ubuntu-system-settings-online-accounts/fix-ap-tests
Reviewer Review Type Date Requested Status
Alexandre Abreu (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+209463@code.launchpad.net

Commit message

Fix tests

Remove the failing (or unstable) tests which required signon-ui. Run autopilot
tests in autopkgtests.

Description of the change

Fix tests

Remove the failing (or unstable) tests which required signon-ui. Run autopilot
tests in autopkgtests.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alexandre Abreu (abreu-alexandre) :
review: Approve
101. By Launchpad Translations on behalf of online-accounts

Launchpad automatic translations update.

102. By Launchpad Translations on behalf of online-accounts

Launchpad automatic translations update.

103. By Launchpad Translations on behalf of online-accounts

Launchpad automatic translations update.

104. By Timo Jyrinki

Manual sync as CI/LP failed in last week's landing.

ubuntu-system-settings-online-accounts (0.3+14.04.20140307.1-0ubuntu1) trusty; urgency=low

  * New rebuild forced

 -- Ubuntu daily release <email address hidden> Fri, 07 Mar 2014 08:37:17 +0000

ubuntu-system-settings-online-accounts (0.3+14.04.20140304.is.0.2~+14.04.20131205-0ubuntu1) trusty; urgency=medium

  * Reverting as desktop image isn't installable (main/universe
    mismatch) and causing all signon-ui tests on Touch failing

 -- Didier Roche <email address hidden> Wed, 05 Mar 2014 12:20:04 +0100

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-03-06 13:36:34 +0000
3+++ debian/control 2014-03-18 15:04:24 +0000
4@@ -22,6 +22,7 @@
5 # code again.
6 Vcs-Bzr: https://code.launchpad.net/~online-accounts/ubuntu-system-settings-online-accounts/trunk
7 X-Python-Version: 2.7
8+XS-Testsuite: autopkgtest
9
10 Package: ubuntu-system-settings-online-accounts
11 Architecture: any
12@@ -89,7 +90,6 @@
13 uoa-integration-tests,
14 libqt5test5,
15 python-autopilot,
16- python-oauth,
17 Description: Online Accounts setup for Ubuntu Touch - tests
18 Online Accounts setup utility for the Ubuntu Touch System Settings.
19 .
20
21=== added directory 'debian/tests'
22=== added file 'debian/tests/autopilot'
23--- debian/tests/autopilot 1970-01-01 00:00:00 +0000
24+++ debian/tests/autopilot 2014-03-18 15:04:24 +0000
25@@ -0,0 +1,6 @@
26+#!/bin/sh
27+
28+set -e
29+
30+autopilot run online_accounts_ui 2> /dev/null
31+
32
33=== added file 'debian/tests/control'
34--- debian/tests/control 1970-01-01 00:00:00 +0000
35+++ debian/tests/control 2014-03-18 15:04:24 +0000
36@@ -0,0 +1,2 @@
37+Tests: autopilot
38+Depends: @
39
40=== modified file 'tests/autopilot/online_accounts_ui/tests/test_online_accounts_ui.py'
41--- tests/autopilot/online_accounts_ui/tests/test_online_accounts_ui.py 2014-03-06 13:36:34 +0000
42+++ tests/autopilot/online_accounts_ui/tests/test_online_accounts_ui.py 2014-03-18 15:04:24 +0000
43@@ -14,228 +14,19 @@
44 from autopilot.matchers import Eventually
45 from testtools.matchers import Contains, Equals, NotEquals, GreaterThan
46 from time import sleep
47-import BaseHTTPServer, SimpleHTTPServer, SocketServer, ssl, cgi
48-import threading
49-import oauth.oauth as oauth
50+import os
51
52 from online_accounts_ui.emulators.items import EmulatorBase
53
54-REQUEST_TOKEN_URL = 'http://localhost:5121/oauth1/request_token'
55-ACCESS_TOKEN_URL = 'http://localhost:5121/oauth1/access_token'
56-AUTHORIZATION_URL = 'http://localhost:5121/oauth1/authorize'
57-CALLBACK_URL = 'http://localhost:5121/success.html'
58-REALM = 'http://photos.example.net/'
59-VERIFIER = 'verifier'
60-
61-class MockOAuthDataStore(oauth.OAuthDataStore):
62- def __init__(self):
63- self.consumer = oauth.OAuthConsumer('C0nsum3rKey', 'C0nsum3rS3cr3t')
64- self.request_token = oauth.OAuthToken('requestkey', 'requestsecret')
65- self.access_token = oauth.OAuthToken('accesskey', 'accesssecret')
66- self.nonce = 'nonce'
67- self.verifier = VERIFIER
68-
69- def lookup_consumer(self, key):
70- if key == self.consumer.key:
71- return self.consumer
72- return None
73-
74- def lookup_token(self, token_type, token):
75- token_attrib = getattr(self, '%s_token' % token_type)
76- if token == token_attrib.key:
77- ## HACK
78- token_attrib.set_callback(CALLBACK_URL)
79- return token_attrib
80- return None
81-
82- def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
83- if oauth_token and oauth_consumer.key == self.consumer.key and (oauth_token.key == self.request_token.key or oauth_token.key == self.access_token.key) and nonce == self.nonce:
84- return self.nonce
85- return None
86-
87- def fetch_request_token(self, oauth_consumer, oauth_callback):
88- if oauth_consumer.key == self.consumer.key:
89- if oauth_callback:
90- # want to check here if callback is sensible
91- # for mock store, we assume it is
92- self.request_token.set_callback(oauth_callback)
93- return self.request_token
94- return None
95-
96- def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
97- if oauth_consumer.key == self.consumer.key and oauth_token.key == self.request_token.key and oauth_verifier == self.verifier:
98- # want to check here if token is authorized
99- # for mock store, we assume it is
100- return self.access_token
101- return None
102-
103- def authorize_request_token(self, oauth_token, user):
104- if oauth_token.key == self.request_token.key:
105- # authorize the request token in the store
106- # for mock store, do nothing
107- self.access_token.username = user
108- return self.request_token
109- return None
110-
111-class OAuth1Handler(BaseHTTPServer.BaseHTTPRequestHandler):
112- def __init__(self, *args, **kwargs):
113- BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
114-
115- def do_HEAD(self):
116- self.send_response(200)
117- self.send_header("Content-type", "text/html")
118- s.end_headers()
119-
120- def do_GET(self):
121- print "Got GET to %s. headers: %s" % (self.path, self.headers)
122- if self.path.startswith('/oauth1/authorize'):
123- oauth_request = oauth.OAuthRequest.from_request(self.command,
124- 'http://localhost:%s%s' % (self.server.server_port, self.path),
125- headers=self.headers)
126- # get the request token
127- self.server.token = self.server.oauth_server.fetch_request_token(oauth_request)
128-
129- self.send_response(200)
130- self.send_header("Content-type", "text/html")
131- self.send_header('Content-Encoding', 'utf-8')
132- self.end_headers()
133- self.wfile.write("""
134- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
135- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
136- <html xmlns="http://www.w3.org/1999/xhtml">
137- <head><title>Login here</title></head>
138- <body>
139- <h3>Login form</h3>
140- <form method="POST" action="http://localhost:%(port)s/login.html">
141- Username: <input type="text" name="username" size="15" /><br />
142- <p><input type="submit" value="Login" /></p>
143- </form>
144- </body>
145- </html>
146- """ % { 'port': self.server.server_port })
147- self.server.show_login_event.set()
148-
149- def do_POST(self):
150- if self.path == '/login.html':
151- form = cgi.FieldStorage(
152- fp=self.rfile,
153- headers=self.headers,
154- environ={'REQUEST_METHOD':'POST',
155- 'CONTENT_TYPE':self.headers['Content-Type'],
156- })
157- # authorize the token
158- token = self.server.oauth_server.authorize_token(self.server.token, form['username'].value)
159- token.set_verifier(VERIFIER)
160- self.send_response(301)
161- self.send_header("Location", token.get_callback_url())
162- self.end_headers()
163- self.server.login_done_event.set()
164- return
165-
166- # construct the oauth request from the request parameters
167- length = int(self.headers.getheader('content-length'))
168- postdata = self.rfile.read(length)
169- oauth_request = oauth.OAuthRequest.from_request(self.command,
170- 'http://localhost:%s%s' % (self.server.server_port, self.path),
171- headers=self.headers, query_string=postdata)
172-
173- if self.path == '/oauth1/request_token':
174- # create a request token
175- token = self.server.oauth_server.fetch_request_token(oauth_request)
176- # send okay response
177- self.send_response(200, 'OK')
178- self.send_header('Content-Type', 'application/x-www-form-urlencoded')
179- self.end_headers()
180- # return the token
181- self.wfile.write(token.to_string())
182- elif self.path == '/oauth1/access_token':
183- # create an access token
184- token = self.server.oauth_server.fetch_access_token(oauth_request)
185- # send okay response
186- self.send_response(200, 'OK')
187- self.send_header('Content-Type', 'application/x-www-form-urlencoded')
188- self.end_headers()
189- # return the token
190- self.wfile.write('%s&ScreenName=%s' % (token.to_string(), token.username))
191-
192-
193-class OAuth1LocalServer:
194- def __init__(self):
195- self.PORT = 5121
196- self.handler = OAuth1Handler
197- self.httpd = BaseHTTPServer.HTTPServer(('localhost', self.PORT), self.handler)
198- self.httpd.oauth_server = oauth.OAuthServer(MockOAuthDataStore())
199- self.httpd.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
200- self.httpd.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
201- self.httpd.show_login_event = threading.Event()
202- self.httpd.login_done_event = threading.Event()
203- self.httpd_thread = threading.Thread(target=self.httpd.serve_forever)
204-
205- def run(self):
206- self.httpd_thread.setDaemon(True)
207- self.httpd_thread.start()
208-
209-
210-class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
211- def do_HEAD(self):
212- self.send_response(200)
213- self.send_header("Content-type", "text/html")
214- s.end_headers()
215-
216- def do_GET(self):
217- self.send_response(200)
218- self.send_header("Content-type", "text/html")
219- self.send_header('Content-Encoding', 'utf-8')
220- self.end_headers()
221- self.wfile.write("""
222-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
223- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
224-<html xmlns="http://www.w3.org/1999/xhtml">
225-<head><title>Login here</title></head>
226-<body>
227-<h3>Login form</h3>
228-<form method="POST" action="https://localhost:%(port)s/login.html">
229- Username: <input type="text" name="username" size="15" /><br />
230- Password: <input type="password" name="password" size="15" /><br />
231- <p><input type="submit" value="Login" /></p>
232-</form>
233-</body>
234-</html>
235-""" % { 'port': self.server.server_port })
236- self.server.show_login_event.set()
237-
238- def do_POST(self):
239- form = cgi.FieldStorage(
240- fp=self.rfile,
241- headers=self.headers,
242- environ={'REQUEST_METHOD':'POST',
243- 'CONTENT_TYPE':self.headers['Content-Type'],
244- })
245- self.send_response(301)
246- self.send_header("Location",
247- "https://localhost:%(port)s/success.html#access_token=%(username)s%(password)s&expires_in=3600" % {
248- 'port': self.server.server_port,
249- 'username': form['username'].value,
250- 'password': form['password'].value
251- })
252- self.end_headers()
253- self.server.login_done_event.set()
254-
255-
256-class LocalServer:
257- def __init__(self):
258- self.PORT = 5120
259- #self.handler = SimpleHTTPServer.SimpleHTTPRequestHandler
260- self.handler = Handler
261- self.httpd = BaseHTTPServer.HTTPServer(("localhost", self.PORT), self.handler)
262- self.httpd.show_login_event = threading.Event()
263- self.httpd.login_done_event = threading.Event()
264- self.httpd.socket = ssl.wrap_socket (self.httpd.socket, certfile='/etc/ssl/certs/uoa-test-server.pem', server_side=True)
265- self.httpd_thread = threading.Thread(target=self.httpd.serve_forever)
266-
267- def run(self):
268- self.httpd_thread.setDaemon(True)
269- self.httpd_thread.start()
270+
271+# You can find a couple of OAuth 1.0a and 2.0 tests in the repository history
272+# (bazaar revision 100).
273+# These tests were removed because the signon-ui window where the webview is
274+# hosted will not be focused properly, both in Mir
275+# (https://bugs.launchpad.net/bugs/1231968) and Compiz
276+# (https://bugs.launchpad.net/bugs/455241)
277+# This issue will be solved when we move the signon-ui implementation in the
278+# same process as online-accounts-ui
279
280
281 class OnlineAccountsUiTests(AutopilotTestCase):
282@@ -264,6 +55,13 @@
283 self.window = self.app.select_single("QQuickView")
284 self.assertThat(self.window.visible, Eventually(Equals(True)))
285
286+ def tearDown(self):
287+ super(OnlineAccountsUiTests, self).tearDown()
288+ # This matches the logic in setUp()
289+ if model() != 'Desktop':
290+ return
291+ self.system_settings.terminate()
292+
293 def test_title(self):
294 """ Checks whether the Online Accounts window title is correct """
295 # On the phone, this fails because of https://bugs.launchpad.net/bugs/1252294
296@@ -287,179 +85,70 @@
297 provider_item = self.app.select_single('Standard', text=provider)
298 self.assertThat(provider_item, NotEquals(None))
299
300- def test_create_oauth1_account(self):
301- """ Test the creation of an OAuth 1.0 account """
302- # On the phone, this fails because of https://bugs.launchpad.net/bugs/1231968
303- if model() != 'Desktop':
304- return
305- server = OAuth1LocalServer()
306- server.run()
307-
308- page = self.app.select_single('NoAccountsPage')
309- self.assertThat(page, NotEquals(None))
310-
311- provider_item = self.app.select_single('Standard', text='FakeOAuthOne')
312- self.assertThat(provider_item, NotEquals(None))
313-
314- # Depending on the number of installed providers, it may be that our
315- # test provider is not visible; in that case, scroll the page
316- self.pointer.move_to_object(page)
317- (page_center_x, page_center_y) = self.pointer.position()
318- page_bottom = page.globalRect[1] + page.globalRect[3]
319- while provider_item.center[1] > page_bottom:
320- self.pointer.move(page_center_x, page_center_y)
321- self.pointer.press()
322- self.pointer.move(page_center_x, page_center_y - provider_item.height * 2)
323- # wait some time before releasing, to avoid a flick
324- sleep(0.2)
325- self.pointer.release()
326- self.pointer.move_to_object(provider_item)
327- self.pointer.click()
328-
329- # At this point, the signon-ui process should be spawned by D-Bus and
330- # try to connect to our local webserver.
331- # Here we wait until we know that the webserver has served the login page:
332- server.httpd.show_login_event.wait(30)
333- self.assertThat(server.httpd.show_login_event.is_set(), Equals(True))
334- server.httpd.show_login_event.clear()
335-
336- # Give some time to signon-ui to render the page
337- sleep(2)
338- #self.signon_ui_window = self.signon_ui.select_single("QQuickView")
339- #self.assertThat(self.signon_ui_window.visible, Eventually(Equals(True)))
340-
341- # Move to the username field
342- self.keyboard.press_and_release('Tab')
343- self.keyboard.type('funnyguy')
344- self.keyboard.press_and_release('Enter')
345-
346- # At this point signon-ui should make a post request with the login
347- # data; let's wait for it:
348- server.httpd.login_done_event.wait(30)
349- self.assertThat(server.httpd.login_done_event.is_set(), Equals(True))
350- server.httpd.login_done_event.clear()
351-
352- if model() == 'Desktop':
353- # Close the signon-ui window
354- self.keyboard.press_and_release('Enter')
355-
356- # The account should be created shortly
357- sleep(5)
358- account_item = self.app.select_single('AccountItem', text='FakeOAuthOne')
359- self.assertThat(account_item, NotEquals(None))
360- self.assertThat(account_item.subText, Equals('funnyguy'))
361-
362- # Delete it
363- self.pointer.move_to_object(account_item)
364- self.pointer.click()
365-
366- sleep(1)
367- edit_page = self.app.select_single('AccountEditPage')
368- self.assertThat(edit_page, NotEquals(None))
369- remove_button = edit_page.select_single('Button')
370- self.assertThat(remove_button, NotEquals(None))
371- self.pointer.move_to_object(remove_button)
372- self.pointer.click()
373-
374- sleep(1)
375- removal_page = self.app.select_single('RemovalConfirmation')
376- self.assertThat(removal_page, NotEquals(None))
377- remove_button = removal_page.select_single('Button', text='Remove')
378- self.assertThat(remove_button, NotEquals(None))
379- self.pointer.move_to_object(remove_button)
380- self.pointer.click()
381-
382- # Check that the account has been deleted
383- sleep(1)
384- account_item = self.app.select_single('AccountItem', text='FakeOAuth')
385- self.assertThat(account_item, Equals(None))
386-
387- def test_create_oauth2_account(self):
388- """ Test the creation of an OAuth 2.0 account """
389- # WebKit2 cannot ignore SSL errors, so this test fails on the phone
390- if model() != 'Desktop':
391- return
392-
393- self.server = LocalServer()
394- self.server.run()
395-
396- page = self.app.select_single('NoAccountsPage')
397- self.assertThat(page, NotEquals(None))
398-
399- provider_item = self.app.select_single('Standard', text='FakeOAuth')
400- self.assertThat(provider_item, NotEquals(None))
401-
402- # Depending on the number of installed providers, it may be that our
403- # test provider is not visible; in that case, scroll the page
404- self.pointer.move_to_object(page)
405- (page_center_x, page_center_y) = self.pointer.position()
406- page_bottom = page.globalRect[1] + page.globalRect[3]
407- while provider_item.center[1] > page_bottom:
408- self.pointer.move(page_center_x, page_center_y)
409- self.pointer.press()
410- self.pointer.move(page_center_x, page_center_y - provider_item.height * 2)
411- # wait some time before releasing, to avoid a flick
412- sleep(0.2)
413- self.pointer.release()
414- self.pointer.move_to_object(provider_item)
415- self.pointer.click()
416-
417- # At this point, the signon-ui process should be spawned by D-Bus and
418- # try to connect to our local webserver.
419- # Here we wait until we know that the webserver has served the login page:
420- self.server.httpd.show_login_event.wait(30)
421- self.assertThat(self.server.httpd.show_login_event.is_set(), Equals(True))
422- self.server.httpd.show_login_event.clear()
423-
424- # Give some time to signon-ui to render the page
425- sleep(2)
426- #self.signon_ui_window = self.signon_ui.select_single("QQuickView")
427- #self.assertThat(self.signon_ui_window.visible, Eventually(Equals(True)))
428-
429- # Move to the username field
430- self.keyboard.press_and_release('Tab')
431- self.keyboard.type('john')
432- self.keyboard.press_and_release('Tab')
433- self.keyboard.type('loser')
434- self.keyboard.press_and_release('Enter')
435-
436- # At this point signon-ui should make a post request with the login
437- # data; let's wait for it:
438- self.server.httpd.login_done_event.wait(30)
439- self.assertThat(self.server.httpd.login_done_event.is_set(), Equals(True))
440- self.server.httpd.login_done_event.clear()
441-
442- if model() == 'Desktop':
443- # Close the signon-ui window
444- self.keyboard.press_and_release('Enter')
445-
446- # The account should be created shortly
447- sleep(5)
448- account_item = self.app.select_single('AccountItem', text='FakeOAuth')
449- self.assertThat(account_item, NotEquals(None))
450- self.assertThat(account_item.subText, Equals('john'))
451-
452- # Delete it
453- self.pointer.move_to_object(account_item)
454- self.pointer.click()
455-
456- sleep(1)
457- edit_page = self.app.select_single('AccountEditPage')
458- self.assertThat(edit_page, NotEquals(None))
459- remove_button = edit_page.select_single('Button')
460- self.assertThat(remove_button, NotEquals(None))
461- self.pointer.move_to_object(remove_button)
462- self.pointer.click()
463-
464- sleep(1)
465- removal_page = self.app.select_single('RemovalConfirmation')
466- self.assertThat(removal_page, NotEquals(None))
467- remove_button = removal_page.select_single('Button', text='Remove')
468- self.assertThat(remove_button, NotEquals(None))
469- self.pointer.move_to_object(remove_button)
470- self.pointer.click()
471-
472- # Check that the account has been deleted
473- sleep(1)
474- account_item = self.app.select_single('AccountItem', text='FakeOAuth')
475- self.assertThat(account_item, Equals(None))
476+ def test_create_account_with_form(self):
477+ """ Test the creation of an account using a username/password form"""
478+ # On the phone, this fails because of https://bugs.launchpad.net/bugs/1252294
479+ if model() != 'Desktop':
480+ return
481+ page = self.app.select_single('NoAccountsPage')
482+ self.assertThat(page, NotEquals(None))
483+
484+ provider_item = self.app.select_single('Standard', text='TestLogin')
485+ self.assertThat(provider_item, NotEquals(None))
486+
487+ # Depending on the number of installed providers, it may be that our
488+ # test provider is not visible; in that case, scroll the page
489+ self.pointer.move_to_object(page)
490+ (page_center_x, page_center_y) = self.pointer.position()
491+ page_bottom = page.globalRect[1] + page.globalRect[3]
492+ while provider_item.center[1] > page_bottom - 20:
493+ self.pointer.move(page_center_x, page_center_y)
494+ self.pointer.press()
495+ self.pointer.move(page_center_x, page_center_y - provider_item.height * 2)
496+ # wait some time before releasing, to avoid a flick
497+ sleep(0.2)
498+ self.pointer.release()
499+ self.pointer.move_to_object(provider_item)
500+ self.pointer.click()
501+
502+ # Move to the username field
503+ username_field = self.app.select_single('TextField', objectName='usernameField')
504+ self.pointer.move_to_object(username_field)
505+ self.pointer.click()
506+ self.keyboard.type('pinkuser')
507+ self.keyboard.press_and_release('Tab')
508+ self.keyboard.type('lolcat')
509+ # Submit
510+ continue_btn = self.app.select_single('Button', objectName='continueButton')
511+ self.pointer.move_to_object(continue_btn)
512+ self.pointer.click()
513+
514+ # The account should be created shortly
515+ sleep(5)
516+ account_item = self.app.select_single('AccountItem', text='TestLogin')
517+ self.assertThat(account_item, NotEquals(None))
518+ self.assertThat(account_item.subText, Equals('pinkuser'))
519+
520+ # Delete it
521+ self.pointer.move_to_object(account_item)
522+ self.pointer.click()
523+
524+ sleep(1)
525+ edit_page = self.app.select_single('AccountEditPage')
526+ self.assertThat(edit_page, NotEquals(None))
527+ remove_button = edit_page.select_single('Button')
528+ self.assertThat(remove_button, NotEquals(None))
529+ self.pointer.move_to_object(remove_button)
530+ self.pointer.click()
531+
532+ sleep(1)
533+ removal_page = self.app.select_single('RemovalConfirmation')
534+ self.assertThat(removal_page, NotEquals(None))
535+ remove_button = removal_page.select_single('Button', text='Remove')
536+ self.assertThat(remove_button, NotEquals(None))
537+ self.pointer.move_to_object(remove_button)
538+ self.pointer.click()
539+
540+ # Check that the account has been deleted
541+ account_item.wait_until_destroyed()
542+

Subscribers

People subscribed via source and target branches