diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b53780b91680b935ab843df5921f0acb08978fd5..4bc37482c8d90a9794647db9d74555f83e46bc2c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,10 @@ LMS: Add feature for providing background grade report generation via Celery
   instructor task, with reports uploaded to S3. Feature is visible on the beta
   instructor dashboard. LMS-58
 
+LMS: Beta-tester status is now set on a per-course-run basis, rather than being valid
+  across all runs with the same course name. Old group membership will still work
+  across runs, but new beta-testers will only be added to a single course run.
+
 LMS: Add a user-visible alert modal when a forums AJAX request fails.
 
 Blades: Add template for checkboxes response to studio. BLD-193.
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py
index 4b50c559deaf9cbcc9d3c037dd2e14d8d511f2c1..8c4fb662f931f530eebd62195f52860c6aaeb0d8 100644
--- a/lms/djangoapps/courseware/access.py
+++ b/lms/djangoapps/courseware/access.py
@@ -241,7 +241,11 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
         # Check start date
         if descriptor.start is not None:
             now = datetime.now(UTC())
-            effective_start = _adjust_start_date_for_beta_testers(user, descriptor)
+            effective_start = _adjust_start_date_for_beta_testers(
+                user,
+                descriptor,
+                course_context=course_context
+            )
             if now > effective_start:
                 # after start date, everyone can see it
                 debug("Allow: now > effective start date")
@@ -337,7 +341,7 @@ def _dispatch(table, action, user, obj):
         type(obj), action))
 
 
-def _adjust_start_date_for_beta_testers(user, descriptor):
+def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None):
     """
     If user is in a beta test group, adjust the start date by the appropriate number of
     days.
@@ -364,7 +368,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor):
         # bail early if no beta testing is set up
         return descriptor.start
 
-    if CourseBetaTesterRole(descriptor.location).has_user(user):
+    if CourseBetaTesterRole(descriptor.location, course_context=course_context).has_user(user):
         debug("Adjust start time: user in beta role for %s", descriptor)
         delta = timedelta(descriptor.days_early_for_beta)
         effective = descriptor.start - delta
diff --git a/lms/djangoapps/courseware/roles.py b/lms/djangoapps/courseware/roles.py
index 1643dd505169f11365600b73b7193e0e6511e560..110bb9f3623b6115c14ed8c4a82ee9d8a050f33e 100644
--- a/lms/djangoapps/courseware/roles.py
+++ b/lms/djangoapps/courseware/roles.py
@@ -187,6 +187,6 @@ class OrgStaffRole(OrgRole):
 
 
 class OrgInstructorRole(OrgRole):
-    """An organization staff member"""
+    """An organization instructor"""
     def __init__(self, *args, **kwargs):
-        super(OrgInstructorRole, self).__init__('staff', *args, **kwargs)
+        super(OrgInstructorRole, self).__init__('instructor', *args, **kwargs)
diff --git a/lms/djangoapps/courseware/tests/factories.py b/lms/djangoapps/courseware/tests/factories.py
index 91f91f2617425f7e93e6889a428a823f8e315c2d..38ad365011d21dda102679357c01e6783a2e0ca0 100644
--- a/lms/djangoapps/courseware/tests/factories.py
+++ b/lms/djangoapps/courseware/tests/factories.py
@@ -14,7 +14,14 @@ from student.tests.factories import RegistrationFactory  # Imported to re-export
 from student.tests.factories import UserProfileFactory as StudentUserProfileFactory
 from courseware.models import StudentModule, XModuleUserStateSummaryField
 from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField
-from courseware.roles import CourseInstructorRole, CourseStaffRole
+from courseware.roles import (
+    CourseInstructorRole,
+    CourseStaffRole,
+    CourseBetaTesterRole,
+    GlobalStaff,
+    OrgStaffRole,
+    OrgInstructorRole,
+)
 
 from xmodule.modulestore import Location
 
@@ -54,6 +61,59 @@ class StaffFactory(UserFactory):
         CourseStaffRole(extracted).add_users(self)
 
 
+class BetaTesterFactory(UserFactory):
+    """
+    Given a course Location, returns a User object with beta-tester
+    permissions for `course`.
+    """
+    last_name = "Beta-Tester"
+
+    @post_generation
+    def course(self, create, extracted, **kwargs):
+        if extracted is None:
+            raise ValueError("Must specify a course location for a beta-tester user")
+        CourseBetaTesterRole(extracted).add_users(self)
+
+
+class OrgStaffFactory(UserFactory):
+    """
+    Given a course Location, returns a User object with org-staff
+    permissions for `course`.
+    """
+    last_name = "Org-Staff"
+
+    @post_generation
+    def course(self, create, extracted, **kwargs):
+        if extracted is None:
+            raise ValueError("Must specify a course location for an org-staff user")
+        OrgStaffRole(extracted).add_users(self)
+
+
+class OrgInstructorFactory(UserFactory):
+    """
+    Given a course Location, returns a User object with org-instructor
+    permissions for `course`.
+    """
+    last_name = "Org-Instructor"
+
+    @post_generation
+    def course(self, create, extracted, **kwargs):
+        if extracted is None:
+            raise ValueError("Must specify a course location for an org-instructor user")
+        OrgInstructorRole(extracted).add_users(self)
+
+
+class GlobalStaffFactory(UserFactory):
+    """
+    Returns a User object with global staff access
+    """
+    last_name = "GlobalStaff"
+
+    @post_generation
+    def set_staff(self, create, extracted, **kwargs):
+        GlobalStaff().add_users(self)
+
+
 class StudentModuleFactory(DjangoModelFactory):
     FACTORY_FOR = StudentModule
 
diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py
index 3639d07afafc1a4b0f99bb78db0c55032efde05b..9ed0dea8e5d87e5be17b415b42e2369cf5c5181e 100644
--- a/lms/djangoapps/courseware/tests/test_view_authentication.py
+++ b/lms/djangoapps/courseware/tests/test_view_authentication.py
@@ -9,14 +9,23 @@ from django.test.utils import override_settings
 
 # Need access to internal func to put users in the right group
 from courseware.access import has_access
-from courseware.roles import CourseBetaTesterRole, CourseInstructorRole, CourseStaffRole, GlobalStaff
 
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 
+from student.tests.factories import UserFactory, CourseEnrollmentFactory
+
 from courseware.tests.helpers import LoginEnrollmentTestCase, check_for_get_code
 from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
+from courseware.tests.factories import (
+    BetaTesterFactory,
+    StaffFactory,
+    GlobalStaffFactory,
+    InstructorFactory,
+    OrgStaffFactory,
+    OrgInstructorFactory,
+)
 
 
 @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@@ -89,13 +98,16 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         # user (the student), and the requesting user (the prof)
         url = reverse('student_progress',
                       kwargs={'course_id': course.id,
-                              'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id})
+                              'student_id': self.enrolled_user.id})
         check_for_get_code(self, 404, url)
 
         # The courseware url should redirect, not 200
         url = self._reverse_urls(['courseware'], course)[0]
         check_for_get_code(self, 302, url)
 
+    def login(self, user):
+        return super(TestViewAuth, self).login(user.email, 'test')
+
     def setUp(self):
 
         self.course = CourseFactory.create(number='999', display_name='Robot_Super_Course')
@@ -103,26 +115,37 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.courseware_chapter = ItemFactory.create(display_name='courseware')
 
         self.test_course = CourseFactory.create(number='666', display_name='Robot_Sub_Course')
-        self.sub_courseware_chapter = ItemFactory.create(parent_location=self.test_course.location,
-                                                         display_name='courseware')
-        self.sub_overview_chapter = ItemFactory.create(parent_location=self.sub_courseware_chapter.location,
-                                                       display_name='Overview')
-        self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location,
-                                                  display_name='Welcome')
-
-        # Create two accounts and activate them.
-        for i in range(len(self.ACCOUNT_INFO)):
-            username, email, password = 'u{0}'.format(i), self.ACCOUNT_INFO[i][0], self.ACCOUNT_INFO[i][1]
-            self.create_account(username, email, password)
-            self.activate_user(email)
+        self.other_org_course = CourseFactory.create(org='Other_Org_Course')
+        self.sub_courseware_chapter = ItemFactory.create(
+            parent_location=self.test_course.location, display_name='courseware'
+        )
+        self.sub_overview_chapter = ItemFactory.create(
+            parent_location=self.sub_courseware_chapter.location,
+            display_name='Overview'
+        )
+        self.welcome_section = ItemFactory.create(
+            parent_location=self.overview_chapter.location,
+            display_name='Welcome'
+        )
+
+        self.unenrolled_user = UserFactory(last_name="Unenrolled")
+
+        self.enrolled_user = UserFactory(last_name="Enrolled")
+        CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.course.id)
+        CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.test_course.id)
+
+        self.staff_user = StaffFactory(course=self.course.location)
+        self.instructor_user = InstructorFactory(course=self.course.location)
+        self.org_staff_user = OrgStaffFactory(course=self.course.location)
+        self.org_instructor_user = OrgInstructorFactory(course=self.course.location)
+        self.global_staff_user = GlobalStaffFactory()
 
     def test_redirection_unenrolled(self):
         """
         Verify unenrolled student is redirected to the 'about' section of the chapter
         instead of the 'Welcome' section after clicking on the courseware tab.
         """
-        email, password = self.ACCOUNT_INFO[0]
-        self.login(email, password)
+        self.login(self.unenrolled_user)
         response = self.client.get(reverse('courseware',
                                            kwargs={'course_id': self.course.id}))
         self.assertRedirects(response,
@@ -134,9 +157,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         Verify enrolled student is redirected to the 'Welcome' section of
         the chapter after clicking on the courseware tab.
         """
-        email, password = self.ACCOUNT_INFO[0]
-        self.login(email, password)
-        self.enroll(self.course)
+        self.login(self.enrolled_user)
 
         response = self.client.get(reverse('courseware',
                                            kwargs={'course_id': self.course.id}))
@@ -152,11 +173,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         Verify non-staff cannot load the instructor
         dashboard, the grade views, and student profile pages.
         """
-        email, password = self.ACCOUNT_INFO[0]
-        self.login(email, password)
-
-        self.enroll(self.course)
-        self.enroll(self.test_course)
+        self.login(self.enrolled_user)
 
         urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}),
                 reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})]
@@ -165,37 +182,69 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         for url in urls:
             check_for_get_code(self, 404, url)
 
+    def test_staff_course_access(self):
+        """
+        Verify staff can load the staff dashboard, the grade views,
+        and student profile pages for their course.
+        """
+        self.login(self.staff_user)
+
+        # Now should be able to get to self.course, but not  self.test_course
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
+        check_for_get_code(self, 200, url)
+
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})
+        check_for_get_code(self, 404, url)
+
     def test_instructor_course_access(self):
         """
         Verify instructor can load the instructor dashboard, the grade views,
         and student profile pages for their course.
         """
-        email, password = self.ACCOUNT_INFO[1]
+        self.login(self.instructor_user)
 
-        # Make the instructor staff in self.course
-        CourseInstructorRole(self.course.location).add_users(User.objects.get(email=email))
+        # Now should be able to get to self.course, but not  self.test_course
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
+        check_for_get_code(self, 200, url)
 
-        self.login(email, password)
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})
+        check_for_get_code(self, 404, url)
 
-        # Now should be able to get to self.course, but not  self.test_course
+    def test_org_staff_access(self):
+        """
+        Verify org staff can load the instructor dashboard, the grade views,
+        and student profile pages for course in their org.
+        """
+        self.login(self.org_staff_user)
         url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
         check_for_get_code(self, 200, url)
 
         url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})
+        check_for_get_code(self, 200, url)
+
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id})
         check_for_get_code(self, 404, url)
 
-    def test_instructor_as_staff_access(self):
+    def test_org_instructor_access(self):
         """
-        Verify the instructor can load staff pages if he is given
-        staff permissions.
+        Verify org instructor can load the instructor dashboard, the grade views,
+        and student profile pages for course in their org.
         """
-        email, password = self.ACCOUNT_INFO[1]
-        self.login(email, password)
+        self.login(self.org_instructor_user)
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
+        check_for_get_code(self, 200, url)
+
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})
+        check_for_get_code(self, 200, url)
 
-        # now make the instructor also staff
-        instructor = User.objects.get(email=email)
-        instructor.is_staff = True
-        instructor.save()
+        url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id})
+        check_for_get_code(self, 404, url)
+
+    def test_global_staff_access(self):
+        """
+        Verify the global staff user can access any course.
+        """
+        self.login(self.global_staff_user)
 
         # and now should be able to load both
         urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}),
@@ -211,8 +260,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         pages.
         """
 
-        student_email, student_password = self.ACCOUNT_INFO[0]
-
         # Make courses start in the future
         now = datetime.datetime.now(pytz.UTC)
         tomorrow = now + datetime.timedelta(days=1)
@@ -225,9 +272,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assertFalse(self.test_course.has_started())
 
         # First, try with an enrolled student
-        self.login(student_email, student_password)
-        self.enroll(self.course, True)
-        self.enroll(self.test_course, True)
+        self.login(self.enrolled_user)
 
         # shouldn't be able to get to anything except the light pages
         self._check_non_staff_light(self.course)
@@ -241,8 +286,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         Make sure that before course start instructors can access the
         page for their course.
         """
-        instructor_email, instructor_password = self.ACCOUNT_INFO[1]
-
         now = datetime.datetime.now(pytz.UTC)
         tomorrow = now + datetime.timedelta(days=1)
         course_data = {'start': tomorrow}
@@ -250,11 +293,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.course = self.update_course(self.course, course_data)
         self.test_course = self.update_course(self.test_course, test_course_data)
 
-        # Make the instructor staff in  self.course
-        CourseStaffRole(self.course.location).add_users(User.objects.get(email=instructor_email))
-
-        self.logout()
-        self.login(instructor_email, instructor_password)
+        self.login(self.instructor_user)
         # Enroll in the classes---can't see courseware otherwise.
         self.enroll(self.course, True)
         self.enroll(self.test_course, True)
@@ -265,13 +304,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self._check_staff(self.course)
 
     @patch.dict('courseware.access.settings.MITX_FEATURES', {'DISABLE_START_DATES': False})
-    def test_dark_launch_staff(self):
+    def test_dark_launch_global_staff(self):
         """
         Make sure that before course start staff can access
         course pages.
         """
-        instructor_email, instructor_password = self.ACCOUNT_INFO[1]
-
         now = datetime.datetime.now(pytz.UTC)
         tomorrow = now + datetime.timedelta(days=1)
         course_data = {'start': tomorrow}
@@ -279,15 +316,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.course = self.update_course(self.course, course_data)
         self.test_course = self.update_course(self.test_course, test_course_data)
 
-        self.login(instructor_email, instructor_password)
+        self.login(self.global_staff_user)
         self.enroll(self.course, True)
         self.enroll(self.test_course, True)
 
-        # now also make the instructor staff
-        instructor = User.objects.get(email=instructor_email)
-        instructor.is_staff = True
-        instructor.save()
-
         # and now should be able to load both
         self._check_staff(self.course)
         self._check_staff(self.test_course)
@@ -297,9 +329,6 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         """
         Check that enrollment periods work.
         """
-        student_email, student_password = self.ACCOUNT_INFO[0]
-        instructor_email, instructor_password = self.ACCOUNT_INFO[1]
-
         # Make courses start in the future
         now = datetime.datetime.now(pytz.UTC)
         tomorrow = now + datetime.timedelta(days=1)
@@ -315,52 +344,53 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.test_course = self.update_course(self.test_course, test_course_data)
 
         # First, try with an enrolled student
-        self.login(student_email, student_password)
+        self.login(self.unenrolled_user)
         self.assertFalse(self.enroll(self.course))
         self.assertTrue(self.enroll(self.test_course))
 
-        # Make the instructor staff in the self.course
-        instructor_role = CourseInstructorRole(self.course.location)
-        instructor_role.add_users(User.objects.get(email=instructor_email))
-
         self.logout()
-        self.login(instructor_email, instructor_password)
+        self.login(self.instructor_user)
         self.assertTrue(self.enroll(self.course))
 
-        # now make the instructor global staff, but not in the instructor group
-        instructor_role.remove_users(User.objects.get(email=instructor_email))
-        GlobalStaff().add_users(User.objects.get(email=instructor_email))
-
         # unenroll and try again
-        self.unenroll(self.course)
+        self.login(self.global_staff_user)
         self.assertTrue(self.enroll(self.course))
 
-    @patch.dict('courseware.access.settings.MITX_FEATURES', {'DISABLE_START_DATES': False})
-    def test_beta_period(self):
-        """
-        Check that beta-test access works.
-        """
-        student_email, student_password = self.ACCOUNT_INFO[0]
-        instructor_email, instructor_password = self.ACCOUNT_INFO[1]
 
-        # Make courses start in the future
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class TestBetatesterAccess(ModuleStoreTestCase):
+
+    def setUp(self):
+
         now = datetime.datetime.now(pytz.UTC)
         tomorrow = now + datetime.timedelta(days=1)
-        course_data = {'start': tomorrow}
 
-        # self.course's hasn't started
-        self.course = self.update_course(self.course, course_data)
-        self.assertFalse(self.course.has_started())
+        self.course = CourseFactory(days_early_for_beta=2, start=tomorrow)
+        self.content = ItemFactory(parent=self.course)
+
+        self.normal_student = UserFactory()
+        self.beta_tester = BetaTesterFactory(course=self.course.location)
 
-        # but should be accessible for beta testers
-        self.course.days_early_for_beta = 2
+    @patch.dict('courseware.access.settings.MITX_FEATURES', {'DISABLE_START_DATES': False})
+    def test_course_beta_period(self):
+        """
+        Check that beta-test access works for courses.
+        """
+        self.assertFalse(self.course.has_started())
 
         # student user shouldn't see it
-        student_user = User.objects.get(email=student_email)
-        self.assertFalse(has_access(student_user, self.course, 'load'))
+        self.assertFalse(has_access(self.normal_student, self.course, 'load'))
+
+        # now the student should see it
+        self.assertTrue(has_access(self.beta_tester, self.course, 'load'))
 
-        # now add the student to the beta test group
-        CourseBetaTesterRole(self.course.location).add_users(student_user)
+    @patch.dict('courseware.access.settings.MITX_FEATURES', {'DISABLE_START_DATES': False})
+    def test_content_beta_period(self):
+        """
+        Check that beta-test access works for content.
+        """
+        # student user shouldn't see it
+        self.assertFalse(has_access(self.normal_student, self.content, 'load', self.course.id))
 
         # now the student should see it
-        self.assertTrue(has_access(student_user, self.course, 'load'))
+        self.assertTrue(has_access(self.beta_tester, self.content, 'load', self.course.id))