From 1b1669352649a47d9b7564516e3a45cf9d65e732 Mon Sep 17 00:00:00 2001
From: Steve Howell 
Date: Fri, 6 Mar 2020 17:40:46 +0000
Subject: [PATCH] tests: Limit email-based logins.
We now have this API...
If you really just need to log in
and not do anything with the actual
user:
    self.login('hamlet')
If you're gonna use the user in the
rest of the test:
    hamlet = self.example_user('hamlet')
    self.login_user(hamlet)
If you are specifically testing
email/password logins (used only in 4 places):
    self.login_by_email(email, password)
And for failures uses this (used twice):
    self.assert_login_failure(email)
---
 analytics/tests/test_views.py                 |  87 ++++---
 corporate/tests/test_stripe.py                |  62 +++--
 zerver/lib/test_classes.py                    |  66 +++--
 zerver/tests/test_alert_words.py              |  16 +-
 zerver/tests/test_attachments.py              |  10 +-
 zerver/tests/test_audit_log.py                |   2 +-
 zerver/tests/test_auth_backends.py            |  30 ++-
 zerver/tests/test_bots.py                     | 157 ++++++------
 zerver/tests/test_create_video_call.py        |   2 +-
 zerver/tests/test_custom_profile_data.py      |  46 ++--
 zerver/tests/test_decorators.py               | 146 +++++------
 zerver/tests/test_digest.py                   |   2 +-
 zerver/tests/test_docs.py                     |   4 +-
 zerver/tests/test_email_change.py             |  30 +--
 zerver/tests/test_email_mirror.py             |  76 +++---
 zerver/tests/test_email_notifications.py      |   9 +-
 zerver/tests/test_event_queue.py              |   3 +-
 zerver/tests/test_events.py                   |  10 +-
 zerver/tests/test_home.py                     |  71 +++---
 zerver/tests/test_hotspots.py                 |   2 +-
 zerver/tests/test_i18n.py                     |   8 +-
 zerver/tests/test_legacy_subject.py           |   2 +-
 zerver/tests/test_link_embed.py               |  12 +-
 zerver/tests/test_logging_handlers.py         |   3 +-
 zerver/tests/test_management_commands.py      |   6 +-
 .../tests/test_message_edit_notifications.py  |   4 +-
 zerver/tests/test_messages.py                 | 226 +++++++++---------
 zerver/tests/test_muting.py                   |  11 +-
 zerver/tests/test_narrow.py                   |  85 ++++---
 zerver/tests/test_new_users.py                |   8 +-
 zerver/tests/test_presence.py                 |  57 +++--
 zerver/tests/test_push_notifications.py       |   6 +-
 zerver/tests/test_realm.py                    |  64 ++---
 zerver/tests/test_realm_domains.py            |  14 +-
 zerver/tests/test_realm_emoji.py              |  65 ++---
 zerver/tests/test_realm_export.py             |  10 +-
 zerver/tests/test_realm_filters.py            |  12 +-
 zerver/tests/test_report.py                   |  15 +-
 zerver/tests/test_service_bot_system.py       |   2 +-
 zerver/tests/test_sessions.py                 |  29 +--
 zerver/tests/test_settings.py                 |  69 +++---
 zerver/tests/test_signup.py                   | 130 +++++-----
 zerver/tests/test_submessage.py               |   4 +-
 zerver/tests/test_subs.py                     | 168 ++++++-------
 zerver/tests/test_thumbnail.py                |  20 +-
 zerver/tests/test_tornado.py                  |   8 +-
 zerver/tests/test_transfer.py                 |   2 +-
 zerver/tests/test_tutorial.py                 |   9 +-
 zerver/tests/test_unread.py                   |  28 +--
 zerver/tests/test_upload.py                   | 188 ++++++++-------
 zerver/tests/test_user_groups.py              |  44 ++--
 zerver/tests/test_user_status.py              |   2 +-
 zerver/tests/test_users.py                    |  64 ++---
 zerver/tests/test_zcommand.py                 |   8 +-
 zerver/tests/test_zephyr.py                   |   6 +-
 55 files changed, 1071 insertions(+), 1149 deletions(-)
diff --git a/analytics/tests/test_views.py b/analytics/tests/test_views.py
index f751fad68d..e546b87237 100644
--- a/analytics/tests/test_views.py
+++ b/analytics/tests/test_views.py
@@ -22,7 +22,7 @@ from zerver.models import Client, get_realm, MultiuseInvite
 class TestStatsEndpoint(ZulipTestCase):
     def test_stats(self) -> None:
         self.user = self.example_user('hamlet')
-        self.login(self.user.email)
+        self.login_user(self.user)
         result = self.client_get('/stats')
         self.assertEqual(result.status_code, 200)
         # Check that we get something back
@@ -30,7 +30,7 @@ class TestStatsEndpoint(ZulipTestCase):
 
     def test_guest_user_cant_access_stats(self) -> None:
         self.user = self.example_user('polonius')
-        self.login(self.user.email)
+        self.login_user(self.user)
         result = self.client_get('/stats')
         self.assert_json_error(result, "Not allowed for guest users", 400)
 
@@ -38,15 +38,15 @@ class TestStatsEndpoint(ZulipTestCase):
         self.assert_json_error(result, "Not allowed for guest users", 400)
 
     def test_stats_for_realm(self) -> None:
-        user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         result = self.client_get('/stats/realm/zulip/')
         self.assertEqual(result.status_code, 302)
 
-        user_profile = self.example_user('hamlet')
-        user_profile.is_staff = True
-        user_profile.save(update_fields=['is_staff'])
+        user = self.example_user('hamlet')
+        user.is_staff = True
+        user.save(update_fields=['is_staff'])
 
         result = self.client_get('/stats/realm/not_existing_realm/')
         self.assertEqual(result.status_code, 302)
@@ -56,15 +56,15 @@ class TestStatsEndpoint(ZulipTestCase):
         self.assert_in_response("Zulip analytics for", result)
 
     def test_stats_for_installation(self) -> None:
-        user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         result = self.client_get('/stats/installation')
         self.assertEqual(result.status_code, 302)
 
-        user_profile = self.example_user('hamlet')
-        user_profile.is_staff = True
-        user_profile.save(update_fields=['is_staff'])
+        user = self.example_user('hamlet')
+        user.is_staff = True
+        user.save(update_fields=['is_staff'])
 
         result = self.client_get('/stats/installation')
         self.assertEqual(result.status_code, 200)
@@ -75,7 +75,7 @@ class TestGetChartData(ZulipTestCase):
         super().setUp()
         self.realm = get_realm('zulip')
         self.user = self.example_user('hamlet')
-        self.login(self.user.email)
+        self.login_user(self.user)
         self.end_times_hour = [ceiling_to_hour(self.realm.date_created) + timedelta(hours=i)
                                for i in range(4)]
         self.end_times_day = [ceiling_to_day(self.realm.date_created) + timedelta(days=i)
@@ -293,16 +293,16 @@ class TestGetChartData(ZulipTestCase):
         self.assert_json_error_contains(result, 'No analytics data available')
 
     def test_get_chart_data_for_realm(self) -> None:
-        user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         result = self.client_get('/json/analytics/chart_data/realm/zulip/',
                                  {'chart_name': 'number_of_humans'})
         self.assert_json_error(result, "Must be an server administrator", 400)
 
-        user_profile = self.example_user('hamlet')
-        user_profile.is_staff = True
-        user_profile.save(update_fields=['is_staff'])
+        user = self.example_user('hamlet')
+        user.is_staff = True
+        user.save(update_fields=['is_staff'])
         stat = COUNT_STATS['realm_active_humans::day']
         self.insert_data(stat, [None], [])
 
@@ -315,16 +315,16 @@ class TestGetChartData(ZulipTestCase):
         self.assert_json_success(result)
 
     def test_get_chart_data_for_installation(self) -> None:
-        user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         result = self.client_get('/json/analytics/chart_data/installation',
                                  {'chart_name': 'number_of_humans'})
         self.assert_json_error(result, "Must be an server administrator", 400)
 
-        user_profile = self.example_user('hamlet')
-        user_profile.is_staff = True
-        user_profile.save(update_fields=['is_staff'])
+        user = self.example_user('hamlet')
+        user.is_staff = True
+        user.save(update_fields=['is_staff'])
         stat = COUNT_STATS['realm_active_humans::day']
         self.insert_data(stat, [None], [])
 
@@ -398,15 +398,13 @@ class TestSupportEndpoint(ZulipTestCase):
                                              'Expires in: 1\xa0day'
                                              ], result)
 
-        cordelia_email = self.example_email("cordelia")
-        self.login(cordelia_email)
+        self.login('cordelia')
 
         result = self.client_get("/activity/support")
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/login/")
 
-        iago_email = self.example_email("iago")
-        self.login(iago_email)
+        self.login('iago')
 
         result = self.client_get("/activity/support")
         self.assert_in_success_response([' None:
-        cordelia = self.example_user("cordelia")
-        self.login(cordelia.email)
+        cordelia = self.example_user('cordelia')
+        self.login_user(cordelia)
 
         result = self.client_post("/activity/support", {"realm_id": "%s" % (cordelia.realm_id,), "plan_type": "2"})
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/login/")
 
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
 
         with mock.patch("analytics.views.do_change_plan_type") as m:
             result = self.client_post("/activity/support", {"realm_id": "%s" % (iago.realm_id,), "plan_type": "2"})
@@ -482,16 +480,15 @@ class TestSupportEndpoint(ZulipTestCase):
             self.assert_in_success_response(["Plan type of Zulip Dev changed from self hosted to limited"], result)
 
     def test_attach_discount(self) -> None:
-        lear_realm = get_realm("lear")
-        cordelia_email = self.example_email("cordelia")
-        self.login(cordelia_email)
+        cordelia = self.example_user('cordelia')
+        lear_realm = get_realm('lear')
+        self.login_user(cordelia)
 
         result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "discount": "25"})
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/login/")
 
-        iago_email = self.example_email("iago")
-        self.login(iago_email)
+        self.login('iago')
 
         with mock.patch("analytics.views.attach_discount_to_realm") as m:
             result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "discount": "25"})
@@ -499,16 +496,15 @@ class TestSupportEndpoint(ZulipTestCase):
             self.assert_in_success_response(["Discount of Lear & Co. changed to 25 from None"], result)
 
     def test_activate_or_deactivate_realm(self) -> None:
-        lear_realm = get_realm("lear")
-        cordelia_email = self.example_email("cordelia")
-        self.login(cordelia_email)
+        cordelia = self.example_user('cordelia')
+        lear_realm = get_realm('lear')
+        self.login_user(cordelia)
 
         result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "status": "deactivated"})
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/login/")
 
-        iago_email = self.example_email("iago")
-        self.login(iago_email)
+        self.login('iago')
 
         with mock.patch("analytics.views.do_deactivate_realm") as m:
             result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "status": "deactivated"})
@@ -521,16 +517,15 @@ class TestSupportEndpoint(ZulipTestCase):
             self.assert_in_success_response(["Realm reactivation email sent to admins of Lear"], result)
 
     def test_scrub_realm(self) -> None:
-        lear_realm = get_realm("lear")
-        cordelia_email = self.example_email("cordelia")
-        self.login(cordelia_email)
+        cordelia = self.example_user('cordelia')
+        lear_realm = get_realm('lear')
+        self.login_user(cordelia)
 
         result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "discount": "25"})
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/login/")
 
-        iago_email = self.example_email("iago")
-        self.login(iago_email)
+        self.login('iago')
 
         with mock.patch("analytics.views.do_scrub_realm") as m:
             result = self.client_post("/activity/support", {"realm_id": "%s" % (lear_realm.id,), "scrub_realm": "scrub_realm"})
diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py
index e2d6c2f72e..c8bbcd7274 100644
--- a/corporate/tests/test_stripe.py
+++ b/corporate/tests/test_stripe.py
@@ -320,15 +320,16 @@ class StripeTest(StripeTestCase):
         mock_billing_logger_error.assert_called()
 
     def test_billing_not_enabled(self) -> None:
+        iago = self.example_user('iago')
         with self.settings(BILLING_ENABLED=False):
-            self.login(self.example_email("iago"))
+            self.login_user(iago)
             response = self.client_get("/upgrade/")
             self.assert_in_success_response(["Page not found (404)"], response)
 
     @mock_stripe(tested_timestamp_fields=["created"])
     def test_upgrade_by_card(self, *mocks: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         response = self.client_get("/upgrade/")
         self.assert_in_success_response(['Pay annually'], response)
         self.assertNotEqual(user.realm.plan_type, Realm.STANDARD)
@@ -438,7 +439,7 @@ class StripeTest(StripeTestCase):
     @mock_stripe(tested_timestamp_fields=["created"])
     def test_upgrade_by_invoice(self, *mocks: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         # Click "Make payment" in Stripe Checkout
         with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
             self.upgrade(invoice=True)
@@ -520,8 +521,12 @@ class StripeTest(StripeTestCase):
 
     @mock_stripe()
     def test_billing_page_permissions(self, *mocks: Mock) -> None:
+        hamlet = self.example_user('hamlet')
+        iago = self.example_user('iago')
+        cordelia = self.example_user('cordelia')
+
         # Check that non-admins can access /upgrade via /billing, when there is no Customer object
-        self.login(self.example_email('hamlet'))
+        self.login_user(hamlet)
         response = self.client_get("/billing/")
         self.assertEqual(response.status_code, 302)
         self.assertEqual('/upgrade/', response.url)
@@ -531,17 +536,18 @@ class StripeTest(StripeTestCase):
         response = self.client_get("/billing/")
         self.assert_in_success_response(["for billing history or to make changes"], response)
         # Check admins can access billing, even though they are not a billing admin
-        self.login(self.example_email('iago'))
+        self.login_user(iago)
         response = self.client_get("/billing/")
         self.assert_in_success_response(["for billing history or to make changes"], response)
         # Check that a non-admin, non-billing admin user does not have access
-        self.login(self.example_email("cordelia"))
+        self.login_user(cordelia)
         response = self.client_get("/billing/")
         self.assert_in_success_response(["You must be an organization administrator"], response)
 
     @mock_stripe(tested_timestamp_fields=["created"])
     def test_upgrade_by_card_with_outdated_seat_count(self, *mocks: Mock) -> None:
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         new_seat_count = 23
         # Change the seat count while the user is going through the upgrade flow
         with patch('corporate.lib.stripe.get_latest_seat_count', return_value=new_seat_count):
@@ -561,7 +567,7 @@ class StripeTest(StripeTestCase):
     @mock_stripe()
     def test_upgrade_where_first_card_fails(self, *mocks: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         # From https://stripe.com/docs/testing#cards: Attaching this card to
         # a Customer object succeeds, but attempts to charge the customer fail.
         with patch("corporate.lib.stripe.billing_logger.error") as mock_billing_logger:
@@ -627,13 +633,15 @@ class StripeTest(StripeTestCase):
         self.assertEqual('/billing/', response.url)
 
     def test_upgrade_with_tampered_seat_count(self) -> None:
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         response = self.upgrade(talk_to_stripe=False, salt='badsalt')
         self.assert_json_error_contains(response, "Something went wrong. Please contact")
         self.assertEqual(ujson.loads(response.content)['error_description'], 'tampered seat count')
 
     def test_upgrade_race_condition(self) -> None:
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
         with patch("corporate.lib.stripe.billing_logger.warning") as mock_billing_logger:
             with self.assertRaises(BillingError) as context:
@@ -649,7 +657,8 @@ class StripeTest(StripeTestCase):
             self.assert_json_error_contains(response, "Something went wrong. Please contact")
             self.assertEqual(ujson.loads(response.content)['error_description'], error_description)
 
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         check_error('unknown billing_modality', {'billing_modality': 'invalid'})
         check_error('unknown schedule', {'schedule': 'invalid'})
         check_error('unknown license_management', {'license_management': 'invalid'})
@@ -679,7 +688,8 @@ class StripeTest(StripeTestCase):
                                         del_args=del_args, **upgrade_params)
             self.assert_json_success(response)
 
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         # Autopay with licenses < seat count
         check_error(False, self.seat_count - 1, self.seat_count, {'license_management': 'manual'})
         # Autopay with not setting licenses
@@ -703,7 +713,8 @@ class StripeTest(StripeTestCase):
 
     @patch("corporate.lib.stripe.billing_logger.error")
     def test_upgrade_with_uncaught_exception(self, mock_: Mock) -> None:
-        self.login(self.example_email("hamlet"))
+        hamlet = self.example_user('hamlet')
+        self.login_user(hamlet)
         with patch("corporate.views.process_initial_upgrade", side_effect=Exception):
             response = self.upgrade(talk_to_stripe=False)
         self.assert_json_error_contains(response, "Something went wrong. Please contact zulip-admin@example.com.")
@@ -711,7 +722,7 @@ class StripeTest(StripeTestCase):
 
     def test_redirect_for_billing_home(self) -> None:
         user = self.example_user("iago")
-        self.login(user.email)
+        self.login_user(user)
         # No Customer yet; check that we are redirected to /upgrade
         response = self.client_get("/billing/")
         self.assertEqual(response.status_code, 302)
@@ -776,7 +787,7 @@ class StripeTest(StripeTestCase):
         # "Billed by invoice", even if you have a card on file
         # user = self.example_user("hamlet")
         # do_create_stripe_customer(user, stripe_create_token().id)
-        # self.login(user.email)
+        # self.login_user(user)
         # self.upgrade(invoice=True)
         # stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
         # self.assertEqual('Billed by invoice', payment_method_string(stripe_customer))
@@ -790,7 +801,7 @@ class StripeTest(StripeTestCase):
         # Attach discount before Stripe customer exists
         user = self.example_user('hamlet')
         attach_discount_to_realm(user.realm, Decimal(85))
-        self.login(user.email)
+        self.login_user(user)
         # Check that the discount appears in page_params
         self.assert_in_success_response(['85'], self.client_get("/upgrade/"))
         # Check that the customer was charged the discounted amount
@@ -826,7 +837,7 @@ class StripeTest(StripeTestCase):
     @mock_stripe()
     def test_replace_payment_source(self, *mocks: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         self.upgrade()
         # Create an open invoice
         stripe_customer_id = Customer.objects.first().stripe_customer_id
@@ -883,7 +894,7 @@ class StripeTest(StripeTestCase):
     @patch("corporate.lib.stripe.billing_logger.info")
     def test_downgrade(self, mock_: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         with patch("corporate.lib.stripe.timezone_now", return_value=self.now):
             self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
         response = self.client_post("/json/billing/plan/change",
@@ -948,7 +959,7 @@ class StripeTest(StripeTestCase):
         # This test is essentially checking that we call make_end_of_cycle_updates_if_needed
         # during the invoicing process.
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         with patch("corporate.lib.stripe.timezone_now", return_value=self.now):
             self.local_upgrade(self.seat_count, True, CustomerPlan.ANNUAL, 'token')
         self.client_post("/json/billing/plan/change",
@@ -971,7 +982,8 @@ class RequiresBillingAccessTest(ZulipTestCase):
 
     def verify_non_admins_blocked_from_endpoint(
             self, url: str, request_data: Optional[Dict[str, Any]]={}) -> None:
-        self.login(self.example_email('cordelia'))
+        cordelia = self.example_user('cordelia')
+        self.login_user(cordelia)
         response = self.client_post(url, request_data)
         self.assert_json_error_contains(response, "Must be a billing administrator or an organization")
 
@@ -996,7 +1008,9 @@ class RequiresBillingAccessTest(ZulipTestCase):
 
     def test_admins_and_billing_admins_can_access(self) -> None:
         # Billing admins have access
-        self.login(self.example_email('hamlet'))
+        hamlet = self.example_user('hamlet')
+        iago = self.example_user('iago')
+        self.login_user(hamlet)
         with patch("corporate.views.do_replace_payment_source") as mocked1:
             response = self.client_post("/json/billing/sources/change",
                                         {'stripe_token': ujson.dumps('token')})
@@ -1004,7 +1018,7 @@ class RequiresBillingAccessTest(ZulipTestCase):
         mocked1.assert_called()
 
         # Realm admins have access, even if they are not billing admins
-        self.login(self.example_email('iago'))
+        self.login_user(iago)
         with patch("corporate.views.do_replace_payment_source") as mocked2:
             response = self.client_post("/json/billing/sources/change",
                                         {'stripe_token': ujson.dumps('token')})
@@ -1191,7 +1205,7 @@ class InvoiceTest(StripeTestCase):
     @mock_stripe()
     def test_invoice_plan(self, *mocks: Mock) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
             self.upgrade()
         # Increase
@@ -1253,7 +1267,7 @@ class InvoiceTest(StripeTestCase):
     def test_fixed_price_plans(self, *mocks: Mock) -> None:
         # Also tests charge_automatically=False
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         with patch('corporate.lib.stripe.timezone_now', return_value=self.now):
             self.upgrade(invoice=True)
         plan = CustomerPlan.objects.first()
diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py
index 36acb7d947..b5c68ea0a5 100644
--- a/zerver/lib/test_classes.py
+++ b/zerver/lib/test_classes.py
@@ -308,9 +308,12 @@ class ZulipTestCase(TestCase):
     def notification_bot(self) -> UserProfile:
         return get_system_bot(settings.NOTIFICATION_BOT)
 
-    def create_test_bot(self, short_name: str, user_profile: UserProfile, full_name: str='Foo Bot',
-                        assert_json_error_msg: str=None, **extras: Any) -> Optional[UserProfile]:
-        self.login(user_profile.delivery_email)
+    def create_test_bot(self, short_name: str,
+                        user_profile: UserProfile,
+                        full_name: str='Foo Bot',
+                        assert_json_error_msg: str=None,
+                        **extras: Any) -> Optional[UserProfile]:
+        self.login_user(user_profile)
         bot_info = {
             'short_name': short_name,
             'full_name': full_name,
@@ -336,18 +339,51 @@ class ZulipTestCase(TestCase):
         self.assertNotEqual(result.status_code, 500)
         return result
 
-    def login(self, email: str, password: Optional[str]=None, fails: bool=False,
-              realm: Optional[Realm]=None) -> HttpResponse:
-        if realm is None:
-            realm = get_realm("zulip")
-        if password is None:
-            password = initial_password(email)
-        if not fails:
-            self.assertTrue(self.client.login(username=email, password=password,
-                                              realm=realm))
-        else:
-            self.assertFalse(self.client.login(username=email, password=password,
-                                               realm=realm))
+    def login(self, name: str) -> None:
+        '''
+        Use this for really simple tests where you just need
+        to be logged in as some user, but don't need the actual
+        user object for anything else.  Try to use 'hamlet' for
+        non-admins and 'iago' for admins:
+
+            self.login('hamlet')
+
+        Try to use 'cordelia' or 'othello' as "other" users.
+        '''
+        assert '@' not in name, 'use login_by_email for email logins'
+        user = self.example_user(name)
+        self.login_user(user)
+
+    def login_by_email(self,
+                       email: str,
+                       password: str) -> None:
+        realm = get_realm("zulip")
+        self.assertTrue(
+            self.client.login(
+                username=email,
+                password=password,
+                realm=realm,
+            )
+        )
+
+    def assert_login_failure(self,
+                             email: str,
+                             password: str) -> None:
+        realm = get_realm("zulip")
+        self.assertFalse(
+            self.client.login(
+                username=email,
+                password=password,
+                realm=realm,
+            )
+        )
+
+    def login_user(self, user_profile: UserProfile) -> None:
+        email = user_profile.delivery_email
+        realm = user_profile.realm
+        password = initial_password(email)
+        self.assertTrue(self.client.login(username=email, password=password,
+                                          realm=realm))
 
     def login_2fa(self, user_profile: UserProfile) -> None:
         """
diff --git a/zerver/tests/test_alert_words.py b/zerver/tests/test_alert_words.py
index b37d31f78c..9e75a0547d 100644
--- a/zerver/tests/test_alert_words.py
+++ b/zerver/tests/test_alert_words.py
@@ -27,8 +27,7 @@ class AlertWordTests(ZulipTestCase):
 
     def test_internal_endpoint(self) -> None:
         user_name = "cordelia"
-        email = self.example_email(user_name)
-        self.login(email)
+        self.login(user_name)
 
         params = {
             'alert_words': ujson.dumps(['milk', 'cookies'])
@@ -98,7 +97,7 @@ class AlertWordTests(ZulipTestCase):
         self.assertEqual(realm_words[user2.id], ['another'])
 
     def test_json_list_default(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_get('/json/users/me/alert_words')
         self.assert_json_success(result)
@@ -108,20 +107,20 @@ class AlertWordTests(ZulipTestCase):
         hamlet = self.example_user('hamlet')
         add_user_alert_words(hamlet, ['one', 'two', 'three'])
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         result = self.client_get('/json/users/me/alert_words')
         self.assert_json_success(result)
         self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])
 
     def test_json_list_add(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one ', '\n two', 'three'])})
         self.assert_json_success(result)
         self.assertEqual(result.json()['alert_words'], ['one', 'two', 'three'])
 
     def test_json_list_remove(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
         self.assert_json_success(result)
@@ -138,7 +137,7 @@ class AlertWordTests(ZulipTestCase):
         return 'has_alert_word' in user_message.flags_list()
 
     def test_alert_flags(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile_hamlet = self.example_user('hamlet')
 
         result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['one', 'two', 'three'])})
@@ -166,9 +165,8 @@ class AlertWordTests(ZulipTestCase):
 
     def test_update_alert_words(self) -> None:
         user_profile = self.example_user('hamlet')
-        me_email = user_profile.email
 
-        self.login(me_email)
+        self.login_user(user_profile)
         result = self.client_post('/json/users/me/alert_words', {'alert_words': ujson.dumps(['ALERT'])})
 
         content = 'this is an ALERT for you'
diff --git a/zerver/tests/test_attachments.py b/zerver/tests/test_attachments.py
index ac19bde7ca..cd65266cfc 100644
--- a/zerver/tests/test_attachments.py
+++ b/zerver/tests/test_attachments.py
@@ -18,7 +18,7 @@ class AttachmentsTests(ZulipTestCase):
 
     def test_list_by_user(self) -> None:
         user_profile = self.example_user('cordelia')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_get('/json/attachments')
         self.assert_json_success(result)
         attachments = user_attachments(user_profile)
@@ -26,7 +26,7 @@ class AttachmentsTests(ZulipTestCase):
 
     def test_remove_attachment_exception(self) -> None:
         user_profile = self.example_user('cordelia')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         with mock.patch('zerver.lib.attachments.delete_message_image', side_effect=Exception()):
             result = self.client_delete('/json/attachments/{id}'.format(id=self.attachment.id))
         self.assert_json_error(result, "An error occurred while deleting the attachment. Please try again later.")
@@ -34,7 +34,7 @@ class AttachmentsTests(ZulipTestCase):
     @mock.patch('zerver.lib.attachments.delete_message_image')
     def test_remove_attachment(self, ignored: Any) -> None:
         user_profile = self.example_user('cordelia')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_delete('/json/attachments/{id}'.format(id=self.attachment.id))
         self.assert_json_success(result)
         attachments = user_attachments(user_profile)
@@ -42,14 +42,14 @@ class AttachmentsTests(ZulipTestCase):
 
     def test_list_another_user(self) -> None:
         user_profile = self.example_user('iago')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_get('/json/attachments')
         self.assert_json_success(result)
         self.assertEqual(result.json()['attachments'], [])
 
     def test_remove_another_user(self) -> None:
         user_profile = self.example_user('iago')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_delete('/json/attachments/{id}'.format(id=self.attachment.id))
         self.assert_json_error(result, 'Invalid attachment')
         user_profile_to_remove = self.example_user('cordelia')
diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py
index fd5183f72c..7248577455 100644
--- a/zerver/tests/test_audit_log.py
+++ b/zerver/tests/test_audit_log.py
@@ -100,7 +100,7 @@ class TestRealmAuditLog(ZulipTestCase):
     def test_change_full_name(self) -> None:
         start = timezone_now()
         new_name = 'George Hamletovich'
-        self.login(self.example_email("iago"))
+        self.login('iago')
         req = dict(full_name=ujson.dumps(new_name))
         result = self.client_patch('/json/users/{}'.format(self.example_user("hamlet").id), req)
         self.assertTrue(result.status_code == 200)
diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py
index 3aeccb0927..531a0e575a 100644
--- a/zerver/tests/test_auth_backends.py
+++ b/zerver/tests/test_auth_backends.py
@@ -268,7 +268,7 @@ class AuthBackendTest(ZulipTestCase):
     def test_login_preview(self) -> None:
         # Test preview=true displays organization login page
         # instead of redirecting to app
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm("zulip")
         result = self.client_get('/login/?preview=true')
         self.assertEqual(result.status_code, 200)
@@ -2100,29 +2100,27 @@ class GoogleAuthBackendTest(SocialAuthBase):
         self.assert_json_error(result, "Invalid subdomain")
 
 class JSONFetchAPIKeyTest(ZulipTestCase):
-    def setUp(self) -> None:
-        super().setUp()
-        self.user_profile = self.example_user('hamlet')
-        self.email = self.user_profile.email
-
     def test_success(self) -> None:
-        self.login(self.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
         result = self.client_post("/json/fetch_api_key",
-                                  dict(user_profile=self.user_profile,
-                                       password=initial_password(self.email)))
+                                  dict(user_profile=user,
+                                       password=initial_password(user.email)))
         self.assert_json_success(result)
 
     def test_not_loggedin(self) -> None:
+        user = self.example_user('hamlet')
         result = self.client_post("/json/fetch_api_key",
-                                  dict(user_profile=self.user_profile,
-                                       password=initial_password(self.email)))
+                                  dict(user_profile=user,
+                                       password=initial_password(user.email)))
         self.assert_json_error(result,
                                "Not logged in: API authentication or user session required", 401)
 
     def test_wrong_password(self) -> None:
-        self.login(self.email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
         result = self.client_post("/json/fetch_api_key",
-                                  dict(user_profile=self.user_profile,
+                                  dict(user_profile=user,
                                        password="wrong"))
         self.assert_json_error(result, "Your username or password is incorrect.", 400)
 
@@ -3780,7 +3778,7 @@ class TestAdminSetBackends(ZulipTestCase):
 
     def test_change_enabled_backends(self) -> None:
         # Log in as admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_patch("/json/realm", {
             'authentication_methods': ujson.dumps({u'Email': False, u'Dev': True})})
         self.assert_json_success(result)
@@ -3790,7 +3788,7 @@ class TestAdminSetBackends(ZulipTestCase):
 
     def test_disable_all_backends(self) -> None:
         # Log in as admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_patch("/json/realm", {
             'authentication_methods': ujson.dumps({u'Email': False, u'Dev': False})})
         self.assert_json_error(result, 'At least one authentication method must be enabled.')
@@ -3800,7 +3798,7 @@ class TestAdminSetBackends(ZulipTestCase):
 
     def test_supported_backends_only_updated(self) -> None:
         # Log in as admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         # Set some supported and unsupported backends
         result = self.client_patch("/json/realm", {
             'authentication_methods': ujson.dumps({u'Email': False, u'Dev': True, u'GitHub': False})})
diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py
index 6def44679b..ad36d1f418 100644
--- a/zerver/tests/test_bots.py
+++ b/zerver/tests/test_bots.py
@@ -59,7 +59,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         return result.json()
 
     def test_bot_domain(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assertTrue(UserProfile.objects.filter(email='hambot-bot@zulip.testserver').exists())
         # The other cases are hard to test directly, since we don't allow creating bots from
@@ -74,7 +74,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_success(result)
 
     def test_add_bot_with_bad_username(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
 
         # Invalid username
@@ -97,7 +97,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @override_settings(FAKE_EMAIL_DOMAIN="invaliddomain")
     def test_add_bot_with_invalid_fake_email_domain(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         bot_info = {
             'full_name': 'The Bot of Hamlet',
@@ -112,7 +112,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_num_bots_equal(0)
 
     def test_add_bot_with_no_name(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         bot_info = dict(
             full_name='a',
@@ -124,7 +124,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     def test_json_users_with_bots(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         self.assert_num_bots_equal(0)
 
         num_bots = 3
@@ -149,7 +149,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_length(queries, 3)
 
     def test_add_bot(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         events = []  # type: List[Mapping[str, Any]]
         with tornado_redirected_to_list(events):
@@ -189,7 +189,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(bot['user_id'], self.get_bot_user(email).id)
 
     def test_add_bot_with_username_in_use(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.create_bot()
         self.assert_num_bots_equal(1)
@@ -215,7 +215,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
     def test_add_bot_with_user_avatar(self) -> None:
         email = 'hambot-bot@zulip.testserver'
         realm = get_realm('zulip')
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         with get_test_image_file('img.png') as fp:
             self.create_bot(file=fp)
@@ -230,7 +230,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertTrue(os.path.exists(avatar_disk_path(profile)))
 
     def test_add_bot_with_too_many_files(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         with get_test_image_file('img.png') as fp1, \
                 get_test_image_file('img.gif') as fp2:
@@ -247,7 +247,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
     def test_add_bot_with_default_sending_stream(self) -> None:
         email = 'hambot-bot@zulip.testserver'
         realm = get_realm('zulip')
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.create_bot(default_sending_stream='Denmark')
         self.assert_num_bots_equal(1)
@@ -260,7 +260,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
     def test_add_bot_with_default_sending_stream_not_subscribed(self) -> None:
         email = 'hambot-bot@zulip.testserver'
         realm = get_realm('zulip')
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.create_bot(default_sending_stream='Rome')
         self.assert_num_bots_equal(1)
@@ -278,7 +278,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
                               Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
         user.refresh_from_db()
 
-        self.login(user.delivery_email)
+        self.login_user(user)
         self.assert_num_bots_equal(0)
         events = []  # type: List[Mapping[str, Any]]
         with tornado_redirected_to_list(events):
@@ -330,7 +330,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         is sent when add_subscriptions_backend is called in the above api call.
         """
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         # Normal user i.e. not a bot.
         request_data = {
@@ -367,7 +367,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(len(mail.outbox), 0)
 
     def test_add_bot_with_default_sending_stream_private_allowed(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = get_stream("Denmark", user_profile.realm)
         self.subscribe(user_profile, stream.name)
@@ -409,7 +409,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(event['users'], {user_profile.id, })
 
     def test_add_bot_with_default_sending_stream_private_denied(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         realm = self.example_user('hamlet').realm
         stream = get_stream("Denmark", realm)
         self.unsubscribe(self.example_user('hamlet'), "Denmark")
@@ -427,7 +427,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_email = 'hambot-bot@zulip.testserver'
         bot_realm = get_realm('zulip')
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.create_bot(default_events_register_stream='Denmark')
         self.assert_num_bots_equal(1)
@@ -438,7 +438,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.default_events_register_stream.name, 'Denmark')
 
     def test_add_bot_with_default_events_register_stream_private_allowed(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = self.subscribe(user_profile, 'Denmark')
         do_change_stream_invite_only(stream, True)
@@ -479,7 +479,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(event['users'], {user_profile.id, })
 
     def test_add_bot_with_default_events_register_stream_private_denied(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         realm = self.example_user('hamlet').realm
         stream = get_stream("Denmark", realm)
         self.unsubscribe(self.example_user('hamlet'), "Denmark")
@@ -495,7 +495,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, "Invalid stream name 'Denmark'")
 
     def test_add_bot_with_default_all_public_streams(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.create_bot(default_all_public_streams=ujson.dumps(True))
         self.assert_num_bots_equal(1)
@@ -507,7 +507,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.default_all_public_streams, True)
 
     def test_deactivate_bot(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot()
         self.assert_num_bots_equal(1)
@@ -518,7 +518,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     def test_deactivate_bogus_bot(self) -> None:
         """Deleting a bogus bot will succeed silently."""
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot()
         self.assert_num_bots_equal(1)
@@ -528,9 +528,8 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_num_bots_equal(1)
 
     def test_deactivate_bot_with_owner_deactivation(self) -> None:
-        email = self.example_email("hamlet")
         user = self.example_user('hamlet')
-        self.login(email)
+        self.login_user(user)
 
         bot_info = {
             'full_name': u'The Bot of Hamlet',
@@ -555,14 +554,14 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         user = self.example_user('hamlet')
         self.assertFalse(user.is_active)
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         all_bots = UserProfile.objects.filter(is_bot=True, bot_owner=user, is_active=True)
         bots = [bot for bot in all_bots]
         self.assertEqual(len(bots), 0)
 
     def test_cannot_deactivate_other_realm_bot(self) -> None:
-        realm = get_realm("zephyr")
-        self.login(self.mit_email("starnine"), realm=realm)
+        user = self.mit_user('starnine')
+        self.login_user(user)
         bot_info = {
             'full_name': 'The Bot in zephyr',
             'short_name': 'starn-bot',
@@ -572,21 +571,21 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_success(result)
         result = self.client_get("/json/bots", subdomain="zephyr")
         bot_email = result.json()['bots'][0]['username']
-        bot = get_user(bot_email, realm)
-        self.login(self.example_email("iago"))
+        bot = get_user(bot_email, user.realm)
+        self.login('iago')
         result = self.client_delete("/json/bots/{}".format(bot.id))
         self.assert_json_error(result, 'No such bot')
 
     def test_bot_deactivation_attacks(self) -> None:
         """You cannot deactivate somebody else's bot."""
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot()
         self.assert_num_bots_equal(1)
 
         # Have Othello try to deactivate both Hamlet and
         # Hamlet's bot.
-        self.login(self.example_email('othello'))
+        self.login('othello')
 
         # Cannot deactivate a user as a bot
         result = self.client_delete("/json/bots/{}".format(self.example_user("hamlet").id))
@@ -597,7 +596,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, 'Insufficient permission')
 
         # But we don't actually deactivate the other person's bot.
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(1)
 
         # Cannot deactivate a bot as a user
@@ -606,13 +605,13 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_num_bots_equal(1)
 
     def test_bot_permissions(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot()
         self.assert_num_bots_equal(1)
 
         # Have Othello try to mess with Hamlet's bots.
-        self.login(self.example_email('othello'))
+        self.login('othello')
         email = 'hambot-bot@zulip.testserver'
 
         result = self.client_post("/json/bots/{}/api_key/regenerate".format(self.get_bot_user(email).id))
@@ -630,7 +629,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         return bots[0]
 
     def test_update_api_key(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         bot = self.get_bot()
         old_api_key = bot['api_key']
@@ -643,7 +642,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(new_api_key, bot['api_key'])
 
     def test_update_api_key_for_invalid_user(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         invalid_user_id = 1000
         result = self.client_post('/json/bots/{}/api_key/regenerate'.format(invalid_user_id))
         self.assert_json_error(result, 'No such bot')
@@ -652,7 +651,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_email = 'hambot-bot@zulip.testserver'
         bot_realm = get_realm('zulip')
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
         self.assert_num_bots_equal(1)
@@ -664,7 +663,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_email = 'hambot-bot@zulip.testserver'
         bot_realm = get_realm('zulip')
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         self.create_bot(bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
         self.assert_num_bots_equal(1)
@@ -679,7 +678,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
             'bot_type': 7,
         }
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.client_post("/json/bots", bot_info)
         self.assert_num_bots_equal(0)
@@ -697,7 +696,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_realm.save(update_fields=['bot_creation_policy'])
 
         # A regular user cannot create a generic bot
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.client_post("/json/bots", bot_info)
         self.assert_num_bots_equal(0)
@@ -711,7 +710,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_type, UserProfile.INCOMING_WEBHOOK_BOT)
 
     def test_no_generic_bot_reactivation_allowed_for_non_admins(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
 
         bot_realm = get_realm('zulip')
@@ -735,7 +734,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_realm.save(update_fields=['bot_creation_policy'])
 
         # An administrator can create any type of bot
-        self.login(self.example_email('iago'))
+        self.login('iago')
         self.assert_num_bots_equal(0)
         self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
         self.assert_num_bots_equal(1)
@@ -753,7 +752,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_realm.save(update_fields=['bot_creation_policy'])
 
         # A regular user cannot create a generic bot
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.client_post("/json/bots", bot_info)
         self.assert_num_bots_equal(0)
@@ -761,7 +760,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
         # Also, a regular user cannot create a incoming bot
         bot_info['bot_type'] = 2
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.assert_num_bots_equal(0)
         result = self.client_post("/json/bots", bot_info)
         self.assert_num_bots_equal(0)
@@ -774,7 +773,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         bot_realm.save(update_fields=['bot_creation_policy'])
 
         # An administrator can create any type of bot
-        self.login(self.example_email('iago'))
+        self.login('iago')
         self.assert_num_bots_equal(0)
         self.create_bot(bot_type=UserProfile.DEFAULT_BOT)
         self.assert_num_bots_equal(1)
@@ -782,7 +781,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_type, UserProfile.DEFAULT_BOT)
 
     def test_patch_bot_full_name(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -802,7 +801,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual('Fred', bot['full_name'])
 
     def test_patch_bot_full_name_in_use(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
 
         original_name = 'The Bot of Hamlet'
 
@@ -852,7 +851,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(bot.full_name, 'Hal')
 
     def test_patch_bot_full_name_non_bot(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
         bot_info = {
             'full_name': 'Fred',
         }
@@ -860,7 +859,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, "No such bot")
 
     def test_patch_bot_owner(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': u'The Bot of Hamlet',
             'short_name': u'hambot',
@@ -877,12 +876,12 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         # Test bot's owner has been changed successfully.
         self.assertEqual(result.json()['bot_owner'], self.example_email('othello'))
 
-        self.login(self.example_email('othello'))
+        self.login('othello')
         bot = self.get_bot()
         self.assertEqual('The Bot of Hamlet', bot['full_name'])
 
     def test_patch_bot_owner_bad_user_id(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assert_num_bots_equal(1)
 
@@ -899,7 +898,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_owner, self.example_user("hamlet"))
 
     def test_patch_bot_owner_deactivated(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assert_num_bots_equal(1)
 
@@ -918,7 +917,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_owner, self.example_user("hamlet"))
 
     def test_patch_bot_owner_must_be_in_same_realm(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assert_num_bots_equal(1)
 
@@ -933,7 +932,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_owner, self.example_user("hamlet"))
 
     def test_patch_bot_owner_noop(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assert_num_bots_equal(1)
 
@@ -950,7 +949,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_owner, self.example_user("hamlet"))
 
     def test_patch_bot_owner_a_bot(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         self.assert_num_bots_equal(1)
 
@@ -971,7 +970,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(profile.bot_owner, self.example_user("hamlet"))
 
     def test_patch_bot_avatar(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1013,7 +1012,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertTrue(os.path.exists(avatar_disk_path(profile)))
 
     def test_patch_bot_to_stream(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1033,7 +1032,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual('Denmark', bot['default_sending_stream'])
 
     def test_patch_bot_to_stream_not_subscribed(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1053,7 +1052,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual('Rome', bot['default_sending_stream'])
 
     def test_patch_bot_to_stream_none(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1076,7 +1075,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(None, bot['default_sending_stream'])
 
     def test_patch_bot_to_stream_private_allowed(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = self.subscribe(user_profile, "Denmark")
         do_change_stream_invite_only(stream, True)
@@ -1101,7 +1100,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual('Denmark', bot['default_sending_stream'])
 
     def test_patch_bot_to_stream_private_denied(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         realm = self.example_user('hamlet').realm
         stream = get_stream("Denmark", realm)
         self.unsubscribe(self.example_user('hamlet'), "Denmark")
@@ -1122,7 +1121,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, "Invalid stream name 'Denmark'")
 
     def test_patch_bot_to_stream_not_found(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1138,7 +1137,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     def test_patch_bot_events_register_stream(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1183,7 +1182,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error_contains(result, 'endpoint does not accept')
 
     def test_patch_bot_events_register_stream_allowed(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = self.subscribe(user_profile, "Denmark")
         do_change_stream_invite_only(stream, True)
@@ -1207,7 +1206,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual('Denmark', bot['default_events_register_stream'])
 
     def test_patch_bot_events_register_stream_denied(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         realm = self.example_user('hamlet').realm
         stream = get_stream("Denmark", realm)
         self.unsubscribe(self.example_user('hamlet'), "Denmark")
@@ -1227,7 +1226,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, "Invalid stream name 'Denmark'")
 
     def test_patch_bot_events_register_stream_none(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1250,7 +1249,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(None, bot['default_events_register_stream'])
 
     def test_patch_bot_events_register_stream_not_found(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1265,7 +1264,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_error(result, "Invalid stream name 'missing'")
 
     def test_patch_bot_default_all_public_streams_true(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1285,7 +1284,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(bot['default_all_public_streams'], True)
 
     def test_patch_bot_default_all_public_streams_false(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1305,7 +1304,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assertEqual(bot['default_all_public_streams'], False)
 
     def test_patch_bot_via_post(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'The Bot of Hamlet',
             'short_name': 'hambot',
@@ -1329,7 +1328,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     def test_patch_bogus_bot(self) -> None:
         """Deleting a bogus bot will succeed silently."""
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.create_bot()
         bot_info = {
             'full_name': 'Fred',
@@ -1340,7 +1339,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_num_bots_equal(1)
 
     def test_patch_outgoing_webhook_bot(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': u'The Bot of Hamlet',
             'short_name': u'hambot',
@@ -1380,7 +1379,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     def test_outgoing_webhook_invalid_interface(self):
         # type: () -> None
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'Outgoing Webhook test bot',
             'short_name': 'outgoingservicebot',
@@ -1396,7 +1395,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
         self.assert_json_success(result)
 
     def test_create_outgoing_webhook_bot(self, **extras: Any) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'Outgoing Webhook test bot',
             'short_name': 'outgoingservicebot',
@@ -1439,7 +1438,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
             self.assertIsNotNone(get_bot_handler(embedded_bot.name))
 
     def test_outgoing_webhook_interface_type(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'Outgoing Webhook test bot',
             'short_name': 'outgoingservicebot',
@@ -1524,7 +1523,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @patch("zerver.lib.integrations.WEBHOOK_INTEGRATIONS", stripe_sample_config_options)
     def test_create_incoming_webhook_bot_with_service_name_and_with_keys(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_metadata = {
             "full_name": "My Stripe Bot",
             "short_name": "my-stripe",
@@ -1540,7 +1539,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @patch("zerver.lib.integrations.WEBHOOK_INTEGRATIONS", stripe_sample_config_options)
     def test_create_incoming_webhook_bot_with_service_name_incorrect_keys(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_metadata = {
             "full_name": "My Stripe Bot",
             "short_name": "my-stripe",
@@ -1557,7 +1556,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @patch("zerver.lib.integrations.WEBHOOK_INTEGRATIONS", stripe_sample_config_options)
     def test_create_incoming_webhook_bot_with_service_name_without_keys(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_metadata = {
             "full_name": "My Stripe Bot",
             "short_name": "my-stripe",
@@ -1573,7 +1572,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @patch("zerver.lib.integrations.WEBHOOK_INTEGRATIONS", stripe_sample_config_options)
     def test_create_incoming_webhook_bot_without_service_name(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_metadata = {
             "full_name": "My Stripe Bot",
             "short_name": "my-stripe",
@@ -1586,7 +1585,7 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 
     @patch("zerver.lib.integrations.WEBHOOK_INTEGRATIONS", stripe_sample_config_options)
     def test_create_incoming_webhook_bot_with_incorrect_service_name(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_metadata = {
             "full_name": "My Stripe Bot",
             "short_name": "my-stripe",
diff --git a/zerver/tests/test_create_video_call.py b/zerver/tests/test_create_video_call.py
index 2d5f77ad5b..f6afd548d9 100644
--- a/zerver/tests/test_create_video_call.py
+++ b/zerver/tests/test_create_video_call.py
@@ -7,7 +7,7 @@ class TestVideoCall(ZulipTestCase):
     def setUp(self) -> None:
         super().setUp()
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email, realm=user_profile.realm)
+        self.login_user(user_profile)
 
     def test_create_video_call_success(self) -> None:
         with mock.patch('zerver.lib.actions.request_zoom_video_call_url', return_value={'join_url': 'example.com'}):
diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py
index 6643e4fce2..12f13b1ab9 100644
--- a/zerver/tests/test_custom_profile_data.py
+++ b/zerver/tests/test_custom_profile_data.py
@@ -28,7 +28,7 @@ class CustomProfileFieldTestCase(ZulipTestCase):
 
 class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
     def test_create(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         data = {"name": u"Phone", "field_type": "text id"}  # type: Dict[str, Any]
         result = self.client_post("/json/realm/profile_fields", info=data)
@@ -69,7 +69,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
                                u'A field with that label already exists.')
 
     def test_create_choice_field(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         data = {}  # type: Dict[str, Union[str, int]]
         data["name"] = "Favorite programming language"
         data["field_type"] = CustomProfileField.CHOICE
@@ -126,7 +126,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_success(result)
 
     def test_create_default_external_account_field(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm("zulip")
         field_type = CustomProfileField.EXTERNAL_ACCOUNT  # type: int
         field_data = ujson.dumps({
@@ -173,7 +173,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_success(result)
 
     def test_create_external_account_field(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         data = {}  # type: Dict[str, Union[str, int, Dict[str, str]]]
         data["name"] = "Twitter"
@@ -270,7 +270,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_error(result, "A field with that label already exists.")
 
     def test_create_field_of_type_user(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         data = {"name": "Your mentor",
                 "field_type": CustomProfileField.USER,
                 }
@@ -278,7 +278,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_success(result)
 
     def test_not_realm_admin(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/realm/profile_fields")
         self.assert_json_error(result, u'Must be an organization administrator')
         result = self.client_delete("/json/realm/profile_fields/1")
@@ -286,7 +286,7 @@ class CreateCustomProfileFieldTest(CustomProfileFieldTestCase):
 
 class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase):
     def test_delete(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         field = CustomProfileField.objects.get(name="Phone number", realm=realm)
         result = self.client_delete("/json/realm/profile_fields/100")
@@ -300,7 +300,7 @@ class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase):
 
     def test_delete_field_value(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
         realm = get_realm("zulip")
 
         invalid_field_id = 1234
@@ -347,7 +347,7 @@ class DeleteCustomProfileFieldTest(CustomProfileFieldTestCase):
 
 class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
     def test_update(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         result = self.client_patch(
             "/json/realm/profile_fields/100",
@@ -433,7 +433,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_success(result)
 
     def test_update_is_aware_of_uniqueness(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         field_1 = try_add_realm_custom_profile_field(realm, u"Phone",
                                                      CustomProfileField.SHORT_TEXT)
@@ -450,7 +450,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
             result, u'A field with that label already exists.')
 
     def assert_error_update_invalid_value(self, field_name: str, new_value: object, error_msg: str) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         field = CustomProfileField.objects.get(name=field_name, realm=realm)
 
@@ -460,7 +460,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_error(result, error_msg)
 
     def test_update_invalid_field(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         data = [{'id': 1234, 'value': '12'}]
         result = self.client_patch("/json/users/me/profile_data", {
             'data': ujson.dumps(data)
@@ -493,7 +493,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
                                                % (invalid_user_id,))
 
     def test_update_profile_data_successfully(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         fields = [
             ('Phone number', '*short* text data'),
@@ -556,7 +556,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
                                                "'foobar' is not a valid choice for '{}'.".format(field_name))
 
     def test_update_choice_field_successfully(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         field = CustomProfileField.objects.get(name='Favorite editor', realm=realm)
         data = [{
@@ -569,7 +569,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_success(result)
 
     def test_null_value_and_rendered_value(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm("zulip")
 
         quote = try_add_realm_custom_profile_field(
@@ -601,7 +601,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
 
     def test_do_update_value_not_changed(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
         realm = get_realm("zulip")
 
         # Set field value:
@@ -618,7 +618,7 @@ class UpdateCustomProfileFieldTest(CustomProfileFieldTestCase):
 
 class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
     def test_list(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_get("/json/realm/profile_fields")
         self.assert_json_success(result)
         self.assertEqual(200, result.status_code)
@@ -626,7 +626,7 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assertEqual(len(content["custom_fields"]), self.original_count)
 
     def test_list_order(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         order = (
             CustomProfileField.objects.filter(realm=realm)
@@ -689,7 +689,7 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
                 user_dict["profile_data"]
 
     def test_get_custom_profile_fields_from_api_for_single_user(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         expected_keys = {
             "result", "msg", "pointer", "client_id", "max_message_id", "user_id",
             "avatar_url", "full_name", "email", "is_bot", "is_admin", "short_name",
@@ -704,7 +704,7 @@ class ListCustomProfileFieldTest(CustomProfileFieldTestCase):
 
 class ReorderCustomProfileFieldTest(CustomProfileFieldTestCase):
     def test_reorder(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         order = (
             CustomProfileField.objects.filter(realm=realm)
@@ -719,7 +719,7 @@ class ReorderCustomProfileFieldTest(CustomProfileFieldTestCase):
             self.assertEqual(field.id, order[field.order])
 
     def test_reorder_duplicates(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         order = (
             CustomProfileField.objects.filter(realm=realm)
@@ -736,7 +736,7 @@ class ReorderCustomProfileFieldTest(CustomProfileFieldTestCase):
             self.assertEqual(field.id, order[field.order])
 
     def test_reorder_unauthorized(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm = get_realm('zulip')
         order = (
             CustomProfileField.objects.filter(realm=realm)
@@ -748,7 +748,7 @@ class ReorderCustomProfileFieldTest(CustomProfileFieldTestCase):
         self.assert_json_error(result, "Must be an organization administrator")
 
     def test_reorder_invalid(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         order = [100, 200, 300]
         result = self.client_patch("/json/realm/profile_fields",
                                    info={'order': ujson.dumps(order)})
diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py
index f8b23d68a9..90cc0ec3cb 100644
--- a/zerver/tests/test_decorators.py
+++ b/zerver/tests/test_decorators.py
@@ -1004,7 +1004,7 @@ class DeactivatedRealmTest(ZulipTestCase):
         # Even if a logged-in session was leaked, it still wouldn't work
         realm.deactivated = False
         realm.save()
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm.deactivated = True
         realm.save()
 
@@ -1028,11 +1028,10 @@ class DeactivatedRealmTest(ZulipTestCase):
         """
         realm = get_realm("zulip")
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
         test_password = "abcd1234"
         user_profile.set_password(test_password)
 
-        self.login(email)
+        self.login_user(user_profile)
         realm.deactivated = True
         realm.save()
         result = self.client_post("/json/fetch_api_key", {"password": test_password})
@@ -1058,14 +1057,13 @@ class LoginRequiredTest(ZulipTestCase):
         Verifies the zulip_login_required decorator blocks deactivated users.
         """
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
 
         # Verify fails if logged-out
         result = self.client_get('/accounts/accept_terms/')
         self.assertEqual(result.status_code, 302)
 
         # Verify succeeds once logged-in
-        self.login(email)
+        self.login_user(user_profile)
         result = self.client_get('/accounts/accept_terms/')
         self.assert_in_response("I agree to the", result)
 
@@ -1077,7 +1075,7 @@ class LoginRequiredTest(ZulipTestCase):
 
         # Verify succeeds if user reactivated
         do_reactivate_user(user_profile)
-        self.login(email)
+        self.login_user(user_profile)
         result = self.client_get('/accounts/accept_terms/')
         self.assert_in_response("I agree to the", result)
 
@@ -1089,28 +1087,26 @@ class LoginRequiredTest(ZulipTestCase):
 
 class FetchAPIKeyTest(ZulipTestCase):
     def test_fetch_api_key_success(self) -> None:
-        email = self.example_email("cordelia")
-
-        self.login(email)
-        result = self.client_post("/json/fetch_api_key", {"password": initial_password(email)})
+        user = self.example_user("cordelia")
+        self.login_user(user)
+        result = self.client_post("/json/fetch_api_key",
+                                  dict(password=initial_password(user.email)))
         self.assert_json_success(result)
 
     def test_fetch_api_key_email_address_visibility(self) -> None:
-        user_profile = self.example_user("cordelia")
-        email = user_profile.email
-        do_set_realm_property(user_profile.realm, "email_address_visibility",
+        user = self.example_user("cordelia")
+        do_set_realm_property(user.realm, "email_address_visibility",
                               Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
 
-        self.login(email)
+        self.login_user(user)
         result = self.client_post("/json/fetch_api_key",
-                                  {"password": initial_password(email)})
+                                  dict(password=initial_password(user.email)))
         self.assert_json_success(result)
 
     def test_fetch_api_key_wrong_password(self) -> None:
-        email = self.example_email("cordelia")
-
-        self.login(email)
-        result = self.client_post("/json/fetch_api_key", {"password": "wrong_password"})
+        self.login('cordelia')
+        result = self.client_post("/json/fetch_api_key",
+                                  dict(password='wrong_password'))
         self.assert_json_error_contains(result, "password is incorrect")
 
 class InactiveUserTest(ZulipTestCase):
@@ -1120,8 +1116,7 @@ class InactiveUserTest(ZulipTestCase):
 
         """
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         do_deactivate_user(user_profile)
 
         result = self.client_post("/json/messages", {"type": "private",
@@ -1132,7 +1127,7 @@ class InactiveUserTest(ZulipTestCase):
 
         # Even if a logged-in session was leaked, it still wouldn't work
         do_reactivate_user(user_profile)
-        self.login(email)
+        self.login_user(user_profile)
         user_profile.is_active = False
         user_profile.save()
 
@@ -1160,7 +1155,7 @@ class InactiveUserTest(ZulipTestCase):
         user_profile.set_password(test_password)
         user_profile.save()
 
-        self.login(email, password=test_password)
+        self.login_by_email(email, password=test_password)
         user_profile.is_active = False
         user_profile.save()
         result = self.client_post("/json/fetch_api_key", {"password": test_password})
@@ -1438,16 +1433,13 @@ class TestHumanUsersOnlyDecorator(ZulipTestCase):
 
 class TestAuthenticatedJsonPostViewDecorator(ZulipTestCase):
     def test_authenticated_json_post_view_if_everything_is_correct(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        self._login(user_email, user_realm)
-        response = self._do_test(user_email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
+        response = self._do_test(user)
         self.assertEqual(response.status_code, 200)
 
     def test_authenticated_json_post_view_with_get_request(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        self._login(user_email, user_realm)
+        self.login('hamlet')
         with mock.patch('logging.warning') as mock_warning:
             result = self.client_get(r'/json/subscriptions/exists', {'stream': 'Verona'})
             self.assertEqual(result.status_code, 405)
@@ -1456,92 +1448,80 @@ class TestAuthenticatedJsonPostViewDecorator(ZulipTestCase):
                              ('Method Not Allowed (%s): %s', 'GET', '/json/subscriptions/exists'))
 
     def test_authenticated_json_post_view_if_subdomain_is_invalid(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        self._login(user_email, user_realm)
+        user = self.example_user('hamlet')
+        self.login_user(user)
         with mock.patch('logging.warning') as mock_warning, \
                 mock.patch('zerver.decorator.get_subdomain', return_value=''):
-            self.assert_json_error_contains(self._do_test(user_email),
+            self.assert_json_error_contains(self._do_test(user),
                                             "Account is not associated with this "
                                             "subdomain")
             mock_warning.assert_called_with(
                 "User {} ({}) attempted to access API on wrong "
-                "subdomain ({})".format(user_email, 'zulip', ''))
+                "subdomain ({})".format(user.email, 'zulip', ''))
 
         with mock.patch('logging.warning') as mock_warning, \
                 mock.patch('zerver.decorator.get_subdomain', return_value='acme'):
-            self.assert_json_error_contains(self._do_test(user_email),
+            self.assert_json_error_contains(self._do_test(user),
                                             "Account is not associated with this "
                                             "subdomain")
             mock_warning.assert_called_with(
                 "User {} ({}) attempted to access API on wrong "
-                "subdomain ({})".format(user_email, 'zulip', 'acme'))
+                "subdomain ({})".format(user.email, 'zulip', 'acme'))
 
     def test_authenticated_json_post_view_if_user_is_incoming_webhook(self) -> None:
-        user_email = 'webhook-bot@zulip.com'
-        user_realm = get_realm('zulip')
-        self._login(user_email, user_realm, password="test")  # we set a password because user is a bot
-        self.assert_json_error_contains(self._do_test(user_email), "Webhook bots can only access webhooks")
+        bot = self.example_user('webhook_bot')
+        bot.set_password('test')
+        bot.save()
+        self.login_by_email(bot.email, password='test')
+        self.assert_json_error_contains(self._do_test(bot), "Webhook bots can only access webhooks")
 
     def test_authenticated_json_post_view_if_user_is_not_active(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        self._login(user_email, user_realm, password="test")
-        # Get user_profile after _login so that we have the latest data.
-        user_profile = get_user(user_email, user_realm)
+        user_profile = self.example_user('hamlet')
+        self.login_user(user_profile)
         # we deactivate user manually because do_deactivate_user removes user session
         user_profile.is_active = False
         user_profile.save()
-        self.assert_json_error_contains(self._do_test(user_email), "Account is deactivated")
+        self.assert_json_error_contains(self._do_test(user_profile), "Account is deactivated")
         do_reactivate_user(user_profile)
 
     def test_authenticated_json_post_view_if_user_realm_is_deactivated(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        user_profile = get_user(user_email, user_realm)
-        self._login(user_email, user_realm)
+        user_profile = self.example_user('hamlet')
+        self.login_user(user_profile)
         # we deactivate user's realm manually because do_deactivate_user removes user session
         user_profile.realm.deactivated = True
         user_profile.realm.save()
-        self.assert_json_error_contains(self._do_test(user_email), "This organization has been deactivated")
+        self.assert_json_error_contains(self._do_test(user_profile), "This organization has been deactivated")
         do_reactivate_realm(user_profile.realm)
 
-    def _do_test(self, user_email: str) -> HttpResponse:
+    def _do_test(self, user: UserProfile) -> HttpResponse:
         stream_name = "stream name"
-        user = get_user(user_email, get_realm('zulip'))
         self.common_subscribe_to_streams(user, [stream_name])
-        data = {"password": initial_password(user_email), "stream": stream_name}
-        return self.client_post(r'/json/subscriptions/exists', data)
-
-    def _login(self, user_email: str, user_realm: Realm, password: str=None) -> None:
-        if password:
-            user_profile = get_user(user_email, user_realm)
-            user_profile.set_password(password)
-            user_profile.save()
-        self.login(user_email, password)
+        data = {"password": initial_password(user.email), "stream": stream_name}
+        return self.client_post('/json/subscriptions/exists', data)
 
 class TestAuthenticatedJsonViewDecorator(ZulipTestCase):
     def test_authenticated_json_view_if_subdomain_is_invalid(self) -> None:
-        user_email = self.example_email("hamlet")
-        self.login(user_email)
+        user = self.example_user('hamlet')
+        email = user.email
+        self.login_user(user)
 
         with mock.patch('logging.warning') as mock_warning, \
                 mock.patch('zerver.decorator.get_subdomain', return_value=''):
-            self.assert_json_error_contains(self._do_test(str(user_email)),
+            self.assert_json_error_contains(self._do_test(email),
                                             "Account is not associated with this "
                                             "subdomain")
             mock_warning.assert_called_with(
                 "User {} ({}) attempted to access API on wrong "
-                "subdomain ({})".format(user_email, 'zulip', ''))
+                "subdomain ({})".format(email, 'zulip', ''))
 
         with mock.patch('logging.warning') as mock_warning, \
                 mock.patch('zerver.decorator.get_subdomain', return_value='acme'):
-            self.assert_json_error_contains(self._do_test(str(user_email)),
+            self.assert_json_error_contains(self._do_test(email),
                                             "Account is not associated with this "
                                             "subdomain")
             mock_warning.assert_called_with(
                 "User {} ({}) attempted to access API on wrong "
-                "subdomain ({})".format(user_email, 'zulip', 'acme'))
+                "subdomain ({})".format(email, 'zulip', 'acme'))
 
     def _do_test(self, user_email: str) -> HttpResponse:
         data = {"password": initial_password(user_email)}
@@ -1549,8 +1529,7 @@ class TestAuthenticatedJsonViewDecorator(ZulipTestCase):
 
 class TestZulipLoginRequiredDecorator(ZulipTestCase):
     def test_zulip_login_required_if_subdomain_is_invalid(self) -> None:
-        user_email = self.example_email("hamlet")
-        self.login(user_email)
+        self.login('hamlet')
 
         with mock.patch('zerver.decorator.get_subdomain', return_value='zulip'):
             result = self.client_get('/accounts/accept_terms/')
@@ -1575,7 +1554,7 @@ class TestZulipLoginRequiredDecorator(ZulipTestCase):
         request.META['PATH_INFO'] = ''
         request.user = hamlet = self.example_user('hamlet')
         request.user.is_verified = lambda: False
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         request.session = self.client.session
         request.get_host = lambda: 'zulip.testserver'
 
@@ -1590,7 +1569,7 @@ class TestZulipLoginRequiredDecorator(ZulipTestCase):
             request.META['PATH_INFO'] = ''
             request.user = hamlet = self.example_user('hamlet')
             request.user.is_verified = lambda: False
-            self.login(hamlet.email)
+            self.login_user(hamlet)
             request.session = self.client.session
             request.get_host = lambda: 'zulip.testserver'
             self.create_default_device(request.user)
@@ -1616,7 +1595,7 @@ class TestZulipLoginRequiredDecorator(ZulipTestCase):
             request.META['PATH_INFO'] = ''
             request.user = hamlet = self.example_user('hamlet')
             request.user.is_verified = lambda: True
-            self.login(hamlet.email)
+            self.login_user(hamlet)
             request.session = self.client.session
             request.get_host = lambda: 'zulip.testserver'
             self.create_default_device(request.user)
@@ -1627,33 +1606,30 @@ class TestZulipLoginRequiredDecorator(ZulipTestCase):
 
 class TestRequireDecorators(ZulipTestCase):
     def test_require_server_admin_decorator(self) -> None:
-        user_email = self.example_email('hamlet')
-        user_realm = get_realm('zulip')
-        self.login(user_email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         result = self.client_get('/activity')
         self.assertEqual(result.status_code, 302)
 
-        user_profile = get_user(user_email, user_realm)
-        user_profile.is_staff = True
-        user_profile.save()
+        user.is_staff = True
+        user.save()
 
         result = self.client_get('/activity')
         self.assertEqual(result.status_code, 200)
 
     def test_require_non_guest_user_decorator(self) -> None:
         guest_user = self.example_user('polonius')
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.common_subscribe_to_streams(guest_user, ["Denmark"])
         self.assert_json_error(result, "Not allowed for guest users")
 
-    def test_require_member_or_admin_decorator(self) -> None:
         outgoing_webhook_bot = self.example_user('outgoing_webhook_bot')
         result = self.api_get(outgoing_webhook_bot, '/api/v1/bots')
         self.assert_json_error(result, "This endpoint does not accept bot requests.")
 
         guest_user = self.example_user('polonius')
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.client_get('/json/bots')
         self.assert_json_error(result, "Not allowed for guest users")
 
@@ -1688,13 +1664,13 @@ class ReturnSuccessOnHeadRequestDecorator(ZulipTestCase):
 
 class RestAPITest(ZulipTestCase):
     def test_method_not_allowed(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_patch('/json/users')
         self.assertEqual(result.status_code, 405)
         self.assert_in_response('Method Not Allowed', result)
 
     def test_options_method(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_options('/json/users')
         self.assertEqual(result.status_code, 204)
         self.assertEqual(str(result['Allow']), 'GET, HEAD, POST')
diff --git a/zerver/tests/test_digest.py b/zerver/tests/test_digest.py
index d6d3cc65fb..210865e1fc 100644
--- a/zerver/tests/test_digest.py
+++ b/zerver/tests/test_digest.py
@@ -280,6 +280,6 @@ class TestDigestEmailMessages(ZulipTestCase):
 
 class TestDigestContentInBrowser(ZulipTestCase):
     def test_get_digest_content_in_browser(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         result = self.client_get("/digest/")
         self.assert_in_success_response(["Click here to log in to Zulip and catch up."], result)
diff --git a/zerver/tests/test_docs.py b/zerver/tests/test_docs.py
index ddd684d72e..7ef2a2b642 100644
--- a/zerver/tests/test_docs.py
+++ b/zerver/tests/test_docs.py
@@ -377,7 +377,7 @@ class PlansPageTest(ZulipTestCase):
         self.assertEqual(result.status_code, 302)
         self.assertEqual(result["Location"], "/accounts/login/?next=plans")
         # Test valid domain, with login
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         result = self.client_get("/plans/", subdomain="zulip")
         self.assert_in_success_response(["Current plan"], result)
         # Test root domain, with login on different domain
@@ -405,7 +405,7 @@ class PlansPageTest(ZulipTestCase):
             self.assertEqual(result.status_code, 302)
             self.assertEqual(result["Location"], "https://zulipchat.com/plans")
 
-            self.login(self.example_email("iago"))
+            self.login('iago')
 
             # SELF_HOSTED should hide the local plans page, even if logged in
             result = self.client_get("/plans/", subdomain="zulip")
diff --git a/zerver/tests/test_email_change.py b/zerver/tests/test_email_change.py
index 0db60c0d96..0c62691a4f 100644
--- a/zerver/tests/test_email_change.py
+++ b/zerver/tests/test_email_change.py
@@ -18,14 +18,14 @@ from zerver.models import get_user_by_delivery_email, EmailChangeStatus, get_rea
 
 class EmailChangeTestCase(ZulipTestCase):
     def test_confirm_email_change_with_non_existent_key(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         key = generate_key()
         url = confirmation_url(key, 'testserver', Confirmation.EMAIL_CHANGE)
         response = self.client_get(url)
         self.assert_in_success_response(["Whoops. We couldn't find your confirmation link in the system."], response)
 
     def test_confirm_email_change_with_invalid_key(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         key = 'invalid_key'
         url = confirmation_url(key, 'testserver', Confirmation.EMAIL_CHANGE)
         response = self.client_get(url)
@@ -35,7 +35,7 @@ class EmailChangeTestCase(ZulipTestCase):
         user_profile = self.example_user('hamlet')
         old_email = user_profile.email
         new_email = 'hamlet-new@zulip.com'
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         obj = EmailChangeStatus.objects.create(new_email=new_email,
                                                old_email=old_email,
                                                user_profile=user_profile,
@@ -55,7 +55,7 @@ class EmailChangeTestCase(ZulipTestCase):
         old_email = user_profile.email
         new_email = 'hamlet-new@zulip.com'
         new_realm = get_realm('zulip')
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         obj = EmailChangeStatus.objects.create(new_email=new_email,
                                                old_email=old_email,
                                                user_profile=user_profile,
@@ -83,8 +83,7 @@ class EmailChangeTestCase(ZulipTestCase):
 
     def test_end_to_end_flow(self) -> None:
         data = {'email': 'hamlet-new@zulip.com'}
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         url = '/json/settings'
         self.assertEqual(len(mail.outbox), 0)
         result = self.client_patch(url, data)
@@ -115,8 +114,7 @@ class EmailChangeTestCase(ZulipTestCase):
     def test_unauthorized_email_change(self) -> None:
         data = {'email': 'hamlet-new@zulip.com'}
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         do_set_realm_property(user_profile.realm, 'email_changes_disabled', True)
         url = '/json/settings'
         result = self.client_patch(url, data)
@@ -126,7 +124,7 @@ class EmailChangeTestCase(ZulipTestCase):
                                 result)
         # Realm admins can change their email address even setting is disabled.
         data = {'email': 'iago-new@zulip.com'}
-        self.login(self.example_email("iago"))
+        self.login('iago')
         url = '/json/settings'
         result = self.client_patch(url, data)
         self.assert_in_success_response(['Check your email for a confirmation link.'], result)
@@ -134,8 +132,7 @@ class EmailChangeTestCase(ZulipTestCase):
     def test_email_change_already_taken(self) -> None:
         data = {'email': 'cordelia@zulip.com'}
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
 
         url = '/json/settings'
         result = self.client_patch(url, data)
@@ -147,8 +144,7 @@ class EmailChangeTestCase(ZulipTestCase):
     def test_unauthorized_email_change_from_email_confirmation_link(self) -> None:
         data = {'email': 'hamlet-new@zulip.com'}
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         url = '/json/settings'
         self.assertEqual(len(mail.outbox), 0)
         result = self.client_patch(url, data)
@@ -173,16 +169,14 @@ class EmailChangeTestCase(ZulipTestCase):
 
     def test_post_invalid_email(self) -> None:
         data = {'email': 'hamlet-new'}
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         url = '/json/settings'
         result = self.client_patch(url, data)
         self.assert_in_response('Invalid address', result)
 
     def test_post_same_email(self) -> None:
         data = {'email': self.example_email("hamlet")}
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         url = '/json/settings'
         result = self.client_patch(url, data)
         self.assertEqual('success', result.json()['result'])
@@ -193,9 +187,9 @@ class EmailChangeTestCase(ZulipTestCase):
         do_set_realm_property(user_profile.realm, 'email_address_visibility',
                               Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS)
 
+        self.login_user(user_profile)
         old_email = user_profile.email
         new_email = 'hamlet-new@zulip.com'
-        self.login(self.example_email('hamlet'))
         obj = EmailChangeStatus.objects.create(new_email=new_email,
                                                old_email=old_email,
                                                user_profile=user_profile,
diff --git a/zerver/tests/test_email_mirror.py b/zerver/tests/test_email_mirror.py
index c74389a968..899d37798a 100644
--- a/zerver/tests/test_email_mirror.py
+++ b/zerver/tests/test_email_mirror.py
@@ -223,7 +223,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
         # build dummy messages for stream
         # test valid incoming stream message is processed properly
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -247,7 +247,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_messages_blank_subject_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -271,7 +271,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_private_stream_email_messages_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.make_stream("private_stream", invite_only=True)
         self.subscribe(user_profile, "private_stream")
         stream = get_stream("private_stream", user_profile.realm)
@@ -296,7 +296,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_multiple_recipient_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -322,7 +322,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_show_sender_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -347,7 +347,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_show_sender_utf8_encoded_sender(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -372,7 +372,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_include_footer_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -400,7 +400,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 
     def test_receive_stream_email_include_quotes_success(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -431,7 +431,7 @@ class TestStreamEmailMessagesSuccess(ZulipTestCase):
 class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
     def test_message_with_valid_attachment(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -464,7 +464,7 @@ class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
 
     def test_message_with_attachment_utf8_filename(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -499,7 +499,7 @@ class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
 
     def test_message_with_valid_nested_attachment(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -537,7 +537,7 @@ class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
 
     def test_message_with_invalid_attachment(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -562,7 +562,7 @@ class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
 
     def test_receive_plaintext_and_html_prefer_text_html_options(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_address = "Denmark.{}@testserver".format(stream.email_token)
@@ -597,7 +597,7 @@ class TestEmailMirrorMessagesWithAttachments(ZulipTestCase):
 
     def test_receive_only_plaintext_with_prefer_html_option(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_address_prefer_html = "Denmark.{}.prefer-html@testserver".format(stream.email_token)
@@ -628,7 +628,7 @@ class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
         # build dummy messages for stream
         # test message with empty body is not sent
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -647,7 +647,7 @@ class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
 
     def test_receive_stream_email_messages_no_textual_body(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -667,7 +667,7 @@ class TestStreamEmailMessagesEmptyBody(ZulipTestCase):
 
     def test_receive_stream_email_messages_empty_body_after_stripping(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
 
@@ -694,8 +694,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
         # build dummy messages for missed messages email reply
         # have Hamlet send Othello a PM. Othello will reply via email
         # Hamlet will receive the message.
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "test_receive_missed_message_email_messages",
                                                      "client": "test suite",
@@ -718,7 +717,6 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
 
         process_message(incoming_valid_message)
 
-        # self.login(self.example_email("hamlet"))
         # confirm that Hamlet got the message
         user_profile = self.example_user('hamlet')
         message = most_recent_message(user_profile)
@@ -733,8 +731,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
         # build dummy messages for missed messages email reply
         # have Othello send Iago and Cordelia a PM. Cordelia will reply via email
         # Iago and Othello will receive the message.
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "test_receive_missed_message_email_messages",
                                                      "client": "test suite",
@@ -782,8 +779,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
         # Hamlet will see the message in the stream.
         self.subscribe(self.example_user("hamlet"), "Denmark")
         self.subscribe(self.example_user("othello"), "Denmark")
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "topic": "test topic",
                                                      "content": "test_receive_missed_stream_message_email_messages",
@@ -817,8 +813,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
     def test_missed_stream_message_email_response_tracks_topic_change(self) -> None:
         self.subscribe(self.example_user("hamlet"), "Denmark")
         self.subscribe(self.example_user("othello"), "Denmark")
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "topic": "test topic",
                                                      "content": "test_receive_missed_stream_message_email_messages",
@@ -858,8 +853,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
     def test_missed_message_email_response_from_deactivated_user(self) -> None:
         self.subscribe(self.example_user("hamlet"), "Denmark")
         self.subscribe(self.example_user("othello"), "Denmark")
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "topic": "test topic",
                                                      "content": "test_receive_missed_stream_message_email_messages",
@@ -890,8 +884,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
     def test_missed_message_email_response_from_deactivated_realm(self) -> None:
         self.subscribe(self.example_user("hamlet"), "Denmark")
         self.subscribe(self.example_user("othello"), "Denmark")
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "topic": "test topic",
                                                      "content": "test_receive_missed_stream_message_email_messages",
@@ -922,8 +915,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
     def test_missed_message_email_multiple_responses(self) -> None:
         self.subscribe(self.example_user("hamlet"), "Denmark")
         self.subscribe(self.example_user("othello"), "Denmark")
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
 
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "topic": "test topic",
@@ -951,8 +943,7 @@ class TestMissedMessageEmailMessages(ZulipTestCase):
 
 class TestEmptyGatewaySetting(ZulipTestCase):
     def test_missed_message(self) -> None:
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "test_receive_missed_message_email_messages",
                                                      "client": "test suite",
@@ -989,8 +980,7 @@ class TestReplyExtraction(ZulipTestCase):
 
         # build dummy messages for stream
         # test valid incoming stream message is processed properly
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
@@ -1027,8 +1017,7 @@ class TestReplyExtraction(ZulipTestCase):
 
         # build dummy messages for stream
         # test valid incoming stream message is processed properly
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
@@ -1122,8 +1111,7 @@ class TestScriptMTA(ZulipTestCase):
 class TestEmailMirrorTornadoView(ZulipTestCase):
 
     def send_private_message(self) -> str:
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_post(
             "/json/messages",
             {
@@ -1220,7 +1208,7 @@ class TestEmailMirrorTornadoView(ZulipTestCase):
 class TestStreamEmailMessagesSubjectStripping(ZulipTestCase):
     def test_process_message_strips_subject(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -1257,7 +1245,7 @@ class TestContentTypeUnspecifiedCharset(ZulipTestCase):
         incoming_message = message_from_string(message_as_string)
 
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -1285,7 +1273,7 @@ class TestEmailMirrorProcessMessageNoValidRecipient(ZulipTestCase):
 class TestEmailMirrorLogAndReport(ZulipTestCase):
     def test_log_and_report(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "errors")
         stream = get_stream("Denmark", user_profile.realm)
         stream_to_address = encode_email_address(stream)
@@ -1331,7 +1319,7 @@ class TestEmailMirrorLogAndReport(ZulipTestCase):
 
     def test_redact_email_address(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "errors")
         stream = get_stream("Denmark", user_profile.realm)
 
diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py
index c435948d83..f3a36f1760 100644
--- a/zerver/tests/test_email_notifications.py
+++ b/zerver/tests/test_email_notifications.py
@@ -403,8 +403,7 @@ class TestMissedMessages(ZulipTestCase):
             '@**King Hamlet** to be deleted')
 
         hamlet = self.example_user('hamlet')
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_patch('/json/messages/' + str(msg_id),
                                    {'message_id': msg_id, 'content': ' '})
         self.assert_json_success(result)
@@ -417,8 +416,7 @@ class TestMissedMessages(ZulipTestCase):
                                             'Extremely personal message! to be deleted!')
 
         hamlet = self.example_user('hamlet')
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_patch('/json/messages/' + str(msg_id),
                                    {'message_id': msg_id, 'content': ' '})
         self.assert_json_success(result)
@@ -437,8 +435,7 @@ class TestMissedMessages(ZulipTestCase):
 
         hamlet = self.example_user('hamlet')
         iago = self.example_user('iago')
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         result = self.client_patch('/json/messages/' + str(msg_id),
                                    {'message_id': msg_id, 'content': ' '})
         self.assert_json_success(result)
diff --git a/zerver/tests/test_event_queue.py b/zerver/tests/test_event_queue.py
index 75e1aa3cf1..1ca6d9b0d0 100644
--- a/zerver/tests/test_event_queue.py
+++ b/zerver/tests/test_event_queue.py
@@ -221,13 +221,12 @@ class MissedMessageNotificationsTest(ZulipTestCase):
         user_profile.enable_online_push_notifications = False
         user_profile.save()
 
-        email = user_profile.email
         # Fetch the Denmark stream for testing
         stream = get_stream("Denmark", user_profile.realm)
         sub = Subscription.objects.get(user_profile=user_profile, recipient__type=Recipient.STREAM,
                                        recipient__type_id=stream.id)
 
-        self.login(email)
+        self.login_user(user_profile)
 
         def change_subscription_properties(user_profile: UserProfile, stream: Stream, sub: Subscription,
                                            properties: Dict[str, bool]) -> None:
diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py
index 65dce6de49..245bfc5957 100644
--- a/zerver/tests/test_events.py
+++ b/zerver/tests/test_events.py
@@ -290,7 +290,7 @@ class GetEventsTest(ZulipTestCase):
         email = user_profile.email
         recipient_user_profile = self.example_user('othello')
         recipient_email = recipient_user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
 
         result = self.tornado_call(get_events, user_profile,
                                    {"apply_markdown": ujson.dumps(True),
@@ -396,7 +396,7 @@ class GetEventsTest(ZulipTestCase):
 
     def test_get_events_narrow(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         def get_message(apply_markdown: bool, client_gravatar: bool) -> Dict[str, Any]:
             result = self.tornado_call(
@@ -2727,7 +2727,7 @@ class EventsRegisterTest(ZulipTestCase):
             ('upload_space_used', equals(6)),
         ])
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         data = {'uri': None}
@@ -2808,7 +2808,7 @@ class EventsRegisterTest(ZulipTestCase):
         ])
 
         do_change_is_admin(self.user_profile, True)
-        self.login(self.user_profile.email)
+        self.login_user(self.user_profile)
 
         with mock.patch('zerver.lib.export.do_export_realm',
                         return_value=create_dummy_file('test-export.tar.gz')):
@@ -3503,7 +3503,7 @@ class FetchQueriesTest(ZulipTestCase):
     def test_queries(self) -> None:
         user = self.example_user("hamlet")
 
-        self.login(user.email)
+        self.login_user(user)
 
         flush_per_request_caches()
         with queries_captured() as queries:
diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py
index cf395baec7..a4e456b17c 100644
--- a/zerver/tests/test_home.py
+++ b/zerver/tests/test_home.py
@@ -225,13 +225,11 @@ class HomeTest(ZulipTestCase):
             "zulip_version",
         ]
 
-        email = self.example_email("hamlet")
-
         # Verify fails if logged-out
         result = self.client_get('/')
         self.assertEqual(result.status_code, 302)
 
-        self.login(email)
+        self.login('hamlet')
 
         # Create bot for realm_bots testing. Must be done before fetching home_page.
         bot_info = {
@@ -285,7 +283,7 @@ class HomeTest(ZulipTestCase):
 
     def test_home_under_2fa_without_otp_device(self) -> None:
         with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
-            self.login(self.example_email("iago"))
+            self.login('iago')
             result = self._get_home_page()
             # Should be successful because otp device is not configured.
             self.assertEqual(result.status_code, 200)
@@ -294,7 +292,7 @@ class HomeTest(ZulipTestCase):
         with self.settings(TWO_FACTOR_AUTHENTICATION_ENABLED=True):
             user_profile = self.example_user('iago')
             self.create_default_device(user_profile)
-            self.login(user_profile.email)
+            self.login_user(user_profile)
             result = self._get_home_page()
             # User should not log in because otp device is configured but
             # 2fa login function was not called.
@@ -307,7 +305,7 @@ class HomeTest(ZulipTestCase):
 
     def test_num_queries_for_realm_admin(self) -> None:
         # Verify number of queries for Realm admin isn't much higher than for normal users.
-        self.login(self.example_email("iago"))
+        self.login('iago')
         flush_per_request_caches()
         with queries_captured() as queries:
             with patch('zerver.lib.cache.cache_set') as cache_mock:
@@ -323,7 +321,7 @@ class HomeTest(ZulipTestCase):
 
         realm_id = main_user.realm_id
 
-        self.login(main_user.email)
+        self.login_user(main_user)
 
         # Try to make page-load do extra work for various subscribed
         # streams.
@@ -377,8 +375,7 @@ class HomeTest(ZulipTestCase):
 
     def test_terms_of_service(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
-        self.login(email)
+        self.login_user(user)
 
         for user_tos_version in [None, '1.1', '2.0.3.4']:
             user.tos_version = user_tos_version
@@ -395,8 +392,7 @@ class HomeTest(ZulipTestCase):
 
     def test_terms_of_service_first_time_template(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
-        self.login(email)
+        self.login_user(user)
 
         user.tos_version = None
         user.save()
@@ -410,8 +406,7 @@ class HomeTest(ZulipTestCase):
             self.assert_in_response("most productive team chat", result)
 
     def test_accept_terms_of_service(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
 
         result = self.client_post('/accounts/accept_terms/')
         self.assertEqual(result.status_code, 200)
@@ -422,8 +417,7 @@ class HomeTest(ZulipTestCase):
         self.assertEqual(result['Location'], '/')
 
     def test_bad_narrow(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         with patch('logging.warning') as mock:
             result = self._get_home_page(stream='Invalid Stream')
         mock.assert_called_once()
@@ -432,19 +426,17 @@ class HomeTest(ZulipTestCase):
 
     def test_bad_pointer(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
         user_profile.pointer = 999999
         user_profile.save()
 
-        self.login(email)
+        self.login_user(user_profile)
         with patch('logging.warning') as mock:
             result = self._get_home_page()
         mock.assert_called_once_with('User %s has invalid pointer 999999' % (user_profile.id,))
         self._sanity_check(result)
 
     def test_topic_narrow(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         result = self._get_home_page(stream='Denmark', topic='lunch')
         self._sanity_check(result)
         html = result.content.decode('utf-8')
@@ -453,11 +445,10 @@ class HomeTest(ZulipTestCase):
                          {"must-revalidate", "no-store", "no-cache"})
 
     def test_notifications_stream(self) -> None:
-        email = self.example_email("hamlet")
         realm = get_realm('zulip')
         realm.notifications_stream_id = get_stream('Denmark', realm).id
         realm.save()
-        self.login(email)
+        self.login('hamlet')
         result = self._get_home_page()
         page_params = self._get_page_params(result)
         self.assertEqual(page_params['realm_notifications_stream_id'], get_stream('Denmark', realm).id)
@@ -491,11 +482,10 @@ class HomeTest(ZulipTestCase):
         return user
 
     def test_signup_notifications_stream(self) -> None:
-        email = self.example_email("hamlet")
         realm = get_realm('zulip')
         realm.signup_notifications_stream = get_stream('Denmark', realm)
         realm.save()
-        self.login(email)
+        self.login('hamlet')
         result = self._get_home_page()
         page_params = self._get_page_params(result)
         self.assertEqual(page_params['realm_signup_notifications_stream_id'], get_stream('Denmark', realm).id)
@@ -504,7 +494,7 @@ class HomeTest(ZulipTestCase):
     def test_people(self) -> None:
         hamlet = self.example_user('hamlet')
         realm = get_realm('zulip')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         for i in range(3):
             self.create_bot(
@@ -626,7 +616,7 @@ class HomeTest(ZulipTestCase):
         user_profile = self.example_user("hamlet")
         stream_name = 'New stream'
         self.subscribe(user_profile, stream_name)
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self._get_home_page(stream=stream_name)
         page_params = self._get_page_params(result)
         self.assertEqual(page_params['narrow_stream'], stream_name)
@@ -637,13 +627,12 @@ class HomeTest(ZulipTestCase):
 
     def test_invites_by_admins_only(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
 
         realm = user_profile.realm
         realm.invite_by_admins_only = True
         realm.save()
 
-        self.login(email)
+        self.login_user(user_profile)
         self.assertFalse(user_profile.is_realm_admin)
         result = self._get_home_page()
         html = result.content.decode('utf-8')
@@ -657,13 +646,12 @@ class HomeTest(ZulipTestCase):
 
     def test_show_invites_for_guest_users(self) -> None:
         user_profile = self.example_user('polonius')
-        email = user_profile.email
 
         realm = user_profile.realm
         realm.invite_by_admins_only = False
         realm.save()
 
-        self.login(email)
+        self.login_user(user_profile)
         self.assertFalse(user_profile.is_realm_admin)
         self.assertFalse(get_realm('zulip').invite_by_admins_only)
         result = self._get_home_page()
@@ -675,7 +663,7 @@ class HomeTest(ZulipTestCase):
 
         # realm admin, but no CustomerPlan -> no billing link
         user = self.example_user('iago')
-        self.login(user.email)
+        self.login_user(user)
         result_html = self._get_home_page().content.decode('utf-8')
         self.assertNotIn('Billing', result_html)
 
@@ -705,7 +693,7 @@ class HomeTest(ZulipTestCase):
 
     def test_show_plans(self) -> None:
         realm = get_realm("zulip")
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
 
         # Show plans link to all users if plan_type is LIMITED
         realm.plan_type = Realm.LIMITED
@@ -725,8 +713,7 @@ class HomeTest(ZulipTestCase):
         self.assertNotIn('Plans', result_html)
 
     def test_desktop_home(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         result = self.client_get("/desktop_home")
         self.assertEqual(result.status_code, 301)
         self.assertTrue(result["Location"].endswith("/desktop_home/"))
@@ -784,8 +771,7 @@ class HomeTest(ZulipTestCase):
                          "/static/images/logo/zulip-org-logo.png?version=0")
 
     def test_generate_204(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         result = self.client_get("/api/v1/generate_204")
         self.assertEqual(result.status_code, 204)
 
@@ -797,8 +783,7 @@ class HomeTest(ZulipTestCase):
         self.assertEqual(sent_time_in_epoch_seconds(user_message), epoch_seconds)
 
     def test_subdomain_homepage(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
         with self.settings(ROOT_DOMAIN_LANDING_PAGE=True):
             with patch('zerver.views.home.get_subdomain', return_value=""):
                 result = self._get_home_page()
@@ -824,7 +809,7 @@ class HomeTest(ZulipTestCase):
         # In this test we make sure if a soft deactivated user had unread
         # messages before deactivation they remain same way after activation.
         long_term_idle_user = self.example_user('hamlet')
-        self.login(long_term_idle_user.email)
+        self.login_user(long_term_idle_user)
         message = 'Test Message 1'
         self.send_test_message(message)
         with queries_captured() as queries:
@@ -836,7 +821,7 @@ class HomeTest(ZulipTestCase):
 
         do_soft_deactivate_users([long_term_idle_user])
 
-        self.login(long_term_idle_user.email)
+        self.login_user(long_term_idle_user)
         message = 'Test Message 2'
         self.send_test_message(message)
         idle_user_msg_list = get_user_messages(long_term_idle_user)
@@ -859,7 +844,7 @@ class HomeTest(ZulipTestCase):
 
         message = 'Test Message 1'
         self.send_test_message(message)
-        self.login(long_term_idle_user.email)
+        self.login_user(long_term_idle_user)
         with queries_captured() as queries:
             self.assertEqual(self.soft_activate_and_get_unread_count(), 2)
         query_count = len(queries)
@@ -883,7 +868,7 @@ class HomeTest(ZulipTestCase):
 
         message = 'Test Message 3'
         self.send_test_message(message)
-        self.login(long_term_idle_user.email)
+        self.login_user(long_term_idle_user)
         with queries_captured() as queries:
             self.assertEqual(self.soft_activate_and_get_unread_count(), 4)
         query_count = len(queries)
@@ -905,7 +890,7 @@ class HomeTest(ZulipTestCase):
         user = self.example_user("hamlet")
         user.default_language = 'es'
         user.save()
-        self.login(user.email)
+        self.login_user(user)
         result = self._get_home_page()
         self.assertEqual(result.status_code, 200)
         with \
@@ -921,7 +906,7 @@ class HomeTest(ZulipTestCase):
         user = self.example_user("hamlet")
         user.default_language = 'es'
         user.save()
-        self.login(user.email)
+        self.login_user(user)
         result = self._get_home_page()
         self.assertEqual(result.status_code, 200)
 
diff --git a/zerver/tests/test_hotspots.py b/zerver/tests/test_hotspots.py
index 326b6dc16f..48633a3037 100644
--- a/zerver/tests/test_hotspots.py
+++ b/zerver/tests/test_hotspots.py
@@ -47,7 +47,7 @@ class TestHotspots(ZulipTestCase):
 
     def test_hotspots_url_endpoint(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         result = self.client_post('/json/users/me/hotspots',
                                   {'hotspot': ujson.dumps('intro_reply')})
         self.assert_json_success(result)
diff --git a/zerver/tests/test_i18n.py b/zerver/tests/test_i18n.py
index a6cca6a4a7..19f53da9d7 100644
--- a/zerver/tests/test_i18n.py
+++ b/zerver/tests/test_i18n.py
@@ -39,7 +39,7 @@ class EmailTranslationTestCase(ZulipTestCase):
         realm.default_language = "de"
         realm.save()
         stream = get_realm_stream("Denmark", realm.id)
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         # TODO: Uncomment and replace with translation once we have German translations for the strings
         # in confirm_new_email.txt.
@@ -121,8 +121,7 @@ class JsonTranslationTestCase(ZulipTestCase):
         dummy_value = "this arg is bad: '{var_name}' (translated to German)"
         mock_gettext.return_value = dummy_value
 
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/invites",
                                   HTTP_ACCEPT_LANGUAGE='de')
 
@@ -136,8 +135,7 @@ class JsonTranslationTestCase(ZulipTestCase):
         dummy_value = "Some other language"
         mock_gettext.return_value = dummy_value
 
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_get("/de/accounts/login/jwt/")
 
         self.assert_json_error_contains(result,
diff --git a/zerver/tests/test_legacy_subject.py b/zerver/tests/test_legacy_subject.py
index af543376e5..436a9de452 100644
--- a/zerver/tests/test_legacy_subject.py
+++ b/zerver/tests/test_legacy_subject.py
@@ -4,7 +4,7 @@ from zerver.lib.test_classes import (
 
 class LegacySubjectTest(ZulipTestCase):
     def test_legacy_subject(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         payload = dict(
             type='stream',
diff --git a/zerver/tests/test_link_embed.py b/zerver/tests/test_link_embed.py
index 5fa3b7ac46..1ee7b86da4 100644
--- a/zerver/tests/test_link_embed.py
+++ b/zerver/tests/test_link_embed.py
@@ -270,7 +270,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_edit_message_history(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         msg_id = self.send_stream_message(user, "Scotland",
                                           topic_name="editing", content="original")
 
@@ -335,7 +335,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_message_update_race_condition(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         original_url = 'http://test.org/'
         edited_url = 'http://edited.org/'
         with mock.patch('zerver.lib.actions.queue_json_publish') as patched:
@@ -467,7 +467,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_link_preview_non_html_data(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         url = 'http://test.org/audio.mp3'
         with mock.patch('zerver.lib.actions.queue_json_publish') as patched:
             msg_id = self.send_stream_message(user, "Scotland", topic_name="foo", content=url)
@@ -495,7 +495,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_link_preview_no_open_graph_image(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         url = 'http://test.org/foo.html'
         with mock.patch('zerver.lib.actions.queue_json_publish') as patched:
             msg_id = self.send_stream_message(user, "Scotland", topic_name="foo", content=url)
@@ -523,7 +523,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_link_preview_open_graph_image_missing_content(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         url = 'http://test.org/foo.html'
         with mock.patch('zerver.lib.actions.queue_json_publish') as patched:
             msg_id = self.send_stream_message(user, "Scotland", topic_name="foo", content=url)
@@ -552,7 +552,7 @@ class PreviewTestCase(ZulipTestCase):
     @override_settings(INLINE_URL_EMBED_PREVIEW=True)
     def test_link_preview_no_content_type_header(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         url = 'http://test.org/'
         with mock.patch('zerver.lib.actions.queue_json_publish') as patched:
             msg_id = self.send_stream_message(user, "Scotland", topic_name="foo", content=url)
diff --git a/zerver/tests/test_logging_handlers.py b/zerver/tests/test_logging_handlers.py
index 83e45d0a8b..b65e04727c 100644
--- a/zerver/tests/test_logging_handlers.py
+++ b/zerver/tests/test_logging_handlers.py
@@ -74,8 +74,7 @@ class AdminNotifyHandlerTest(ZulipTestCase):
         handler.emit(record)
 
     def simulate_error(self) -> logging.LogRecord:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         with patch("zerver.decorator.rate_limit") as rate_limit_patch:
             rate_limit_patch.side_effect = capture_and_throw
             result = self.client_get("/json/users")
diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py
index a8c77098ce..cd6df04329 100644
--- a/zerver/tests/test_management_commands.py
+++ b/zerver/tests/test_management_commands.py
@@ -340,7 +340,7 @@ class TestSendToEmailMirror(ZulipTestCase):
     def test_sending_a_fixture(self) -> None:
         fixture_path = "zerver/tests/fixtures/email/1.txt"
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
 
         call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path))
@@ -352,7 +352,7 @@ class TestSendToEmailMirror(ZulipTestCase):
     def test_sending_a_json_fixture(self) -> None:
         fixture_path = "zerver/tests/fixtures/email/1.json"
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark")
 
         call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path))
@@ -364,7 +364,7 @@ class TestSendToEmailMirror(ZulipTestCase):
     def test_stream_option(self) -> None:
         fixture_path = "zerver/tests/fixtures/email/1.txt"
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, "Denmark2")
 
         call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path), "--stream=Denmark2")
diff --git a/zerver/tests/test_message_edit_notifications.py b/zerver/tests/test_message_edit_notifications.py
index 60b7039dd7..87008c185a 100644
--- a/zerver/tests/test_message_edit_notifications.py
+++ b/zerver/tests/test_message_edit_notifications.py
@@ -42,7 +42,7 @@ class EditMessageSideEffectsTest(ZulipTestCase):
         hamlet = self.example_user('hamlet')
         cordelia = self.example_user('cordelia')
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         message_id = self.send_personal_message(
             hamlet,
@@ -70,7 +70,7 @@ class EditMessageSideEffectsTest(ZulipTestCase):
         cordelia.enable_online_push_notifications = enable_online_push_notifications
         cordelia.save()
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         self.subscribe(hamlet, 'Scotland')
         self.subscribe(cordelia, 'Scotland')
 
diff --git a/zerver/tests/test_messages.py b/zerver/tests/test_messages.py
index 4d0a6f68f8..38fc0b3bbf 100644
--- a/zerver/tests/test_messages.py
+++ b/zerver/tests/test_messages.py
@@ -141,7 +141,7 @@ class TopicHistoryTest(ZulipTestCase):
 
         # Now subscribe this MIT user to the new stream and verify
         # that the new topic is not accessible
-        self.login(user_profile.email, realm=user_profile.realm)
+        self.login_user(user_profile)
         self.subscribe(user_profile, stream_name)
         endpoint = '/json/users/me/%d/topics' % (stream.id,)
         result = self.client_get(endpoint, dict(), subdomain="zephyr")
@@ -152,9 +152,8 @@ class TopicHistoryTest(ZulipTestCase):
     def test_topics_history(self) -> None:
         # verified: int(UserMessage.flags.read) == 1
         user_profile = self.example_user('iago')
-        email = user_profile.email
+        self.login_user(user_profile)
         stream_name = 'Verona'
-        self.login(email)
 
         stream = get_stream(stream_name, user_profile.realm)
         recipient = stream.recipient
@@ -222,7 +221,7 @@ class TopicHistoryTest(ZulipTestCase):
         # Now try as cordelia, who we imagine as a totally new user in
         # that she doesn't have UserMessage rows.  We should see the
         # same results for a public stream.
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.client_get(endpoint, dict())
         self.assert_json_success(result)
         history = result.json()['topics']
@@ -260,8 +259,7 @@ class TopicHistoryTest(ZulipTestCase):
         self.assertNotIn('topic2', [topic['name'] for topic in history])
 
     def test_bad_stream_id(self) -> None:
-        email = self.example_email("iago")
-        self.login(email)
+        self.login('iago')
 
         # non-sensible stream id
         endpoint = '/json/users/me/9999999999/topics'
@@ -302,7 +300,7 @@ class TopicDeleteTest(ZulipTestCase):
         last_msg_id = self.send_stream_message(user_profile, stream_name, topic_name=topic_name)
 
         # Deleting the topic
-        self.login(user_profile.email, realm=user_profile.realm)
+        self.login_user(user_profile)
         endpoint = '/json/streams/' + str(stream.id) + '/delete_topic'
         result = self.client_post(endpoint, {
             "topic_name": topic_name
@@ -317,7 +315,7 @@ class TopicDeleteTest(ZulipTestCase):
         # ADMIN USER subscribed now
         user_profile = self.example_user('iago')
         self.subscribe(user_profile, stream_name)
-        self.login(user_profile.email, realm=user_profile.realm)
+        self.login_user(user_profile)
         new_last_msg_id = self.send_stream_message(user_profile, stream_name, topic_name=topic_name)
 
         # Now admin deletes all messages in topic -- which should only
@@ -488,7 +486,7 @@ class TestAddressee(ZulipTestCase):
 
     def test_addressee_legacy_build_for_user_ids(self) -> None:
         realm = get_realm('zulip')
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_ids = [self.example_user('cordelia').id,
                     self.example_user('othello').id]
 
@@ -504,7 +502,7 @@ class TestAddressee(ZulipTestCase):
 
     def test_addressee_legacy_build_for_stream_id(self) -> None:
         realm = get_realm('zulip')
-        self.login(self.example_email('iago'))
+        self.login('iago')
         sender = self.example_user('iago')
         self.subscribe(sender, "Denmark")
         stream = get_stream('Denmark', realm)
@@ -755,7 +753,7 @@ class PersonalMessagesTest(ZulipTestCase):
         """
         Make sure `is_private` flag is not leaked to the API.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.send_personal_message(self.example_user("hamlet"),
                                    self.example_user("cordelia"),
                                    "test")
@@ -858,7 +856,7 @@ class PersonalMessagesTest(ZulipTestCase):
         """
         If you send a personal, only you and the recipient see it.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assert_personal(self.example_email("hamlet"), self.example_email("othello"))
 
     def test_private_message_policy(self) -> None:
@@ -866,7 +864,7 @@ class PersonalMessagesTest(ZulipTestCase):
         Tests that PRIVATE_MESSAGE_POLICY_DISABLED works correctly.
         """
         user_profile = self.example_user("hamlet")
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         do_set_realm_property(user_profile.realm, "private_message_policy",
                               Realm.PRIVATE_MESSAGE_POLICY_DISABLED)
         with self.assertRaises(JsonableError):
@@ -881,7 +879,7 @@ class PersonalMessagesTest(ZulipTestCase):
         """
         Sending a PM containing non-ASCII characters succeeds.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assert_personal(self.example_email("hamlet"), self.example_email("othello"), u"hümbüǵ")
 
 class StreamMessagesTest(ZulipTestCase):
@@ -911,7 +909,7 @@ class StreamMessagesTest(ZulipTestCase):
         non_bot_subscribers = [user_profile for user_profile in subscribers
                                if not user_profile.is_bot]
         a_subscriber = non_bot_subscribers[0]
-        self.login(a_subscriber.email)
+        self.login_user(a_subscriber)
         self.send_stream_message(a_subscriber, stream_name,
                                  content=content, topic_name=topic_name)
 
@@ -1190,7 +1188,7 @@ class StreamMessagesTest(ZulipTestCase):
         Sending a stream message containing non-ASCII characters in the stream
         name, topic, or message body succeeds.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         # Subscribe everyone to a stream with non-ASCII characters.
         non_ascii_stream_name = u"hümbüǵ"
@@ -1409,7 +1407,7 @@ class MessageDictTest(ZulipTestCase):
                          sender.full_name)
 
     def test_missing_anchor(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_get(
             '/json/messages?use_first_unread_anchor=false&num_before=1&num_after=1')
 
@@ -1417,7 +1415,7 @@ class MessageDictTest(ZulipTestCase):
             result, "Missing 'anchor' argument.")
 
     def test_invalid_anchor(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_get(
             '/json/messages?use_first_unread_anchor=false&num_before=1&num_after=1&anchor=chocolate')
 
@@ -1481,7 +1479,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a message to a stream to which you are subscribed is
         successful.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -1529,7 +1527,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a message to a stream (by stream ID) to which you are
         subscribed is successful.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm = get_realm('zulip')
         stream = get_stream('Verona', realm)
         result = self.client_post("/json/messages", {"type": "stream",
@@ -1546,7 +1544,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending messages to streams which only the admins can create and post to.
         """
         admin_profile = self.example_user("iago")
-        self.login(admin_profile.email)
+        self.login_user(admin_profile)
 
         stream_name = "Verona"
         stream = get_stream(stream_name, admin_profile.realm)
@@ -1562,7 +1560,7 @@ class MessagePOSTTest(ZulipTestCase):
         self._send_and_verify_message(admin_owned_bot, stream_name)
 
         non_admin_profile = self.example_user("hamlet")
-        self.login(non_admin_profile.email)
+        self.login_user(non_admin_profile)
 
         # Non admins and their owned bots cannot send to STREAM_POST_POLICY_ADMINS streams
         self._send_and_verify_message(non_admin_profile, stream_name,
@@ -1598,7 +1596,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending messages to streams which new members cannot create and post to.
         """
         admin_profile = self.example_user("iago")
-        self.login(admin_profile.email)
+        self.login_user(admin_profile)
 
         do_set_realm_property(admin_profile.realm, 'waiting_period_threshold', 10)
         admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
@@ -1621,7 +1619,7 @@ class MessagePOSTTest(ZulipTestCase):
         self._send_and_verify_message(admin_owned_bot, stream_name)
 
         non_admin_profile = self.example_user("hamlet")
-        self.login(non_admin_profile.email)
+        self.login_user(non_admin_profile)
 
         non_admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
         non_admin_profile.save()
@@ -1679,7 +1677,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a message to a nonexistent stream fails.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertFalse(Stream.objects.filter(name="nonexistent_stream"))
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "nonexistent_stream",
@@ -1692,7 +1690,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Nonexistent stream name with bad characters should be escaped properly.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertFalse(Stream.objects.filter(name="""&<"'>"""))
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": """&<"'>""",
@@ -1706,7 +1704,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a personal message to a valid username is successful.
         """
         user_profile = self.example_user("hamlet")
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "Test message",
                                                      "client": "test suite",
@@ -1746,7 +1744,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a personal message to a valid user ID is successful.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post(
             "/json/messages",
             {
@@ -1766,7 +1764,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a personal message to a valid user ID is successful.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post(
             "/json/messages",
             {
@@ -1792,7 +1790,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a personal message to yourself plus another user is successful,
         and counts as a message just to that user.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {
             "type": "private",
             "content": "Test message",
@@ -1808,7 +1806,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a personal message to an invalid email returns error JSON.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "Test message",
                                                      "client": "test suite",
@@ -1821,7 +1819,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         target_user_profile = self.example_user("othello")
         do_deactivate_user(target_user_profile)
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {
             "type": "private",
             "content": "Test message",
@@ -1841,7 +1839,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a message of unknown type returns error JSON.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "invalid type",
                                                      "content": "Test message",
                                                      "client": "test suite",
@@ -1852,7 +1850,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a message that is empty or only whitespace should fail
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": " ",
                                                      "client": "test suite",
@@ -1863,7 +1861,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a message that has empty string topic should fail
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -1875,7 +1873,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a message without topic should fail
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -1886,7 +1884,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Messages other than the type of "private" or "stream" are considered as invalid
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "invalid",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -1898,7 +1896,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending private message without recipients should fail
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "private",
                                                      "content": "Test content",
                                                      "client": "test suite",
@@ -1936,7 +1934,8 @@ class MessagePOSTTest(ZulipTestCase):
         """
         Sending a mirrored personal message via the browser should not work.
         """
-        self.login(self.mit_email("starnine"), realm=get_realm("zephyr"))
+        user = self.mit_user('starnine')
+        self.login_user(user)
         result = self.client_post("/json/messages",
                                   {"type": "private",
                                    "sender": self.mit_email("sipbtest"),
@@ -1987,7 +1986,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         A message with null bytes in it is handled.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         post_data = {"type": "stream", "to": "Verona", "client": "test suite",
                      "content": u"  I like null bytes \x00 in my content", "topic": "Test topic"}
         result = self.client_post("/json/messages", post_data)
@@ -1997,7 +1996,7 @@ class MessagePOSTTest(ZulipTestCase):
         """
         A message with mixed whitespace at the end is cleaned up.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         post_data = {"type": "stream", "to": "Verona", "client": "test suite",
                      "content": "  I like whitespace at the end! \n\n \n", "topic": "Test topic"}
         result = self.client_post("/json/messages", post_data)
@@ -2010,7 +2009,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a message longer than the maximum message length succeeds but is
         truncated.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         long_message = "A" * (MAX_MESSAGE_LENGTH + 1)
         post_data = {"type": "stream", "to": "Verona", "client": "test suite",
                      "content": long_message, "topic": "Test topic"}
@@ -2026,7 +2025,7 @@ class MessagePOSTTest(ZulipTestCase):
         Sending a message with a topic longer than the maximum topic length
         succeeds, but the topic is truncated.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         long_topic = "A" * (MAX_TOPIC_NAME_LENGTH + 1)
         post_data = {"type": "stream", "to": "Verona", "client": "test suite",
                      "content": "test content", "topic": long_topic}
@@ -2038,7 +2037,7 @@ class MessagePOSTTest(ZulipTestCase):
                          "A" * (MAX_TOPIC_NAME_LENGTH - 3) + "...")
 
     def test_send_forged_message_as_not_superuser(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -2048,7 +2047,7 @@ class MessagePOSTTest(ZulipTestCase):
         self.assert_json_error(result, "User not authorized for this query")
 
     def test_send_message_as_not_superuser_to_different_domain(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages", {"type": "stream",
                                                      "to": "Verona",
                                                      "client": "test suite",
@@ -2127,18 +2126,18 @@ class MessagePOSTTest(ZulipTestCase):
             self, create_mirrored_message_users_mock: Any) -> None:
         create_mirrored_message_users_mock.return_value = mock.Mock()
         user = self.mit_user("starnine")
-        user_id = user.id
+        self.login_user(user)
         result = self.api_post(user, "/api/v1/messages",
                                {"type": "private",
                                 "sender": self.mit_email("sipbtest"),
                                 "content": "Test message",
                                 "client": "zephyr_mirror",
-                                "to": ujson.dumps([user_id])},
+                                "to": ujson.dumps([user.id])},
                                subdomain="zephyr")
         self.assert_json_error(result, "Mirroring not allowed with recipient user IDs")
 
     def test_send_message_irc_mirror(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         bot_info = {
             'full_name': 'IRC bot',
             'short_name': 'irc',
@@ -2347,7 +2346,7 @@ class ScheduledMessageTest(ZulipTestCase):
                             defer_until: str='', tz_guess: str='',
                             delivery_type: str='send_later',
                             realm_str: str='zulip') -> HttpResponse:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         topic_name = ''
         if msg_type == 'stream':
@@ -2491,7 +2490,7 @@ class EditMessageTest(ZulipTestCase):
     def test_save_message(self) -> None:
         """This is also tested by a client test, but here we can verify
         the cache against the database"""
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                           topic_name="editing", content="before edit")
         result = self.client_patch("/json/messages/" + str(msg_id), {
@@ -2509,7 +2508,7 @@ class EditMessageTest(ZulipTestCase):
         self.check_message(msg_id, topic_name="edited")
 
     def test_fetch_raw_message(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_personal_message(
             from_user=self.example_user("hamlet"),
             to_user=self.example_user("cordelia"),
@@ -2523,17 +2522,17 @@ class EditMessageTest(ZulipTestCase):
         result = self.client_get('/json/messages/999999')
         self.assert_json_error(result, 'Invalid message(s)')
 
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.client_get('/json/messages/' + str(msg_id))
         self.assert_json_success(result)
 
-        self.login(self.example_email("othello"))
+        self.login('othello')
         result = self.client_get('/json/messages/' + str(msg_id))
         self.assert_json_error(result, 'Invalid message(s)')
 
     def test_fetch_raw_message_stream_wrong_realm(self) -> None:
         user_profile = self.example_user("hamlet")
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         stream = self.make_stream('public_stream')
         self.subscribe(user_profile, stream.name)
         msg_id = self.send_stream_message(user_profile, stream.name,
@@ -2541,25 +2540,26 @@ class EditMessageTest(ZulipTestCase):
         result = self.client_get('/json/messages/' + str(msg_id))
         self.assert_json_success(result)
 
-        self.login(self.mit_email("sipbtest"), realm=get_realm("zephyr"))
+        mit_user = self.mit_user('sipbtest')
+        self.login_user(mit_user)
         result = self.client_get('/json/messages/' + str(msg_id), subdomain="zephyr")
         self.assert_json_error(result, 'Invalid message(s)')
 
     def test_fetch_raw_message_private_stream(self) -> None:
         user_profile = self.example_user("hamlet")
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         stream = self.make_stream('private_stream', invite_only=True)
         self.subscribe(user_profile, stream.name)
         msg_id = self.send_stream_message(user_profile, stream.name,
                                           topic_name="test", content="test")
         result = self.client_get('/json/messages/' + str(msg_id))
         self.assert_json_success(result)
-        self.login(self.example_email("othello"))
+        self.login('othello')
         result = self.client_get('/json/messages/' + str(msg_id))
         self.assert_json_error(result, 'Invalid message(s)')
 
     def test_edit_message_no_permission(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_stream_message(self.example_user("iago"), "Scotland",
                                           topic_name="editing", content="before edit")
         result = self.client_patch("/json/messages/" + str(msg_id), {
@@ -2569,7 +2569,7 @@ class EditMessageTest(ZulipTestCase):
         self.assert_json_error(result, "You don't have permission to edit this message")
 
     def test_edit_message_no_changes(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                           topic_name="editing", content="before edit")
         result = self.client_patch("/json/messages/" + str(msg_id), {
@@ -2578,7 +2578,7 @@ class EditMessageTest(ZulipTestCase):
         self.assert_json_error(result, "Nothing to change")
 
     def test_edit_message_no_topic(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                           topic_name="editing", content="before edit")
         result = self.client_patch("/json/messages/" + str(msg_id), {
@@ -2588,7 +2588,7 @@ class EditMessageTest(ZulipTestCase):
         self.assert_json_error(result, "Topic can't be empty")
 
     def test_edit_message_no_content(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                           topic_name="editing", content="before edit")
         result = self.client_patch("/json/messages/" + str(msg_id), {
@@ -2602,7 +2602,7 @@ class EditMessageTest(ZulipTestCase):
     def test_edit_message_history_disabled(self) -> None:
         user_profile = self.example_user("hamlet")
         do_set_realm_property(user_profile.realm, "allow_edit_history", False)
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         # Single-line edit
         msg_id_1 = self.send_stream_message(self.example_user("hamlet"),
@@ -2631,7 +2631,7 @@ class EditMessageTest(ZulipTestCase):
             self.assertNotIn("edit_history", msg)
 
     def test_edit_message_history(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         # Single-line edit
         msg_id_1 = self.send_stream_message(
@@ -2704,7 +2704,7 @@ class EditMessageTest(ZulipTestCase):
 
     def test_edit_link(self) -> None:
         # Link editing
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         msg_id_1 = self.send_stream_message(
             self.example_user("hamlet"),
             "Scotland",
@@ -2737,7 +2737,7 @@ class EditMessageTest(ZulipTestCase):
                           ' 
'))
 
     def test_edit_history_unedited(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
 
         msg_id = self.send_stream_message(
             self.example_user('hamlet'),
@@ -2756,7 +2756,7 @@ class EditMessageTest(ZulipTestCase):
         hamlet = self.example_user('hamlet')
         cordelia = self.example_user('cordelia')
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         self.subscribe(hamlet, 'Scotland')
         self.subscribe(cordelia, 'Scotland')
 
@@ -2774,7 +2774,7 @@ class EditMessageTest(ZulipTestCase):
     def test_edit_cases(self) -> None:
         """This test verifies the accuracy of construction of Zulip's edit
         history data structures."""
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         hamlet = self.example_user('hamlet')
         msg_id = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                           topic_name="topic 1", content="content 1")
@@ -2823,7 +2823,7 @@ class EditMessageTest(ZulipTestCase):
         self.assertEqual(history[0]['prev_content'], 'content 3')
         self.assertEqual(history[0]['user_id'], hamlet.id)
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_patch("/json/messages/" + str(msg_id), {
             'message_id': msg_id,
             'topic': 'topic 4',
@@ -2921,7 +2921,7 @@ class EditMessageTest(ZulipTestCase):
             self.assert_json_error(result, error)
             self.check_message(id_, topic_name=old_topic, content=old_content)
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         # send a message in the past
         id_ = self.send_stream_message(self.example_user("iago"), "Scotland",
                                        content="content", topic_name="topic")
@@ -2983,7 +2983,7 @@ class EditMessageTest(ZulipTestCase):
             self.assert_json_error(result, error)
             self.check_message(id_, topic_name=old_topic, content=old_content)
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         # send a message in the past
         id_ = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                        content="content", topic_name="topic")
@@ -2994,35 +2994,35 @@ class EditMessageTest(ZulipTestCase):
         # any user can edit the topic of a message
         set_message_editing_params(True, 0, True)
         # log in as a new user
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_edit_message_assert_success(id_, 'A')
 
         # only admins can edit the topics of messages
-        self.login(self.example_email("iago"))
+        self.login('iago')
         set_message_editing_params(True, 0, False)
         do_edit_message_assert_success(id_, 'B')
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_edit_message_assert_error(id_, 'C', "You don't have permission to edit this message")
 
         # users cannot edit topics if allow_message_editing is False
-        self.login(self.example_email("iago"))
+        self.login('iago')
         set_message_editing_params(False, 0, True)
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_edit_message_assert_error(id_, 'D', "Your organization has turned off message editing")
 
         # non-admin users cannot edit topics sent > 24 hrs ago
         message.date_sent = message.date_sent - datetime.timedelta(seconds=90000)
         message.save()
-        self.login(self.example_email("iago"))
+        self.login('iago')
         set_message_editing_params(True, 0, True)
         do_edit_message_assert_success(id_, 'E')
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_edit_message_assert_error(id_, 'F', "The time limit for editing this message has passed")
 
         # anyone should be able to edit "no topic" indefinitely
         message.set_topic_name("(no topic)")
         message.save()
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_edit_message_assert_success(id_, 'D')
 
     @mock.patch("zerver.lib.actions.send_event")
@@ -3032,10 +3032,10 @@ class EditMessageTest(ZulipTestCase):
         cordelia = self.example_user("cordelia")
         self.make_stream(stream_name, history_public_to_subscribers=True)
         self.subscribe(hamlet, stream_name)
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         message_id = self.send_stream_message(hamlet, stream_name, "Where am I?")
 
-        self.login(cordelia.email)
+        self.login_user(cordelia)
         self.subscribe(cordelia, stream_name)
         message = Message.objects.get(id=message_id)
 
@@ -3093,7 +3093,7 @@ class EditMessageTest(ZulipTestCase):
         # changed because she is not a subscriber and doesn't have a UserMessage row.
         self.subscribe(hamlet, stream_name)
         self.unsubscribe(cordelia, stream_name)
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         users_to_be_notified = list(map(notify, [hamlet.id]))
         do_update_message_topic_success(hamlet, message, "Change again", users_to_be_notified)
 
@@ -3105,7 +3105,7 @@ class EditMessageTest(ZulipTestCase):
         self.make_stream(stream_name, history_public_to_subscribers=True)
         self.subscribe(hamlet, stream_name)
         self.subscribe(cordelia, stream_name)
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         message_id = self.send_stream_message(hamlet, stream_name, "Hello everyone")
 
         def notify(user_id: int) -> Dict[str, Any]:
@@ -3134,7 +3134,7 @@ class EditMessageTest(ZulipTestCase):
         self.assertTrue(called)
 
     def test_propagate_topic_forward(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         id1 = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                        topic_name="topic1")
         id2 = self.send_stream_message(self.example_user("iago"), "Scotland",
@@ -3160,7 +3160,7 @@ class EditMessageTest(ZulipTestCase):
         self.check_message(id5, topic_name="edited")
 
     def test_propagate_all_topics(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         id1 = self.send_stream_message(self.example_user("hamlet"), "Scotland",
                                        topic_name="topic1")
         id2 = self.send_stream_message(self.example_user("hamlet"), "Scotland",
@@ -3341,7 +3341,7 @@ class MessageAccessTests(ZulipTestCase):
             "hello",
         )
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/messages/flags",
                                   {"messages": ujson.dumps([message]),
                                    "op": "add",
@@ -3378,7 +3378,7 @@ class MessageAccessTests(ZulipTestCase):
         You can set a message as starred/un-starred through
         POST /json/messages/flags.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         message_ids = [self.send_personal_message(self.example_user("hamlet"),
                                                   self.example_user("hamlet"),
                                                   "test")]
@@ -3408,7 +3408,7 @@ class MessageAccessTests(ZulipTestCase):
         """
         stream_name = "new_stream"
         self.subscribe(self.example_user("hamlet"), stream_name)
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         message_ids = [
             self.send_stream_message(self.example_user("hamlet"), stream_name, "test"),
         ]
@@ -3425,7 +3425,7 @@ class MessageAccessTests(ZulipTestCase):
         ]
 
         # Now login as another user who wasn't on that stream
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         # Send a message to yourself to make sure we have at least one with the read flag
         sent_message_ids = [
             self.send_personal_message(
@@ -3467,7 +3467,8 @@ class MessageAccessTests(ZulipTestCase):
         self.assert_json_success(result)
 
         # But it still doesn't work if you're in another realm
-        self.login(self.mit_email("sipbtest"), realm=get_realm("zephyr"))
+        user = self.mit_user('sipbtest')
+        self.login_user(user)
         result = self.change_star(message_ids, subdomain="zephyr")
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3476,7 +3477,7 @@ class MessageAccessTests(ZulipTestCase):
         You can set a message as starred/un-starred through
         POST /json/messages/flags.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         message_ids = [
             self.send_personal_message(
                 self.example_user("hamlet"),
@@ -3486,7 +3487,7 @@ class MessageAccessTests(ZulipTestCase):
         ]
 
         # Starring private messages you didn't receive fails.
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.change_star(message_ids)
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3494,7 +3495,7 @@ class MessageAccessTests(ZulipTestCase):
         stream_name = "private_stream"
         self.make_stream(stream_name, invite_only=True)
         self.subscribe(self.example_user("hamlet"), stream_name)
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         message_ids = [
             self.send_stream_message(self.example_user("hamlet"), stream_name, "test"),
         ]
@@ -3504,7 +3505,7 @@ class MessageAccessTests(ZulipTestCase):
         self.assert_json_success(result)
 
         # Starring private stream messages you didn't receive fails.
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.change_star(message_ids)
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3512,7 +3513,7 @@ class MessageAccessTests(ZulipTestCase):
         self.make_stream(stream_name, invite_only=True,
                          history_public_to_subscribers=True)
         self.subscribe(self.example_user("hamlet"), stream_name)
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         message_ids = [
             self.send_stream_message(self.example_user("hamlet"), stream_name, "test"),
         ]
@@ -3520,7 +3521,7 @@ class MessageAccessTests(ZulipTestCase):
         # With stream.history_public_to_subscribers = True, you still
         # can't see it if you didn't receive the message and are
         # not subscribed.
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.change_star(message_ids)
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3534,7 +3535,7 @@ class MessageAccessTests(ZulipTestCase):
         New messages aren't starred.
         """
         sender = self.example_user('hamlet')
-        self.login(sender.email)
+        self.login_user(sender)
         content = "Test message for star"
         self.send_stream_message(sender, "Verona",
                                  content=content)
@@ -3551,14 +3552,14 @@ class MessageAccessTests(ZulipTestCase):
         stream_name = "public_stream"
         self.make_stream(stream_name)
         self.subscribe(normal_user, stream_name)
-        self.login(normal_user.email)
+        self.login_user(normal_user)
 
         message_id = [
             self.send_stream_message(normal_user, stream_name, "test 1")
         ]
 
         guest_user = self.example_user('polonius')
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.change_star(message_id)
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3568,11 +3569,11 @@ class MessageAccessTests(ZulipTestCase):
         self.assert_json_success(result)
 
         # And messages sent after they join
-        self.login(normal_user.email)
+        self.login_user(normal_user)
         message_id = [
             self.send_stream_message(normal_user, stream_name, "test 2")
         ]
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.change_star(message_id)
         self.assert_json_success(result)
 
@@ -3582,14 +3583,14 @@ class MessageAccessTests(ZulipTestCase):
         stream_name = "private_stream"
         stream = self.make_stream(stream_name, invite_only=True)
         self.subscribe(normal_user, stream_name)
-        self.login(normal_user.email)
+        self.login_user(normal_user)
 
         message_id = [
             self.send_stream_message(normal_user, stream_name, "test 1")
         ]
 
         guest_user = self.example_user('polonius')
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.change_star(message_id)
         self.assert_json_error(result, 'Invalid message(s)')
 
@@ -3607,17 +3608,17 @@ class MessageAccessTests(ZulipTestCase):
 
         # With history not public to subscribers, they can still see new messages
         do_change_stream_invite_only(stream, True, history_public_to_subscribers=False)
-        self.login(normal_user.email)
+        self.login_user(normal_user)
         message_id = [
             self.send_stream_message(normal_user, stream_name, "test 2")
         ]
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.change_star(message_id)
         self.assert_json_success(result)
 
     def test_bulk_access_messages_private_stream(self) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
 
         stream_name = "private_stream"
         stream = self.make_stream(stream_name, invite_only=True,
@@ -3664,7 +3665,7 @@ class MessageAccessTests(ZulipTestCase):
 
     def test_bulk_access_messages_public_stream(self) -> None:
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
 
         # Testing messages accessiblity including a public stream message
         stream_name = "public_stream"
@@ -3928,7 +3929,6 @@ class LogDictTest(ZulipTestCase):
         stream_name = 'Denmark'
         topic_name = 'Copenhagen'
         content = 'find me some good coffee shops'
-        # self.login(self.example_email("hamlet"))
         message_id = self.send_stream_message(user, stream_name,
                                               topic_name=topic_name,
                                               content=content)
@@ -4037,7 +4037,7 @@ class CheckMessageTest(ZulipTestCase):
 
 class DeleteMessageTest(ZulipTestCase):
     def test_delete_message_invalid_request_format(self) -> None:
-        self.login("iago@zulip.com")
+        self.login('iago')
         hamlet = self.example_user('hamlet')
         msg_id = self.send_stream_message(hamlet, "Scotland")
         result = self.client_delete('/json/messages/{msg_id}'.format(msg_id=msg_id + 1),
@@ -4049,7 +4049,7 @@ class DeleteMessageTest(ZulipTestCase):
     def test_delete_message_by_user(self) -> None:
         def set_message_deleting_params(allow_message_deleting: bool,
                                         message_content_delete_limit_seconds: int) -> None:
-            self.login("iago@zulip.com")
+            self.login('iago')
             result = self.client_patch("/json/realm", {
                 'allow_message_deleting': ujson.dumps(allow_message_deleting),
                 'message_content_delete_limit_seconds': message_content_delete_limit_seconds
@@ -4057,24 +4057,24 @@ class DeleteMessageTest(ZulipTestCase):
             self.assert_json_success(result)
 
         def test_delete_message_by_admin(msg_id: int) -> HttpResponse:
-            self.login("iago@zulip.com")
+            self.login('iago')
             result = self.client_delete('/json/messages/{msg_id}'.format(msg_id=msg_id))
             return result
 
         def test_delete_message_by_owner(msg_id: int) -> HttpResponse:
-            self.login("hamlet@zulip.com")
+            self.login('hamlet')
             result = self.client_delete('/json/messages/{msg_id}'.format(msg_id=msg_id))
             return result
 
         def test_delete_message_by_other_user(msg_id: int) -> HttpResponse:
-            self.login("cordelia@zulip.com")
+            self.login('cordelia')
             result = self.client_delete('/json/messages/{msg_id}'.format(msg_id=msg_id))
             return result
 
         # Test if message deleting is not allowed(default).
         set_message_deleting_params(False, 0)
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         msg_id = self.send_stream_message(hamlet, "Scotland")
 
         result = test_delete_message_by_owner(msg_id=msg_id)
diff --git a/zerver/tests/test_muting.py b/zerver/tests/test_muting.py
index ef20e9e4bf..4a0324a054 100644
--- a/zerver/tests/test_muting.py
+++ b/zerver/tests/test_muting.py
@@ -58,7 +58,7 @@ class MutedTopicsTests(ZulipTestCase):
 
     def test_add_muted_topic(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         stream = get_stream('Verona', user.realm)
 
@@ -86,9 +86,8 @@ class MutedTopicsTests(ZulipTestCase):
 
     def test_remove_muted_topic(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
         realm = user.realm
-        self.login(email)
+        self.login_user(user)
 
         stream = get_stream(u'Verona', realm)
         recipient = stream.recipient
@@ -117,9 +116,8 @@ class MutedTopicsTests(ZulipTestCase):
 
     def test_muted_topic_add_invalid(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
         realm = user.realm
-        self.login(email)
+        self.login_user(user)
 
         stream = get_stream('Verona', realm)
         recipient = stream.recipient
@@ -151,9 +149,8 @@ class MutedTopicsTests(ZulipTestCase):
 
     def test_muted_topic_remove_invalid(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
         realm = user.realm
-        self.login(email)
+        self.login_user(user)
         stream = get_stream('Verona', realm)
 
         url = '/api/v1/users/me/subscriptions/muted_topics'
diff --git a/zerver/tests/test_narrow.py b/zerver/tests/test_narrow.py
index 85299ad6b8..c103c20cf6 100644
--- a/zerver/tests/test_narrow.py
+++ b/zerver/tests/test_narrow.py
@@ -1093,7 +1093,7 @@ class GetOldMessagesTest(ZulipTestCase):
         """
         Test old `/json/messages` returns reactions.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         def get_content_type(apply_markdown: bool) -> str:
             req = dict(
@@ -1117,11 +1117,11 @@ class GetOldMessagesTest(ZulipTestCase):
         """
         Test old `/json/messages` returns reactions.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         messages = self.get_and_check_messages(dict())
         message_id = messages['messages'][0]['id']
 
-        self.login(self.example_email("othello"))
+        self.login('othello')
         reaction_name = 'thumbs_up'
         reaction_info = {
             'emoji_name': reaction_name
@@ -1131,7 +1131,7 @@ class GetOldMessagesTest(ZulipTestCase):
         payload = self.client_post(url, reaction_info)
         self.assert_json_success(payload)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         messages = self.get_and_check_messages({})
         message_to_assert = None
         for message in messages['messages']:
@@ -1148,7 +1148,7 @@ class GetOldMessagesTest(ZulipTestCase):
         A call to GET /json/messages with valid parameters returns a list of
         messages.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.get_and_check_messages(dict())
 
         # We have to support the legacy tuple style while there are old
@@ -1162,7 +1162,7 @@ class GetOldMessagesTest(ZulipTestCase):
         The client_gravatar flag determines whether we send avatar_url.
         """
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         self.send_personal_message(hamlet, self.example_user("iago"))
 
@@ -1219,7 +1219,7 @@ class GetOldMessagesTest(ZulipTestCase):
                      if not m.is_stream_message()]
         for personal in personals:
             emails = dr_emails(get_display_recipient(personal.recipient))
-            self.login(me.email)
+            self.login_user(me)
             narrow = [dict(operator='pm-with', operand=emails)]  # type: List[Dict[str, Any]]
             result = self.get_and_check_messages(dict(narrow=ujson.dumps(narrow)))
 
@@ -1236,7 +1236,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     def test_get_visible_messages_with_narrow_pm_with(self) -> None:
         me = self.example_user('hamlet')
-        self.login(me.email)
+        self.login_user(me)
         self.subscribe(self.example_user("hamlet"), 'Scotland')
 
         message_ids = []
@@ -1302,7 +1302,7 @@ class GetOldMessagesTest(ZulipTestCase):
             ),
         )
 
-        self.login(me.email)
+        self.login_user(me)
         test_operands = [self.example_email("cordelia"), self.example_user("cordelia").id]
         for operand in test_operands:
             narrow = [dict(operator='group-pm-with', operand=operand)]
@@ -1313,7 +1313,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     def test_get_visible_messages_with_narrow_group_pm_with(self) -> None:
         me = self.example_user('hamlet')
-        self.login(me.email)
+        self.login_user(me)
 
         message_ids = []
         message_ids.append(
@@ -1362,7 +1362,7 @@ class GetOldMessagesTest(ZulipTestCase):
         content = 'hello @**King Hamlet**'
         new_message_id = self.send_stream_message(cordelia, stream_name, content=content)
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         narrow = [
             dict(operator='stream', operand=stream_name)
         ]
@@ -1394,7 +1394,7 @@ class GetOldMessagesTest(ZulipTestCase):
         A request for old messages with a narrow by stream only returns
         messages for that stream.
         """
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         # We need to subscribe to a stream and then send a message to
         # it to ensure that we actually have a stream message in this
         # narrow view.
@@ -1416,7 +1416,7 @@ class GetOldMessagesTest(ZulipTestCase):
                 self.assertEqual(message["recipient_id"], stream_recipient_id)
 
     def test_get_visible_messages_with_narrow_stream(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.subscribe(self.example_user("hamlet"), 'Scotland')
 
         message_ids = []
@@ -1431,19 +1431,20 @@ class GetOldMessagesTest(ZulipTestCase):
         A request for old messages for a user in the mit.edu relam with unicode
         stream name should be correctly escaped in the database query.
         """
-        self.login(self.mit_email("starnine"), realm=get_realm("zephyr"))
+        user = self.mit_user('starnine')
+        self.login_user(user)
         # We need to susbcribe to a stream and then send a message to
         # it to ensure that we actually have a stream message in this
         # narrow view.
         lambda_stream_name = u"\u03bb-stream"
-        stream = self.subscribe(self.mit_user("starnine"), lambda_stream_name)
+        stream = self.subscribe(user, lambda_stream_name)
         self.assertTrue(stream.is_in_zephyr_realm)
 
         lambda_stream_d_name = u"\u03bb-stream.d"
-        self.subscribe(self.mit_user("starnine"), lambda_stream_d_name)
+        self.subscribe(user, lambda_stream_d_name)
 
-        self.send_stream_message(self.mit_user("starnine"), u"\u03bb-stream")
-        self.send_stream_message(self.mit_user("starnine"), u"\u03bb-stream.d")
+        self.send_stream_message(user, u"\u03bb-stream")
+        self.send_stream_message(user, u"\u03bb-stream.d")
 
         narrow = [dict(operator='stream', operand=u'\u03bb-stream')]
         result = self.get_and_check_messages(dict(num_after=2,
@@ -1465,8 +1466,7 @@ class GetOldMessagesTest(ZulipTestCase):
         topic name should be correctly escaped in the database query.
         """
         mit_user_profile = self.mit_user("starnine")
-        email = mit_user_profile.email
-        self.login(email, realm=get_realm("zephyr"))
+        self.login_user(mit_user_profile)
         # We need to susbcribe to a stream and then send a message to
         # it to ensure that we actually have a stream message in this
         # narrow view.
@@ -1495,12 +1495,11 @@ class GetOldMessagesTest(ZulipTestCase):
         We handle .d grouping for MIT realm personal messages correctly.
         """
         mit_user_profile = self.mit_user("starnine")
-        email = mit_user_profile.email
 
         # We need to susbcribe to a stream and then send a message to
         # it to ensure that we actually have a stream message in this
         # narrow view.
-        self.login(email, realm=mit_user_profile.realm)
+        self.login_user(mit_user_profile)
         self.subscribe(mit_user_profile, "Scotland")
 
         self.send_stream_message(mit_user_profile, "Scotland", topic_name=u".d.d")
@@ -1531,7 +1530,7 @@ class GetOldMessagesTest(ZulipTestCase):
         A request for old messages with a narrow by sender only returns
         messages sent by that person.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         # We need to send a message here to ensure that we actually
         # have a stream message in this narrow view.
         self.send_stream_message(self.example_user("hamlet"), "Scotland")
@@ -1562,7 +1561,7 @@ class GetOldMessagesTest(ZulipTestCase):
     @override_settings(USING_PGROONGA=False)
     def test_messages_in_narrow(self) -> None:
         user = self.example_user("cordelia")
-        self.login(user.email)
+        self.login_user(user)
 
         def send(content: str) -> int:
             msg_id = self.send_stream_message(
@@ -1595,7 +1594,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     @override_settings(USING_PGROONGA=False)
     def test_get_messages_with_search(self) -> None:
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
 
         messages_to_search = [
             ('breakfast', 'there are muffins in the conference room'),
@@ -1724,7 +1723,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     @override_settings(USING_PGROONGA=False)
     def test_get_visible_messages_with_search(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.subscribe(self.example_user("hamlet"), 'Scotland')
 
         messages_to_search = [
@@ -1754,7 +1753,7 @@ class GetOldMessagesTest(ZulipTestCase):
         )
         self._update_tsvector_index()
 
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
 
         stream_search_narrow = [
             dict(operator='search', operand='special'),
@@ -1772,7 +1771,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     @override_settings(USING_PGROONGA=True)
     def test_get_messages_with_search_pgroonga(self) -> None:
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
 
         next_message_id = self.get_last_message().id + 1
 
@@ -1910,7 +1909,7 @@ class GetOldMessagesTest(ZulipTestCase):
 
     def test_messages_in_narrow_for_non_search(self) -> None:
         user = self.example_user("cordelia")
-        self.login(user.email)
+        self.login_user(user)
 
         def send(content: str) -> int:
             msg_id = self.send_stream_message(
@@ -1946,7 +1945,7 @@ class GetOldMessagesTest(ZulipTestCase):
         Test that specifying an anchor but 0 for num_before and num_after
         returns at most 1 message.
         """
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         anchor = self.send_stream_message(self.example_user("cordelia"), "Verona")
 
         narrow = [dict(operator='sender', operand=self.example_email("cordelia"))]
@@ -1967,7 +1966,7 @@ class GetOldMessagesTest(ZulipTestCase):
             for message in messages:
                 assert(message["id"] in message_ids)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         Message.objects.all().delete()
 
@@ -2211,7 +2210,7 @@ class GetOldMessagesTest(ZulipTestCase):
         anchor, num_before, and num_after are all required
         POST parameters for get_messages.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         required_args = (("num_before", 1), ("num_after", 1))  # type: Tuple[Tuple[str, int], ...]
 
@@ -2226,7 +2225,7 @@ class GetOldMessagesTest(ZulipTestCase):
         A call to GET /json/messages requesting more than
         MAX_MESSAGES_PER_FETCH messages returns an error message.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_get("/json/messages", dict(anchor=1, num_before=3000, num_after=3000))
         self.assert_json_error(result, "Too many messages requested (maximum 5000).")
         result = self.client_get("/json/messages", dict(anchor=1, num_before=6000, num_after=0))
@@ -2239,7 +2238,7 @@ class GetOldMessagesTest(ZulipTestCase):
         num_before, num_after, and narrow must all be non-negative
         integers or strings that can be converted to non-negative integers.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         other_params = [("narrow", {}), ("anchor", 0)]
         int_params = ["num_before", "num_after"]
@@ -2261,7 +2260,7 @@ class GetOldMessagesTest(ZulipTestCase):
         """
         narrow must be a list of string pairs.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         other_params = [("anchor", 0), ("num_before", 0), ("num_after", 0)]  # type: List[Tuple[str, Union[int, str, bool]]]
 
@@ -2277,7 +2276,7 @@ class GetOldMessagesTest(ZulipTestCase):
         """
         Unrecognized narrow operators are rejected.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         for operator in ['', 'foo', 'stream:verona', '__init__']:
             narrow = [dict(operator=operator, operand='')]
             params = dict(anchor=0, num_before=0, num_after=0, narrow=ujson.dumps(narrow))
@@ -2286,7 +2285,7 @@ class GetOldMessagesTest(ZulipTestCase):
                                             "Invalid narrow operator: unknown operator")
 
     def test_invalid_narrow_operand_in_dict(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         # str or int is required for sender, group-pm-with, stream
         invalid_operands = [['1'], [2], None]
@@ -2336,7 +2335,7 @@ class GetOldMessagesTest(ZulipTestCase):
         If an invalid stream name is requested in get_messages, an error is
         returned.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         bad_stream_content = (0, [], ["x", "y"])  # type: Tuple[int, List[None], List[str]]
         self.exercise_bad_narrow_operand("stream", bad_stream_content,
                                          "Bad value for 'narrow'")
@@ -2346,13 +2345,13 @@ class GetOldMessagesTest(ZulipTestCase):
         If an invalid 'pm-with' is requested in get_messages, an
         error is returned.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         bad_stream_content = (0, [], ["x", "y"])  # type: Tuple[int, List[None], List[str]]
         self.exercise_bad_narrow_operand("pm-with", bad_stream_content,
                                          "Bad value for 'narrow'")
 
     def test_bad_narrow_nonexistent_stream(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.exercise_bad_narrow_operand("stream", ['non-existent stream'],
                                          "Invalid narrow operator: unknown stream")
 
@@ -2361,12 +2360,12 @@ class GetOldMessagesTest(ZulipTestCase):
                                                         'Invalid narrow operator: unknown stream')
 
     def test_bad_narrow_nonexistent_email(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.exercise_bad_narrow_operand("pm-with", ['non-existent-user@zulip.com'],
                                          "Invalid narrow operator: unknown user")
 
     def test_bad_narrow_pm_with_id_list(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         self.exercise_bad_narrow_operand('pm-with', [-24],
                                          "Bad value for 'narrow': [[\"pm-with\",-24]]")
 
@@ -2867,7 +2866,7 @@ WHERE user_profile_id = {hamlet_id} AND (content ILIKE '%jumping%' OR subject IL
 
     @override_settings(USING_PGROONGA=False)
     def test_get_messages_with_search_using_email(self) -> None:
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
 
         messages_to_search = [
             ('say hello', 'How are you doing, @**Othello, the Moor of Venice**?'),
diff --git a/zerver/tests/test_new_users.py b/zerver/tests/test_new_users.py
index e6fb68a3b8..f84ac5413b 100644
--- a/zerver/tests/test_new_users.py
+++ b/zerver/tests/test_new_users.py
@@ -63,8 +63,8 @@ class SendLoginEmailTest(ZulipTestCase):
 
     def test_dont_send_login_emails_if_send_login_emails_is_false(self) -> None:
         self.assertFalse(settings.SEND_LOGIN_EMAILS)
-        email = self.example_email('hamlet')
-        self.login(email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         self.assertEqual(len(mail.outbox), 0)
 
@@ -98,13 +98,13 @@ class SendLoginEmailTest(ZulipTestCase):
         do_change_notification_settings(user, "enable_login_emails", False)
         self.assertFalse(user.enable_login_emails)
         with mock.patch('zerver.signals.timezone_now', return_value=mock_time):
-            self.login(user.email)
+            self.login_user(user)
         self.assertEqual(len(mail.outbox), 0)
 
         do_change_notification_settings(user, "enable_login_emails", True)
         self.assertTrue(user.enable_login_emails)
         with mock.patch('zerver.signals.timezone_now', return_value=mock_time):
-            self.login(user.email)
+            self.login_user(user)
         self.assertEqual(len(mail.outbox), 1)
 
 
diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py
index a8233c9f5c..b45ea12f60 100644
--- a/zerver/tests/test_presence.py
+++ b/zerver/tests/test_presence.py
@@ -27,7 +27,6 @@ from zerver.models import (
     UserProfile,
     UserPresence,
     flush_per_request_caches,
-    get_realm,
 )
 
 import datetime
@@ -35,7 +34,7 @@ import datetime
 class ActivityTest(ZulipTestCase):
     @mock.patch("stripe.Customer.list", return_value=[])
     def test_activity(self, unused_mock: mock.Mock) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         client, _ = Client.objects.get_or_create(name='website')
         query = '/json/users/me/pointer'
         last_visit = timezone_now()
@@ -95,7 +94,7 @@ class UserPresenceModelTests(ZulipTestCase):
         presence_dct = get_status_dict_by_realm(user_profile.realm_id)
         self.assertEqual(len(presence_dct), 0)
 
-        self.login(email)
+        self.login_user(user_profile)
         result = self.client_post("/json/users/me/presence", {'status': 'active'})
         self.assert_json_success(result)
 
@@ -130,7 +129,7 @@ class UserPresenceModelTests(ZulipTestCase):
         user_profile = self.example_user('hamlet')
         email = user_profile.email
 
-        self.login(email)
+        self.login_user(user_profile)
         result = self.client_post("/json/users/me/presence", {'status': 'active'})
         self.assert_json_success(result)
 
@@ -154,16 +153,18 @@ class UserPresenceModelTests(ZulipTestCase):
 
 class UserPresenceTests(ZulipTestCase):
     def test_invalid_presence(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        user = self.example_user("hamlet")
+        self.login_user(user)
         result = self.client_post("/json/users/me/presence", {'status': 'foo'})
         self.assert_json_error(result, 'Invalid status: foo')
 
     def test_set_idle(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
         client = 'website'
 
+        user = self.example_user("hamlet")
+        email = user.email
+        self.login_user(user)
+
         result = self.client_post("/json/users/me/presence", {'status': 'idle'})
         self.assert_json_success(result)
         json = result.json()
@@ -173,8 +174,10 @@ class UserPresenceTests(ZulipTestCase):
         self.assertEqual(list(json['presences'].keys()), [self.example_email("hamlet")])
         timestamp = json['presences'][email][client]['timestamp']
 
-        email = self.example_email("othello")
-        self.login(email)
+        user = self.example_user("othello")
+        email = user.email
+        self.login_user(user)
+
         result = self.client_post("/json/users/me/presence", {'status': 'idle'})
         json = result.json()
         self.assertEqual(json['presences'][email][client]['status'], 'idle')
@@ -184,7 +187,7 @@ class UserPresenceTests(ZulipTestCase):
         self.assertGreaterEqual(newer_timestamp, timestamp)
 
     def test_set_active(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         client = 'website'
 
         result = self.client_post("/json/users/me/presence", {'status': 'idle'})
@@ -193,7 +196,7 @@ class UserPresenceTests(ZulipTestCase):
         self.assertEqual(result.json()['presences'][self.example_email("hamlet")][client]['status'], 'idle')
 
         email = self.example_email("othello")
-        self.login(self.example_email("othello"))
+        self.login('othello')
         result = self.client_post("/json/users/me/presence", {'status': 'idle'})
         self.assert_json_success(result)
         json = result.json()
@@ -210,7 +213,7 @@ class UserPresenceTests(ZulipTestCase):
     def test_new_user_input(self, unused_mock: mock.Mock) -> None:
         """Mostly a test for UserActivityInterval"""
         user_profile = self.example_user("hamlet")
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(UserActivityInterval.objects.filter(user_profile=user_profile).count(), 0)
         time_zero = timezone_now().replace(microsecond=0)
         with mock.patch('zerver.views.presence.timezone_now', return_value=time_zero):
@@ -282,7 +285,7 @@ class UserPresenceTests(ZulipTestCase):
     def test_filter_presence_idle_user_ids(self) -> None:
         user_profile = self.example_user("hamlet")
         from zerver.lib.actions import filter_presence_idle_user_ids
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         self.assertEqual(filter_presence_idle_user_ids({user_profile.id}), [user_profile.id])
         self.client_post("/json/users/me/presence", {'status': 'idle'})
@@ -298,7 +301,8 @@ class UserPresenceTests(ZulipTestCase):
 
     def test_no_mit(self) -> None:
         """Zephyr mirror realms such as MIT never get a list of users"""
-        self.login(self.mit_email("espuser"), realm=get_realm("zephyr"))
+        user = self.mit_user('espuser')
+        self.login_user(user)
         result = self.client_post("/json/users/me/presence", {'status': 'idle'},
                                   subdomain="zephyr")
         self.assert_json_success(result)
@@ -307,8 +311,7 @@ class UserPresenceTests(ZulipTestCase):
     def test_mirror_presence(self) -> None:
         """Zephyr mirror realms find out the status of their mirror bot"""
         user_profile = self.mit_user('espuser')
-        email = user_profile.email
-        self.login(email, realm=user_profile.realm)
+        self.login_user(user_profile)
 
         def post_presence() -> Dict[str, Any]:
             result = self.client_post("/json/users/me/presence", {'status': 'idle'},
@@ -337,13 +340,14 @@ class UserPresenceTests(ZulipTestCase):
         )
 
     def test_same_realm(self) -> None:
-        self.login(self.mit_email("espuser"), realm=get_realm("zephyr"))
+        espuser = self.mit_user('espuser')
+        self.login_user(espuser)
         self.client_post("/json/users/me/presence", {'status': 'idle'},
                          subdomain="zephyr")
         self.logout()
 
         # Ensure we don't see hamlet@zulip.com information leakage
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/users/me/presence", {'status': 'idle'})
         self.assert_json_success(result)
         json = result.json()
@@ -357,7 +361,7 @@ class SingleUserPresenceTests(ZulipTestCase):
 
         # First, we setup the test with some data
         user = self.example_user("othello")
-        self.login(self.example_email("othello"))
+        self.login_user(user)
         result = self.client_post("/json/users/me/presence", {'status': 'active'})
         result = self.client_post("/json/users/me/presence", {'status': 'active'},
                                   HTTP_USER_AGENT="ZulipDesktop/1.0")
@@ -379,13 +383,14 @@ class SingleUserPresenceTests(ZulipTestCase):
         result = self.client_get("/json/users/default-bot@zulip.com/presence")
         self.assert_json_error(result, "Presence is not supported for bot users.")
 
-        self.login(self.mit_email("sipbtest"), realm=get_realm("zephyr"))
+        sipbtest = self.mit_user('sipbtest')
+        self.login_user(sipbtest)
         result = self.client_get("/json/users/othello@zulip.com/presence",
                                  subdomain="zephyr")
         self.assert_json_error(result, "No such user")
 
         # Then, we check everything works
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_get("/json/users/othello@zulip.com/presence")
         result_dict = result.json()
         self.assertEqual(
@@ -395,7 +400,7 @@ class SingleUserPresenceTests(ZulipTestCase):
 
     def test_ping_only(self) -> None:
 
-        self.login(self.example_email("othello"))
+        self.login('othello')
         req = dict(
             status='active',
             ping_only='true',
@@ -407,7 +412,7 @@ class UserPresenceAggregationTests(ZulipTestCase):
     def _send_presence_for_aggregated_tests(self, user: UserProfile, status: str,
                                             validate_time: datetime.datetime) -> Dict[str, Dict[str, Any]]:
         email = user.email
-        self.login(email)
+        self.login_user(user)
         timezone_util = 'zerver.views.presence.timezone_now'
         with mock.patch(timezone_util, return_value=validate_time - datetime.timedelta(seconds=5)):
             self.client_post("/json/users/me/presence", {'status': status})
@@ -475,7 +480,7 @@ class UserPresenceAggregationTests(ZulipTestCase):
 
     def test_aggregated_presense_mixed(self) -> None:
         user = self.example_user("othello")
-        self.login(user.email)
+        self.login_user(user)
         validate_time = timezone_now()
         with mock.patch('zerver.views.presence.timezone_now',
                         return_value=validate_time - datetime.timedelta(seconds=3)):
@@ -492,7 +497,7 @@ class UserPresenceAggregationTests(ZulipTestCase):
 
     def test_aggregated_presense_offline(self) -> None:
         user = self.example_user("othello")
-        self.login(user.email)
+        self.login_user(user)
         validate_time = timezone_now()
         with self.settings(OFFLINE_THRESHOLD_SECS=1):
             result_dict = self._send_presence_for_aggregated_tests(user, 'idle', validate_time)
diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py
index 64c1d5dba6..700f10b4a9 100644
--- a/zerver/tests/test_push_notifications.py
+++ b/zerver/tests/test_push_notifications.py
@@ -282,8 +282,7 @@ class PushBouncerNotificationTest(BouncerTestCase):
         """
         mock_request.side_effect = self.bounce_request
         user = self.example_user('cordelia')
-        email = user.email
-        self.login(email)
+        self.login_user(user)
         server = RemoteZulipServer.objects.get(uuid=self.server_uuid)
 
         endpoints = [
@@ -1552,8 +1551,7 @@ class TestNumPushDevicesForUser(PushNotificationTest):
 class TestPushApi(ZulipTestCase):
     def test_push_api(self) -> None:
         user = self.example_user('cordelia')
-        email = user.email
-        self.login(email)
+        self.login_user(user)
 
         endpoints = [
             ('/json/users/me/apns_device_token', 'apple-tokenaz'),
diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py
index a993e76a30..3b1787d5d2 100644
--- a/zerver/tests/test_realm.py
+++ b/zerver/tests/test_realm.py
@@ -8,7 +8,6 @@ from django.conf import settings
 from typing import Any, Dict, List, Mapping
 
 from zerver.lib.actions import (
-    do_change_is_admin,
     do_change_realm_subdomain,
     do_set_realm_property,
     do_deactivate_realm,
@@ -81,9 +80,7 @@ class RealmTest(ZulipTestCase):
         ))
 
     def test_update_realm_description(self) -> None:
-        email = self.example_email("iago")
-        self.login(email)
-        realm = get_realm('zulip')
+        self.login('iago')
         new_description = u'zulip dev group'
         data = dict(description=ujson.dumps(new_description))
         events = []  # type: List[Mapping[str, Any]]
@@ -106,8 +103,7 @@ class RealmTest(ZulipTestCase):
         data = dict(description=ujson.dumps(new_description))
 
         # create an admin user
-        email = self.example_email("iago")
-        self.login(email)
+        self.login('iago')
 
         result = self.client_patch('/json/realm', data)
         self.assert_json_error(result, 'Organization description is too long.')
@@ -119,8 +115,7 @@ class RealmTest(ZulipTestCase):
         data = dict(name=ujson.dumps(new_name))
 
         # create an admin user
-        email = self.example_email("iago")
-        self.login(email)
+        self.login('iago')
 
         result = self.client_patch('/json/realm', data)
         self.assert_json_error(result, 'Organization name is too long.')
@@ -130,10 +125,7 @@ class RealmTest(ZulipTestCase):
     def test_admin_restrictions_for_changing_realm_name(self) -> None:
         new_name = 'Mice will play while the cat is away'
 
-        user_profile = self.example_user('othello')
-        email = user_profile.email
-        self.login(email)
-        do_change_is_admin(user_profile, False)
+        self.login('othello')
 
         req = dict(name=ujson.dumps(new_name))
         result = self.client_patch('/json/realm', req)
@@ -142,8 +134,7 @@ class RealmTest(ZulipTestCase):
     def test_unauthorized_name_change(self) -> None:
         data = {'full_name': 'Sir Hamlet'}
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         do_set_realm_property(user_profile.realm, 'name_changes_disabled', True)
         url = '/json/settings'
         result = self.client_patch(url, data)
@@ -152,7 +143,7 @@ class RealmTest(ZulipTestCase):
         self.assert_in_response("", result)
         # Realm admins can change their name even setting is disabled.
         data = {'full_name': 'New Iago'}
-        self.login(self.example_email("iago"))
+        self.login('iago')
         url = '/json/settings'
         result = self.client_patch(url, data)
         self.assert_in_success_response(['"full_name":"New Iago"'], result)
@@ -263,8 +254,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_notifications_stream(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
 
         disabled_notif_stream_id = -1
         req = dict(notifications_stream_id = ujson.dumps(disabled_notif_stream_id))
@@ -300,8 +290,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_signup_notifications_stream(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
 
         disabled_signup_notifications_stream_id = -1
         req = dict(signup_notifications_stream_id = ujson.dumps(disabled_signup_notifications_stream_id))
@@ -341,8 +330,7 @@ class RealmTest(ZulipTestCase):
         realm = get_realm('zulip')
         self.assertNotEqual(realm.default_language, new_lang)
         # we need an admin user.
-        email = self.example_email("iago")
-        self.login(email)
+        self.login('iago')
 
         req = dict(default_language=ujson.dumps(new_lang))
         result = self.client_patch('/json/realm', req)
@@ -361,8 +349,7 @@ class RealmTest(ZulipTestCase):
         self.assertNotEqual(realm.default_language, invalid_lang)
 
     def test_deactivate_realm_by_admin(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         realm = get_realm('zulip')
         self.assertFalse(realm.deactivated)
 
@@ -372,8 +359,7 @@ class RealmTest(ZulipTestCase):
         self.assertTrue(realm.deactivated)
 
     def test_deactivate_realm_by_non_admin(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         realm = get_realm('zulip')
         self.assertFalse(realm.deactivated)
 
@@ -384,8 +370,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_bot_creation_policy(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
         req = dict(bot_creation_policy = ujson.dumps(Realm.BOT_CREATION_LIMIT_GENERIC_BOTS))
         result = self.client_patch('/json/realm', req)
         self.assert_json_success(result)
@@ -401,7 +386,7 @@ class RealmTest(ZulipTestCase):
         hamlet = self.example_user("hamlet")
         cordelia = self.example_user("cordelia")
 
-        self.login(user_profile.delivery_email)
+        self.login_user(user_profile)
         invalid_value = 12
         req = dict(email_address_visibility = ujson.dumps(invalid_value))
         result = self.client_patch('/json/realm', req)
@@ -452,8 +437,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_stream_creation_policy(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
         req = dict(create_stream_policy = ujson.dumps(Realm.CREATE_STREAM_POLICY_ADMINS))
         result = self.client_patch('/json/realm', req)
         self.assert_json_success(result)
@@ -465,8 +449,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_invite_to_stream_policy(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
         req = dict(invite_to_stream_policy = ujson.dumps(Realm.INVITE_TO_STREAM_POLICY_ADMINS))
         result = self.client_patch('/json/realm', req)
         self.assert_json_success(result)
@@ -478,8 +461,7 @@ class RealmTest(ZulipTestCase):
 
     def test_user_group_edit_policy(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
         req = dict(user_group_edit_policy = ujson.dumps(Realm.USER_GROUP_EDIT_POLICY_ADMINS))
         result = self.client_patch('/json/realm', req)
         self.assert_json_success(result)
@@ -491,8 +473,7 @@ class RealmTest(ZulipTestCase):
 
     def test_private_message_policy(self) -> None:
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
         req = dict(private_message_policy = ujson.dumps(Realm.PRIVATE_MESSAGE_POLICY_DISABLED))
         result = self.client_patch('/json/realm', req)
         self.assert_json_success(result)
@@ -521,8 +502,7 @@ class RealmTest(ZulipTestCase):
         )
 
         # We need an admin user.
-        email = 'iago@zulip.com'
-        self.login(email)
+        self.login('iago')
 
         for name in integer_values:
             invalid_value = invalid_values.get(name)
@@ -547,8 +527,7 @@ class RealmTest(ZulipTestCase):
 
     def test_change_video_chat_provider(self) -> None:
         self.assertEqual(get_realm('zulip').video_chat_provider, Realm.VIDEO_CHAT_PROVIDERS['jitsi_meet']['id'])
-        email = self.example_email("iago")
-        self.login(email)
+        self.login('iago')
 
         invalid_video_chat_provider_value = 0
         req = {"video_chat_provider": ujson.dumps(invalid_video_chat_provider_value)}
@@ -699,10 +678,7 @@ class RealmAPITest(ZulipTestCase):
 
     def setUp(self) -> None:
         super().setUp()
-        user_profile = self.example_user('cordelia')
-        email = user_profile.email
-        self.login(email)
-        do_change_is_admin(user_profile, True)
+        self.login('iago')
 
     def set_up_db(self, attr: str, value: Any) -> None:
         realm = get_realm('zulip')
diff --git a/zerver/tests/test_realm_domains.py b/zerver/tests/test_realm_domains.py
index 86b7bdb7de..901d5853b7 100644
--- a/zerver/tests/test_realm_domains.py
+++ b/zerver/tests/test_realm_domains.py
@@ -21,7 +21,7 @@ class RealmDomainTest(ZulipTestCase):
         do_set_realm_property(realm, 'emails_restricted_to_domains', True)
 
     def test_list_realm_domains(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         RealmDomain.objects.create(realm=realm, domain='acme.com', allow_subdomains=True)
         result = self.client_get("/json/realm/domains")
@@ -33,7 +33,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assertEqual(received, expected)
 
     def test_not_realm_admin(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_post("/json/realm/domains")
         self.assert_json_error(result, 'Must be an organization administrator')
         result = self.client_patch("/json/realm/domains/15")
@@ -42,7 +42,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assert_json_error(result, 'Must be an organization administrator')
 
     def test_create_realm_domain(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         data = {'domain': ujson.dumps(''),
                 'allow_subdomains': ujson.dumps(True)}
         result = self.client_post("/json/realm/domains", info=data)
@@ -59,7 +59,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assert_json_error(result, 'The domain acme.com is already a part of your organization.')
 
         mit_user_profile = self.mit_user("sipbtest")
-        self.login(mit_user_profile.email, realm=get_realm("zephyr"))
+        self.login_user(mit_user_profile)
 
         do_change_is_admin(mit_user_profile, True)
 
@@ -68,7 +68,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assert_json_success(result)
 
     def test_patch_realm_domain(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         RealmDomain.objects.create(realm=realm, domain='acme.com',
                                    allow_subdomains=False)
@@ -87,7 +87,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assert_json_error(result, 'No entry found for domain non-existent.com.')
 
     def test_delete_realm_domain(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         RealmDomain.objects.create(realm=realm, domain='acme.com')
         result = self.client_delete("/json/realm/domains/non-existent.com")
@@ -100,7 +100,7 @@ class RealmDomainTest(ZulipTestCase):
         self.assertTrue(realm.emails_restricted_to_domains)
 
     def test_delete_all_realm_domains(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         query = RealmDomain.objects.filter(realm=realm)
 
diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py
index fe5a15ac65..5ff7c8ef5f 100644
--- a/zerver/tests/test_realm_emoji.py
+++ b/zerver/tests/test_realm_emoji.py
@@ -26,7 +26,7 @@ class RealmEmojiTest(ZulipTestCase):
 
     def test_list(self) -> None:
         emoji_author = self.example_user('iago')
-        self.login(emoji_author.email)
+        self.login_user(emoji_author)
         self.create_test_emoji('my_emoji', emoji_author)
 
         result = self.client_get("/json/realm/emoji")
@@ -35,8 +35,7 @@ class RealmEmojiTest(ZulipTestCase):
         self.assertEqual(len(result.json()["emoji"]), 2)
 
     def test_list_no_author(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         realm = get_realm('zulip')
         realm_emoji = self.create_test_emoji_with_no_author('my_emoji', realm)
 
@@ -50,8 +49,7 @@ class RealmEmojiTest(ZulipTestCase):
     def test_list_admins_only(self) -> None:
         # Test that realm emoji list is public and realm emojis
         # having no author are also there in the list.
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         realm = get_realm('zulip')
         realm.add_emoji_by_admins_only = True
         realm.save()
@@ -65,8 +63,9 @@ class RealmEmojiTest(ZulipTestCase):
         self.assertIsNone(test_emoji['author'])
 
     def test_upload(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        user = self.example_user('iago')
+        email = user.email
+        self.login_user(user)
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/my_emoji', info=emoji_data)
@@ -92,24 +91,21 @@ class RealmEmojiTest(ZulipTestCase):
         )
 
     def test_upload_exception(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/my_em*oji', info=emoji_data)
         self.assert_json_error(result, 'Invalid characters in emoji name')
 
     def test_upload_uppercase_exception(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/my_EMoji', info=emoji_data)
         self.assert_json_error(result, 'Invalid characters in emoji name')
 
     def test_upload_admins_only(self) -> None:
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         realm = get_realm('zulip')
         realm.add_emoji_by_admins_only = True
         realm.save()
@@ -119,8 +115,7 @@ class RealmEmojiTest(ZulipTestCase):
         self.assert_json_error(result, 'Must be an organization administrator')
 
     def test_upload_anyone(self) -> None:
-        email = self.example_email('othello')
-        self.login(email)
+        self.login('othello')
         realm = get_realm('zulip')
         realm.add_emoji_by_admins_only = False
         realm.save()
@@ -130,8 +125,7 @@ class RealmEmojiTest(ZulipTestCase):
         self.assert_json_success(result)
 
     def test_emoji_upload_by_guest_user(self) -> None:
-        email = self.example_email('polonius')
-        self.login(email)
+        self.login('polonius')
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/my_emoji', info=emoji_data)
@@ -139,7 +133,7 @@ class RealmEmojiTest(ZulipTestCase):
 
     def test_delete(self) -> None:
         emoji_author = self.example_user('iago')
-        self.login(emoji_author.email)
+        self.login_user(emoji_author)
         realm_emoji = self.create_test_emoji('my_emoji', emoji_author)
         result = self.client_delete('/json/realm/emoji/my_emoji')
         self.assert_json_success(result)
@@ -154,8 +148,7 @@ class RealmEmojiTest(ZulipTestCase):
         self.assertEqual(test_emoji["deactivated"], True)
 
     def test_delete_no_author(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         realm = get_realm('zulip')
         self.create_test_emoji_with_no_author('my_emoji', realm)
         result = self.client_delete('/json/realm/emoji/my_emoji')
@@ -163,7 +156,7 @@ class RealmEmojiTest(ZulipTestCase):
 
     def test_delete_admins_only(self) -> None:
         emoji_author = self.example_user('othello')
-        self.login(emoji_author.email)
+        self.login_user(emoji_author)
         realm = get_realm('zulip')
         realm.add_emoji_by_admins_only = True
         realm.save()
@@ -180,46 +173,42 @@ class RealmEmojiTest(ZulipTestCase):
         realm.save()
 
         self.create_test_emoji('my_emoji_1', emoji_author)
-        self.login(emoji_author.email)
+        self.login_user(emoji_author)
         result = self.client_delete("/json/realm/emoji/my_emoji_1")
         self.assert_json_success(result)
         self.logout()
 
         self.create_test_emoji('my_emoji_2', emoji_author)
-        self.login(self.example_email('iago'))
+        self.login('iago')
         result = self.client_delete("/json/realm/emoji/my_emoji_2")
         self.assert_json_success(result)
         self.logout()
 
         self.create_test_emoji('my_emoji_3', emoji_author)
-        self.login(self.example_email('cordelia'))
+        self.login('cordelia')
         result = self.client_delete("/json/realm/emoji/my_emoji_3")
         self.assert_json_error(result, 'Must be an organization administrator or emoji author')
 
     def test_delete_exception(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         result = self.client_delete("/json/realm/emoji/invalid_emoji")
         self.assert_json_error(result, "Emoji 'invalid_emoji' does not exist")
 
     def test_multiple_upload(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp1, get_test_image_file('img.png') as fp2:
             result = self.client_post('/json/realm/emoji/my_emoji', {'f1': fp1, 'f2': fp2})
         self.assert_json_error(result, 'You must upload exactly one file.')
 
     def test_emoji_upload_file_size_error(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp:
             with self.settings(MAX_EMOJI_FILE_SIZE=0):
                 result = self.client_post('/json/realm/emoji/my_emoji', {'file': fp})
         self.assert_json_error(result, 'Uploaded file is larger than the allowed limit of 0 MB')
 
     def test_upload_already_existed_emoji(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/green_tick', info=emoji_data)
@@ -227,8 +216,7 @@ class RealmEmojiTest(ZulipTestCase):
 
     def test_reupload(self) -> None:
         # An user should be able to reupload an emoji with same name.
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with get_test_image_file('img.png') as fp1:
             emoji_data = {'f1': fp1}
             result = self.client_post('/json/realm/emoji/my_emoji', info=emoji_data)
@@ -248,8 +236,7 @@ class RealmEmojiTest(ZulipTestCase):
         self.assertEqual(len(emojis), 3)
 
     def test_failed_file_upload(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         with mock.patch('zerver.lib.upload.write_local_file', side_effect=Exception()):
             with get_test_image_file('img.png') as fp1:
                 emoji_data = {'f1': fp1}
@@ -262,13 +249,13 @@ class RealmEmojiTest(ZulipTestCase):
         # other user B.
         emoji_author_1 = self.example_user('cordelia')
         self.create_test_emoji('test_emoji', emoji_author_1)
-        self.login(emoji_author_1.email)
+        self.login_user(emoji_author_1)
         result = self.client_delete('/json/realm/emoji/test_emoji')
         self.assert_json_success(result)
 
         emoji_author_2 = self.example_user('othello')
         self.create_test_emoji('test_emoji', emoji_author_2)
-        self.login(emoji_author_2.email)
+        self.login_user(emoji_author_2)
         result = self.client_delete('/json/realm/emoji/test_emoji')
         self.assert_json_success(result)
 
@@ -285,6 +272,6 @@ class RealmEmojiTest(ZulipTestCase):
 
         emoji_author_2 = self.example_user('othello')
         self.create_test_emoji('test_emoji', emoji_author_2)
-        self.login(emoji_author_2.email)
+        self.login_user(emoji_author_2)
         result = self.client_delete('/json/realm/emoji/test_emoji')
         self.assert_json_success(result)
diff --git a/zerver/tests/test_realm_export.py b/zerver/tests/test_realm_export.py
index b70da60a3b..2e2a9c85c4 100644
--- a/zerver/tests/test_realm_export.py
+++ b/zerver/tests/test_realm_export.py
@@ -28,14 +28,14 @@ class RealmExportTest(ZulipTestCase):
 
     def test_export_as_not_admin(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         with self.assertRaises(JsonableError):
             export_realm(self.client_post, user)
 
     @use_s3_backend
     def test_endpoint_s3(self) -> None:
         admin = self.example_user('iago')
-        self.login(admin.email)
+        self.login_user(admin)
         bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
         tarball_path = create_dummy_file('test-export.tar.gz')
 
@@ -94,7 +94,7 @@ class RealmExportTest(ZulipTestCase):
 
     def test_endpoint_local_uploads(self) -> None:
         admin = self.example_user('iago')
-        self.login(admin.email)
+        self.login_user(admin)
         tarball_path = create_dummy_file('test-export.tar.gz')
 
         # Test the export logic.
@@ -153,7 +153,7 @@ class RealmExportTest(ZulipTestCase):
 
     def test_realm_export_rate_limited(self) -> None:
         admin = self.example_user('iago')
-        self.login(admin.email)
+        self.login_user(admin)
 
         current_log = RealmAuditLog.objects.filter(
             event_type=RealmAuditLog.REALM_EXPORTED)
@@ -171,7 +171,7 @@ class RealmExportTest(ZulipTestCase):
 
     def test_upload_and_message_limit(self) -> None:
         admin = self.example_user('iago')
-        self.login(admin.email)
+        self.login_user(admin)
         realm_count = RealmCount.objects.create(realm_id=admin.realm.id,
                                                 end_time=timezone_now(),
                                                 subgroup=1,
diff --git a/zerver/tests/test_realm_filters.py b/zerver/tests/test_realm_filters.py
index 392e01fa9c..8870545ddc 100644
--- a/zerver/tests/test_realm_filters.py
+++ b/zerver/tests/test_realm_filters.py
@@ -9,8 +9,7 @@ from zerver.models import RealmFilter, get_realm
 class RealmFilterTest(ZulipTestCase):
 
     def test_list(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         realm = get_realm('zulip')
         do_add_realm_filter(
             realm,
@@ -22,8 +21,7 @@ class RealmFilterTest(ZulipTestCase):
         self.assertEqual(len(result.json()["filters"]), 1)
 
     def test_create(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         data = {"pattern": "", "url_format_string": "https://realm.com/my_realm_filter/%(id)s"}
         result = self.client_post("/json/realm/filters", info=data)
         self.assert_json_error(result, 'This field cannot be blank.')
@@ -97,16 +95,14 @@ class RealmFilterTest(ZulipTestCase):
         self.assertIsNotNone(re.match(data['pattern'], 'zulip/zulip#123'))
 
     def test_not_realm_admin(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         result = self.client_post("/json/realm/filters")
         self.assert_json_error(result, 'Must be an organization administrator')
         result = self.client_delete("/json/realm/filters/15")
         self.assert_json_error(result, 'Must be an organization administrator')
 
     def test_delete(self) -> None:
-        email = self.example_email('iago')
-        self.login(email)
+        self.login('iago')
         realm = get_realm('zulip')
         filter_id = do_add_realm_filter(
             realm,
diff --git a/zerver/tests/test_report.py b/zerver/tests/test_report.py
index 961e543b3c..4a9fbc0910 100644
--- a/zerver/tests/test_report.py
+++ b/zerver/tests/test_report.py
@@ -32,8 +32,7 @@ class StatsMock:
 
 class TestReport(ZulipTestCase):
     def test_send_time(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
 
         params = dict(
             time=5,
@@ -58,8 +57,7 @@ class TestReport(ZulipTestCase):
         self.assertEqual(stats_mock.func_calls, expected_calls)
 
     def test_narrow_time(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
 
         params = dict(
             initial_core=5,
@@ -80,8 +78,7 @@ class TestReport(ZulipTestCase):
         self.assertEqual(stats_mock.func_calls, expected_calls)
 
     def test_unnarrow_time(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
 
         params = dict(
             initial_core=5,
@@ -101,8 +98,8 @@ class TestReport(ZulipTestCase):
 
     @override_settings(BROWSER_ERROR_REPORTING=True)
     def test_report_error(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         params = fix_params(dict(
             message='hello',
@@ -128,7 +125,7 @@ class TestReport(ZulipTestCase):
             self.assertEqual(report[k], params[k])
 
         self.assertEqual(report['more_info'], dict(foo='bar', draft_content="'**xxxxx**'"))
-        self.assertEqual(report['user_email'], email)
+        self.assertEqual(report['user_email'], user.email)
 
         # Teset with no more_info
         del params['more_info']
diff --git a/zerver/tests/test_service_bot_system.py b/zerver/tests/test_service_bot_system.py
index ee0c3d1c35..0aa4fe0414 100644
--- a/zerver/tests/test_service_bot_system.py
+++ b/zerver/tests/test_service_bot_system.py
@@ -256,7 +256,7 @@ class TestServiceBotStateHandler(ZulipTestCase):
 
     def test_internal_endpoint(self):
         # type: () -> None
-        self.login(self.user_profile.email)
+        self.login_user(self.user_profile)
 
         # Store some data.
         initial_dict = {'key 1': 'value 1', 'key 2': 'value 2', 'key 3': 'value 3'}
diff --git a/zerver/tests/test_sessions.py b/zerver/tests/test_sessions.py
index 87a1b5e667..ec805c4a43 100644
--- a/zerver/tests/test_sessions.py
+++ b/zerver/tests/test_sessions.py
@@ -14,7 +14,7 @@ from zerver.lib.sessions import (
 )
 
 from zerver.models import (
-    get_realm, Realm
+    get_realm, Realm, UserProfile
 )
 
 from zerver.lib.test_classes import ZulipTestCase
@@ -23,11 +23,11 @@ import mock
 
 class TestSessions(ZulipTestCase):
 
-    def do_test_session(self, user: str,
+    def do_test_session(self, user: UserProfile,
                         action: Callable[[], Any],
                         realm: Realm,
                         expected_result: bool) -> None:
-        self.login(user, realm=realm)
+        self.login_user(user)
         self.assertIn('_auth_user_id', self.client.session)
         action()
         if expected_result:
@@ -38,8 +38,7 @@ class TestSessions(ZulipTestCase):
 
     def test_delete_session(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         self.assertIn('_auth_user_id', self.client.session)
         for session in user_sessions(user_profile):
             delete_session(session)
@@ -48,34 +47,33 @@ class TestSessions(ZulipTestCase):
 
     def test_delete_user_sessions(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.do_test_session(str(email), lambda: delete_user_sessions(user_profile),
+        self.do_test_session(user_profile, lambda: delete_user_sessions(user_profile),
                              get_realm("zulip"), True)
-        self.do_test_session(str(self.example_email("othello")),
+        self.do_test_session(self.example_user("othello"),
                              lambda: delete_user_sessions(user_profile),
                              get_realm("zulip"), False)
 
     def test_delete_realm_user_sessions(self) -> None:
         realm = get_realm('zulip')
-        self.do_test_session(self.example_email("hamlet"),
+        self.do_test_session(self.example_user("hamlet"),
                              lambda: delete_realm_user_sessions(realm),
                              get_realm("zulip"), True)
-        self.do_test_session(self.mit_email("sipbtest"),
+        self.do_test_session(self.mit_user("sipbtest"),
                              lambda: delete_realm_user_sessions(realm),
                              get_realm("zephyr"), False)
 
     def test_delete_all_user_sessions(self) -> None:
-        self.do_test_session(self.example_email("hamlet"),
+        self.do_test_session(self.example_user("hamlet"),
                              lambda: delete_all_user_sessions(),
                              get_realm("zulip"), True)
-        self.do_test_session(self.mit_email("sipbtest"),
+        self.do_test_session(self.mit_user("sipbtest"),
                              lambda: delete_all_user_sessions(),
                              get_realm("zephyr"), True)
 
     def test_delete_all_deactivated_user_sessions(self) -> None:
 
         # Test that no exception is thrown with a logged-out session
-        self.login(self.example_email("othello"))
+        self.login('othello')
         self.assertIn('_auth_user_id', self.client.session)
         self.client_post('/accounts/logout/')
         delete_all_deactivated_user_sessions()
@@ -83,15 +81,14 @@ class TestSessions(ZulipTestCase):
         self.assertEqual('/login/', result.url)
 
         # Test nothing happens to an active user's session
-        self.login(self.example_email("othello"))
+        self.login('othello')
         self.assertIn('_auth_user_id', self.client.session)
         delete_all_deactivated_user_sessions()
         self.assertIn('_auth_user_id', self.client.session)
 
         # Test that a deactivated session gets logged out
         user_profile_3 = self.example_user('cordelia')
-        email_3 = user_profile_3.email
-        self.login(email_3)
+        self.login_user(user_profile_3)
         self.assertIn('_auth_user_id', self.client.session)
         user_profile_3.is_active = False
         user_profile_3.save()
diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py
index 227241c200..21d4699481 100644
--- a/zerver/tests/test_settings.py
+++ b/zerver/tests/test_settings.py
@@ -11,8 +11,10 @@ from zerver.lib.test_classes import ZulipTestCase
 from zerver.lib.test_helpers import get_test_image_file
 from zerver.lib.users import get_all_api_keys
 from zerver.lib.rate_limiter import add_ratelimit_rule, remove_ratelimit_rule
-from zerver.models import get_realm, UserProfile, \
+from zerver.models import (
+    UserProfile,
     get_user_profile_by_api_key
+)
 
 class ChangeSettingsTest(ZulipTestCase):
 
@@ -22,7 +24,7 @@ class ChangeSettingsTest(ZulipTestCase):
     # DEPRECATED, to be deleted after all uses of check_for_toggle_param
     # are converted into check_for_toggle_param_patch.
     def check_for_toggle_param(self, pattern: str, param: str) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         json_result = self.client_post(pattern,
                                        {param: ujson.dumps(True)})
@@ -41,7 +43,7 @@ class ChangeSettingsTest(ZulipTestCase):
     # TODO: requires method consolidation, right now, there's no alternative
     # for check_for_toggle_param for PATCH.
     def check_for_toggle_param_patch(self, pattern: str, param: str) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         json_result = self.client_patch(pattern,
                                         {param: ujson.dumps(True)})
@@ -62,26 +64,37 @@ class ChangeSettingsTest(ZulipTestCase):
         A call to /json/settings with valid parameters changes the user's
         settings correctly and returns correct values.
         """
-        self.login(self.example_email("hamlet"))
+        user = self.example_user('hamlet')
+        self.login_user(user)
         json_result = self.client_patch(
             "/json/settings",
             dict(
                 full_name='Foo Bar',
-                old_password=initial_password(self.example_email("hamlet")),
+                old_password=initial_password(user.email),
                 new_password='foobar1',
             ))
         self.assert_json_success(json_result)
         result = ujson.loads(json_result.content)
         self.check_well_formed_change_settings_response(result)
-        self.assertEqual(self.example_user('hamlet').
-                         full_name, "Foo Bar")
+
+        user.refresh_from_db()
+        self.assertEqual(user.full_name, "Foo Bar")
         self.logout()
-        self.login(self.example_email("hamlet"), "foobar1")
-        user_profile = self.example_user('hamlet')
-        self.assert_logged_in_user_id(user_profile.id)
+
+        # This is one of the few places we log in directly
+        # with Django's client (to test the password change
+        # with as few moving parts as possible).
+        self.assertTrue(
+            self.client.login(
+                username=user.email,
+                password='foobar1',
+                realm=user.realm
+            )
+        )
+        self.assert_logged_in_user_id(user.id)
 
     def test_password_change_check_strength(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with self.settings(PASSWORD_MIN_LENGTH=3, PASSWORD_MIN_GUESSES=1000):
             json_result = self.client_patch(
                 "/json/settings",
@@ -103,8 +116,7 @@ class ChangeSettingsTest(ZulipTestCase):
 
     def test_illegal_name_changes(self) -> None:
         user = self.example_user('hamlet')
-        email = user.email
-        self.login(email)
+        self.login_user(user)
         full_name = user.full_name
 
         with self.settings(NAME_CHANGES_DISABLED=True):
@@ -130,8 +142,7 @@ class ChangeSettingsTest(ZulipTestCase):
         self.assert_json_error(json_result, 'Name too short!')
 
     def test_illegal_characters_in_name_changes(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        self.login('hamlet')
 
         # Now try a name with invalid characters
         json_result = self.client_patch("/json/settings",
@@ -139,9 +150,9 @@ class ChangeSettingsTest(ZulipTestCase):
         self.assert_json_error(json_result, 'Invalid characters in name!')
 
     def test_change_email_to_disposable_email(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
-        realm = get_realm("zulip")
+        hamlet = self.example_user("hamlet")
+        self.login_user(hamlet)
+        realm = hamlet.realm
         realm.disallow_disposable_email_addresses = True
         realm.emails_restricted_to_domains = False
         realm.save()
@@ -165,7 +176,7 @@ class ChangeSettingsTest(ZulipTestCase):
         pattern = "/json/settings/notifications"
         param = "notification_sound"
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         json_result = self.client_patch(pattern,
                                         {param: ujson.dumps("invalid")})
@@ -197,7 +208,7 @@ class ChangeSettingsTest(ZulipTestCase):
         self.check_for_toggle_param('/json/users/me/enter-sends', "enter_sends")
 
     def test_wrong_old_password(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_patch(
             "/json/settings",
             dict(
@@ -207,7 +218,7 @@ class ChangeSettingsTest(ZulipTestCase):
         self.assert_json_error(result, "Wrong password!")
 
     def test_wrong_old_password_rate_limiter(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with self.settings(RATE_LIMITING_AUTHENTICATE=True):
             add_ratelimit_rule(10, 2, domain='authenticate_by_username')
             start_time = time.time()
@@ -255,7 +266,7 @@ class ChangeSettingsTest(ZulipTestCase):
         self.init_default_ldap_database()
         ldap_user_attr_map = {'full_name': 'cn', 'short_name': 'sn'}
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         with self.settings(LDAP_APPEND_DOMAIN="zulip.com",
                            AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
@@ -301,7 +312,7 @@ class ChangeSettingsTest(ZulipTestCase):
         to this API, or it should fail.  (Eventually, we should
         probably use a patch interface for these changes.)
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = self.client_patch("/json/settings",
                                    dict(old_password='ignored',))
         self.assert_json_error(result, "Please fill out all fields.")
@@ -315,8 +326,7 @@ class ChangeSettingsTest(ZulipTestCase):
             demote_inactive_streams = 2,
         )  # type: Dict[str, Any]
 
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         test_value = test_changes.get(setting_name)
         # Error if a setting in UserProfile.property_types does not have test values
         if test_value is None:
@@ -356,8 +366,7 @@ class ChangeSettingsTest(ZulipTestCase):
             self.do_test_change_user_display_setting(setting)
 
     def do_change_emojiset(self, emojiset: str) -> HttpResponse:
-        email = self.example_email('hamlet')
-        self.login(email)
+        self.login('hamlet')
         data = {'emojiset': ujson.dumps(emojiset)}
         result = self.client_patch("/json/settings/display", data)
         return result
@@ -376,9 +385,7 @@ class ChangeSettingsTest(ZulipTestCase):
             self.assert_json_success(result)
 
     def test_avatar_changes_disabled(self) -> None:
-        user = self.example_user('hamlet')
-        email = user.email
-        self.login(email)
+        self.login('hamlet')
 
         with self.settings(AVATAR_CHANGES_DISABLED=True):
             result = self.client_delete("/json/users/me/avatar")
@@ -394,7 +401,7 @@ class UserChangesTest(ZulipTestCase):
         user = self.example_user('hamlet')
         email = user.email
 
-        self.login(email)
+        self.login_user(user)
         old_api_keys = get_all_api_keys(user)
         # Ensure the old API keys are in the authentication cache, so
         # that the below logic can test whether we have a cache-flushing bug.
diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py
index f64716ad3b..7b3f34e73e 100644
--- a/zerver/tests/test_signup.py
+++ b/zerver/tests/test_signup.py
@@ -218,10 +218,11 @@ class PasswordResetTest(ZulipTestCase):
     """
 
     def test_password_reset(self) -> None:
-        email = self.example_email("hamlet")
+        user = self.example_user("hamlet")
+        email = user.email
         old_password = initial_password(email)
 
-        self.login(email)
+        self.login_user(user)
 
         # test password reset template
         result = self.client_get('/accounts/password/reset/')
@@ -274,12 +275,12 @@ class PasswordResetTest(ZulipTestCase):
             self.assertTrue(result["Location"].endswith("/password/done/"))
 
             # log back in with new password
-            self.login(email, password='f657gdGGk9')
+            self.login_by_email(email, password='f657gdGGk9')
             user_profile = self.example_user('hamlet')
             self.assert_logged_in_user_id(user_profile.id)
 
             # make sure old password no longer works
-            self.login(email, password=old_password, fails=True)
+            self.assert_login_failure(email, password=old_password)
 
     def test_password_reset_for_non_existent_user(self) -> None:
         email = 'nonexisting@mars.com'
@@ -509,7 +510,7 @@ class LoginTest(ZulipTestCase):
     """
 
     def test_login(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         self.assert_logged_in_user_id(user_profile.id)
 
@@ -645,7 +646,7 @@ class LoginTest(ZulipTestCase):
         self.assertEqual('/accounts/deactivated/', result.url)
 
     def test_logout(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         # We use the logout API, not self.logout, to make sure we test
         # the actual logout code path.
         self.client_post('/accounts/logout/')
@@ -667,7 +668,7 @@ class LoginTest(ZulipTestCase):
 
         # Logging in succeeds.
         self.logout()
-        self.login(email, password)
+        self.login_by_email(email, password)
         self.assert_logged_in_user_id(user_profile.id)
 
     @override_settings(TWO_FACTOR_AUTHENTICATION_ENABLED=False)
@@ -675,7 +676,7 @@ class LoginTest(ZulipTestCase):
         """You will be redirected to the app's main page if you land on the
         login page when already logged in.
         """
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         response = self.client_get("/login/")
         self.assertEqual(response["Location"], "http://zulip.testserver")
 
@@ -691,7 +692,7 @@ class LoginTest(ZulipTestCase):
         user_profile = self.example_user("cordelia")
         self.create_default_device(user_profile)
 
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         self.login_2fa(user_profile)
 
         response = self.client_get("/login/")
@@ -764,7 +765,7 @@ class InviteUserTest(InviteUserBase):
         A call to /json/invites with valid parameters causes an invitation
         email to be sent.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invitee = "alice-test@zulip.com"
         self.assert_json_success(self.invite(invitee, ["Denmark"]))
         self.assertTrue(find_key_by_email(invitee))
@@ -775,7 +776,7 @@ class InviteUserTest(InviteUserBase):
         invitee = "alice-test@zulip.com"
         stream_name = 'Denmark'
 
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         result = self.invite(invitee, [stream_name])
         self.assert_json_success(result)
@@ -812,7 +813,7 @@ class InviteUserTest(InviteUserBase):
         ]
         invitees = ','.join(invite_emails)
 
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         realm.max_invites = realm_max
         realm.date_created = timezone_now()
@@ -874,7 +875,7 @@ class InviteUserTest(InviteUserBase):
 
     def test_cross_realm_bot(self) -> None:
         inviter = self.example_user('hamlet')
-        self.login(inviter.email)
+        self.login_user(inviter)
 
         cross_realm_bot_email = 'emailgateway@zulip.com'
         legit_new_email = 'fred@zulip.com'
@@ -901,7 +902,7 @@ class InviteUserTest(InviteUserBase):
         has a mirror dummy account.
         '''
         inviter = self.example_user('hamlet')
-        self.login(inviter.email)
+        self.login_user(inviter)
 
         mirror_user = self.example_user('cordelia')
         mirror_user.is_mirror_dummy = True
@@ -927,7 +928,7 @@ class InviteUserTest(InviteUserBase):
         Test that a new user invited to a stream receives some initial
         history but only from public streams.
         """
-        self.login(self.example_email('iago'))
+        self.login('iago')
         invitee = self.nonreg_email('alice')
         result = self.invite(invitee, ["Denmark"],
                              invite_as=PreregistrationUser.INVITE_AS['REALM_ADMIN'])
@@ -944,7 +945,7 @@ class InviteUserTest(InviteUserBase):
         Test that a new user invited to a stream receives some initial
         history but only from public streams.
         """
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         invitee = self.nonreg_email('alice')
         response = self.invite(invitee, ["Denmark"],
                                invite_as=PreregistrationUser.INVITE_AS['REALM_ADMIN'])
@@ -955,13 +956,13 @@ class InviteUserTest(InviteUserBase):
         Test inviting a user as invalid type of user i.e. type of invite_as
         is not in PreregistrationUser.INVITE_AS
         """
-        self.login(self.example_email('iago'))
+        self.login('iago')
         invitee = self.nonreg_email('alice')
         response = self.invite(invitee, ["Denmark"], invite_as=100)
         self.assert_json_error(response, "Must be invited as an valid type of user")
 
     def test_successful_invite_user_as_guest_from_normal_account(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         invitee = self.nonreg_email('alice')
         self.assert_json_success(self.invite(invitee, ["Denmark"],
                                              invite_as=PreregistrationUser.INVITE_AS['GUEST_USER']))
@@ -973,7 +974,7 @@ class InviteUserTest(InviteUserBase):
         self.assertTrue(invitee_profile.is_guest)
 
     def test_successful_invite_user_as_guest_from_admin_account(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
         invitee = self.nonreg_email('alice')
         self.assert_json_success(self.invite(invitee, ["Denmark"],
                                              invite_as=PreregistrationUser.INVITE_AS['GUEST_USER']))
@@ -989,7 +990,7 @@ class InviteUserTest(InviteUserBase):
         A call to /json/invites with valid parameters causes an invitation
         email to be sent.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         email = "alice-test@zulip.com"
         invitee = "Alice Test <{}>".format(email)
         self.assert_json_success(self.invite(invitee, ["Denmark"]))
@@ -1001,7 +1002,7 @@ class InviteUserTest(InviteUserBase):
         A call to /json/invites with valid parameters causes an invitation
         email to be sent.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         email = "alice-test@zulip.com"
         email2 = "bob-test@zulip.com"
         invitee = "Alice Test <{}>, {}".format(email, email2)
@@ -1018,7 +1019,7 @@ class InviteUserTest(InviteUserBase):
         realm.invite_by_admins_only = True
         realm.save()
 
-        self.login("hamlet@zulip.com")
+        self.login('hamlet')
         email = "alice-test@zulip.com"
         email2 = "bob-test@zulip.com"
         invitee = "Alice Test <{}>, {}".format(email, email2)
@@ -1026,7 +1027,7 @@ class InviteUserTest(InviteUserBase):
                                "Must be an organization administrator")
 
         # Now verify an administrator can do it
-        self.login("iago@zulip.com")
+        self.login('iago')
         self.assert_json_success(self.invite(invitee, ["Denmark"]))
         self.assertTrue(find_key_by_email(email))
         self.assertTrue(find_key_by_email(email2))
@@ -1037,7 +1038,7 @@ class InviteUserTest(InviteUserBase):
         Test that a new user invited to a stream receives some initial
         history but only from public streams.
         """
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         private_stream_name = "Secret"
         self.make_stream(private_stream_name, invite_only=True)
@@ -1084,7 +1085,7 @@ class InviteUserTest(InviteUserBase):
         """
         Invites multiple users with a variety of delimiters.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         # Intentionally use a weird string.
         self.assert_json_success(self.invite(
             """bob-test@zulip.com,     carol-test@zulip.com,
@@ -1109,7 +1110,7 @@ earl-test@zulip.com""", ["Denmark"]))
     def test_invite_too_many_users(self) -> None:
         # Only a light test of this pathway; e.g. doesn't test that
         # the limit gets reset after 24 hours
-        self.login(self.example_email("iago"))
+        self.login('iago')
         invitee_emails = "1@zulip.com, 2@zulip.com"
         self.invite(invitee_emails, ["Denmark"])
         invitee_emails = ", ".join([str(i) for i in range(get_realm("zulip").max_invites - 1)])
@@ -1125,7 +1126,7 @@ earl-test@zulip.com""", ["Denmark"]))
         realm = get_realm('zulip')
         do_set_realm_property(realm, 'emails_restricted_to_domains', True)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invitee_emails = "foo@zulip.com"
         self.assert_json_error(self.invite(invitee_emails, []),
                                "You must specify at least one stream for invitees to join.")
@@ -1145,7 +1146,7 @@ earl-test@zulip.com""", ["Denmark"]))
         """
         Guest user can't invite new users
         """
-        self.login(self.example_email("polonius"))
+        self.login('polonius')
         invitee = "alice-test@zulip.com"
         self.assert_json_error(self.invite(invitee, ["Denmark"]), "Not allowed for guest users")
         self.assertEqual(find_key_by_email(invitee), None)
@@ -1155,7 +1156,7 @@ earl-test@zulip.com""", ["Denmark"]))
         """
         Tests inviting to a non-existent stream.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assert_json_error(self.invite("iago-test@zulip.com", ["NotARealStream"]),
                                "Stream does not exist with id: {}. No invites were sent.".format(self.INVALID_STREAM_ID))
         self.check_sent_emails([])
@@ -1164,7 +1165,7 @@ earl-test@zulip.com""", ["Denmark"]))
         """
         If you invite an address already using Zulip, no invitation is sent.
         """
-        self.login(self.example_email("cordelia"))
+        self.login('hamlet')
 
         hamlet_email = 'hAmLeT@zUlIp.com'
         result = self.invite(hamlet_email, ["Denmark"])
@@ -1180,7 +1181,7 @@ earl-test@zulip.com""", ["Denmark"]))
         If you invite a mix of already existing and new users, invitations are
         only sent to the new users.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         existing = [self.example_email("hamlet"), u"othello@zulip.com"]
         new = [u"foo-test@zulip.com", u"bar-test@zulip.com"]
         invitee_emails = "\n".join(existing + new)
@@ -1211,7 +1212,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.emails_restricted_to_domains = True
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo@example.com"
 
         self.assert_json_error(
@@ -1228,7 +1229,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.disallow_disposable_email_addresses = True
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo@mailnator.com"
 
         self.assert_json_error(
@@ -1244,7 +1245,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.emails_restricted_to_domains = False
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo@example.com"
 
         self.assert_json_success(self.invite(external_address, ["Denmark"]))
@@ -1261,7 +1262,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.emails_restricted_to_domains = False
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo@example.com"
 
         self.assert_json_success(self.invite(external_address, ["Denmark"]))
@@ -1286,7 +1287,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.disallow_disposable_email_addresses = False
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo@mailnator.com"
 
         self.assert_json_success(self.invite(external_address, ["Denmark"]))
@@ -1312,7 +1313,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         zulip_realm.emails_restricted_to_domains = False
         zulip_realm.save()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         external_address = "foo+label@zulip.com"
 
         self.assert_json_success(self.invite(external_address, ["Denmark"]))
@@ -1326,7 +1327,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         self.assert_in_response("Zulip Dev, does not allow signups using emails\n        that contains +", result)
 
     def test_invalid_email_check_after_confirming_email(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         email = "test@zulip.com"
 
         self.assert_json_success(self.invite(email, ["Denmark"]))
@@ -1344,7 +1345,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         """
         Inviting someone to streams with non-ASCII characters succeeds.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invitee = "alice-test@zulip.com"
 
         stream_name = u"hümbüǵ"
@@ -1358,17 +1359,17 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         from django.core.mail import outbox
 
         # All users belong to zulip realm
-        referrer_user = 'hamlet'
-        current_user_email = self.example_email(referrer_user)
-        self.login(current_user_email)
+        referrer_name = 'hamlet'
+        current_user = self.example_user(referrer_name)
+        self.login_user(current_user)
         invitee_email = self.nonreg_email('alice')
         self.assert_json_success(self.invite(invitee_email, ["Denmark"]))
         self.assertTrue(find_key_by_email(invitee_email))
         self.check_sent_emails([invitee_email])
 
-        data = {"email": invitee_email, "referrer_email": current_user_email}
+        data = {"email": invitee_email, "referrer_email": current_user.email}
         invitee = PreregistrationUser.objects.get(email=data["email"])
-        referrer = self.example_user(referrer_user)
+        referrer = self.example_user(referrer_name)
         link = create_confirmation_link(invitee, referrer.realm.host, Confirmation.INVITATION)
         context = common_context(referrer)
         context.update({
@@ -1408,7 +1409,7 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
         self.assertEqual(len(email_jobs_to_deliver), 0)
 
     def test_no_invitation_reminder_when_link_expires_quickly(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         # Check invitation reminder email is scheduled with 4 day link expiry
         with self.settings(INVITATION_LINK_VALIDITY_DAYS=4):
             self.invite('alice@zulip.com', ['Denmark'])
@@ -1463,7 +1464,7 @@ class InvitationsTestCase(InviteUserBase):
         self.assertNotEqual(days_to_activate, "Wrong")
         self.assertNotEqual(active_value, "Wrong")
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         user_profile = self.example_user("iago")
 
         prereg_user_one = PreregistrationUser(email="TestOne@zulip.com", referred_by=user_profile)
@@ -1495,7 +1496,7 @@ class InvitationsTestCase(InviteUserBase):
         A DELETE call to /json/invites/ should delete the invite and
         any scheduled invitation reminder emails.
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
 
         invitee = "DeleteMe@zulip.com"
         self.assert_json_success(self.invite(invitee, ['Denmark']))
@@ -1519,7 +1520,7 @@ class InvitationsTestCase(InviteUserBase):
         A DELETE call to /json/invites/multiuse should delete the
         multiuse_invite.
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
 
         zulip_realm = get_realm("zulip")
         multiuse_invite = MultiuseInvite.objects.create(referred_by=self.example_user("hamlet"), realm=zulip_realm)
@@ -1543,7 +1544,7 @@ class InvitationsTestCase(InviteUserBase):
         A POST call to /json/invites//resend should send an invitation reminder email
         and delete any scheduled invitation reminder email.
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
         invitee = "resend_me@zulip.com"
 
         self.assert_json_success(self.invite(invitee, ['Denmark']))
@@ -1580,7 +1581,7 @@ class InvitationsTestCase(InviteUserBase):
         inviter = UserProfile.objects.exclude(realm=get_realm('zulip')).first()
         prereg_user = PreregistrationUser.objects.create(
             email='email', referred_by=inviter, realm=inviter.realm)
-        self.login(self.example_email("iago"))
+        self.login('iago')
         error_result = self.client_post('/json/invites/' + str(prereg_user.id) + '/resend')
         self.assert_json_error(error_result, "No such invitation")
         error_result = self.client_delete('/json/invites/' + str(prereg_user.id))
@@ -1715,7 +1716,7 @@ class MultiuseInviteTest(ZulipTestCase):
         self.check_user_subscribed_only_to_streams(name2, streams)
 
     def test_create_multiuse_link_api_call(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
 
         result = self.client_post('/json/invites/multiuse')
         self.assert_json_success(result)
@@ -1724,7 +1725,7 @@ class MultiuseInviteTest(ZulipTestCase):
         self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
 
     def test_create_multiuse_link_with_specified_streams_api_call(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
         stream_names = ["Rome", "Scotland", "Venice"]
         streams = [get_stream(stream_name, self.realm) for stream_name in stream_names]
         stream_ids = [stream.id for stream in streams]
@@ -1738,7 +1739,7 @@ class MultiuseInviteTest(ZulipTestCase):
         self.check_user_subscribed_only_to_streams("test", streams)
 
     def test_only_admin_can_create_multiuse_link_api_call(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
         # Only admins should be able to create multiuse invites even if
         # invite_by_admins_only is set to False.
         self.realm.invite_by_admins_only = False
@@ -1750,12 +1751,12 @@ class MultiuseInviteTest(ZulipTestCase):
         invite_link = result.json()["invite_link"]
         self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
 
-        self.login(self.example_email('hamlet'))
+        self.login('hamlet')
         result = self.client_post('/json/invites/multiuse')
         self.assert_json_error(result, "Must be an organization administrator")
 
     def test_create_multiuse_link_invalid_stream_api_call(self) -> None:
-        self.login(self.example_email('iago'))
+        self.login('iago')
         result = self.client_post('/json/invites/multiuse',
                                   {"stream_ids": ujson.dumps([54321])})
         self.assert_json_error(result, "Invalid stream id 54321. No invites were sent.")
@@ -2582,7 +2583,7 @@ class UserSignUpTest(InviteUserBase):
         lear_realm = get_realm("lear")
         zulip_realm = get_realm("zulip")
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
         hamlet_in_zulip = get_user(self.example_email("hamlet"), zulip_realm)
@@ -3283,7 +3284,7 @@ class UserSignUpTest(InviteUserBase):
                 AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map,
         ):
             # Invite user.
-            self.login(self.example_email('iago'))
+            self.login('iago')
             response = self.invite(invitee_emails='newuser@zulip.com',
                                    stream_names=streams,
                                    invite_as=invite_as)
@@ -3527,27 +3528,26 @@ class UserSignUpTest(InviteUserBase):
 class DeactivateUserTest(ZulipTestCase):
 
     def test_deactivate_user(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
         user = self.example_user('hamlet')
+        email = user.email
+        self.login_user(user)
         self.assertTrue(user.is_active)
         result = self.client_delete('/json/users/me')
         self.assert_json_success(result)
         user = self.example_user('hamlet')
         self.assertFalse(user.is_active)
-        self.login(email, fails=True)
+        password = initial_password(email)
+        self.assert_login_failure(email, password=password)
 
     def test_do_not_deactivate_final_admin(self) -> None:
-        email = self.example_email("iago")
-        self.login(email)
         user = self.example_user('iago')
+        self.login_user(user)
         self.assertTrue(user.is_active)
         result = self.client_delete('/json/users/me')
         self.assert_json_error(result, "Cannot deactivate the only organization administrator.")
         user = self.example_user('iago')
         self.assertTrue(user.is_active)
         self.assertTrue(user.is_realm_admin)
-        email = self.example_email("hamlet")
         user_2 = self.example_user('hamlet')
         do_change_is_admin(user_2, True)
         self.assertTrue(user_2.is_realm_admin)
@@ -3559,8 +3559,8 @@ class DeactivateUserTest(ZulipTestCase):
         realm = get_realm('zulip')
         UserProfile.objects.filter(realm=realm).exclude(
             role=UserProfile.ROLE_REALM_ADMINISTRATOR).update(is_active=False)
-        email = self.example_email("iago")
-        self.login(email)
+        user = self.example_user("iago")
+        self.login_user(user)
         result = self.client_delete('/json/users/me')
         self.assert_json_error(result, "Cannot deactivate the only user.")
 
diff --git a/zerver/tests/test_submessage.py b/zerver/tests/test_submessage.py
index a85b7a8d88..845b70f3c9 100644
--- a/zerver/tests/test_submessage.py
+++ b/zerver/tests/test_submessage.py
@@ -83,7 +83,7 @@ class TestBasics(ZulipTestCase):
             sender=cordelia,
             stream_name=stream_name,
         )
-        self.login(cordelia.email)
+        self.login_user(cordelia)
 
         payload = dict(
             message_id=message_id,
@@ -114,7 +114,7 @@ class TestBasics(ZulipTestCase):
             sender=cordelia,
             stream_name=stream_name,
         )
-        self.login(cordelia.email)
+        self.login_user(cordelia)
 
         payload = dict(
             message_id=message_id,
diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py
index 60bcb9bd64..1ae20036c4 100644
--- a/zerver/tests/test_subs.py
+++ b/zerver/tests/test_subs.py
@@ -176,7 +176,7 @@ class TestCreateStreams(ZulipTestCase):
     def test_create_api_multiline_description(self) -> None:
         user = self.example_user("hamlet")
         realm = user.realm
-        self.login(user.email)
+        self.login_user(user)
         post_data = {'subscriptions': ujson.dumps([{"name": 'new_stream',
                                                     "description": "multi\nline\ndescription"}]),
                      'invite_only': ujson.dumps(False)}
@@ -253,7 +253,7 @@ class TestCreateStreams(ZulipTestCase):
         self.subscribe(hamlet, announce_stream.name)
 
         notification_bot = UserProfile.objects.get(full_name="Notification Bot")
-        self.login(iago.email)
+        self.login_user(iago)
 
         initial_message_count = Message.objects.count()
         initial_usermessage_count = UserMessage.objects.count()
@@ -328,8 +328,7 @@ class RecipientTest(ZulipTestCase):
 class StreamAdminTest(ZulipTestCase):
     def test_make_stream_public(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         self.make_stream('private_stream', invite_only=True)
 
         do_change_is_admin(user_profile, True)
@@ -359,8 +358,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_make_stream_private(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         realm = user_profile.realm
         self.make_stream('public_stream', realm=realm)
 
@@ -378,8 +376,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_make_stream_public_zephyr_mirror(self) -> None:
         user_profile = self.mit_user('starnine')
-        email = user_profile.email
-        self.login(email, realm=get_realm("zephyr"))
+        self.login_user(user_profile)
         realm = user_profile.realm
         self.make_stream('target_stream', realm=realm, invite_only=True)
         self.subscribe(user_profile, 'target_stream')
@@ -399,8 +396,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_make_stream_private_with_public_history(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         realm = user_profile.realm
         self.make_stream('public_history_stream', realm=realm)
 
@@ -419,8 +415,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_try_make_stream_public_with_private_history(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         realm = user_profile.realm
         self.make_stream('public_stream', realm=realm)
 
@@ -439,8 +434,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_deactivate_stream_backend(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         stream = self.make_stream('new_stream')
         self.subscribe(user_profile, stream.name)
         do_change_is_admin(user_profile, True)
@@ -472,8 +466,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_deactivate_stream_backend_requires_existing_stream(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         self.make_stream('new_stream')
         do_change_is_admin(user_profile, True)
 
@@ -482,7 +475,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_deactivate_stream_backend_requires_realm_admin(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.subscribe(user_profile, 'new_stream')
 
         stream_id = get_stream('new_stream', user_profile.realm).id
@@ -491,7 +484,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_private_stream_live_updates(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         do_change_is_admin(user_profile, True)
 
@@ -540,8 +533,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_rename_stream(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         realm = user_profile.realm
         stream = self.subscribe(user_profile, 'stream_name1')
         do_change_is_admin(user_profile, True)
@@ -655,8 +647,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_rename_stream_requires_realm_admin(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         self.make_stream('stream_name1')
 
         stream_id = get_stream('stream_name1', user_profile.realm).id
@@ -666,7 +657,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_notify_on_stream_rename(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         self.make_stream('stream_name1')
 
         stream = self.subscribe(user_profile, 'stream_name1')
@@ -688,7 +679,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_realm_admin_can_update_unsub_private_stream(self) -> None:
         iago = self.example_user('iago')
-        self.login(iago.email)
+        self.login_user(iago)
         result = self.common_subscribe_to_streams(iago, ["private_stream"],
                                                   dict(principals=ujson.dumps([self.example_email("hamlet")])),
                                                   invite_only=True)
@@ -711,8 +702,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_change_stream_description(self) -> None:
         user_profile = self.example_user('iago')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         realm = user_profile.realm
         self.subscribe(user_profile, 'stream_name1')
 
@@ -769,8 +759,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_change_stream_description_requires_realm_admin(self) -> None:
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
 
         self.subscribe(user_profile, 'stream_name1')
         do_change_is_admin(user_profile, False)
@@ -782,7 +771,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_change_to_stream_post_policy_admins(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         self.subscribe(user_profile, 'stream_name1')
         do_change_is_admin(user_profile, True)
@@ -796,7 +785,7 @@ class StreamAdminTest(ZulipTestCase):
 
     def test_change_stream_post_policy_requires_realm_admin(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         self.subscribe(user_profile, 'stream_name1')
         do_change_is_admin(user_profile, False)
@@ -822,8 +811,7 @@ class StreamAdminTest(ZulipTestCase):
         Create a stream for deletion by an administrator.
         """
         user_profile = self.example_user('hamlet')
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         stream = self.make_stream(stream_name, invite_only=invite_only)
 
         # For testing deleting streams you aren't on.
@@ -893,7 +881,7 @@ class StreamAdminTest(ZulipTestCase):
         You must be on the realm to create a stream.
         """
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         other_realm = Realm.objects.create(string_id='other')
         stream = self.make_stream('other_realm_stream', realm=other_realm)
@@ -946,8 +934,7 @@ class StreamAdminTest(ZulipTestCase):
         else:
             user_profile = self.example_user('hamlet')
 
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
 
         # Set up the stream.
         stream_name = u"hümbüǵ"
@@ -1040,8 +1027,7 @@ class StreamAdminTest(ZulipTestCase):
         user_profile = self.example_user('hamlet')
         user_profile.date_joined = timezone_now()
         user_profile.save()
-        email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         do_change_is_admin(user_profile, False)
 
         # Allow all members to create streams.
@@ -1115,10 +1101,9 @@ class StreamAdminTest(ZulipTestCase):
 
         do_set_realm_property(hamlet_user.realm, 'invite_to_stream_policy',
                               Realm.INVITE_TO_STREAM_POLICY_WAITING_PERIOD)
-        hamlet_email = hamlet_user.email
         cordelia_email = cordelia_user.email
 
-        self.login(hamlet_email)
+        self.login_user(hamlet_user)
         do_change_is_admin(hamlet_user, True)
 
         # Hamlet creates a stream as an admin..
@@ -1170,10 +1155,9 @@ class StreamAdminTest(ZulipTestCase):
         """
         Trying to unsubscribe an invalid user from a stream fails gracefully.
         """
-        user_profile = self.example_user('hamlet')
-        admin_email = user_profile.email
-        self.login(admin_email)
-        do_change_is_admin(user_profile, True)
+        admin = self.example_user('iago')
+        self.login_user(admin)
+        self.assertTrue(admin.is_realm_admin)
 
         stream_name = u"hümbüǵ"
         self.make_stream(stream_name)
@@ -1214,7 +1198,7 @@ class DefaultStreamTest(ZulipTestCase):
     def test_api_calls(self) -> None:
         user_profile = self.example_user('hamlet')
         do_change_is_admin(user_profile, True)
-        self.login(user_profile.email)
+        self.login_user(user_profile)
 
         stream_name = 'stream ADDED via api'
         ensure_stream(user_profile.realm, stream_name)
@@ -1343,7 +1327,7 @@ class DefaultStreamGroupTest(ZulipTestCase):
             do_create_default_stream_group(realm, new_group_name, "This is group1", remaining_streams)
 
     def test_api_calls(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         realm = user_profile.realm
         do_change_is_admin(user_profile, True)
@@ -1493,7 +1477,7 @@ class DefaultStreamGroupTest(ZulipTestCase):
         self.assert_json_error(result, "Default stream group with id '{}' does not exist.".format(group_id))
 
     def test_invalid_default_stream_group_name(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         user_profile = self.example_user('iago')
         realm = user_profile.realm
 
@@ -1536,7 +1520,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         any invalid hex color codes are bounced.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
 
         old_subs, _ = gather_subscriptions(test_user)
         sub = old_subs[0]
@@ -1578,7 +1562,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         Updating the color property requires a `stream_id` key.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
         result = self.api_post(test_user, "/api/v1/users/me/subscriptions/properties",
                                {"subscription_data": ujson.dumps([{"property": "color",
                                                                    "value": "#ffffff"}])})
@@ -1590,7 +1574,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         Updating the color property requires a subscribed stream.
         """
         test_user = self.example_user("hamlet")
-        self.login(test_user.email)
+        self.login_user(test_user)
 
         subscribed, unsubscribed, never_subscribed = gather_subscriptions_helper(test_user)
         not_subbed = unsubscribed + never_subscribed
@@ -1606,7 +1590,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         Updating the color property requires a color.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
         subs = gather_subscriptions(test_user)[0]
         result = self.api_post(test_user, "/api/v1/users/me/subscriptions/properties",
                                {"subscription_data": ujson.dumps([{"property": "color",
@@ -1620,7 +1604,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         sets the property.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
 
         subs = gather_subscriptions(test_user)[0]
         sub = subs[0]
@@ -1641,7 +1625,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         pin_to_top data pins the stream.
         """
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         old_subs, _ = gather_subscriptions(user)
         sub = old_subs[0]
@@ -1660,7 +1644,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
 
     def test_change_is_muted(self) -> None:
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
         subs = gather_subscriptions(test_user)[0]
 
         sub = Subscription.objects.get(recipient__type=Recipient.STREAM,
@@ -1722,7 +1706,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         Trying to set a property incorrectly returns a JSON error.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
         subs = gather_subscriptions(test_user)[0]
 
         property_name = "is_muted"
@@ -1792,7 +1776,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
 
     def test_json_subscription_property_invalid_stream(self) -> None:
         test_user = self.example_user("hamlet")
-        self.login(test_user.email)
+        self.login_user(test_user)
 
         stream_id = 1000
         result = self.api_post(test_user, "/api/v1/users/me/subscriptions/properties",
@@ -1806,7 +1790,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
         Trying to set an invalid property returns a JSON error.
         """
         test_user = self.example_user('hamlet')
-        self.login(test_user.email)
+        self.login_user(test_user)
         subs = gather_subscriptions(test_user)[0]
         result = self.api_post(test_user, "/api/v1/users/me/subscriptions/properties",
                                {"subscription_data": ujson.dumps([{"property": "bad",
@@ -1818,7 +1802,7 @@ class SubscriptionPropertiesTest(ZulipTestCase):
 class SubscriptionRestApiTest(ZulipTestCase):
     def test_basic_add_delete(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         # add
         request = {
@@ -1840,7 +1824,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_add_with_color(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         # add with color proposition
         request = {
@@ -1862,7 +1846,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
         """
         user = self.example_user('hamlet')
 
-        self.login(user.email)
+        self.login_user(user)
         subs = gather_subscriptions(user)[0]
         result = self.api_patch(user, "/api/v1/users/me/subscriptions/%d" % (subs[0]["stream_id"],),
                                 {'property': 'color', 'value': '#c2c2c2'})
@@ -1875,7 +1859,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
         user = self.example_user('hamlet')
 
-        self.login(user.email)
+        self.login_user(user)
         subs = gather_subscriptions(user)[0]
 
         result = self.api_patch(user, "/api/v1/users/me/subscriptions/%d" % (subs[0]["stream_id"],),
@@ -1888,7 +1872,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
         Trying to set an invalid stream id returns a JSON error.
         """
         user = self.example_user("hamlet")
-        self.login(user.email)
+        self.login_user(user)
         result = self.api_patch(user, "/api/v1/users/me/subscriptions/121",
                                 {'property': 'is_muted', 'value': 'somevalue'})
         self.assert_json_error(result,
@@ -1896,7 +1880,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_bad_add_parameters(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         def check_for_error(val: Any, expected_message: str) -> None:
             request = {
@@ -1911,7 +1895,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_bad_principals(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         request = {
             'add': ujson.dumps([{'name': 'my_new_stream'}]),
@@ -1922,7 +1906,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_bad_delete_parameters(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         request = {
             'delete': ujson.dumps([{'name': 'my_test_stream_1'}])
@@ -1932,7 +1916,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_add_or_delete_not_specified(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         result = self.api_patch(user, "/api/v1/users/me/subscriptions", {})
         self.assert_json_error(result,
@@ -1943,7 +1927,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
         Only way to force an error is with a empty string.
         """
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         invalid_stream_name = ""
         request = {
@@ -1955,7 +1939,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_stream_name_too_long(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         long_stream_name = "a" * 61
         request = {
@@ -1967,7 +1951,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
 
     def test_stream_name_contains_null(self) -> None:
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         stream_name = "abc\000"
         request = {
@@ -2015,7 +1999,7 @@ class SubscriptionAPITest(ZulipTestCase):
         self.user_profile = self.example_user('hamlet')
         self.test_email = self.user_profile.email
         self.test_user = self.user_profile
-        self.login(self.test_email)
+        self.login_user(self.user_profile)
         self.test_realm = self.user_profile.realm
         self.streams = self.get_streams(self.user_profile)
 
@@ -3047,7 +3031,7 @@ class SubscriptionAPITest(ZulipTestCase):
         self.assertEqual(num_subscribers_for_stream_id(stream.id), 1)
 
         # A user who is subscribed still sees the stream exists
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.client_post("/json/subscriptions/exists",
                                   {"stream": stream_name, "autosubscribe": "false"})
         self.assert_json_success(result)
@@ -3140,7 +3124,7 @@ class SubscriptionAPITest(ZulipTestCase):
         admin_user = self.example_user("iago")
         non_admin_user = self.example_user("cordelia")
 
-        self.login(admin_user.email)
+        self.login_user(admin_user)
 
         for stream_name in ["stream1", "stream2", "stream3", ]:
             self.make_stream(stream_name, realm=realm, invite_only=False)
@@ -3249,7 +3233,7 @@ class GetStreamsTest(ZulipTestCase):
         test_bot = self.create_test_bot('foo', hamlet, bot_owner=hamlet)
         assert test_bot is not None
         realm = get_realm('zulip')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         # Check it correctly lists the bot owner's subs with
         # include_owner_subscribed=true
@@ -3377,7 +3361,7 @@ class GetStreamsTest(ZulipTestCase):
         """
         user = self.example_user('hamlet')
         realm = get_realm('zulip')
-        self.login(user.email)
+        self.login_user(user)
 
         # Check it correctly lists the user's subs with include_public=false
         result = self.api_get(user, "/api/v1/streams?include_public=false")
@@ -3407,19 +3391,17 @@ class GetStreamsTest(ZulipTestCase):
                          sorted(all_streams))
 
 class StreamIdTest(ZulipTestCase):
-    def setUp(self) -> None:
-        super().setUp()
-        self.user_profile = self.example_user('hamlet')
-        self.email = self.user_profile.email
-        self.login(self.email)
-
     def test_get_stream_id(self) -> None:
-        stream = gather_subscriptions(self.user_profile)[0][0]
+        user = self.example_user('hamlet')
+        self.login_user(user)
+        stream = gather_subscriptions(user)[0][0]
         result = self.client_get("/json/get_stream_id?stream=%s" % (stream['name'],))
         self.assert_json_success(result)
         self.assertEqual(result.json()['stream_id'], stream['stream_id'])
 
     def test_get_stream_id_wrong_name(self) -> None:
+        user = self.example_user('hamlet')
+        self.login_user(user)
         result = self.client_get("/json/get_stream_id?stream=wrongname")
         self.assert_json_error(result, u"Invalid stream name 'wrongname'")
 
@@ -3430,7 +3412,7 @@ class InviteOnlyStreamTest(ZulipTestCase):
         you aren't subscribed, you'll get a 400.
         """
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
         # Create Saxony as an invite-only stream.
         self.assert_json_success(
             self.common_subscribe_to_streams(user, ["Saxony"],
@@ -3447,7 +3429,7 @@ class InviteOnlyStreamTest(ZulipTestCase):
         """
 
         user = self.example_user('hamlet')
-        self.login(user.email)
+        self.login_user(user)
 
         result1 = self.common_subscribe_to_streams(user, ["Saxony"], invite_only=True)
         self.assert_json_success(result1)
@@ -3479,14 +3461,14 @@ class InviteOnlyStreamTest(ZulipTestCase):
         # Subscribing oneself to an invite-only stream is not allowed
         user_profile = self.example_user('othello')
         email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         result = self.common_subscribe_to_streams(user_profile, [stream_name])
         self.assert_json_error(result, 'Unable to access stream (Saxony).')
 
         # authorization_errors_fatal=False works
         user_profile = self.example_user('othello')
         email = user_profile.email
-        self.login(email)
+        self.login_user(user_profile)
         result = self.common_subscribe_to_streams(user_profile, [stream_name],
                                                   extra_post_data={'authorization_errors_fatal': ujson.dumps(False)})
         self.assert_json_success(result)
@@ -3497,7 +3479,7 @@ class InviteOnlyStreamTest(ZulipTestCase):
 
         # Inviting another user to an invite-only stream is allowed
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.common_subscribe_to_streams(
             user_profile, [stream_name],
             extra_post_data={'principals': ujson.dumps([self.example_email("othello")])})
@@ -3521,7 +3503,7 @@ class GetSubscribersTest(ZulipTestCase):
         super().setUp()
         self.user_profile = self.example_user('hamlet')
         self.email = self.user_profile.email
-        self.login(self.email)
+        self.login_user(self.user_profile)
 
     def assert_user_got_subscription_notification(self, expected_msg: str) -> None:
         # verify that the user was sent a message informing them about the subscription
@@ -3830,10 +3812,10 @@ class GetSubscribersTest(ZulipTestCase):
         # Create a stream for which Hamlet is the only subscriber.
         stream_name = "Saxony"
         self.common_subscribe_to_streams(self.user_profile, [stream_name])
-        other_email = self.example_email("othello")
+        other_user = self.example_user("othello")
 
         # Fetch the subscriber list as a non-member.
-        self.login(other_email)
+        self.login_user(other_user)
         self.make_successful_subscriber_request(stream_name)
 
     def test_subscriber_private_stream(self) -> None:
@@ -3847,12 +3829,12 @@ class GetSubscribersTest(ZulipTestCase):
 
         stream_id = get_stream(stream_name, self.user_profile.realm).id
         # Verify another user can't get the data.
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         result = self.client_get("/json/streams/%d/members" % (stream_id,))
         self.assert_json_error(result, u'Invalid stream id')
 
         # But an organization administrator can
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_get("/json/streams/%d/members" % (stream_id,))
         self.assert_json_success(result)
 
@@ -3901,7 +3883,7 @@ class GetSubscribersTest(ZulipTestCase):
         self.assert_json_error(result, "Invalid stream id")
 
         # Try to fetch the subscriber list as a non-member & realm-admin-user.
-        self.login(self.example_email("iago"))
+        self.login('iago')
         self.make_successful_subscriber_request(stream_name)
 
 class AccessStreamTest(ZulipTestCase):
@@ -3913,7 +3895,7 @@ class AccessStreamTest(ZulipTestCase):
         hamlet = self.example_user('hamlet')
 
         stream_name = "new_private_stream"
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         self.common_subscribe_to_streams(hamlet, [stream_name],
                                          invite_only=True)
         stream = get_stream(stream_name, hamlet.realm)
@@ -3980,7 +3962,7 @@ class AccessStreamTest(ZulipTestCase):
 
     def test_stream_access_by_guest(self) -> None:
         guest_user_profile = self.example_user('polonius')
-        self.login(guest_user_profile.email)
+        self.login_user(guest_user_profile)
         stream_name = "public_stream_1"
         stream = self.make_stream(stream_name, guest_user_profile.realm, invite_only=False)
 
diff --git a/zerver/tests/test_thumbnail.py b/zerver/tests/test_thumbnail.py
index 53234fd278..e0ff45ec40 100644
--- a/zerver/tests/test_thumbnail.py
+++ b/zerver/tests/test_thumbnail.py
@@ -34,7 +34,7 @@ class ThumbnailTest(ZulipTestCase):
             settings.S3_AVATAR_BUCKET)
 
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         fp = StringIO("zulip!")
         fp.name = "zulip.jpeg"
 
@@ -88,7 +88,7 @@ class ThumbnailTest(ZulipTestCase):
         self.assertIn(expected_part_url, result.url)
 
         # Test with another user trying to access image using thumbor.
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri,))
         self.assertEqual(result.status_code, 403, result)
         self.assert_in_response("You are not authorized to view this file.", result)
@@ -96,7 +96,7 @@ class ThumbnailTest(ZulipTestCase):
     def test_external_source_type(self) -> None:
         def run_test_with_image_url(image_url: str) -> None:
             # Test full size image.
-            self.login(self.example_email("hamlet"))
+            self.login('hamlet')
             quoted_url = urllib.parse.quote(image_url, safe='')
             encoded_url = base64.urlsafe_b64encode(image_url.encode()).decode('utf-8')
             result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url,))
@@ -137,7 +137,7 @@ class ThumbnailTest(ZulipTestCase):
 
             # Test with another user trying to access image using thumbor.
             # File should be always accessible to user in case of external source
-            self.login(self.example_email("iago"))
+            self.login('iago')
             result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_url,))
             self.assertEqual(result.status_code, 302, result)
             expected_part_url = '/smart/filters:no_upscale()/' + encoded_url + '/source_type/external'
@@ -162,7 +162,7 @@ class ThumbnailTest(ZulipTestCase):
             hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8')
             return url_in_result % (sharpen_filter, hex_uri)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.jpeg"
 
@@ -246,14 +246,14 @@ class ThumbnailTest(ZulipTestCase):
         self.assertIn(expected_part_url, result.url)
 
         # Test with another user trying to access image using thumbor.
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri,))
         self.assertEqual(result.status_code, 403, result)
         self.assert_in_response("You are not authorized to view this file.", result)
 
     @override_settings(THUMBOR_URL='127.0.0.1:9995')
     def test_with_static_files(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         uri = '/static/images/cute/turtle.png'
         quoted_uri = urllib.parse.quote(uri[1:], safe='')
         result = self.client_get("/thumbnail?url=%s&size=full" % (quoted_uri,))
@@ -261,7 +261,7 @@ class ThumbnailTest(ZulipTestCase):
         self.assertEqual(uri, result.url)
 
     def test_with_thumbor_disabled(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.jpeg"
 
@@ -305,7 +305,7 @@ class ThumbnailTest(ZulipTestCase):
         self.assertEqual(base, result.url)
 
     def test_with_different_THUMBOR_URL(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.jpeg"
 
@@ -337,7 +337,7 @@ class ThumbnailTest(ZulipTestCase):
             hex_uri = base64.urlsafe_b64encode(uri.encode()).decode('utf-8')
             return url_in_result % (sharpen_filter, hex_uri)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.jpeg"
 
diff --git a/zerver/tests/test_tornado.py b/zerver/tests/test_tornado.py
index 607229561a..16e4ed11f1 100644
--- a/zerver/tests/test_tornado.py
+++ b/zerver/tests/test_tornado.py
@@ -63,8 +63,8 @@ class TornadoWebTestCase(AsyncHTTPTestCase, ZulipTestCase):
         self.set_http_headers(kwargs)
         self.fetch_async('GET', path, **kwargs)
 
-    def login(self, *args: Any, **kwargs: Any) -> None:
-        super().login(*args, **kwargs)
+    def login_user(self, *args: Any, **kwargs: Any) -> None:
+        super().login_user(*args, **kwargs)
         session_cookie = settings.SESSION_COOKIE_NAME
         session_key = self.client.session.session_key
         self.session_cookie = {
@@ -91,13 +91,13 @@ class TornadoWebTestCase(AsyncHTTPTestCase, ZulipTestCase):
 
 class EventsTestCase(TornadoWebTestCase):
     def test_create_queue(self) -> None:
-        self.login(self.example_email('hamlet'))
+        self.login_user(self.example_user('hamlet'))
         queue_id = self.create_queue()
         self.assertIn(queue_id, event_queue.clients)
 
     def test_events_async(self) -> None:
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         event_queue_id = self.create_queue()
         data = {
             'queue_id': event_queue_id,
diff --git a/zerver/tests/test_transfer.py b/zerver/tests/test_transfer.py
index 068fed0520..4e8996a507 100644
--- a/zerver/tests/test_transfer.py
+++ b/zerver/tests/test_transfer.py
@@ -32,7 +32,7 @@ class TransferUploadsToS3Test(ZulipTestCase):
     def test_transfer_avatars_to_s3(self) -> None:
         bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
 
diff --git a/zerver/tests/test_tutorial.py b/zerver/tests/test_tutorial.py
index 1741a17a26..b4efb6e07d 100644
--- a/zerver/tests/test_tutorial.py
+++ b/zerver/tests/test_tutorial.py
@@ -20,8 +20,8 @@ class TutorialTests(ZulipTestCase):
         internal_send_private_message(welcome_bot.realm, welcome_bot, user, content)
 
     def test_tutorial_status(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         cases = [
             ('started', UserProfile.TUTORIAL_STARTED),
@@ -35,11 +35,10 @@ class TutorialTests(ZulipTestCase):
             self.assertEqual(user.tutorial_status, expected_db_status)
 
     def test_single_response_to_pm(self) -> None:
-        user_email = 'hamlet@zulip.com'
         user = self.example_user('hamlet')
         bot = get_system_bot(settings.WELCOME_BOT)
         content = 'whatever'
-        self.login(user_email)
+        self.login_user(user)
         self.send_personal_message(user, bot, content)
         user_messages = message_stream_count(user)
         expected_response = ("Congratulations on your first reply! :tada:\n\n"
@@ -55,7 +54,7 @@ class TutorialTests(ZulipTestCase):
         user2 = self.example_user('cordelia')
         bot = get_system_bot(settings.WELCOME_BOT)
         content = "whatever"
-        self.login(user1.email)
+        self.login_user(user1)
         self.send_huddle_message(user1, [bot, user2], content)
         user1_messages = message_stream_count(user1)
         self.assertEqual(most_recent_message(user1).content, content)
diff --git a/zerver/tests/test_unread.py b/zerver/tests/test_unread.py
index adaa4e6a2a..16f559db98 100644
--- a/zerver/tests/test_unread.py
+++ b/zerver/tests/test_unread.py
@@ -37,7 +37,7 @@ class PointerTest(ZulipTestCase):
         Posting a pointer to /update (in the form {"pointer": pointer}) changes
         the pointer we store for your UserProfile.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(self.example_user('hamlet').pointer, -1)
         msg_id = self.send_stream_message(self.example_user("othello"), "Verona")
         result = self.client_post("/json/users/me/pointer", {"pointer": msg_id})
@@ -61,7 +61,7 @@ class PointerTest(ZulipTestCase):
         Posting json to /json/users/me/pointer which does not contain a pointer key/value pair
         returns a 400 and error message.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(self.example_user('hamlet').pointer, -1)
         result = self.client_post("/json/users/me/pointer", {"foo": 1})
         self.assert_json_error(result, "Missing 'pointer' argument")
@@ -72,7 +72,7 @@ class PointerTest(ZulipTestCase):
         Posting json to /json/users/me/pointer with an invalid pointer returns a 400 and error
         message.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(self.example_user('hamlet').pointer, -1)
         result = self.client_post("/json/users/me/pointer", {"pointer": "foo"})
         self.assert_json_error(result, "Bad value for 'pointer': foo")
@@ -83,7 +83,7 @@ class PointerTest(ZulipTestCase):
         Posting json to /json/users/me/pointer with an out of range (< 0) pointer returns a 400
         and error message.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(self.example_user('hamlet').pointer, -1)
         result = self.client_post("/json/users/me/pointer", {"pointer": -2})
         self.assert_json_error(result, "Bad value for 'pointer': -2")
@@ -95,7 +95,7 @@ class PointerTest(ZulipTestCase):
         return an unread message older than the current pointer, when there's
         no narrow set.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         # Ensure the pointer is not set (-1)
         self.assertEqual(self.example_user('hamlet').pointer, -1)
 
@@ -177,7 +177,7 @@ class PointerTest(ZulipTestCase):
         self.assertEqual(messages_response['anchor'], new_message_id)
 
     def test_visible_messages_use_first_unread_anchor(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assertEqual(self.example_user('hamlet').pointer, -1)
 
         result = self.client_post("/json/mark_all_as_read")
@@ -222,7 +222,7 @@ class UnreadCountTests(ZulipTestCase):
 
     # Sending a new message results in unread UserMessages being created
     def test_new_message(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         content = "Test message for unset read bit"
         last_msg = self.send_stream_message(self.example_user("hamlet"), "Verona", content)
         user_messages = list(UserMessage.objects.filter(message=last_msg))
@@ -233,7 +233,7 @@ class UnreadCountTests(ZulipTestCase):
                 self.assertFalse(um.flags.read)
 
     def test_update_flags(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_post("/json/messages/flags",
                                   {"messages": ujson.dumps(self.unread_msg_ids),
@@ -262,7 +262,7 @@ class UnreadCountTests(ZulipTestCase):
                 self.assertEqual(msg['flags'], [])
 
     def test_mark_all_in_stream_read(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = self.subscribe(user_profile, "test_stream")
         self.subscribe(self.example_user("cordelia"), "test_stream")
@@ -302,7 +302,7 @@ class UnreadCountTests(ZulipTestCase):
                 self.assertFalse(msg.flags.read)
 
     def test_mark_all_in_invalid_stream_read(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invalid_stream_id = "12345678"
         result = self.client_post("/json/mark_stream_as_read", {
             "stream_id": invalid_stream_id
@@ -310,7 +310,7 @@ class UnreadCountTests(ZulipTestCase):
         self.assert_json_error(result, 'Invalid stream id')
 
     def test_mark_all_topics_unread_with_invalid_stream_name(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invalid_stream_id = "12345678"
         result = self.client_post("/json/mark_topic_as_read", {
             "stream_id": invalid_stream_id,
@@ -319,7 +319,7 @@ class UnreadCountTests(ZulipTestCase):
         self.assert_json_error(result, "Invalid stream id")
 
     def test_mark_all_in_stream_topic_read(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         self.subscribe(user_profile, "test_stream")
 
@@ -356,7 +356,7 @@ class UnreadCountTests(ZulipTestCase):
                 self.assertFalse(msg.flags.read)
 
     def test_mark_all_in_invalid_topic_read(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         invalid_topic_name = "abc"
         result = self.client_post("/json/mark_topic_as_read", {
             "stream_id": get_stream("Denmark", get_realm("zulip")).id,
@@ -490,7 +490,7 @@ class PushNotificationMarkReadFlowsTest(ZulipTestCase):
     @mock.patch('zerver.lib.push_notifications.push_notifications_enabled', return_value=True)
     def test_track_active_mobile_push_notifications(self, mock_push_notifications: mock.MagicMock) -> None:
         mock_push_notifications.return_value = True
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user_profile = self.example_user('hamlet')
         stream = self.subscribe(user_profile, "test_stream")
         second_stream = self.subscribe(user_profile, "second_stream")
diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py
index d35a163952..14a95d432b 100644
--- a/zerver/tests/test_upload.py
+++ b/zerver/tests/test_upload.py
@@ -9,6 +9,7 @@ from zerver.lib.avatar import (
 )
 from zerver.lib.avatar_hash import user_avatar_path
 from zerver.lib.bugdown import url_filename
+from zerver.lib.initial_password import initial_password
 from zerver.lib.realm_icon import realm_icon_url
 from zerver.lib.realm_logo import get_realm_logo_url
 from zerver.lib.test_classes import ZulipTestCase, UploadSerializeMixin
@@ -87,7 +88,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(b"zulip!", data)
 
         # Files uploaded through the API should be accesible via the web client
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         self.assert_url_serves_contents_of_file(uri, b"zulip!")
 
     def test_mobile_api_endpoint(self) -> None:
@@ -138,7 +139,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         """
         Attempting to upload big files should fail.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("bah!")
         fp.name = "a.txt"
 
@@ -152,7 +153,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         """
         Attempting to upload two files should fail.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("bah!")
         fp.name = "a.txt"
         fp2 = StringIO("pshaw!")
@@ -165,7 +166,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         """
         Calling this endpoint with no files should fail.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_post("/json/user_uploads")
         self.assert_json_error(result, "You must specify a file to upload")
@@ -178,7 +179,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         entry in the database. This entry will be marked unclaimed till a message
         refers it.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
 
@@ -203,7 +204,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.assertIn('title="zulip.txt"', self.get_last_message().rendered_content)
 
     def test_file_download_unauthed(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -218,7 +219,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         '''
         Trying to download deleted files should return 404 error
         '''
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -233,7 +234,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         Trying to download a file that was never uploaded will return a json_error
         '''
         hamlet = self.example_user("hamlet")
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         response = self.client_get("http://localhost:9991/user_uploads/%s/ff/gg/abc.py" % (
             hamlet.realm_id,))
         self.assertEqual(response.status_code, 404)
@@ -241,7 +242,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
 
     def test_delete_old_unclaimed_attachments(self) -> None:
         # Upload some files and make them older than a weeek
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         d1 = StringIO("zulip!")
         d1.name = "dummy_1.txt"
         result = self.client_post("/json/user_uploads", {'file': d1})
@@ -273,7 +274,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
 
     def test_attachment_url_without_upload(self) -> None:
         hamlet = self.example_user("hamlet")
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         body = "Test message ...[zulip.txt](http://localhost:9991/user_uploads/%s/64/fake_path_id.txt)" % (
             hamlet.realm_id,)
         self.send_stream_message(self.example_user("hamlet"), "Denmark", body, "test")
@@ -284,7 +285,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         This test tries to claim the same attachment twice. The messages field in
         the Attachment model should have both the messages in its entry.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         d1 = StringIO("zulip!")
         d1.name = "dummy_1.txt"
         result = self.client_post("/json/user_uploads", {'file': d1})
@@ -302,7 +303,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
     def test_multiple_claim_attachments_different_owners(self) -> None:
         """This test tries to claim the same attachment more than once, first
         with a private stream and then with different recipients."""
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         d1 = StringIO("zulip!")
         d1.name = "dummy_1.txt"
         result = self.client_post("/json/user_uploads", {'file': d1})
@@ -346,7 +347,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         hamlet = self.example_user('hamlet')
         host = hamlet.realm.host
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         result = self.client_post("/json/user_uploads", {'file': f1})
         f1_path_id = re.sub('/user_uploads/', '', result.json()['uri'])
 
@@ -398,7 +399,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         """
         Unicode filenames should be processed correctly.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         for expected in ["Здравейте.txt", "test"]:
             fp = StringIO("bah!")
             fp.name = urllib.parse.quote(expected)
@@ -410,7 +411,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         """
         Realm quota for uploading should not be exceeded.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         d1 = StringIO("zulip!")
         d1.name = "dummy_1.txt"
@@ -449,7 +450,9 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
     def test_cross_realm_file_access(self) -> None:
 
         def create_user(email: str, realm_id: str) -> UserProfile:
-            self.register(email, 'test', subdomain=realm_id)
+            password = initial_password(email)
+            if password is not None:
+                self.register(email, password, subdomain=realm_id)
             return get_user(email, get_realm(realm_id))
 
         test_subdomain = "uploadtest.example.com"
@@ -460,13 +463,13 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         r1 = Realm.objects.create(string_id=test_subdomain, invite_required=False)
         RealmDomain.objects.create(realm=r1, domain=test_subdomain)
 
-        create_user(user1_email, test_subdomain)
-        create_user(user2_email, 'zulip')
+        user_1 = create_user(user1_email, test_subdomain)
+        user_2 = create_user(user2_email, 'zulip')
         user_3 = create_user(user3_email, test_subdomain)
         host = user_3.realm.host
 
         # Send a message from @zulip.com -> @uploadtest.example.com
-        self.login(user2_email, 'test')
+        self.login_user(user_2)
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -481,7 +484,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
                 content=body,
             )
 
-        self.login(user1_email, 'test', realm=r1)
+        self.login_user(user_1)
         response = self.client_get(uri, subdomain=test_subdomain)
         self.assertEqual(response.status_code, 200)
         data = b"".join(response.streaming_content)
@@ -489,33 +492,35 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.logout()
 
         # Confirm other cross-realm users can't read it.
-        self.login(user3_email, 'test', realm=r1)
+        self.login_user(user_3)
         response = self.client_get(uri, subdomain=test_subdomain)
         self.assertEqual(response.status_code, 403)
         self.assert_in_response("You are not authorized to view this file.", response)
 
     def test_file_download_authorization_invite_only(self) -> None:
-        user = self.example_user("hamlet")
-        subscribed_emails = [user.email, self.example_email("cordelia")]
+        hamlet = self.example_user("hamlet")
+        cordelia = self.example_user('cordelia')
+        realm = hamlet.realm
+        subscribed_users = [hamlet, cordelia]
         unsubscribed_users = [self.example_user("othello"), self.example_user("prospero")]
         stream_name = "test-subscribe"
-        self.make_stream(stream_name, realm=user.realm, invite_only=True, history_public_to_subscribers=False)
+        self.make_stream(stream_name, realm=realm, invite_only=True, history_public_to_subscribers=False)
 
-        for email in subscribed_emails:
-            self.subscribe(get_user(email, user.realm), stream_name)
+        for subscribed_user in subscribed_users:
+            self.subscribe(subscribed_user, stream_name)
 
-        self.login(user.email)
+        self.login_user(hamlet)
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
         uri = result.json()['uri']
         fp_path_id = re.sub('/user_uploads/', '', uri)
-        body = "First message ...[zulip.txt](http://{}/user_uploads/".format(user.realm.host) + fp_path_id + ")"
-        self.send_stream_message(user, stream_name, body, "test")
+        body = "First message ...[zulip.txt](http://{}/user_uploads/".format(realm.host) + fp_path_id + ")"
+        self.send_stream_message(hamlet, stream_name, body, "test")
         self.logout()
 
         # Owner user should be able to view file
-        self.login(user.email)
+        self.login_user(hamlet)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 200)
@@ -525,7 +530,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(len(queries), 5)
 
         # Subscribed user who recieved the message should be able to view file
-        self.login(subscribed_emails[1])
+        self.login_user(cordelia)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 200)
@@ -549,15 +554,16 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
 
     def test_file_download_authorization_invite_only_with_shared_history(self) -> None:
         user = self.example_user("hamlet")
-        subscribed_emails = [user.email, self.example_email("polonius")]
-        unsubscribed_emails = [self.example_email("othello"), self.example_email("prospero")]
+        polonius = self.example_user('polonius')
+        subscribed_users = [user, polonius]
+        unsubscribed_users = [self.example_user("othello"), self.example_user("prospero")]
         stream_name = "test-subscribe"
         self.make_stream(stream_name, realm=user.realm, invite_only=True, history_public_to_subscribers=True)
 
-        for email in subscribed_emails:
-            self.subscribe(get_user(email, user.realm), stream_name)
+        for subscribed_user in subscribed_users:
+            self.subscribe(subscribed_user, stream_name)
 
-        self.login(user.email)
+        self.login_user(user)
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -570,10 +576,10 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         # Add aaron as a subscribed after the message was sent
         late_subscribed_user = self.example_user("aaron")
         self.subscribe(late_subscribed_user, stream_name)
-        subscribed_emails.append(late_subscribed_user.email)
+        subscribed_users.append(late_subscribed_user)
 
         # Owner user should be able to view file
-        self.login(user.email)
+        self.login_user(user)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 200)
@@ -583,7 +589,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(len(queries), 5)
 
         # Originally subscribed user should be able to view file
-        self.login(subscribed_emails[1])
+        self.login_user(polonius)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 200)
@@ -593,7 +599,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(len(queries), 6)
 
         # Subscribed user who did not receive the message should also be able to view file
-        self.login(late_subscribed_user.email)
+        self.login_user(late_subscribed_user)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 200)
@@ -603,8 +609,8 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         # It takes a few extra queries to verify access because of shared history.
         self.assertEqual(len(queries), 9)
 
-        def assert_cannot_access_file(user_email: str) -> None:
-            self.login(user_email)
+        def assert_cannot_access_file(user: UserProfile) -> None:
+            self.login_user(user)
             with queries_captured() as queries:
                 response = self.client_get(uri)
             self.assertEqual(response.status_code, 403)
@@ -614,7 +620,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
             self.logout()
 
         # Unsubscribed user should not be able to view file
-        for unsubscribed_user in unsubscribed_emails:
+        for unsubscribed_user in unsubscribed_users:
             assert_cannot_access_file(unsubscribed_user)
 
     def test_multiple_message_attachment_file_download(self) -> None:
@@ -624,7 +630,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
             self.make_stream(stream_name, realm=hamlet.realm, invite_only=True, history_public_to_subscribers=True)
             self.subscribe(hamlet, stream_name)
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -636,7 +642,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.logout()
 
         user = self.example_user("aaron")
-        self.login(user.email)
+        self.login_user(user)
         with queries_captured() as queries:
             response = self.client_get(uri)
             self.assertEqual(response.status_code, 403)
@@ -661,13 +667,13 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
         self.logout()
 
     def test_file_download_authorization_public(self) -> None:
-        subscribed_users = [self.example_email("hamlet"), self.example_email("iago")]
-        unsubscribed_users = [self.example_email("othello"), self.example_email("prospero")]
+        subscribed_users = [self.example_user("hamlet"), self.example_user("iago")]
+        unsubscribed_users = [self.example_user("othello"), self.example_user("prospero")]
         realm = get_realm("zulip")
-        for email in subscribed_users:
-            self.subscribe(get_user(email, realm), "test-subscribe")
+        for subscribed_user in subscribed_users:
+            self.subscribe(subscribed_user, "test-subscribe")
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -679,7 +685,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
 
         # Now all users should be able to access the files
         for user in subscribed_users + unsubscribed_users:
-            self.login(user)
+            self.login_user(user)
             response = self.client_get(uri)
             data = b"".join(response.streaming_content)
             self.assertEqual(b"zulip!", data)
@@ -690,7 +696,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase):
                               content_disposition: str='') -> None:
             with self.settings(SENDFILE_BACKEND='django_sendfile.backends.nginx'):
                 _get_sendfile.clear()  # To clearout cached version of backend from djangosendfile
-                self.login(self.example_email("hamlet"))
+                self.login('hamlet')
                 fp = StringIO("zulip!")
                 fp.name = name
                 result = self.client_post("/json/user_uploads", {'file': fp})
@@ -806,7 +812,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         """
         Attempting to upload two files should fail.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as fp1, \
                 get_test_image_file('img.png') as fp2:
             result = self.client_post("/json/users/me/avatar", {'f1': fp1, 'f2': fp2})
@@ -816,7 +822,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         """
         Calling this endpoint with no files should fail.
         """
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         result = self.client_post("/json/users/me/avatar")
         self.assert_json_error(result, "You must upload exactly one avatar.")
@@ -825,7 +831,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         """
         Attempting to upload avatar on a realm with avatar changes disabled should fail.
         """
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         do_set_realm_property(self.example_user("cordelia").realm, "avatar_changes_disabled", True)
 
         with get_test_image_file('img.png') as fp1:
@@ -842,7 +848,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
     corrupt_files = ['text.txt', 'corrupt.png', 'corrupt.gif']
 
     def test_get_gravatar_avatar(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         cordelia = self.example_user('cordelia')
 
         cordelia.avatar_source = UserProfile.AVATAR_FROM_GRAVATAR
@@ -859,7 +865,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
 
     def test_get_user_avatar(self) -> None:
         hamlet = self.example_user("hamlet")
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         cordelia = self.example_user('cordelia')
         cross_realm_bot = get_system_bot(settings.WELCOME_BOT)
 
@@ -903,7 +909,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
 
     def test_get_user_avatar_medium(self) -> None:
         hamlet = self.example_user("hamlet")
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         cordelia = self.example_user('cordelia')
 
         cordelia.avatar_source = UserProfile.AVATAR_FROM_USER
@@ -935,7 +941,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
 
         # It's debatable whether we should generate avatars for non-users,
         # but this test just validates the current code's behavior.
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         response = self.client_get("/avatar/nonexistent_user@zulip.com?foo=bar")
         redirect_url = response['Location']
@@ -950,7 +956,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         for fname, rfname in self.correct_files:
             # TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
             # with self.subTest(fname=fname):
-            self.login(self.example_email("hamlet"))
+            self.login('hamlet')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/users/me/avatar", {'file': fp})
 
@@ -987,7 +993,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
             version += 1
 
     def test_copy_avatar_image(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
 
@@ -1010,7 +1016,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(open(source_medium_path_id, "rb").read(), open(target_medium_path_id, "rb").read())
 
     def test_delete_avatar_image(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
 
@@ -1038,7 +1044,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         """
         for fname in self.corrupt_files:
             # with self.subTest(fname=fname):
-            self.login(self.example_email("hamlet"))
+            self.login('hamlet')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/users/me/avatar", {'file': fp})
 
@@ -1050,7 +1056,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         """
         A DELETE request to /json/users/me/avatar should delete the profile picture and return gravatar URL
         """
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         cordelia = self.example_user("cordelia")
         cordelia.avatar_source = UserProfile.AVATAR_FROM_USER
         cordelia.save()
@@ -1071,7 +1077,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(user_profile.avatar_version, 2)
 
     def test_avatar_upload_file_size_error(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file(self.correct_files[0][0]) as fp:
             with self.settings(MAX_AVATAR_FILE_SIZE=0):
                 result = self.client_post("/json/users/me/avatar", {'file': fp})
@@ -1129,7 +1135,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         Attempting to upload two files should fail.
         """
         # Log in as admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         with get_test_image_file('img.png') as fp1, \
                 get_test_image_file('img.png') as fp2:
             result = self.client_post("/json/realm/icon", {'f1': fp1, 'f2': fp2})
@@ -1139,7 +1145,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         """
         Calling this endpoint with no files should fail.
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
 
         result = self.client_post("/json/realm/icon")
         self.assert_json_error(result, "You must upload exactly one icon.")
@@ -1154,13 +1160,13 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
     corrupt_files = ['text.txt', 'corrupt.png', 'corrupt.gif']
 
     def test_no_admin_user_upload(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file(self.correct_files[0][0]) as fp:
             result = self.client_post("/json/realm/icon", {'file': fp})
         self.assert_json_error(result, 'Must be an organization administrator')
 
     def test_get_gravatar_icon(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm = get_realm('zulip')
         do_change_icon_source(realm, Realm.ICON_FROM_GRAVATAR)
         with self.settings(ENABLE_GRAVATAR=True):
@@ -1174,7 +1180,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
             self.assertTrue(redirect_url.endswith(realm_icon_url(realm) + '&foo=bar'))
 
     def test_get_realm_icon(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         realm = get_realm('zulip')
         do_change_icon_source(realm, Realm.ICON_UPLOADED)
@@ -1190,7 +1196,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         for fname, rfname in self.correct_files:
             # TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
             # with self.subTest(fname=fname):
-            self.login(self.example_email("iago"))
+            self.login('iago')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/realm/icon", {'file': fp})
             realm = get_realm('zulip')
@@ -1211,7 +1217,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         """
         for fname in self.corrupt_files:
             # with self.subTest(fname=fname):
-            self.login(self.example_email("iago"))
+            self.login('iago')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/realm/icon", {'file': fp})
 
@@ -1221,7 +1227,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         """
         A DELETE request to /json/realm/icon should delete the realm icon and return gravatar URL
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         do_change_icon_source(realm, Realm.ICON_UPLOADED)
 
@@ -1234,7 +1240,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(realm.icon_source, Realm.ICON_FROM_GRAVATAR)
 
     def test_realm_icon_version(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         icon_version = realm.icon_version
         self.assertEqual(icon_version, 1)
@@ -1244,7 +1250,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(realm.icon_version, icon_version + 1)
 
     def test_realm_icon_upload_file_size_error(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         with get_test_image_file(self.correct_files[0][0]) as fp:
             with self.settings(MAX_ICON_FILE_SIZE=0):
                 result = self.client_post("/json/realm/icon", {'file': fp})
@@ -1262,7 +1268,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
         Attempting to upload two files should fail.
         """
         # Log in as admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         with get_test_image_file('img.png') as fp1, \
                 get_test_image_file('img.png') as fp2:
             result = self.client_post("/json/realm/logo", {'f1': fp1, 'f2': fp2,
@@ -1273,7 +1279,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
         """
         Calling this endpoint with no files should fail.
         """
-        self.login(self.example_email("iago"))
+        self.login('iago')
 
         result = self.client_post("/json/realm/logo", {'night': ujson.dumps(self.night)})
         self.assert_json_error(result, "You must upload exactly one logo.")
@@ -1288,7 +1294,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
     corrupt_files = ['text.txt', 'corrupt.png', 'corrupt.gif']
 
     def test_no_admin_user_upload(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file(self.correct_files[0][0]) as fp:
             result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
         self.assert_json_error(result, 'Must be an organization administrator')
@@ -1296,13 +1302,13 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
     def test_upload_limited_plan_type(self) -> None:
         user_profile = self.example_user("iago")
         do_change_plan_type(user_profile.realm, Realm.LIMITED)
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         with get_test_image_file(self.correct_files[0][0]) as fp:
             result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
         self.assert_json_error(result, 'Feature unavailable on your current plan.')
 
     def test_get_default_logo(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm = get_realm('zulip')
         do_change_logo_source(realm, Realm.LOGO_UPLOADED, self.night)
         response = self.client_get("/json/realm/logo", {'night': ujson.dumps(self.night)})
@@ -1311,7 +1317,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
                          '&night=%s' % (str(self.night).lower(),))
 
     def test_get_realm_logo(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         realm = get_realm('zulip')
         do_change_logo_source(realm, Realm.LOGO_UPLOADED, self.night)
         response = self.client_get("/json/realm/logo", {'night': ujson.dumps(self.night)})
@@ -1327,7 +1333,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
         for fname, rfname in self.correct_files:
             # TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
             # with self.subTest(fname=fname):
-            self.login(self.example_email("iago"))
+            self.login('iago')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
             realm = get_realm('zulip')
@@ -1347,7 +1353,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
         """
         for fname in self.corrupt_files:
             # with self.subTest(fname=fname):
-            self.login(self.example_email("iago"))
+            self.login('iago')
             with get_test_image_file(fname) as fp:
                 result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
 
@@ -1358,7 +1364,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
         A DELETE request to /json/realm/logo should delete the realm logo and return gravatar URL
         """
 
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         do_change_logo_source(realm, Realm.LOGO_UPLOADED, self.night)
         result = self.client_delete("/json/realm/logo", {'night': ujson.dumps(self.night)})
@@ -1370,7 +1376,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
             self.assertEqual(realm.logo_source, Realm.LOGO_DEFAULT)
 
     def test_logo_version(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         realm = get_realm('zulip')
         if self.night:
             version = realm.night_logo_version
@@ -1386,7 +1392,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
             self.assertEqual(realm.logo_version, version + 1)
 
     def test_logo_upload_file_size_error(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         with get_test_image_file(self.correct_files[0][0]) as fp:
             with self.settings(MAX_LOGO_FILE_SIZE=0):
                 result = self.client_post("/json/realm/logo", {'file': fp, 'night':
@@ -1417,7 +1423,7 @@ class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
         self.assertEqual(len(b'zulip!'), uploaded_file.size)
 
     def test_delete_message_image_local(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
         result = self.client_post("/json/user_uploads", {'file': fp})
@@ -1549,7 +1555,7 @@ class S3Test(ZulipTestCase):
         """
         create_s3_buckets(settings.S3_AUTH_UPLOADS_BUCKET)
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         fp = StringIO("zulip!")
         fp.name = "zulip.txt"
 
@@ -1604,7 +1610,7 @@ class S3Test(ZulipTestCase):
     def test_copy_avatar_image(self) -> None:
         bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
 
@@ -1649,7 +1655,7 @@ class S3Test(ZulipTestCase):
     def test_delete_avatar_image(self) -> None:
         bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         with get_test_image_file('img.png') as image_file:
             self.client_post("/json/users/me/avatar", {'file': image_file})
 
@@ -1893,7 +1899,7 @@ class DecompressionBombTests(ZulipTestCase):
         }
 
     def test_decompression_bomb(self) -> None:
-        self.login(self.example_email("iago"))
+        self.login('iago')
         with get_test_image_file("bomb.png") as fp:
             for url, error_string in self.test_urls.items():
                 fp.seek(0, 0)
diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py
index fdd7e3d42a..b7e53a0f82 100644
--- a/zerver/tests/test_user_groups.py
+++ b/zerver/tests/test_user_groups.py
@@ -85,7 +85,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         hamlet = self.example_user('hamlet')
 
         # Test success
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id]),
@@ -118,7 +118,7 @@ class UserGroupAPITestCase(ZulipTestCase):
     def test_user_group_get(self) -> None:
         # Test success
         user_profile = self.example_user('hamlet')
-        self.login(user_profile.email)
+        self.login_user(user_profile)
         result = self.client_get('/json/user_groups')
         self.assert_json_success(result)
         self.assert_length(result.json()['user_groups'], UserGroup.objects.filter(realm=user_profile.realm).count())
@@ -127,7 +127,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         guest_user = self.example_user('polonius')
 
         # Guest users can't create user group
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         params = {
             'name': 'support',
             'members': ujson.dumps([guest_user.id]),
@@ -138,7 +138,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
     def test_user_group_update(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id]),
@@ -166,7 +166,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when user not a member of user group tries to modify it
         cordelia = self.example_user('cordelia')
-        self.login(cordelia.email)
+        self.login_user(cordelia)
         params = {
             'name': 'help',
             'description': 'Troubleshooting',
@@ -177,7 +177,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when organization admin tries to modify group
         iago = self.example_user('iago')
-        self.login(iago.email)
+        self.login_user(iago)
         params = {
             'name': 'help',
             'description': 'Troubleshooting',
@@ -188,7 +188,7 @@ class UserGroupAPITestCase(ZulipTestCase):
     def test_user_group_update_by_guest_user(self) -> None:
         hamlet = self.example_user('hamlet')
         guest_user = self.example_user('polonius')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id, guest_user.id]),
@@ -199,7 +199,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         user_group = UserGroup.objects.get(name='support')
 
         # Guest user can't edit any detail of an user group
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         params = {
             'name': 'help',
             'description': 'Troubleshooting team',
@@ -209,7 +209,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
     def test_user_group_update_to_already_existing_name(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         realm = get_realm('zulip')
         support_user_group = create_user_group('support', [hamlet], realm)
         marketing_user_group = create_user_group('marketing', [hamlet], realm)
@@ -223,7 +223,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
     def test_user_group_delete(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id]),
@@ -254,7 +254,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.assertEqual(UserGroup.objects.count(), 2)
         self.logout()
         cordelia = self.example_user('cordelia')
-        self.login(cordelia.email)
+        self.login_user(cordelia)
 
         result = self.client_delete('/json/user_groups/{}'.format(user_group.id))
         self.assert_json_error(result, "Only group members and organization administrators can administer this group.")
@@ -263,7 +263,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when organization admin tries to delete group
         iago = self.example_user('iago')
-        self.login(iago.email)
+        self.login_user(iago)
 
         result = self.client_delete('/json/user_groups/{}'.format(user_group.id))
         self.assert_json_success(result)
@@ -273,7 +273,7 @@ class UserGroupAPITestCase(ZulipTestCase):
     def test_user_group_delete_by_guest_user(self) -> None:
         hamlet = self.example_user('hamlet')
         guest_user = self.example_user('polonius')
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id, guest_user.id]),
@@ -284,13 +284,13 @@ class UserGroupAPITestCase(ZulipTestCase):
         user_group = UserGroup.objects.get(name='support')
 
         # Guest users can't delete any user group(not even those of which they are a member)
-        self.login(guest_user.email)
+        self.login_user(guest_user)
         result = self.client_delete('/json/user_groups/{}'.format(user_group.id))
         self.assert_json_error(result, "Not allowed for guest users")
 
     def test_update_members_of_user_group(self) -> None:
         hamlet = self.example_user('hamlet')
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         params = {
             'name': 'support',
             'members': ujson.dumps([hamlet.id]),
@@ -322,7 +322,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when user not a member of user group tries to add members to it
         cordelia = self.example_user('cordelia')
-        self.login(cordelia.email)
+        self.login_user(cordelia)
         add = [cordelia.id]
         params = {'add': ujson.dumps(add)}
         result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
@@ -333,7 +333,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when organization admin tries to add members to group
         iago = self.example_user('iago')
-        self.login(iago.email)
+        self.login_user(iago)
         aaron = self.example_user('aaron')
         add = [aaron.id]
         params = {'add': ujson.dumps(add)}
@@ -346,7 +346,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
         # For normal testing we again login with hamlet
         self.logout()
-        self.login(hamlet.email)
+        self.login_user(hamlet)
         # Test remove members
         params = {'delete': ujson.dumps([othello.id])}
         result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
@@ -373,7 +373,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
         # Test when user not a member of user group tries to remove members
         self.logout()
-        self.login(cordelia.email)
+        self.login_user(cordelia)
         params = {'delete': ujson.dumps([hamlet.id])}
         result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
                                   info=params)
@@ -383,7 +383,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         self.logout()
         # Test when organization admin tries to remove members from group
         iago = self.example_user('iago')
-        self.login(iago.email)
+        self.login_user(iago)
         result = self.client_post('/json/user_groups/{}/members'.format(user_group.id),
                                   info=params)
         self.assert_json_success(result)
@@ -445,7 +445,7 @@ class UserGroupAPITestCase(ZulipTestCase):
         iago = self.example_user('iago')
         hamlet = self.example_user('hamlet')
         cordelia = self.example_user('cordelia')
-        self.login(iago.email)
+        self.login_user(iago)
         do_set_realm_property(iago.realm, 'user_group_edit_policy',
                               Realm.USER_GROUP_EDIT_POLICY_ADMINS)
 
@@ -490,7 +490,7 @@ class UserGroupAPITestCase(ZulipTestCase):
 
         self.logout()
 
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         # Test creating a group
         params = {
diff --git a/zerver/tests/test_user_status.py b/zerver/tests/test_user_status.py
index 6847f532f7..cf31aff63a 100644
--- a/zerver/tests/test_user_status.py
+++ b/zerver/tests/test_user_status.py
@@ -157,7 +157,7 @@ class UserStatusTest(ZulipTestCase):
         hamlet = self.example_user('hamlet')
         realm_id = hamlet.realm_id
 
-        self.login(hamlet.email)
+        self.login_user(hamlet)
 
         # Try to omit parameter--this should be an error.
         payload = dict()  # type: Dict[str, Any]
diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py
index 4093731e3e..6c793c769c 100644
--- a/zerver/tests/test_users.py
+++ b/zerver/tests/test_users.py
@@ -117,7 +117,7 @@ class PermissionTest(ZulipTestCase):
         self.assertTrue(user_profile in admin_users)
 
     def test_updating_non_existent_user(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         admin = self.example_user('hamlet')
         do_change_is_admin(admin, True)
 
@@ -126,7 +126,7 @@ class PermissionTest(ZulipTestCase):
         self.assert_json_error(result, 'No such user')
 
     def test_admin_api(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         admin = self.example_user('hamlet')
         user = self.example_user('othello')
         realm = admin.realm
@@ -167,7 +167,7 @@ class PermissionTest(ZulipTestCase):
         self.assertEqual(person['is_admin'], False)
 
         # Cannot take away from last admin
-        self.login(self.example_email("iago"))
+        self.login('iago')
         req = dict(is_admin=ujson.dumps(False))
         events = []
         with tornado_redirected_to_list(events):
@@ -183,14 +183,14 @@ class PermissionTest(ZulipTestCase):
         self.assert_json_error(result, 'Cannot remove the only organization administrator')
 
         # Make sure only admins can patch other user's info.
-        self.login(self.example_email("othello"))
+        self.login('othello')
         result = self.client_patch('/json/users/{}'.format(self.example_user("hamlet").id), req)
         self.assert_json_error(result, 'Insufficient permission')
 
     def test_admin_api_hide_emails(self) -> None:
         user = self.example_user('hamlet')
         admin = self.example_user('iago')
-        self.login(user.email)
+        self.login_user(user)
 
         # First, verify client_gravatar works normally
         result = self.client_get('/json/users?client_gravatar=true')
@@ -243,7 +243,7 @@ class PermissionTest(ZulipTestCase):
         # required in apps like the mobile apps.
         # delivery_email is sent for admins.
         admin.refresh_from_db()
-        self.login(admin.delivery_email)
+        self.login_user(admin)
         result = self.client_get('/json/users?client_gravatar=true')
         self.assert_json_success(result)
         members = result.json()['members']
@@ -253,14 +253,14 @@ class PermissionTest(ZulipTestCase):
         self.assertEqual(hamlet['delivery_email'], self.example_email("hamlet"))
 
     def test_user_cannot_promote_to_admin(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         req = dict(is_admin=ujson.dumps(True))
         result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
         self.assert_json_error(result, 'Insufficient permission')
 
     def test_admin_user_can_change_full_name(self) -> None:
         new_name = 'new name'
-        self.login(self.example_email("iago"))
+        self.login('iago')
         hamlet = self.example_user('hamlet')
         req = dict(full_name=ujson.dumps(new_name))
         result = self.client_patch('/json/users/{}'.format(hamlet.id), req)
@@ -272,28 +272,28 @@ class PermissionTest(ZulipTestCase):
         self.assert_json_success(result)
 
     def test_non_admin_cannot_change_full_name(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         req = dict(full_name=ujson.dumps('new name'))
         result = self.client_patch('/json/users/{}'.format(self.example_user('othello').id), req)
         self.assert_json_error(result, 'Insufficient permission')
 
     def test_admin_cannot_set_long_full_name(self) -> None:
         new_name = 'a' * (UserProfile.MAX_NAME_LENGTH + 1)
-        self.login(self.example_email("iago"))
+        self.login('iago')
         req = dict(full_name=ujson.dumps(new_name))
         result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
         self.assert_json_error(result, 'Name too long!')
 
     def test_admin_cannot_set_short_full_name(self) -> None:
         new_name = 'a'
-        self.login(self.example_email("iago"))
+        self.login('iago')
         req = dict(full_name=ujson.dumps(new_name))
         result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
         self.assert_json_error(result, 'Name too short!')
 
     def test_admin_cannot_set_full_name_with_invalid_characters(self) -> None:
         new_name = 'Opheli*'
-        self.login(self.example_email("iago"))
+        self.login('iago')
         req = dict(full_name=ujson.dumps(new_name))
         result = self.client_patch('/json/users/{}'.format(self.example_user('hamlet').id), req)
         self.assert_json_error(result, 'Invalid characters in name!')
@@ -329,7 +329,7 @@ class PermissionTest(ZulipTestCase):
 
     def test_change_regular_member_to_guest(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
 
         hamlet = self.example_user("hamlet")
         self.assertFalse(hamlet.is_guest)
@@ -357,7 +357,7 @@ class PermissionTest(ZulipTestCase):
 
     def test_change_guest_to_regular_member(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
 
         polonius = self.example_user("polonius")
         self.assertTrue(polonius.is_guest)
@@ -375,7 +375,7 @@ class PermissionTest(ZulipTestCase):
 
     def test_change_admin_to_guest(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
         hamlet = self.example_user("hamlet")
         do_change_is_admin(hamlet, True)
         self.assertFalse(hamlet.is_guest)
@@ -409,7 +409,7 @@ class PermissionTest(ZulipTestCase):
 
     def test_change_guest_to_admin(self) -> None:
         iago = self.example_user("iago")
-        self.login(iago.email)
+        self.login_user(iago)
         polonius = self.example_user("polonius")
         self.assertTrue(polonius.is_guest)
         self.assertFalse(polonius.is_realm_admin)
@@ -438,7 +438,7 @@ class PermissionTest(ZulipTestCase):
 
     def test_admin_user_can_change_profile_data(self) -> None:
         realm = get_realm('zulip')
-        self.login(self.example_email("iago"))
+        self.login('iago')
         new_profile_data = []
         cordelia = self.example_user("cordelia")
 
@@ -556,7 +556,7 @@ class PermissionTest(ZulipTestCase):
                 self.assertEqual(field_dict['value'], new_fields[str(field_dict['name'])])
 
     def test_non_admin_user_cannot_change_profile_data(self) -> None:
-        self.login(self.example_email("cordelia"))
+        self.login('cordelia')
         hamlet = self.example_user("hamlet")
         realm = get_realm("zulip")
 
@@ -583,9 +583,8 @@ class AdminCreateUserTest(ZulipTestCase):
         # path.
 
         admin = self.example_user('hamlet')
-        admin_email = admin.email
         realm = admin.realm
-        self.login(admin_email)
+        self.login_user(admin)
         do_change_is_admin(admin, True)
 
         result = self.client_post("/json/users", dict())
@@ -921,7 +920,7 @@ class ActivateTest(ZulipTestCase):
     def test_api(self) -> None:
         admin = self.example_user('othello')
         do_change_is_admin(admin, True)
-        self.login(self.example_email("othello"))
+        self.login('othello')
 
         user = self.example_user('hamlet')
         self.assertTrue(user.is_active)
@@ -939,7 +938,7 @@ class ActivateTest(ZulipTestCase):
     def test_api_with_nonexistent_user(self) -> None:
         admin = self.example_user('othello')
         do_change_is_admin(admin, True)
-        self.login(self.example_email("othello"))
+        self.login('othello')
 
         # Cannot deactivate a user with the bot api
         result = self.client_delete('/json/bots/{}'.format(self.example_user("hamlet").id))
@@ -967,7 +966,7 @@ class ActivateTest(ZulipTestCase):
     def test_api_with_insufficient_permissions(self) -> None:
         non_admin = self.example_user('othello')
         do_change_is_admin(non_admin, False)
-        self.login(self.example_email("othello"))
+        self.login('othello')
 
         # Cannot deactivate a user with the users api
         result = self.client_delete('/json/users/{}'.format(self.example_user("hamlet").id))
@@ -1242,7 +1241,7 @@ class RecipientInfoTest(ZulipTestCase):
 
 class BulkUsersTest(ZulipTestCase):
     def test_client_gravatar_option(self) -> None:
-        self.login(self.example_email('cordelia'))
+        self.login('cordelia')
 
         hamlet = self.example_user('hamlet')
 
@@ -1276,8 +1275,8 @@ class BulkUsersTest(ZulipTestCase):
 
 class GetProfileTest(ZulipTestCase):
 
-    def common_update_pointer(self, email: str, pointer: int) -> None:
-        self.login(email)
+    def common_update_pointer(self, user: UserProfile, pointer: int) -> None:
+        self.login_user(user)
         result = self.client_post("/json/users/me/pointer", {"pointer": pointer})
         self.assert_json_success(result)
 
@@ -1301,8 +1300,8 @@ class GetProfileTest(ZulipTestCase):
         return json
 
     def test_get_pointer(self) -> None:
-        email = self.example_email("hamlet")
-        self.login(email)
+        user = self.example_user("hamlet")
+        self.login_user(user)
         result = self.client_get("/json/users/me/pointer")
         self.assert_json_success(result)
         self.assertIn("pointer", result.json())
@@ -1322,7 +1321,7 @@ class GetProfileTest(ZulipTestCase):
         self.assertEqual(user_profile.email, email)
 
     def test_get_user_profile(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         result = ujson.loads(self.client_get('/json/users/me').content)
         self.assertEqual(result['short_name'], 'hamlet')
         self.assertEqual(result['email'], self.example_email("hamlet"))
@@ -1330,7 +1329,7 @@ class GetProfileTest(ZulipTestCase):
         self.assertIn("user_id", result)
         self.assertFalse(result['is_bot'])
         self.assertFalse(result['is_admin'])
-        self.login(self.example_email("iago"))
+        self.login('iago')
         result = ujson.loads(self.client_get('/json/users/me').content)
         self.assertEqual(result['short_name'], 'iago')
         self.assertEqual(result['email'], self.example_email("iago"))
@@ -1371,11 +1370,12 @@ class GetProfileTest(ZulipTestCase):
 
         json = self.common_get_profile("hamlet")
 
-        self.common_update_pointer(self.example_email("hamlet"), id2)
+        hamlet = self.example_user('hamlet')
+        self.common_update_pointer(hamlet, id2)
         json = self.common_get_profile("hamlet")
         self.assertEqual(json["pointer"], id2)
 
-        self.common_update_pointer(self.example_email("hamlet"), id1)
+        self.common_update_pointer(hamlet, id1)
         json = self.common_get_profile("hamlet")
         self.assertEqual(json["pointer"], id2)  # pointer does not move backwards
 
diff --git a/zerver/tests/test_zcommand.py b/zerver/tests/test_zcommand.py
index ff7e61d41e..937af006b9 100644
--- a/zerver/tests/test_zcommand.py
+++ b/zerver/tests/test_zcommand.py
@@ -7,7 +7,7 @@ from zerver.lib.test_classes import (
 class ZcommandTest(ZulipTestCase):
 
     def test_invalid_zcommand(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         payload = dict(command="/boil-ocean")
         result = self.client_post("/json/zcommand", payload)
@@ -18,14 +18,14 @@ class ZcommandTest(ZulipTestCase):
         self.assert_json_error(result, "There should be a leading slash in the zcommand.")
 
     def test_ping_zcommand(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
 
         payload = dict(command="/ping")
         result = self.client_post("/json/zcommand", payload)
         self.assert_json_success(result)
 
     def test_night_zcommand(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user = self.example_user('hamlet')
         user.night_mode = False
         user.save()
@@ -40,7 +40,7 @@ class ZcommandTest(ZulipTestCase):
         self.assertIn('still in night mode', result.json()['msg'])
 
     def test_day_zcommand(self) -> None:
-        self.login(self.example_email("hamlet"))
+        self.login('hamlet')
         user = self.example_user('hamlet')
         user.night_mode = True
         user.save()
diff --git a/zerver/tests/test_zephyr.py b/zerver/tests/test_zephyr.py
index 56ad78d1d7..8f671eb5f1 100644
--- a/zerver/tests/test_zephyr.py
+++ b/zerver/tests/test_zephyr.py
@@ -11,8 +11,8 @@ from zerver.models import get_user, get_realm
 
 class ZephyrTest(ZulipTestCase):
     def test_webathena_kerberos_login(self) -> None:
-        email = self.example_email('hamlet')
-        self.login(email)
+        user = self.example_user('hamlet')
+        self.login_user(user)
 
         def post(subdomain: Any, **kwargs: Any) -> HttpResponse:
             params = {k: ujson.dumps(v) for k, v in kwargs.items()}
@@ -29,7 +29,7 @@ class ZephyrTest(ZulipTestCase):
         realm = get_realm('zephyr')
         user = get_user(email, realm)
         api_key = get_api_key(user)
-        self.login(email, realm=realm)
+        self.login_user(user)
 
         def ccache_mock(**kwargs: Any) -> Any:
             return patch('zerver.views.zephyr.make_ccache', **kwargs)