Repository: oppia/oppia

Issue:
Console errors when searching the library using mobile device on the test server
## Steps to reproduce:
1. Navigate to https://oppiatestserver.appspot.com/library
2. Chose Pixel 2 using Chrome dev tools.
3. Type in any query in the search bar.
4. Observe the console.

## Observed behavior:

![errrrrrrr from 2018-07-25 02-45-59](https://user-images.githubusercontent.com/24826041/43166734-fec7326a-8fb4-11e8-80d8-b0880401beb0.png)

## Expected behavior:
There should be no console errors.

Note: This is only reproducible on mobile device.
(Please preserve the logs too on the console).


Discussion:

Resolving patch:
diff --git a/feconf.py b/feconf.py
--- a/feconf.py
+++ b/feconf.py
@@ -556,10 +556,15 @@ def get_empty_ratings():
     u'19': 'example_exploration_in_collection1.yaml',
     u'20': 'example_exploration_in_collection2.yaml',
     u'21': 'example_exploration_in_collection3.yaml',
+    u'22': 'protractor_mobile_test_exploration.yaml',
+    u'23': 'rating_test.yaml',
+    u'24': 'learner_flow_test.yaml',
+    u'25': 'exploration_player_test.yaml',
 }
 
 DEMO_COLLECTIONS = {
-    u'0': 'welcome_to_collections.yaml'
+    u'0': 'welcome_to_collections.yaml',
+    u'1': 'learner_flow_test_collection.yaml'
 }
 
 # IDs of explorations which should not be displayable in either the learner or
diff --git a/scripts/pre_commit_linter.py b/scripts/pre_commit_linter.py
--- a/scripts/pre_commit_linter.py
+++ b/scripts/pre_commit_linter.py
@@ -215,6 +215,7 @@
     'core/templates/dev/head/expressions/ExpressionParserService.js')
 
 CONFIG_FILE_PATHS = (
+    'core/tests/.browserstack.env.example',
     'core/tests/protractor.conf.js',
     'core/tests/karma.conf.js',
     'core/templates/dev/head/mathjaxConfig.js',


Test patch:
diff --git a/core/tests/.browserstack.env.example b/core/tests/.browserstack.env.example
new file mode 100644
--- /dev/null
+++ b/core/tests/.browserstack.env.example
@@ -0,0 +1,11 @@
+// This is the sample environment file which is
+// required to run e2e tests on Browserstack.
+// To run the tests, create a new file,
+// .browserstack.env, copy the contents of
+// this file in it and then put in the
+// actual browserstack credentials.
+
+USERNAME={Browserstack username}
+ACCESS_KEY={Browserstack access key}
+DEVICE={The Mobile Device}
+OS_VERSION={The OS Version}
diff --git a/core/tests/protractor-browserstack.conf.js b/core/tests/protractor-browserstack.conf.js
new file mode 100755
--- /dev/null
+++ b/core/tests/protractor-browserstack.conf.js
@@ -0,0 +1,236 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview Protractor configuration to run tests on Browserstack.
+ */
+
+var browserstack = require('browserstack-local');
+var dotenv = require('dotenv');
+
+// eslint-disable-next-line angular/di
+var result = dotenv.config({path: 'core/tests/.browserstack.env'});
+
+// A reference configuration file.
+exports.config = {
+  // ----- How to setup Selenium -----
+  //
+  // There are three ways to specify how to use Selenium. Specify one of the
+  // following:
+  //
+  // 1. seleniumServerJar - to start Selenium Standalone locally.
+  // 2. seleniumAddress - to connect to a Selenium server which is already
+  //    running.
+  // 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.
+  //
+  // If the chromeOnly option is specified, no Selenium server will be started,
+  // and chromeDriver will be used directly (from the location specified in
+  // chromeDriver)
+
+  // The location of the selenium standalone server .jar file, relative
+  // to the location of this config. If no other method of starting selenium
+  // is found, this will default to
+  // node_modules/protractor/selenium/selenium-server...
+  seleniumServerJar: null,
+  // The port to start the selenium server on, or null if the server should
+  // find its own unused port.
+  seleniumPort: null,
+  // Chromedriver location is used to help the selenium standalone server
+  // find chromedriver. This will be passed to the selenium jar as
+  // the system property webdriver.chrome.driver. If null, selenium will
+  // attempt to find chromedriver using PATH.
+  chromeDriver: './selenium/chromedriver',
+  // If true, only chromedriver will be started, not a standalone selenium.
+  // Tests for browsers other than chrome will not run.
+  chromeOnly: false,
+  // Additional command line options to pass to selenium. For example,
+  // if you need to change the browser timeout, use
+  // seleniumArgs: ['-browserTimeout=60'],
+  seleniumArgs: [],
+
+  // If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
+  // The tests will be run remotely using SauceLabs.
+  sauceUser: null,
+  sauceKey: null,
+
+  // The address of a running selenium server. If specified, Protractor will
+  // connect to an already running instance of selenium. This usually looks like
+  // seleniumAddress: 'http://localhost:4444/wd/hub'
+  seleniumAddress: 'http://hub-cloud.browserstack.com/wd/hub',
+
+  // The timeout for each script run on the browser. This should be longer
+  // than the maximum time your application needs to stabilize between tasks.
+  // (Note that the hint tooltip has a 60-second timeout.)
+  allScriptsTimeout: 180000,
+
+  // ----- What tests to run -----
+  //
+  // When run without a command line parameter, all suites will run. If run
+  // with --suite=smoke, only the patterns matched by that suite will run.
+  suites: {
+    // When running the tests on browserstack, all suites should be added
+    // in 'full' individually. This is to parallelize the suites on
+    // browserstack.
+    full: [
+      'protractor/accessibility.js',
+      'protractor/learnerFlow.js',
+      'protractor/libraryFlow.js',
+      'protractor_mobile/navigation.js',
+      'protractor/profileMenuFlow.js',
+      'protractor/subscriptionsFlow.js'
+    ],
+
+    accessibility: [
+      'protractor/accessibility.js'
+    ],
+
+    learner: [
+      'protractor/learnerFlow.js'
+    ],
+
+    library: [
+      'protractor/libraryFlow.js'
+    ],
+
+    navigation: [
+      'protractor_mobile/navigation.js'
+    ],
+
+    profileMenu: [
+      'protractor/profileMenuFlow.js'
+    ],
+
+    subscriptions: [
+      'protractor/subscriptionsFlow.js'
+    ]
+  },
+
+  // ----- Capabilities to be passed to the webdriver instance ----
+  //
+  // For a full list of available capabilities, see
+  // https://code.google.com/p/selenium/wiki/DesiredCapabilities
+  // and
+  // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
+  capabilities: {
+    'browserstack.user': process.env.USERNAME,
+    'browserstack.key': process.env.ACCESS_KEY,
+    'browserstack.local': true,
+    'browserstack.localIdentifier': process.env.BROWSERSTACK_LOCAL_IDENTIFIER,
+    'browserstack.debug': true,
+    'browserstack.networkLogs': true,
+    'browserstack.appium_version': '1.7.2',
+    device: process.env.DEVICE,
+    os_version: process.env.OS_VERSION,
+    real_mobile: 'true',
+    browserName: ''
+  },
+
+  // Code to start browserstack local before start of test
+  beforeLaunch: function() {
+    var checkSuites = function() {
+      // eslint-disable-next-line no-console
+      console.log(
+        'Checking whether the suites in "full" match the actual suites...');
+      var suitesInFull = exports.config.suites.full;
+      var allSuiteGroups = Object.keys(exports.config.suites);
+      for (var suiteIndex in allSuiteGroups) {
+        if (allSuiteGroups[suiteIndex] === 'full') {
+          continue;
+        }
+        suiteGroup = allSuiteGroups[suiteIndex];
+        // The suiteFile is the first element of each suiteGroup array.
+        suiteFile = exports.config.suites[suiteGroup][0];
+        if (!suitesInFull.includes(suiteFile)) {
+          // eslint-disable-next-line no-console
+          console.log(
+            'Expected test file ' + suiteFile + ' to be in "full" ' +
+            'but it was not present.');
+          process.exit(1);
+        }
+      }
+    };
+    checkSuites();
+    // eslint-disable-next-line no-console
+    console.log('Connecting browserstack local');
+    return new Promise(function(resolve, reject) {
+      exports.bs_local = new browserstack.Local();
+      exports.bs_local.start({
+        key: exports.config.capabilities['browserstack.key']
+      }, function(error) {
+        if (error) {
+          return reject(error);
+        }
+        // eslint-disable-next-line no-console
+        console.log('Connected. Now testing...');
+
+        resolve();
+      });
+    });
+  },
+
+  // Code to stop browserstack local after end of test
+  afterLaunch: function() {
+    return new Promise(function(resolve, reject) {
+      exports.bs_local.stop(resolve);
+    });
+  },
+
+  // If you would like to run more than one instance of webdriver on the same
+  // tests, use multiCapabilities, which takes an array of capabilities.
+  // If this is specified, capabilities will be ignored.
+  multiCapabilities: [],
+
+  // ----- More information for your tests ----
+  //
+  // A base URL for your application under test. Calls to protractor.get()
+  // with relative paths will be prepended with this.
+  baseUrl: 'http://localhost:9001',
+
+  // Selector for the element housing the angular app - this defaults to
+  // body, but is necessary if ng-app is on a descendant of <body>
+  rootElement: 'body',
+
+  onPrepare: function() {
+    browser.isMobile = true;
+
+    var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
+    jasmine.getEnv().addReporter(new SpecReporter({
+      displayStacktrace: 'all',
+      displaySpecDuration: true
+    }));
+  },
+
+  // ----- The test framework -----
+  //
+  // Jasmine and Cucumber are fully supported as a test and assertion framework.
+  // Mocha has limited beta support. You will need to include your own
+  // assertion framework if working with mocha.
+  framework: 'jasmine2',
+
+  // ----- Options to be passed to minijasminenode -----
+  //
+  // See the full list at https://github.com/juliemr/minijasminenode
+  jasmineNodeOpts: {
+    // The onComplete method will be called just before the driver quits.
+    onComplete: null,
+    // If true, display spec names.
+    isVerbose: false,
+    // If true, print colors to the terminal.
+    showColors: true,
+    // If true, include stack traces in failures.
+    includeStackTrace: true,
+    // Default time to wait in ms before a test fails.
+    defaultTimeoutInterval: 1200000
+  }
+};
diff --git a/core/tests/protractor.conf.js b/core/tests/protractor.conf.js
--- a/core/tests/protractor.conf.js
+++ b/core/tests/protractor.conf.js
@@ -57,8 +57,12 @@ exports.config = {
   // When run without a command line parameter, all suites will run. If run
   // with --suite=smoke, only the patterns matched by that suite will run.
   suites: {
+    // The tests on Travis are run individually to parallelize
+    // them. Therefore, we mention the complete directory
+    // in 'full'.
     full: [
-      'protractor/*.js'
+      'protractor/*.js',
+      'protractor_desktop/*.js'
     ],
 
     // Unfortunately, adding more than one file to a test suite results in
@@ -68,39 +72,55 @@ exports.config = {
     ],
 
     collections: [
-      'protractor/collections.js'
+      'protractor_desktop/collections.js'
     ],
 
     editorAndPlayer: [
-      'protractor/editorAndPlayer.js',
-    ],
-
-    stateEditor: [
-      'protractor/stateEditor.js',
+      'protractor_desktop/editorAndPlayer.js',
     ],
 
     editorFeatures: [
-      'protractor/editorFeatures.js'
+      'protractor_desktop/editorFeatures.js'
     ],
 
     embedding: [
-      'protractor/embedding.js'
+      'protractor_desktop/embedding.js'
     ],
 
     extensions: [
-      'protractor/extensions.js'
+      'protractor_desktop/extensions.js'
     ],
 
-    learnerDashboard: [
-      'protractor/learnerDashboard.js'
+    learnerDashboardSubscriptionsAndFeedbackThreads: [
+      'protractor_desktop/learnerDashboardSubscriptionsAndFeedbackThreads.js'
+    ],
+
+    learner: [
+      'protractor/learnerFlow.js'
     ],
 
     library: [
-      'protractor/publicationAndLibrary.js'
+      'protractor/libraryFlow.js'
+    ],
+
+    profileMenu: [
+      'protractor/profileMenuFlow.js'
+    ],
+
+    publication: [
+      'protractor_desktop/publicationAndLibrary.js'
+    ],
+
+    stateEditor: [
+      'protractor_desktop/stateEditor.js',
+    ],
+
+    subscriptions: [
+      'protractor/subscriptionsFlow.js'
     ],
 
     users: [
-      'protractor/userJourneys.js',
+      'protractor_desktop/userJourneys.js',
     ],
   },
 
@@ -146,6 +166,7 @@ exports.config = {
   // You can specify a file containing code to run by setting onPrepare to
   // the filename string.
   onPrepare: function() {
+    browser.isMobile = false;
     // At this point, global 'protractor' object will be set up, and jasmine
     // will be available. For example, you can add a Jasmine reporter with:
     //     jasmine.getEnv().addReporter(new jasmine.JUnitXmlReporter(
diff --git a/core/tests/protractor/accessibility.js b/core/tests/protractor/accessibility.js
--- a/core/tests/protractor/accessibility.js
+++ b/core/tests/protractor/accessibility.js
@@ -18,7 +18,6 @@
  */
 
 var general = require('../protractor_utils/general.js');
-var until = protractor.ExpectedConditions;
 var waitFor = require('../protractor_utils/waitFor.js');
 
 var LibraryPage = require('../protractor_utils/LibraryPage.js');
@@ -45,3 +44,13 @@ describe('screenreader and keyboard user accessibility features', function() {
     general.checkForConsoleErrors([]);
   });
 });
+
+describe('Cache Slugs', function() {
+  it('should check that errors get logged for missing resources', function() {
+    browser.get('/console_errors');
+    var expectedErrors = [
+      'http://localhost:9001/build/fail/logo/288x128_logo_white.png'
+    ];
+    general.checkConsoleErrorsExist(expectedErrors);
+  });
+});
diff --git a/core/tests/protractor/learnerDashboard.js b/core/tests/protractor/learnerDashboard.js
deleted file mode 100644
--- a/core/tests/protractor/learnerDashboard.js
+++ /dev/null
@@ -1,455 +0,0 @@
-// Copyright 2018 The Oppia Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview End-to-end tests for the learner dashboard page and
- * subscriptions functionality.
- */
-
-var forms = require('../protractor_utils/forms.js');
-var general = require('../protractor_utils/general.js');
-var users = require('../protractor_utils/users.js');
-var waitFor = require('../protractor_utils/waitFor.js');
-var workflow = require('../protractor_utils/workflow.js');
-
-var AdminPage = require('../protractor_utils/AdminPage.js');
-var CreatorDashboardPage =
-  require('../protractor_utils/CreatorDashboardPage.js');
-var CollectionEditorPage =
-  require('../protractor_utils/CollectionEditorPage.js');
-var ExplorationEditorPage =
-  require('../protractor_utils/ExplorationEditorPage.js');
-var ExplorationPlayerPage =
-  require('../protractor_utils/ExplorationPlayerPage.js');
-var LearnerDashboardPage =
-  require('../protractor_utils/LearnerDashboardPage.js');
-var LibraryPage = require('../protractor_utils/LibraryPage.js');
-var PreferencesPage = require('../protractor_utils/PreferencesPage.js');
-var SubscriptionDashboardPage =
-  require('../protractor_utils/SubscriptionDashboardPage.js');
-
-describe('Learner dashboard functionality', function() {
-  var creatorDashboardPage = null;
-  var explorationEditorPage = null;
-  var explorationEditorMainTab = null;
-  var explorationPlayerPage = null;
-  var libraryPage = null;
-  var learnerDashboardPage = null;
-  var subscriptionDashboardPage = null;
-
-  beforeAll(function() {
-    libraryPage = new LibraryPage.LibraryPage();
-    learnerDashboardPage = new LearnerDashboardPage.LearnerDashboardPage();
-    collectionEditorPage = new CollectionEditorPage.CollectionEditorPage();
-    creatorDashboardPage = new CreatorDashboardPage.CreatorDashboardPage();
-    explorationEditorPage = new ExplorationEditorPage.ExplorationEditorPage();
-    explorationEditorMainTab = explorationEditorPage.getMainTab();
-    explorationEditorSettingsTab = explorationEditorPage.getSettingsTab();
-    explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
-    subscriptionDashboardPage =
-      new SubscriptionDashboardPage.SubscriptionDashboardPage();
-  });
-
-  var createAboutOppiaExploration = function() {
-    creatorDashboardPage.get();
-    creatorDashboardPage.clickCreateActivityButton();
-    waitFor.pageToFullyLoad();
-    explorationEditorMainTab.exitTutorial();
-    explorationEditorMainTab.setStateName('First');
-    explorationEditorMainTab.setContent(forms.toRichText(
-      'Hi there, I’m Oppia! I’m an online personal tutor for everybody!'));
-    explorationEditorMainTab.setInteraction('Continue');
-    var responseEditor = explorationEditorMainTab.getResponseEditor('default');
-    responseEditor.setDestination('Second', true, null);
-    explorationEditorMainTab.moveToState('Second');
-    explorationEditorMainTab.setContent(forms.toRichText(
-      'So what can I tell you?'));
-    explorationEditorMainTab.setInteraction('MultipleChoiceInput', [
-      forms.toRichText('How do your explorations work?'),
-      forms.toRichText('What can you tell me about this website?'),
-      forms.toRichText('How can I contribute to Oppia?'),
-      forms.toRichText('Those were all the questions I had!')
-    ]);
-    explorationEditorMainTab.addResponse(
-      'MultipleChoiceInput', null, 'End Card', true, 'Equals',
-      'Those were all the questions I had!');
-    responseEditor = explorationEditorMainTab.getResponseEditor('default');
-    responseEditor.setFeedback(forms.toRichText('I do not know!'));
-    explorationEditorMainTab.moveToState('End Card');
-    explorationEditorMainTab.setContent(
-      forms.toRichText('Congratulations, you have finished!'));
-    explorationEditorMainTab.setInteraction('EndExploration');
-    explorationEditorPage.navigateToSettingsTab();
-    explorationEditorSettingsTab.setTitle('About Oppia');
-    explorationEditorSettingsTab.setCategory('Algorithm');
-    explorationEditorSettingsTab.setObjective('Learn more about Oppia');
-    explorationEditorSettingsTab.setLanguage('English');
-    explorationEditorPage.saveChanges();
-    workflow.publishExploration();
-  };
-
-  it('displays incomplete and completed explorations', function() {
-    users.createAndLoginUser('originalCreator@learnerDashboard.com',
-      'originalCreator');
-    // Create exploration 'About Oppia'
-    createAboutOppiaExploration();
-    // Create a second exploration named 'Dummy Exploration'.
-    workflow.createAndPublishExploration(
-      'Dummy Exploration',
-      'Astronomy',
-      'To expand the horizon of the minds!',
-      'English'
-    );
-    users.logout();
-
-    users.createAndLoginUser('learner@learnerDashboard.com',
-      'learnerlearnerDashboard');
-    // Play exploration 'Dummy Exploration'
-    libraryPage.get();
-    libraryPage.findExploration('Dummy Exploration');
-    libraryPage.playExploration('Dummy Exploration');
-    explorationPlayerPage.expectExplorationNameToBe('Dummy Exploration');
-
-    // Play exploration 'About Oppia'.
-    libraryPage.get();
-    libraryPage.findExploration('About Oppia');
-    libraryPage.playExploration('About Oppia');
-    explorationPlayerPage.expectExplorationNameToBe('About Oppia');
-    explorationPlayerPage.submitAnswer('Continue', null);
-    explorationPlayerPage.expectExplorationToNotBeOver();
-
-    // User clicks on Oppia logo to leave exploration.
-    var oppiaLogo = element(by.css('.protractor-test-oppia-main-logo'));
-    oppiaLogo.click();
-    general.acceptAlert();
-    // Wait for /learner_dashboard to load.
-    waitFor.pageToFullyLoad();
-
-    learnerDashboardPage.navigateToInCompleteSection();
-    learnerDashboardPage.navigateToIncompleteExplorationsSection();
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
-      'About Oppia');
-    // Learner Dashboard should display 'Dummy Exploration' as complete.
-    learnerDashboardPage.navigateToCompletedSection();
-    learnerDashboardPage.navigateToCompletedExplorationsSection();
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
-      'Dummy Exploration');
-
-    // Now play exploration 'About Oppia' completely.
-    libraryPage.get();
-    libraryPage.findExploration('About Oppia');
-    libraryPage.playExploration('About Oppia');
-    waitFor.pageToFullyLoad();
-    explorationPlayerPage.expectExplorationNameToBe('About Oppia');
-    explorationPlayerPage.submitAnswer('Continue', null);
-    explorationPlayerPage.submitAnswer(
-      'MultipleChoiceInput', 'Those were all the questions I had!');
-
-    // Both should be added to the completed section.
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToCompletedSection();
-    learnerDashboardPage.navigateToCompletedExplorationsSection();
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
-      'About Oppia');
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
-      'Dummy Exploration');
-    users.logout();
-
-    // Login as Admin and delete exploration 'About Oppia'.
-    users.createAndLoginAdminUser('inspector@learnerDashboard.com',
-      'inspector');
-    libraryPage.get();
-    libraryPage.findExploration('About Oppia');
-    libraryPage.playExploration('About Oppia');
-    // Wait for player page to completely load
-    waitFor.pageToFullyLoad();
-    general.getExplorationIdFromPlayer().then(function(explorationId) {
-      general.openEditor(explorationId);
-    });
-    explorationEditorPage.navigateToSettingsTab();
-    explorationEditorSettingsTab.deleteExploration();
-    users.logout();
-
-    // Verify exploration 'About Oppia' is deleted from learner dashboard.
-    users.login('learner@learnerDashboard.com');
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToCompletedSection();
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
-      'Dummy Exploration');
-    learnerDashboardPage.expectTitleOfExplorationSummaryTileToBeHidden(
-      'About Oppia');
-    users.logout();
-  });
-
-  it('displays incomplete and completed collections', function() {
-    users.createAndLoginUser('explorationCreator@learnerDashboard.com',
-      'explorationCreator');
-    // Create first exploration named 'Head of Collection'
-    createAboutOppiaExploration();
-    // Create a second exploration named 'Collection Exploration'.
-    workflow.createAndPublishExploration(
-      'Collection Exploration',
-      'Architect',
-      'To be a part of a collection!',
-      'English'
-    );
-    users.logout();
-
-    // Login to admin account
-    users.createAndLoginAdminUser(
-      'testCollectionAdm@learnerDashboard.com',
-      'testcollectionAdmlearnerDashboard');
-    // Create new 'Test Collection' containing exploration 'Head of Collection'.
-    workflow.createCollectionAsAdmin();
-    collectionEditorPage.searchForAndAddExistingExploration(
-      'About Oppia');
-    collectionEditorPage.saveDraft();
-    collectionEditorPage.closeSaveModal();
-    collectionEditorPage.publishCollection();
-    collectionEditorPage.setTitle('Test Collection');
-    collectionEditorPage.setObjective('This is a test collection.');
-    collectionEditorPage.setCategory('Algebra');
-    collectionEditorPage.saveChanges();
-    users.logout();
-
-    users.createAndLoginUser(
-      'learner4@learnerDashboard.com', 'learner4learnerDashboard');
-    // Go to 'Test Collection' and play it.
-    libraryPage.get();
-    libraryPage.findCollection('Test Collection');
-    libraryPage.playCollection('Test Collection');
-    var firstExploration = element.all(
-      by.css('.protractor-test-collection-exploration')).first();
-    // Click first exploration in collection.
-    waitFor.elementToBeClickable(
-      firstExploration, 'Could not click first exploration in collection');
-    firstExploration.click();
-    explorationPlayerPage.submitAnswer('Continue', null);
-    explorationPlayerPage.expectExplorationToNotBeOver();
-
-    // User clicks on Oppia logo to leave exploration.
-    var oppiaLogo = element(by.css('.protractor-test-oppia-main-logo'));
-    oppiaLogo.click();
-    general.acceptAlert();
-    // Wait for /learner_dashboard to load.
-    waitFor.pageToFullyLoad();
-
-    // Learner Dashboard should display 'Test Collection' as incomplete.
-    learnerDashboardPage.navigateToInCompleteSection();
-    learnerDashboardPage.navigateToIncompleteCollectionsSection();
-    learnerDashboardPage.expectTitleOfCollectionSummaryTileToMatch(
-      'Test Collection');
-
-    libraryPage.get();
-    libraryPage.findCollection('Test Collection');
-    libraryPage.playCollection('Test Collection');
-    var firstExploration = element.all(
-      by.css('.protractor-test-collection-exploration')).first();
-    // Click first exploration in collection.
-    waitFor.elementToBeClickable(
-      firstExploration, 'Could not click first exploration in collection');
-    firstExploration.click();
-
-    // Complete the exploration and rate it 5 stars!
-    explorationPlayerPage.expectExplorationNameToBe('About Oppia');
-    explorationPlayerPage.submitAnswer('Continue', null);
-    explorationPlayerPage.submitAnswer(
-      'MultipleChoiceInput', 'Those were all the questions I had!');
-    explorationPlayerPage.rateExploration(5);
-
-    // The collection should be found in the 'Completed' section.
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToCompletedSection();
-    learnerDashboardPage.navigateToCompletedCollectionsSection();
-    learnerDashboardPage.expectTitleOfCollectionSummaryTileToMatch(
-      'Test Collection');
-    users.logout();
-
-    // Add exploration 'Collection Exploration' to 'Test Collection'
-    // and publish it
-    users.login('testCollectionAdm@learnerDashboard.com');
-    creatorDashboardPage.get();
-    creatorDashboardPage.navigateToCollectionEditor();
-    collectionEditorPage.searchForAndAddExistingExploration(
-      'Collection Exploration');
-    collectionEditorPage.saveDraft();
-    collectionEditorPage.setCommitMessage('Add Collection Exploration');
-    collectionEditorPage.closeSaveModal();
-    users.logout();
-
-    // Verify 'Test Collection' is now in the incomplete section.
-    users.login('learner4@learnerDashboard.com');
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToIncompleteCollectionsSection();
-    learnerDashboardPage.expectTitleOfCollectionSummaryTileToMatch(
-      'Test Collection');
-    users.logout();
-  });
-
-  it('displays learners subscriptions', function() {
-    users.createUser('learner1@learnerDashboard.com',
-      'learner1learnerDashboard');
-    var creator1Id = 'creatorName';
-    users.createUser(creator1Id + '@learnerDashboard.com', creator1Id);
-    var creator2Id = 'collectionAdm';
-    users.createUser(creator2Id + '@learnerDashboard.com',
-      creator2Id);
-    users.login(creator1Id + '@learnerDashboard.com');
-    workflow.createAndPublishExploration(
-      'Activations',
-      'Chemistry',
-      'Learn about different types of chemistry activations.',
-      'English'
-    );
-    users.logout();
-
-    users.login('learner1@learnerDashboard.com');
-    // Subscribe to both the creators.
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(creator1Id);
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(creator2Id);
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-
-    // Completing exploration 'Activations' to activate /learner_dashboard
-    libraryPage.get();
-    libraryPage.findExploration('Activations');
-    libraryPage.playExploration('Activations');
-    explorationPlayerPage.expectExplorationNameToBe('Activations');
-    explorationPlayerPage.rateExploration(4);
-
-    // Both creators should be present in the subscriptions section of the
-    // dashboard.
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToSubscriptionsSection();
-    // The last user (collectionAdm) that learner subsribes to is placed first
-    // in the list.
-    learnerDashboardPage.expectSubscriptionFirstNameToMatch('collect...');
-    // The first user (creatorName) that learner subscribes to is placed
-    // last in the list.
-    learnerDashboardPage.expectSubscriptionLastNameToMatch('creator...');
-    users.logout();
-  });
-
-  it('displays learner feedback threads', function() {
-    users.createUser('learner2@learnerDashboard.com',
-      'learner2learnerDashboard');
-    users.createUser(
-      'feedbackAdm@learnerDashboard.com', 'feedbackAdmlearnerDashboard');
-    users.login('feedbackAdm@learnerDashboard.com');
-    workflow.createAndPublishExploration(
-      'BUS101',
-      'Business',
-      'Learn about different business regulations around the world.',
-      'English'
-    );
-    users.logout();
-
-    users.login('learner2@learnerDashboard.com');
-    var feedback = 'A good exploration. Would love to see a few more questions';
-    libraryPage.get();
-    libraryPage.findExploration('BUS101');
-    libraryPage.playExploration('BUS101');
-    explorationPlayerPage.submitFeedback(feedback);
-
-    // Verify feedback thread is created.
-    learnerDashboardPage.get();
-    learnerDashboardPage.navigateToFeedbackSection();
-    learnerDashboardPage.expectFeedbackExplorationTitleToMatch('BUS101');
-    learnerDashboardPage.navigateToFeedbackThread();
-    learnerDashboardPage.expectFeedbackMessageToMatch(feedback);
-    users.logout();
-  });
-
-  afterEach(function() {
-    general.checkForConsoleErrors([]);
-  });
-});
-
-
-describe('Subscriptions functionality', function() {
-  var creatorDashboardPage = null;
-  var preferencesPage = null;
-  var subscriptionDashboardPage = null;
-
-  beforeEach(function() {
-    creatorDashboardPage = new CreatorDashboardPage.CreatorDashboardPage();
-    preferencesPage = new PreferencesPage.PreferencesPage();
-    subscriptionDashboardPage = (
-      new SubscriptionDashboardPage.SubscriptionDashboardPage());
-  });
-
-  it('handle subscriptions to creators correctly', function() {
-    // Create two creators.
-    users.createUser('creator1Id@subscriptions.com', 'creator1Idsubscriptions');
-    users.createUser('creator2Id@subscriptions.com', 'creator2Idsubscriptions');
-
-    // Create a learner who subscribes to both the creators.
-    users.createUser('learner1@subscriptions.com', 'learner1subscriptions');
-    users.login('learner1@subscriptions.com');
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(
-      'creator1Idsubscriptions');
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(
-      'creator2Idsubscriptions');
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    preferencesPage.get();
-    preferencesPage.expectDisplayedFirstSubscriptionToBe('creator...');
-    preferencesPage.expectDisplayedLastSubscriptionToBe('creator...');
-    users.logout();
-
-    // Create a learner who subscribes to creator1Id and unsubscribes from the
-    // creator2Id.
-    users.createUser('learner2@subscriptions.com', 'learner2subscriptions');
-    users.login('learner2@subscriptions.com');
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(
-      'creator1Idsubscriptions');
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    subscriptionDashboardPage.navigateToUserSubscriptionPage(
-      'creator2Idsubscriptions');
-
-    // Subscribe and then unsubscribe from the same user.
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    subscriptionDashboardPage.navigateToSubscriptionButton();
-    preferencesPage.get();
-    preferencesPage.expectSubscriptionCountToEqual(1);
-    preferencesPage.expectDisplayedFirstSubscriptionToBe('creator...');
-    users.logout();
-
-    // Verify there are 2 subscribers.
-    users.login('creator1Id@subscriptions.com');
-    // Need to go exploration editor to activate /creator_dashboard
-    creatorDashboardPage.get();
-    creatorDashboardPage.clickCreateActivityButton();
-    creatorDashboardPage.get();
-    creatorDashboardPage.navigateToSubscriptionDashboard();
-    subscriptionDashboardPage.expectSubscriptionFirstNameToMatch('learner...');
-    subscriptionDashboardPage.expectSubscriptionLastNameToMatch('learner...');
-    users.logout();
-
-    // Verify there are 1 subscriber.
-    users.login('creator2Id@subscriptions.com');
-    // Need to go exploration editor to activate /creator_dashboard
-    creatorDashboardPage.get();
-    creatorDashboardPage.clickCreateActivityButton();
-    creatorDashboardPage.get();
-    creatorDashboardPage.navigateToSubscriptionDashboard();
-    subscriptionDashboardPage.expectSubscriptionCountToEqual(1);
-    subscriptionDashboardPage.expectSubscriptionLastNameToMatch('learner...');
-    users.logout();
-  });
-
-  afterEach(function() {
-    general.checkForConsoleErrors([]);
-  });
-});
diff --git a/core/tests/protractor/learnerFlow.js b/core/tests/protractor/learnerFlow.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor/learnerFlow.js
@@ -0,0 +1,439 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview End-to-end tests for the learner flow.
+ */
+
+var forms = require('../protractor_utils/forms.js');
+var general = require('../protractor_utils/general.js');
+var users = require('../protractor_utils/users.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+var workflow = require('../protractor_utils/workflow.js');
+
+var AdminPage = require('../protractor_utils/AdminPage.js');
+var CreatorDashboardPage =
+  require('../protractor_utils/CreatorDashboardPage.js');
+var CollectionEditorPage =
+  require('../protractor_utils/CollectionEditorPage.js');
+var ExplorationEditorPage =
+  require('../protractor_utils/ExplorationEditorPage.js');
+var ExplorationPlayerPage =
+  require('../protractor_utils/ExplorationPlayerPage.js');
+var LearnerDashboardPage =
+  require('../protractor_utils/LearnerDashboardPage.js');
+var LibraryPage = require('../protractor_utils/LibraryPage.js');
+
+describe('Learner dashboard functionality', function() {
+  var adminPage = null;
+  var creatorDashboardPage = null;
+  var explorationEditorPage = null;
+  var explorationEditorMainTab = null;
+  var explorationPlayerPage = null;
+  var libraryPage = null;
+  var learnerDashboardPage = null;
+  var oppiaLogo = element(by.css('.protractor-test-oppia-main-logo'));
+  var continueButton = element(by.css('.protractor-test-continue-button'));
+  var clickContinueButton = function() {
+    waitFor.elementToBeClickable(
+      continueButton, 'Could not click continue button');
+    continueButton.click();
+    waitFor.pageToFullyLoad();
+  };
+
+  var createDummyExplorationOnDesktop = function() {
+    creatorDashboardPage.get();
+    creatorDashboardPage.clickCreateActivityButton();
+    waitFor.pageToFullyLoad();
+    explorationEditorMainTab.exitTutorial();
+    explorationEditorMainTab.setStateName('First');
+    explorationEditorMainTab.setContent(forms.toRichText(
+      'Hi there, I’m Oppia! I’m an online personal tutor for everybody!'));
+    explorationEditorMainTab.setInteraction('Continue');
+    var responseEditor = explorationEditorMainTab.getResponseEditor('default');
+    responseEditor.setDestination('Second', true, null);
+    explorationEditorMainTab.moveToState('Second');
+    explorationEditorMainTab.setContent(forms.toRichText(
+      'So what can I tell you?'));
+    explorationEditorMainTab.setInteraction('MultipleChoiceInput', [
+      forms.toRichText('How do your explorations work?'),
+      forms.toRichText('What can you tell me about this website?'),
+      forms.toRichText('How can I contribute to Oppia?'),
+      forms.toRichText('Those were all the questions I had!')
+    ]);
+    explorationEditorMainTab.addResponse(
+      'MultipleChoiceInput', null, 'End Card', true, 'Equals',
+      'Those were all the questions I had!');
+    responseEditor = explorationEditorMainTab.getResponseEditor('default');
+    responseEditor.setFeedback(forms.toRichText('I do not know!'));
+    explorationEditorMainTab.moveToState('End Card');
+    explorationEditorMainTab.setContent(
+      forms.toRichText('Congratulations, you have finished!'));
+    explorationEditorMainTab.setInteraction('EndExploration');
+    explorationEditorPage.navigateToSettingsTab();
+    explorationEditorSettingsTab.setTitle('Dummy Exploration');
+    explorationEditorSettingsTab.setCategory('Algorithm');
+    explorationEditorSettingsTab.setObjective('Learn more about Oppia');
+    explorationEditorSettingsTab.setLanguage('English');
+    explorationEditorPage.saveChanges();
+    workflow.publishExploration();
+  };
+
+  beforeAll(function() {
+    adminPage = new AdminPage.AdminPage();
+    libraryPage = new LibraryPage.LibraryPage();
+    learnerDashboardPage = new LearnerDashboardPage.LearnerDashboardPage();
+    // The editor and player page objects are only required for desktop testing.
+    if (!browser.isMobile) {
+      collectionEditorPage = new CollectionEditorPage.CollectionEditorPage();
+      creatorDashboardPage = new CreatorDashboardPage.CreatorDashboardPage();
+      explorationEditorPage = new ExplorationEditorPage.ExplorationEditorPage();
+      explorationEditorMainTab = explorationEditorPage.getMainTab();
+      explorationEditorSettingsTab = explorationEditorPage.getSettingsTab();
+      explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
+    }
+  });
+
+  it('visits the exploration player and plays the correct exploration',
+    function() {
+      users.createUser('expCreator@learnerDashboard.com',
+        'expCreator');
+      users.login('expCreator@learnerDashboard.com', true);
+      // Create or load an exploration named 'Exploration Player Test'.
+      if (browser.isMobile) {
+        adminPage.reloadExploration('exploration_player_test.yaml');
+      } else {
+        workflow.createAndPublishExploration(
+          'Exploration Player Test',
+          'Astronomy',
+          'To test the exploration player',
+          'English'
+        );
+      }
+      users.logout();
+      var PLAYER_USERNAME = 'expPlayerDesktopAndMobile';
+      users.createAndLoginUser(
+        'expPlayerDesktopAndMobile@learnerFlow.com', PLAYER_USERNAME);
+      libraryPage.get();
+      libraryPage.findExploration('Exploration Player Test');
+      libraryPage.playExploration('Exploration Player Test');
+    });
+
+  it('visits the collection player and plays the correct collection',
+    function() {
+      users.createUser('expOfCollectionCreator@learnerDashboard.com',
+        'expOfCollectionCreator');
+      users.login('expOfCollectionCreator@learnerDashboard.com', true);
+      // Create or load a collection named
+      // 'Introduction to Collections in Oppia'.
+      if (browser.isMobile) {
+        adminPage.reloadCollection(0);
+      } else {
+        workflow.createAndPublishExploration(
+          'Demo Exploration',
+          'Algebra',
+          'To test collection player',
+          'English'
+        );
+        // Update the role of the user to admin since only admin users
+        // can create a collection.
+        adminPage.get();
+        adminPage.updateRole('expOfCollectionCreator', 'admin');
+        workflow.createCollectionAsAdmin();
+        collectionEditorPage.searchForAndAddExistingExploration(
+          'Demo Exploration');
+        collectionEditorPage.saveDraft();
+        collectionEditorPage.closeSaveModal();
+        collectionEditorPage.publishCollection();
+        collectionEditorPage.setTitle('Introduction to Collections in Oppia');
+        collectionEditorPage.setObjective(
+          'This is a collection to test player.');
+        collectionEditorPage.setCategory('Algebra');
+        collectionEditorPage.saveChanges();
+      }
+      users.logout();
+      var PLAYER_USERNAME = 'collectionPlayerDesktopAndMobile';
+      users.createAndLoginUser(
+        'collectionPlayerDesktopAndMobile@learnerFlow.com', PLAYER_USERNAME);
+      libraryPage.get();
+      libraryPage.findCollection('Introduction to Collections in Oppia');
+      libraryPage.playCollection('Introduction to Collections in Oppia');
+    });
+
+  it('displays incomplete and completed explorations', function() {
+    users.createUser('originalCreator@learnerDashboard.com',
+      'originalCreator');
+    users.login('originalCreator@learnerDashboard.com', true);
+    // Create or load explorations.
+    if (browser.isMobile) {
+      adminPage.reloadExploration('learner_flow_test.yaml');
+      adminPage.reloadExploration('protractor_mobile_test_exploration.yaml');
+    } else {
+      // Create exploration 'Dummy Exploration'
+      createDummyExplorationOnDesktop();
+      // Create a second exploration named 'Test Exploration'.
+      workflow.createAndPublishExploration(
+        'Test Exploration',
+        'Astronomy',
+        'To expand the horizon of the minds!',
+        'English'
+      );
+    }
+    users.logout();
+    users.createAndLoginUser('learner@learnerDashboard.com',
+      'learnerlearnerDashboard');
+    // Go to 'Dummy Exploration'.
+    libraryPage.get();
+    libraryPage.findExploration('Dummy Exploration');
+    libraryPage.playExploration('Dummy Exploration');
+    waitFor.pageToFullyLoad();
+    // Leave this exploration incomplete.
+    if (browser.isMobile) {
+      clickContinueButton();
+    } else {
+      // The exploration header is only visible in desktop browsers.
+      explorationPlayerPage.expectExplorationNameToBe('Dummy Exploration');
+      explorationPlayerPage.submitAnswer('Continue', null);
+      explorationPlayerPage.expectExplorationToNotBeOver();
+    }
+    // User clicks on Oppia logo to leave exploration.
+    oppiaLogo.click();
+    general.acceptAlert();
+    // Wait for /learner_dashboard to load.
+    waitFor.pageToFullyLoad();
+
+    // Go to 'Test Exploration'.
+    libraryPage.get();
+    libraryPage.findExploration('Test Exploration');
+    libraryPage.playExploration('Test Exploration');
+    waitFor.pageToFullyLoad();
+    oppiaLogo.click();
+    waitFor.pageToFullyLoad();
+    // Learner Dashboard should display 'Dummy Exploration'
+    // as incomplete.
+    learnerDashboardPage.checkIncompleteExplorationSection('Dummy Exploration');
+    // Learner Dashboard should display 'Test Exploration'
+    // exploration as complete.
+    learnerDashboardPage.checkCompleteExplorationSection('Test Exploration');
+
+    libraryPage.get();
+    libraryPage.findExploration('Dummy Exploration');
+    libraryPage.playExploration('Dummy Exploration');
+    waitFor.pageToFullyLoad();
+    // Now complete the 'Dummmy Exploration'.
+    if (browser.isMobile) {
+      clickContinueButton();
+      // Navigate to the second page.
+      clickContinueButton();
+    } else {
+      explorationPlayerPage.expectExplorationNameToBe('Dummy Exploration');
+      explorationPlayerPage.submitAnswer('Continue', null);
+      explorationPlayerPage.submitAnswer(
+        'MultipleChoiceInput', 'Those were all the questions I had!');
+    }
+    // Both should be added to the completed section.
+    learnerDashboardPage.get();
+    learnerDashboardPage.checkCompleteExplorationSection('Dummy Exploration');
+    learnerDashboardPage.checkCompleteExplorationSection('Test Exploration');
+    users.logout();
+
+    // For desktop, go to the exploration editor page and
+    // delete 'Dummy Exploration'.
+    if (!browser.isMobile) {
+      // Login as Admin and delete exploration 'Dummy Exploration'.
+      users.createAndLoginAdminUser('inspector@learnerDashboard.com',
+        'inspector');
+      libraryPage.get();
+      libraryPage.findExploration('Dummy Exploration');
+      libraryPage.playExploration('Dummy Exploration');
+      // Wait for player page to completely load
+      waitFor.pageToFullyLoad();
+      general.getExplorationIdFromPlayer().then(function(explorationId) {
+        general.openEditor(explorationId);
+      });
+      explorationEditorPage.navigateToSettingsTab();
+      explorationEditorSettingsTab.deleteExploration();
+      users.logout();
+
+      // Verify exploration 'Dummy Exploration' is deleted
+      // from learner dashboard.
+      users.login('learner@learnerDashboard.com');
+      learnerDashboardPage.get();
+      learnerDashboardPage.navigateToCompletedSection();
+      learnerDashboardPage.expectTitleOfExplorationSummaryTileToMatch(
+        'Test Exploration');
+      learnerDashboardPage.expectTitleOfExplorationSummaryTileToBeHidden(
+        'Dummy Exploration');
+    }
+  });
+
+  it('displays incomplete and completed collections', function() {
+    users.createUser('explorationCreator@learnerDashboard.com',
+      'explorationCreator');
+    users.login('explorationCreator@learnerDashboard.com', true);
+    // Create or load a collection.
+    if (browser.isMobile) {
+      adminPage.reloadCollection(1);
+    } else {
+      // Create first exploration named 'Dummy Exploration'.
+      createDummyExplorationOnDesktop();
+      // Create a second exploration named 'Collection Exploration'.
+      workflow.createAndPublishExploration(
+        'Collection Exploration',
+        'Architect',
+        'To be a part of a collection!',
+        'English'
+      );
+      // Update the role of the user to admin since only admin users
+      // can create a collection.
+      adminPage.get();
+      adminPage.updateRole('explorationCreator', 'admin');
+      // Create new 'Test Collection' containing
+      // exploration 'Dummy Exploration'.
+      workflow.createCollectionAsAdmin();
+      collectionEditorPage.searchForAndAddExistingExploration(
+        'Dummy Exploration');
+      collectionEditorPage.saveDraft();
+      collectionEditorPage.closeSaveModal();
+      collectionEditorPage.publishCollection();
+      collectionEditorPage.setTitle('Test Collection');
+      collectionEditorPage.setObjective('This is a test collection.');
+      collectionEditorPage.setCategory('Algebra');
+      collectionEditorPage.saveChanges();
+    }
+    users.logout();
+    users.createAndLoginUser(
+      'learner4@learnerDashboard.com', 'learner4learnerDashboard');
+
+    // Go to 'Test Collection' and play it.
+    libraryPage.get();
+    libraryPage.findCollection('Test Collection');
+    libraryPage.playCollection('Test Collection');
+    waitFor.pageToFullyLoad();
+    // The collection player has two sets of SVGs -- one which is
+    // rendered for desktop and the other which is rendered for mobile.
+    var firstExploration = browser.isMobile ? element.all(
+      by.css('.protractor-mobile-test-collection-exploration')).first() :
+      element.all(
+        by.css('.protractor-test-collection-exploration')).first();
+    // Click first exploration in collection.
+    waitFor.elementToBeClickable(
+      firstExploration, 'Could not click first exploration in collection');
+    firstExploration.click();
+    waitFor.pageToFullyLoad();
+    // Leave this collection incomplete.
+    if (browser.isMobile) {
+      // In mobile, 'Play Exploration' button also needs to be clicked
+      // to begin an exploration which is a part of a collection.
+      var playExploration = element(
+        by.css('.protractor-test-play-exploration-button'));
+      waitFor.elementToBeClickable(
+        playExploration, 'Could not click play exploration button');
+      playExploration.click();
+      waitFor.pageToFullyLoad();
+      clickContinueButton();
+    } else {
+      explorationPlayerPage.submitAnswer('Continue', null);
+      explorationPlayerPage.expectExplorationToNotBeOver();
+    }
+    // User clicks on Oppia logo to leave collection.
+    oppiaLogo.click();
+    general.acceptAlert();
+    // Wait for /learner_dashboard to load.
+    waitFor.pageToFullyLoad();
+
+    // Learner Dashboard should display
+    // 'Test Collection' as incomplete.
+    learnerDashboardPage.checkIncompleteCollectionSection('Test Collection');
+    // Now find and play 'Test Collection' completely.
+    libraryPage.get();
+    libraryPage.findCollection('Test Collection');
+    libraryPage.playCollection('Test Collection');
+    waitFor.pageToFullyLoad();
+    // The collection player has two sets of SVGs -- one which is
+    // rendered for desktop and the other which is rendered for mobile.
+    var firstExploration = browser.isMobile ? element.all(
+      by.css('.protractor-mobile-test-collection-exploration')).first() :
+      element.all(
+        by.css('.protractor-test-collection-exploration')).first();
+    // Click first exploration in collection.
+    waitFor.elementToBeClickable(
+      firstExploration, 'Could not click first exploration in collection');
+    firstExploration.click();
+    waitFor.pageToFullyLoad();
+    if (browser.isMobile) {
+      var playExploration = element(
+        by.css('.protractor-test-play-exploration-button'));
+      waitFor.elementToBeClickable(
+        playExploration, 'Could not click play exploration button');
+      playExploration.click();
+      waitFor.pageToFullyLoad();
+      clickContinueButton();
+      waitFor.pageToFullyLoad();
+      clickContinueButton();
+      waitFor.pageToFullyLoad();
+    } else {
+      explorationPlayerPage.expectExplorationNameToBe('Dummy Exploration');
+      explorationPlayerPage.submitAnswer('Continue', null);
+      explorationPlayerPage.submitAnswer(
+        'MultipleChoiceInput', 'Those were all the questions I had!');
+    }
+    // Learner Dashboard should display
+    // 'Test Collection' as complete.
+    learnerDashboardPage.get();
+    learnerDashboardPage.checkCompleteCollectionSection('Test Collection');
+    users.logout();
+
+    // This part of the test is desktop-only for the following reasons:
+    // 1. A user can only add an existing exploration to a collection it has
+    //    created. For desktop tests, a user creates a collection and later on,
+    //    it adds an existing exploration to the same collection. In case of
+    //    mobile tests, a predefined collection is loaded and is not created by
+    //    the user. Therefore, it cannot add an existing exploration to the
+    //    predefined collection.
+    // 2. This part involves the collection editor page, which has certain
+    //    components that are not mobile-friendly.
+    // 3. Creating and later on, editing a collection involves an admin user and
+    //    not a super admin. For mobile tests, we sign-in as a super admin.
+    // 4. The feature of adding an existing exploration to a collection using
+    //    the collection editor page is in beta presently.
+    if (!browser.isMobile) {
+      // Add exploration 'Collection Exploration' to 'Test Collection'
+      // and publish it.
+      users.login('explorationCreator@learnerDashboard.com');
+      creatorDashboardPage.get();
+      waitFor.pageToFullyLoad();
+      // Click on 'Collections' tab.
+      var collectionsTab = element(by.css('.protractor-test-collections-tab'));
+      collectionsTab.click();
+      creatorDashboardPage.navigateToCollectionEditor();
+      collectionEditorPage.searchForAndAddExistingExploration(
+        'Collection Exploration');
+      collectionEditorPage.saveDraft();
+      collectionEditorPage.setCommitMessage('Add Collection Exploration');
+      collectionEditorPage.closeSaveModal();
+      users.logout();
+
+      // Verify 'Test Collection' is now in the incomplete section.
+      users.login('learner4@learnerDashboard.com');
+      learnerDashboardPage.get();
+      learnerDashboardPage.checkIncompleteCollectionSection('Test Collection');
+    }
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+    users.logout();
+  });
+});
diff --git a/core/tests/protractor/libraryFlow.js b/core/tests/protractor/libraryFlow.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor/libraryFlow.js
@@ -0,0 +1,181 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview End-to-end tests for library flow.
+ */
+
+var AdminPage = require('../protractor_utils/AdminPage.js');
+var CollectionEditorPage =
+  require('../protractor_utils/CollectionEditorPage.js');
+var ExplorationPlayerPage = require(
+  '../protractor_utils/ExplorationPlayerPage.js');
+var LibraryPage = require('../protractor_utils/LibraryPage.js');
+var general = require('../protractor_utils/general.js');
+var users = require('../protractor_utils/users.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+var workflow = require('../protractor_utils/workflow.js');
+
+describe('Library pages tour', function() {
+  var EXPLORATION_TITLE = 'Test Exploration';
+  var EXPLORATION_OBJECTIVE = 'To learn testing';
+  var EXPLORATION_CATEGORY = 'Algorithms';
+  var EXPLORATION_LANGUAGE = 'English';
+  var EXPLORATION_RATING = 4;
+  var SEARCH_TERM = 'python';
+  var libraryPage = null;
+  var explorationPlayerPage = null;
+
+  beforeEach(function() {
+    libraryPage = new LibraryPage.LibraryPage();
+    explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
+  });
+
+  var visitRecentlyPublishedPage = function() {
+    browser.get('library/recently_published');
+    waitFor.pageToFullyLoad();
+  };
+
+  var rateExploration = function() {
+    var adminPage = new AdminPage.AdminPage();
+    users.createUser('random@gmail.com', 'random');
+    users.login('random@gmail.com', true);
+    // We need an exploration to rate here.
+    if (browser.isMobile) {
+      adminPage.reloadExploration('protractor_mobile_test_exploration.yaml');
+    } else {
+      workflow.createAndPublishExploration(
+        EXPLORATION_TITLE,
+        EXPLORATION_CATEGORY,
+        EXPLORATION_OBJECTIVE,
+        EXPLORATION_LANGUAGE
+      );
+    }
+    libraryPage.get();
+    libraryPage.findExploration(EXPLORATION_TITLE);
+    libraryPage.playExploration(EXPLORATION_TITLE);
+    explorationPlayerPage.rateExploration(EXPLORATION_RATING);
+    users.logout();
+  };
+
+  it('visits the search page', function() {
+    libraryPage.get();
+    libraryPage.findExploration(SEARCH_TERM);
+    expect(browser.getCurrentUrl()).toContain('search/find?q=python');
+  });
+
+  it('visits the library index page', function() {
+    libraryPage.get();
+  });
+
+  it('visits the top rated page', function() {
+    // To visit the top rated page, at least one
+    // exploration has to be rated by the user.
+    rateExploration();
+    libraryPage.get();
+    element(by.css('.protractor-test-library-top-rated')).click();
+    waitFor.pageToFullyLoad();
+    expect(browser.getCurrentUrl()).toContain('library/top_rated');
+  });
+
+  it('visits the recent explorations page', function() {
+    visitRecentlyPublishedPage();
+    expect(browser.getCurrentUrl()).toContain('library/recently_published');
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+  });
+});
+
+describe('Rating', function() {
+  var EXPLORATION_RATINGTEST = 'RatingTest';
+  var CATEGORY_BUSINESS = 'Business';
+  var LANGUAGE_ENGLISH = 'English';
+  var MINIMUM_ACCEPTABLE_NUMBER_OF_RATINGS = 1;
+  var adminPage = null;
+  var libraryPage = null;
+  var explorationPlayerPage = null;
+
+  beforeEach(function() {
+    adminPage = new AdminPage.AdminPage();
+    libraryPage = new LibraryPage.LibraryPage();
+    explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
+  });
+
+  var addRating = function(userEmail, userName, explorationName, ratingValue) {
+    users.createUser(userEmail, userName);
+    users.login(userEmail);
+    libraryPage.get();
+    libraryPage.findExploration(EXPLORATION_RATINGTEST);
+    libraryPage.playExploration(EXPLORATION_RATINGTEST);
+
+    // The exploration header is only visible in desktop browsers.
+    if (!browser.isMobile) {
+      explorationPlayerPage.expectExplorationNameToBe(explorationName);
+    }
+    explorationPlayerPage.rateExploration(ratingValue);
+    users.logout();
+  };
+
+  it('should display ratings on exploration when minimum ratings have been ' +
+     'submitted', function() {
+    users.createUser('user11@explorationRating.com', 'user11Rating');
+    // Create a test exploration.
+    users.login('user11@explorationRating.com', true);
+
+    // We need a test exploration here.
+    if (browser.isMobile) {
+      // In case of a mobile device, load a demo test exploration
+      // from the admin page.
+      adminPage.reloadExploration('rating_test.yaml');
+    } else {
+      // For a desktop browser, create and publish an exploration.
+      workflow.createAndPublishExploration(
+        EXPLORATION_RATINGTEST, CATEGORY_BUSINESS,
+        'this is an objective', LANGUAGE_ENGLISH);
+    }
+    users.logout();
+
+    // Create test users, play exploration and review them after completion.
+    for (var i = 0; i < MINIMUM_ACCEPTABLE_NUMBER_OF_RATINGS - 1; i++) {
+      var userEmail = 'NoDisplay' + i + '@explorationRating.com';
+      var username = 'NoDisplay' + i;
+      addRating(userEmail, username, EXPLORATION_RATINGTEST, 4);
+    }
+
+    libraryPage.get();
+    libraryPage.findExploration(EXPLORATION_RATINGTEST);
+    libraryPage.expectExplorationRatingToEqual(EXPLORATION_RATINGTEST, 'N/A');
+
+    var userEmail = 'Display@explorationRating.com';
+    var username = 'Display';
+    addRating(userEmail, username, EXPLORATION_RATINGTEST, 4);
+
+    libraryPage.get();
+    libraryPage.findExploration(EXPLORATION_RATINGTEST);
+    libraryPage.expectExplorationRatingToEqual(EXPLORATION_RATINGTEST, '4.0');
+
+    libraryPage.playExploration(EXPLORATION_RATINGTEST);
+    // The information card is only visible in desktop browsers.
+    if (!browser.isMobile) {
+      explorationPlayerPage.expectExplorationRatingOnInformationCardToEqual(
+        '4.0');
+    }
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+  });
+});
diff --git a/core/tests/protractor/profileMenuFlow.js b/core/tests/protractor/profileMenuFlow.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor/profileMenuFlow.js
@@ -0,0 +1,117 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview End-to-end tests to login, check various pages
+ * and then logout.
+ */
+
+var LearnerDashboardPage = require(
+  '../protractor_utils/LearnerDashboardPage.js');
+var general = require('../protractor_utils/general.js');
+var users = require('../protractor_utils/users.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+
+describe('Profile menu flow', function() {
+  var learnerDashboardPage = null;
+
+  beforeAll(function() {
+    learnerDashboardPage = new LearnerDashboardPage.LearnerDashboardPage();
+    var VISITOR_USERNAME = 'desktopAndMobileVisitor';
+    users.createAndLoginUser(
+      'desktopAndMobileVisitor@profileMenuFlow.com', VISITOR_USERNAME);
+  });
+
+  it('should land on the learner dashboard after successful login',
+    function() {
+      expect(browser.getCurrentUrl()).toEqual(
+        'http://localhost:9001/learner_dashboard');
+    });
+
+  describe('profile dropdown menu', function() {
+    beforeEach(function() {
+      users.login('desktopAndMobileVisitor@profileMenuFlow.com');
+      learnerDashboardPage.get();
+      var profileDropdown = element(by.css(
+        '.protractor-test-profile-dropdown'));
+      waitFor.elementToBeClickable(
+        profileDropdown, 'Could not click profile dropdown');
+      profileDropdown.click();
+    });
+
+    it('should visit the profile page from the profile dropdown menu',
+      function() {
+        var profileLink = element(by.css(
+          '.protractor-test-profile-link'));
+        waitFor.elementToBeClickable(
+          profileLink, 'Could not click on the profile link');
+        profileLink.click();
+        waitFor.pageToFullyLoad();
+        expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/profile/desktopAndMobileVisitor');
+      });
+
+    it('should visit the creator dashboard from the profile dropdown menu',
+      function() {
+        var creatorDashboardLink = element(by.css(
+          '.protractor-test-creator-dashboard-link'));
+        waitFor.elementToBeClickable(
+          creatorDashboardLink,
+          'Could not click on the creator dashboard link');
+        creatorDashboardLink.click();
+        waitFor.pageToFullyLoad();
+        expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/creator_dashboard');
+      });
+
+    it('should visit the learner dashboard from the profile dropdown menu',
+      function() {
+        var learnerDashboardLink = element(by.css(
+          '.protractor-test-learner-dashboard-link'));
+        waitFor.elementToBeClickable(
+          learnerDashboardLink,
+          'Could not click on the learner dashboard link');
+        learnerDashboardLink.click();
+        waitFor.pageToFullyLoad();
+        expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/learner_dashboard');
+      });
+
+    it('should visit the notifications page from the profile dropdown menu',
+      function() {
+        var notificationsDashboardLink = element(by.css(
+          '.protractor-test-notifications-link'));
+        waitFor.elementToBeClickable(
+          notificationsDashboardLink,
+          'Could not click on the notifications dashboard link');
+        notificationsDashboardLink.click();
+        waitFor.pageToFullyLoad();
+        expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/notifications_dashboard');
+      });
+
+    it('should visit the preferences page from the profile dropdown menu',
+      function() {
+        var preferencesLink = element(by.css(
+          '.protractor-test-preferences-link'));
+        waitFor.elementToBeClickable(
+          preferencesLink,
+          'Could not click on the preferences link');
+        preferencesLink.click();
+        waitFor.pageToFullyLoad();
+        expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/preferences');
+      });
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+    users.logout();
+  });
+});
diff --git a/core/tests/protractor/subscriptionsFlow.js b/core/tests/protractor/subscriptionsFlow.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor/subscriptionsFlow.js
@@ -0,0 +1,106 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview End-to-end tests for the subscriptions functionality on desktop
+ * and mobile.
+ */
+
+var general = require('../protractor_utils/general.js');
+var users = require('../protractor_utils/users.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+
+var AdminPage = require('../protractor_utils/AdminPage.js');
+var CreatorDashboardPage =
+  require('../protractor_utils/CreatorDashboardPage.js');
+var PreferencesPage = require('../protractor_utils/PreferencesPage.js');
+var SubscriptionDashboardPage =
+  require('../protractor_utils/SubscriptionDashboardPage.js');
+
+describe('Subscriptions functionality', function() {
+  var creatorDashboardPage = null;
+  var preferencesPage = null;
+  var subscriptionDashboardPage = null;
+
+  beforeEach(function() {
+    creatorDashboardPage = new CreatorDashboardPage.CreatorDashboardPage();
+    preferencesPage = new PreferencesPage.PreferencesPage();
+    subscriptionDashboardPage = (
+      new SubscriptionDashboardPage.SubscriptionDashboardPage());
+  });
+
+  it('handle subscriptions to creators correctly', function() {
+    // Create two creators.
+    users.createUser('creator1Id@subscriptions.com', 'creator1Idsubscriptions');
+    users.createUser('creator2Id@subscriptions.com', 'creator2Idsubscriptions');
+
+    // Create a learner who subscribes to both the creators.
+    users.createUser('learner1@subscriptions.com', 'learner1subscriptions');
+    users.login('learner1@subscriptions.com');
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(
+      'creator1Idsubscriptions');
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(
+      'creator2Idsubscriptions');
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    preferencesPage.get();
+    preferencesPage.expectDisplayedFirstSubscriptionToBe('creator...');
+    preferencesPage.expectDisplayedLastSubscriptionToBe('creator...');
+    users.logout();
+
+    // Create a learner who subscribes to creator1Id and unsubscribes from the
+    // creator2Id.
+    users.createUser('learner2@subscriptions.com', 'learner2subscriptions');
+    users.login('learner2@subscriptions.com');
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(
+      'creator1Idsubscriptions');
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(
+      'creator2Idsubscriptions');
+
+    // Subscribe and then unsubscribe from the same user.
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    preferencesPage.get();
+    preferencesPage.expectSubscriptionCountToEqual(1);
+    preferencesPage.expectDisplayedFirstSubscriptionToBe('creator...');
+    users.logout();
+
+    // Verify there are 2 subscribers.
+    users.login('creator1Id@subscriptions.com');
+    // Go to the creator_dashboard.
+    creatorDashboardPage.get();
+    creatorDashboardPage.clickCreateNewExplorationButton();
+    creatorDashboardPage.get();
+    creatorDashboardPage.navigateToSubscriptionDashboard();
+    subscriptionDashboardPage.expectSubscriptionFirstNameToMatch('learner...');
+    subscriptionDashboardPage.expectSubscriptionLastNameToMatch('learner...');
+    users.logout();
+
+    // Verify there are 1 subscriber.
+    users.login('creator2Id@subscriptions.com');
+    // Go to the creator_dashboard.
+    creatorDashboardPage.get();
+    creatorDashboardPage.clickCreateNewExplorationButton();
+    creatorDashboardPage.get();
+    creatorDashboardPage.navigateToSubscriptionDashboard();
+    subscriptionDashboardPage.expectSubscriptionCountToEqual(1);
+    subscriptionDashboardPage.expectSubscriptionLastNameToMatch('learner...');
+    users.logout();
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+  });
+});
diff --git a/core/tests/protractor/collections.js b/core/tests/protractor_desktop/collections.js
similarity index 100%
rename from core/tests/protractor/collections.js
rename to core/tests/protractor_desktop/collections.js
diff --git a/core/tests/protractor/editorAndPlayer.js b/core/tests/protractor_desktop/editorAndPlayer.js
similarity index 92%
rename from core/tests/protractor/editorAndPlayer.js
rename to core/tests/protractor_desktop/editorAndPlayer.js
--- a/core/tests/protractor/editorAndPlayer.js
+++ b/core/tests/protractor_desktop/editorAndPlayer.js
@@ -637,67 +637,3 @@ describe('Full exploration editor', function() {
     general.checkForConsoleErrors([]);
   });
 });
-
-
-describe('Rating', function() {
-  var EXPLORATION_RATINGTEST = 'RatingTest';
-  var CATEGORY_BUSINESS = 'Business';
-  var LANGUAGE_ENGLISH = 'English';
-  var MINIMUM_ACCEPTABLE_NUMBER_OF_RATINGS = 1;
-  var libraryPage = null;
-  var explorationPlayerPage = null;
-
-  beforeEach(function() {
-    libraryPage = new LibraryPage.LibraryPage();
-    explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
-  });
-
-  var addRating = function(userEmail, userName, explorationName, ratingValue) {
-    users.createUser(userEmail, userName);
-    users.login(userEmail);
-    libraryPage.get();
-    libraryPage.findExploration(EXPLORATION_RATINGTEST);
-    libraryPage.playExploration(EXPLORATION_RATINGTEST);
-    explorationPlayerPage.expectExplorationNameToBe(explorationName);
-    explorationPlayerPage.rateExploration(ratingValue);
-    users.logout();
-  };
-
-  it('should display ratings on exploration when minimum ratings have been ' +
-     'submitted', function() {
-    users.createUser('user11@explorationRating.com', 'user11Rating');
-    // Create a test exploration.
-    users.login('user11@explorationRating.com');
-    workflow.createAndPublishExploration(
-      EXPLORATION_RATINGTEST, CATEGORY_BUSINESS,
-      'this is an objective', LANGUAGE_ENGLISH);
-    users.logout();
-
-    // Create test users, play exploration and review them after completion.
-    for (var i = 0; i < MINIMUM_ACCEPTABLE_NUMBER_OF_RATINGS - 1; i++) {
-      var userEmail = 'NoDisplay' + i + '@explorationRating.com';
-      var username = 'NoDisplay' + i;
-      addRating(userEmail, username, EXPLORATION_RATINGTEST, 4);
-    }
-
-    libraryPage.get();
-    libraryPage.findExploration(EXPLORATION_RATINGTEST);
-    libraryPage.expectExplorationRatingToEqual(EXPLORATION_RATINGTEST, 'N/A');
-
-    var userEmail = 'Display@explorationRating.com';
-    var username = 'Display';
-    addRating(userEmail, username, EXPLORATION_RATINGTEST, 4);
-
-    libraryPage.get();
-    libraryPage.findExploration(EXPLORATION_RATINGTEST);
-    libraryPage.expectExplorationRatingToEqual(EXPLORATION_RATINGTEST, '4.0');
-
-    libraryPage.playExploration(EXPLORATION_RATINGTEST);
-    explorationPlayerPage.expectExplorationRatingOnInformationCardToEqual(
-      '4.0');
-  });
-
-  afterEach(function() {
-    general.checkForConsoleErrors([]);
-  });
-});
diff --git a/core/tests/protractor/editorFeatures.js b/core/tests/protractor_desktop/editorFeatures.js
similarity index 100%
rename from core/tests/protractor/editorFeatures.js
rename to core/tests/protractor_desktop/editorFeatures.js
diff --git a/core/tests/protractor/embedding.js b/core/tests/protractor_desktop/embedding.js
similarity index 99%
rename from core/tests/protractor/embedding.js
rename to core/tests/protractor_desktop/embedding.js
--- a/core/tests/protractor/embedding.js
+++ b/core/tests/protractor_desktop/embedding.js
@@ -36,7 +36,7 @@ describe('Embedding', function() {
   explorationEditorMainTab = explorationEditorPage.getMainTab();
   explorationEditorSettingsTab = explorationEditorPage.getSettingsTab();
 
-  var createCountingExploration = function () {
+  var createCountingExploration = function() {
     // Intro.
     explorationEditorMainTab.setStateName('Intro');
     explorationEditorMainTab.setContent(forms.toRichText(
diff --git a/core/tests/protractor/extensions.js b/core/tests/protractor_desktop/extensions.js
similarity index 100%
rename from core/tests/protractor/extensions.js
rename to core/tests/protractor_desktop/extensions.js
diff --git a/core/tests/protractor_desktop/learnerDashboardSubscriptionsAndFeedbackThreads.js b/core/tests/protractor_desktop/learnerDashboardSubscriptionsAndFeedbackThreads.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor_desktop/learnerDashboardSubscriptionsAndFeedbackThreads.js
@@ -0,0 +1,141 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview End-to-end tests for the learner dashboard page and
+ * subscriptions functionality.
+ */
+
+var forms = require('../protractor_utils/forms.js');
+var general = require('../protractor_utils/general.js');
+var users = require('../protractor_utils/users.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+var workflow = require('../protractor_utils/workflow.js');
+
+var AdminPage = require('../protractor_utils/AdminPage.js');
+var CreatorDashboardPage =
+  require('../protractor_utils/CreatorDashboardPage.js');
+var CollectionEditorPage =
+  require('../protractor_utils/CollectionEditorPage.js');
+var ExplorationEditorPage =
+  require('../protractor_utils/ExplorationEditorPage.js');
+var ExplorationPlayerPage =
+  require('../protractor_utils/ExplorationPlayerPage.js');
+var LearnerDashboardPage =
+  require('../protractor_utils/LearnerDashboardPage.js');
+var LibraryPage = require('../protractor_utils/LibraryPage.js');
+var PreferencesPage = require('../protractor_utils/PreferencesPage.js');
+var SubscriptionDashboardPage =
+  require('../protractor_utils/SubscriptionDashboardPage.js');
+
+describe('Learner dashboard functionality', function() {
+  var creatorDashboardPage = null;
+  var explorationEditorPage = null;
+  var explorationEditorMainTab = null;
+  var explorationPlayerPage = null;
+  var libraryPage = null;
+  var learnerDashboardPage = null;
+  var subscriptionDashboardPage = null;
+
+  beforeAll(function() {
+    libraryPage = new LibraryPage.LibraryPage();
+    learnerDashboardPage = new LearnerDashboardPage.LearnerDashboardPage();
+    collectionEditorPage = new CollectionEditorPage.CollectionEditorPage();
+    creatorDashboardPage = new CreatorDashboardPage.CreatorDashboardPage();
+    explorationEditorPage = new ExplorationEditorPage.ExplorationEditorPage();
+    explorationEditorMainTab = explorationEditorPage.getMainTab();
+    explorationEditorSettingsTab = explorationEditorPage.getSettingsTab();
+    explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
+    subscriptionDashboardPage =
+      new SubscriptionDashboardPage.SubscriptionDashboardPage();
+  });
+
+  it('displays learners subscriptions', function() {
+    users.createUser('learner1@learnerDashboard.com',
+      'learner1learnerDashboard');
+    var creator1Id = 'creatorName';
+    users.createUser(creator1Id + '@learnerDashboard.com', creator1Id);
+    var creator2Id = 'collectionAdm';
+    users.createUser(creator2Id + '@learnerDashboard.com',
+      creator2Id);
+    users.login(creator1Id + '@learnerDashboard.com');
+    workflow.createAndPublishExploration(
+      'Activations',
+      'Chemistry',
+      'Learn about different types of chemistry activations.',
+      'English'
+    );
+    users.logout();
+
+    users.login('learner1@learnerDashboard.com');
+    // Subscribe to both the creators.
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(creator1Id);
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+    subscriptionDashboardPage.navigateToUserSubscriptionPage(creator2Id);
+    subscriptionDashboardPage.navigateToSubscriptionButton();
+
+    // Completing exploration 'Activations' to activate /learner_dashboard
+    libraryPage.get();
+    libraryPage.findExploration('Activations');
+    libraryPage.playExploration('Activations');
+    explorationPlayerPage.expectExplorationNameToBe('Activations');
+    explorationPlayerPage.rateExploration(4);
+
+    // Both creators should be present in the subscriptions section of the
+    // dashboard.
+    learnerDashboardPage.get();
+    learnerDashboardPage.navigateToSubscriptionsSection();
+    // The last user (collectionAdm) that learner subsribes to is placed first
+    // in the list.
+    learnerDashboardPage.expectSubscriptionFirstNameToMatch('collect...');
+    // The first user (creatorName) that learner subscribes to is placed
+    // last in the list.
+    learnerDashboardPage.expectSubscriptionLastNameToMatch('creator...');
+    users.logout();
+  });
+
+  it('displays learner feedback threads', function() {
+    users.createUser('learner2@learnerDashboard.com',
+      'learner2learnerDashboard');
+    users.createUser(
+      'feedbackAdm@learnerDashboard.com', 'feedbackAdmlearnerDashboard');
+    users.login('feedbackAdm@learnerDashboard.com');
+    workflow.createAndPublishExploration(
+      'BUS101',
+      'Business',
+      'Learn about different business regulations around the world.',
+      'English'
+    );
+    users.logout();
+
+    users.login('learner2@learnerDashboard.com');
+    var feedback = 'A good exploration. Would love to see a few more questions';
+    libraryPage.get();
+    libraryPage.findExploration('BUS101');
+    libraryPage.playExploration('BUS101');
+    explorationPlayerPage.submitFeedback(feedback);
+
+    // Verify feedback thread is created.
+    learnerDashboardPage.get();
+    learnerDashboardPage.navigateToFeedbackSection();
+    learnerDashboardPage.expectFeedbackExplorationTitleToMatch('BUS101');
+    learnerDashboardPage.navigateToFeedbackThread();
+    learnerDashboardPage.expectFeedbackMessageToMatch(feedback);
+    users.logout();
+  });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+  });
+});
diff --git a/core/tests/protractor/publicationAndLibrary.js b/core/tests/protractor_desktop/publicationAndLibrary.js
similarity index 100%
rename from core/tests/protractor/publicationAndLibrary.js
rename to core/tests/protractor_desktop/publicationAndLibrary.js
diff --git a/core/tests/protractor/stateEditor.js b/core/tests/protractor_desktop/stateEditor.js
similarity index 100%
rename from core/tests/protractor/stateEditor.js
rename to core/tests/protractor_desktop/stateEditor.js
diff --git a/core/tests/protractor/userJourneys.js b/core/tests/protractor_desktop/userJourneys.js
similarity index 80%
rename from core/tests/protractor/userJourneys.js
rename to core/tests/protractor_desktop/userJourneys.js
--- a/core/tests/protractor/userJourneys.js
+++ b/core/tests/protractor_desktop/userJourneys.js
@@ -33,8 +33,6 @@ var users = require('../protractor_utils/users.js');
 var waitFor = require('../protractor_utils/waitFor.js');
 var workflow = require('../protractor_utils/workflow.js');
 
-var ERROR_PAGE_URL_SUFFIX = '/console_errors';
-
 var _selectLanguage = function(language) {
   element(by.css('.protractor-test-i18n-language-selector')).
     element(by.cssContainingText('option', language)).click();
@@ -87,37 +85,6 @@ describe('Basic user journeys', function() {
     });
   });
 
-  describe('Login Flow', function() {
-    beforeEach(function() {
-      users.createAndLoginUser('randomuser@gmail.com', 'r4nd0m');
-    });
-
-    it('visits the links in the dropdown', function() {
-      var profileDropdown = element(by.css(
-        '.protractor-test-profile-dropdown'));
-
-      var classNames = [
-        '.protractor-test-profile-link',
-        '.protractor-test-dashboard-link',
-        '.protractor-test-preferences-link',
-        '.protractor-test-notifications-link'
-      ];
-      classNames.forEach(function(className) {
-        browser.actions().mouseMove(profileDropdown).perform();
-        var dropdownElement = element.all(by.css(className)).first();
-        waitFor.elementToBeClickable(
-          dropdownElement, 'Could not click topnav dropdown');
-        dropdownElement.click();
-        waitFor.pageToFullyLoad();
-      });
-    });
-
-    afterEach(function() {
-      general.checkForConsoleErrors([]);
-      users.logout();
-    });
-  });
-
   describe('Preferences', function() {
     var preferencesPage = null;
 
@@ -149,70 +116,8 @@ describe('Basic user journeys', function() {
 
     afterEach(function() {
       general.checkForConsoleErrors([]);
-    });
-  });
-
-  describe('Library pages tour', function() {
-    var EXPLORATION_TITLE = 'Test Exploration';
-    var EXPLORATION_OBJECTIVE = 'To learn testing';
-    var EXPLORATION_CATEGORY = 'Algorithms';
-    var EXPLORATION_LANGUAGE = 'English';
-    var EXPLORATION_RATING = 4;
-    var SEARCH_TERM = 'python';
-    var libraryPage = null;
-    var explorationPlayerPage = null;
-
-    beforeEach(function() {
-      libraryPage = new LibraryPage.LibraryPage();
-      explorationPlayerPage = new ExplorationPlayerPage.ExplorationPlayerPage();
-    });
-
-    var visitRecentlyPublishedPage = function() {
-      browser.get('library/recently_published');
-      waitFor.pageToFullyLoad();
-    };
-
-    it('visits the search page', function() {
-      libraryPage.get();
-      libraryPage.findExploration(SEARCH_TERM);
-      expect(browser.getCurrentUrl()).toContain('search/find?q=python');
-    });
-
-    it('visits the library index page', function() {
-      libraryPage.get();
-    });
-
-    it('visits the top rated page', function() {
-      // To visit the top rated page, at least one
-      // exploration has to be rated by the user
-      users.createUser('random@gmail.com', 'random');
-      users.login('random@gmail.com');
-      workflow.createAndPublishExploration(
-        EXPLORATION_TITLE,
-        EXPLORATION_CATEGORY,
-        EXPLORATION_OBJECTIVE,
-        EXPLORATION_LANGUAGE
-      );
-      libraryPage.get();
-      libraryPage.findExploration(EXPLORATION_TITLE);
-      libraryPage.playExploration(EXPLORATION_TITLE);
-      explorationPlayerPage.rateExploration(EXPLORATION_RATING);
-
-      libraryPage.get();
-      element(by.css('.protractor-test-library-top-rated')).click();
-      waitFor.pageToFullyLoad();
-      expect(browser.getCurrentUrl()).toContain('library/top_rated');
       users.logout();
     });
-
-    it('visits the recent explorations page', function() {
-      visitRecentlyPublishedPage();
-      expect(browser.getCurrentUrl()).toContain('library/recently_published');
-    });
-
-    afterEach(function() {
-      general.checkForConsoleErrors([]);
-    });
   });
 });
 
@@ -461,14 +366,3 @@ describe('Site language', function() {
     general.checkForConsoleErrors([]);
   });
 });
-
-
-describe('Cache Slugs', function() {
-  it('should check that errors get logged for missing resources', function() {
-    browser.get(ERROR_PAGE_URL_SUFFIX);
-    var expectedErrors = [
-      'http://localhost:9001/build/fail/logo/288x128_logo_white.png'
-    ];
-    general.checkConsoleErrorsExist(expectedErrors);
-  });
-});
diff --git a/core/tests/protractor_mobile/navigation.js b/core/tests/protractor_mobile/navigation.js
new file mode 100644
--- /dev/null
+++ b/core/tests/protractor_mobile/navigation.js
@@ -0,0 +1,178 @@
+// Copyright 2018 The Oppia Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @fileoverview  End-to-end tests for testing navigation
+ * on mobile as a guest and check for any console errors.
+ */
+
+var general = require('../protractor_utils/general.js');
+var waitFor = require('../protractor_utils/waitFor.js');
+
+var LibraryPage = require('../protractor_utils/LibraryPage.js');
+
+describe('Navigation features on mobile', function() {
+  var libraryPage = null;
+
+  beforeEach(function() {
+    libraryPage = new LibraryPage.LibraryPage();
+    libraryPage.get();
+  });
+
+  it('should open the sidebar menu by clicking on the hamburger button',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var sidebarMenuOpen = element(by.css('.oppia-sidebar-menu-open'));
+      expect(sidebarMenuOpen).not.toBeNull();
+    });
+
+  it('should navigate to About page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var aboutLink = element(by.css('.protractor-mobile-test-about-link'));
+      waitFor.elementToBeClickable(
+        aboutLink, 'Could not click about link');
+      aboutLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/about');
+    });
+
+  it('should navigate to Get Started page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var getStartedLink = element(
+        by.css('.protractor-mobile-test-get-started-link'));
+      waitFor.elementToBeClickable(
+        getStartedLink, 'Could not click get started link');
+      getStartedLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/get_started');
+    });
+
+  it('should navigate to Teach with Oppia page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var teachLink = element(
+        by.css('.protractor-mobile-test-teach-link'));
+      waitFor.elementToBeClickable(
+        teachLink, 'Could not click teach link');
+      teachLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/teach');
+    });
+
+  it('should navigate to Donate page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var donateLink = element(
+        by.css('.protractor-mobile-test-donate-link'));
+      waitFor.elementToBeClickable(
+        donateLink, 'Could not click donate link');
+      donateLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/donate');
+    });
+
+  it('should navigate to Contact page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var contactLink = element(
+        by.css('.protractor-mobile-test-contact-link'));
+      waitFor.elementToBeClickable(
+        contactLink, 'Could not click contact link');
+      contactLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/contact');
+    });
+
+  it('should navigate to Library page using the sidebar menu',
+    function() {
+      var navbarButton = element(
+        by.css('.protractor-mobile-test-navbar-button'));
+      waitFor.elementToBeClickable(
+        navbarButton, 'Could not click navbar button');
+      navbarButton.click();
+      var libraryLink = element(by.css('.protractor-mobile-test-library-link'));
+      waitFor.elementToBeClickable(
+        libraryLink, 'Could not click library link');
+      libraryLink.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/library');
+    });
+
+  it('should navigate to Splash page by clicking on the Oppia logo',
+    function() {
+      var oppiaLogo = element(by.css('.protractor-test-oppia-main-logo'));
+      waitFor.elementToBeClickable(
+        oppiaLogo, 'Could not click oppia logo');
+      oppiaLogo.click();
+      waitFor.pageToFullyLoad();
+      expect(browser.getCurrentUrl()).toEqual('http://localhost:9001/splash');
+    });
+
+  it('should navigate to the login page by clicking on the sign in button',
+    function() {
+      var signInButton = element(
+        by.css('.protractor-mobile-test-login'));
+      waitFor.elementToBeClickable(
+        signInButton, 'Could not click sign in button');
+      // For the rationale behind this statement, see
+      // https://github.com/angular/protractor/issues/2643#issuecomment-213257116
+      // Here, we are transitioning from an angular page (Library page) to a
+      // non-angular page (Login page) using the 'Sign In' button. Protractor,
+      // by default, waits for angular to load completely. Since there is no
+      // angular on the login page, the test times out saying,
+      // "Could not find Angular on this page...". The
+      // browser.ignoreSynchronization = true asks Protractor
+      // not to wait for the Angular page.
+      browser.ignoreSynchronization = true;
+      signInButton.click();
+      // We should not wait for angular here since
+      // the login page is non-angular.
+      expect(browser.getCurrentUrl()).toEqual(
+        'http://localhost:9001/_ah/login?continue=http%3A//localhost%3A9001/signup%3Freturn_url%3Dhttp%253A%252F%252Flocalhost%253A9001%252Flibrary');
+      // As soon as this page loads up, we are again
+      // setting browser.ignoreSynchronization = false
+      // to prevent any flakiness.
+      browser.ignoreSynchronization = false;
+    });
+
+  afterEach(function() {
+    general.checkForConsoleErrors([]);
+  });
+});
diff --git a/core/tests/protractor_utils/AdminPage.js b/core/tests/protractor_utils/AdminPage.js
--- a/core/tests/protractor_utils/AdminPage.js
+++ b/core/tests/protractor_utils/AdminPage.js
@@ -34,6 +34,66 @@ var AdminPage = function(){
   var roleSelect = element(by.css('.protractor-update-form-role-select'));
   var statusMessage = element(by.css('[ng-if="statusMessage"]'));
 
+  // The reload functions are used for mobile testing
+  // done via Browserstack. These functions may cause
+  // a problem when used to run tests directly on Travis.
+  if (general.isInDevMode()) {
+    var explorationElements = element.all(by.css(
+      '.protractor-test-reload-exploration-row'
+    ));
+
+    var reloadAllExplorationsButtons = element.all(by.css(
+      '.protractor-test-reload-all-explorations-button'
+    ));
+
+    var reloadCollectionButtons = element.all(by.css(
+      '.protractor-test-reload-collection-button'));
+
+    var getExplorationTitleElement = function(explorationElement) {
+      return explorationElement.element(
+        by.css('.protractor-test-reload-exploration-title')
+      );
+    };
+
+    var getExplorationElementReloadButton = function(explorationElement) {
+      return explorationElement.element(
+        by.css('.protractor-test-reload-exploration-button')
+      );
+    };
+
+    this.reloadCollection = function(collectionId) {
+      this.get();
+      reloadCollectionButtons.get(collectionId).click();
+      general.acceptAlert();
+      // Time is needed for the reloading to complete.
+      waitFor.textToBePresentInElement(
+        statusMessage, 'Data reloaded successfully.',
+        'Collection could not be reloaded');
+      return true;
+    };
+
+    // The name should be as given in the admin page (including '.yaml' if
+    // necessary).
+    this.reloadExploration = function(name) {
+      this.get();
+      explorationElements.map(function(explorationElement) {
+        getExplorationTitleElement(explorationElement)
+          .getText().then(function(title) {
+          // We use match here in case there is whitespace around the name
+            if (title.match(name)) {
+              getExplorationElementReloadButton(explorationElement).click();
+              general.acceptAlert();
+              // Time is needed for the reloading to complete.
+              waitFor.textToBePresentInElement(
+                statusMessage, 'Data reloaded successfully.',
+                'Exploration could not be reloaded');
+              return true;
+            }
+          });
+      });
+    };
+  }
+
   var saveConfigProperty = function(configProperty) {
     return configProperty.element(by.css('.protractor-test-config-title'))
       .getText()
diff --git a/core/tests/protractor_utils/CollectionEditorPage.js b/core/tests/protractor_utils/CollectionEditorPage.js
--- a/core/tests/protractor_utils/CollectionEditorPage.js
+++ b/core/tests/protractor_utils/CollectionEditorPage.js
@@ -158,7 +158,7 @@ var CollectionEditorPage = function() {
   };
 
   // Saves changes and publishes collection.
-  this.saveChanges = function () {
+  this.saveChanges = function() {
     waitFor.elementToBeClickable(
       saveChangesButton, 'Save Changes button is not clickable');
     saveChangesButton.click();
diff --git a/core/tests/protractor_utils/CreatorDashboardPage.js b/core/tests/protractor_utils/CreatorDashboardPage.js
--- a/core/tests/protractor_utils/CreatorDashboardPage.js
+++ b/core/tests/protractor_utils/CreatorDashboardPage.js
@@ -35,6 +35,8 @@ var CreatorDashboardPage = function() {
     element(by.css('.protractor-test-create-collection'));
   var createExplorationButton =
     element(by.css('.protractor-test-create-exploration'));
+  var createNewExplorationButton =
+    element(by.css('.protractor-test-create-new-exploration-button'));
 
     // Returns a promise of all explorations with the given name.
   var _getExplorationElements = function(explorationTitle) {
@@ -91,6 +93,15 @@ var CreatorDashboardPage = function() {
     waitFor.pageToFullyLoad();
   };
 
+  this.clickCreateNewExplorationButton = function() {
+    waitFor.elementToBeClickable(
+      createNewExplorationButton,
+      'Create Exploration button on the creator' +
+      'dashboard page takes too long to be clickable');
+    createNewExplorationButton.click();
+    waitFor.pageToFullyLoad();
+  };
+
   this.navigateToCollectionEditor = function() {
     waitFor.elementToBeClickable(
       collectionCard,
diff --git a/core/tests/protractor_utils/ExplorationEditorFeedbackTab.js b/core/tests/protractor_utils/ExplorationEditorFeedbackTab.js
--- a/core/tests/protractor_utils/ExplorationEditorFeedbackTab.js
+++ b/core/tests/protractor_utils/ExplorationEditorFeedbackTab.js
@@ -20,7 +20,7 @@
 var general = require('./general.js');
 var waitFor = require('./waitFor.js');
 
-var ExplorationEditorFeedbackTab = function () {
+var ExplorationEditorFeedbackTab = function() {
   /*
    * Interactive elements
    */
diff --git a/core/tests/protractor_utils/ExplorationEditorMainTab.js b/core/tests/protractor_utils/ExplorationEditorMainTab.js
--- a/core/tests/protractor_utils/ExplorationEditorMainTab.js
+++ b/core/tests/protractor_utils/ExplorationEditorMainTab.js
@@ -35,7 +35,7 @@ var ExplorationEditorMainTab = function() {
     by.css('.protractor-test-add-response-details'));
   var addResponseHeader = element(
     by.css('.protractor-test-add-response-modal-header'));
-  var multipleChoiceAnswerOptions = function (optionNum) {
+  var multipleChoiceAnswerOptions = function(optionNum) {
     return element(
       by.cssContainingText('.protractor-test-html-select-option', optionNum));
   };
diff --git a/core/tests/protractor_utils/LearnerDashboardPage.js b/core/tests/protractor_utils/LearnerDashboardPage.js
--- a/core/tests/protractor_utils/LearnerDashboardPage.js
+++ b/core/tests/protractor_utils/LearnerDashboardPage.js
@@ -141,7 +141,7 @@ var LearnerDashboardPage = function() {
     var explorationTitle = element(
       by.cssContainingText('.protractor-test-exp-summary-tile-title', title));
     waitFor.visibilityOf(
-      explorationTitle, 'Unable to find collection ' + title);
+      explorationTitle, 'Unable to find exploration ' + title);
     expect(explorationTitle.isDisplayed()).toBe(true);
   };
 
@@ -171,6 +171,34 @@ var LearnerDashboardPage = function() {
       feedbackMessage.first(), 'Feedback Message takes too long to appear');
     expect(feedbackMessage.first().getText()).toMatch(message);
   };
+
+  this.checkIncompleteExplorationSection = function(explorationTitle) {
+    this.navigateToInCompleteSection();
+    this.navigateToIncompleteExplorationsSection();
+    this.expectTitleOfExplorationSummaryTileToMatch(
+      explorationTitle);
+  };
+
+  this.checkCompleteExplorationSection = function(explorationTitle) {
+    this.navigateToCompletedSection();
+    this.navigateToCompletedExplorationsSection();
+    this.expectTitleOfExplorationSummaryTileToMatch(
+      explorationTitle);
+  };
+
+  this.checkIncompleteCollectionSection = function(collectionTitle) {
+    this.navigateToInCompleteSection();
+    this.navigateToIncompleteCollectionsSection();
+    this.expectTitleOfCollectionSummaryTileToMatch(
+      collectionTitle);
+  };
+
+  this.checkCompleteCollectionSection = function(collectionTitle) {
+    this.navigateToCompletedSection();
+    this.navigateToCompletedCollectionsSection();
+    this.expectTitleOfCollectionSummaryTileToMatch(
+      collectionTitle);
+  };
 };
 
 exports.LearnerDashboardPage = LearnerDashboardPage;
diff --git a/core/tests/protractor_utils/LibraryPage.js b/core/tests/protractor_utils/LibraryPage.js
--- a/core/tests/protractor_utils/LibraryPage.js
+++ b/core/tests/protractor_utils/LibraryPage.js
@@ -44,8 +44,8 @@ var LibraryPage = function(){
   var languageSelector = forms.MultiSelectEditor(
     element(by.css('.protractor-test-search-bar-language-selector'))
   );
-  var searchInput = element.all(
-    by.css('.protractor-test-search-input')).first();
+  var searchInputs = element.all(
+    by.css('.protractor-test-search-input'));
 
   // Returns a promise of all explorations with the given name.
   var _getExplorationElements = function(name) {
@@ -59,6 +59,20 @@ var LibraryPage = function(){
     );
   };
 
+  var _submitSearchQuery = function(searchQuery) {
+    // The library page has two search bar input elements.
+    // The first search bar input element is visible only in a desktop
+    // browser and is invisible in case of a mobile browser.
+    // The second search bar input element is visible when the library
+    // page is rendered for mobile device.
+
+    // get function is a zero-based index.
+    var searchInput = (
+      browser.isMobile ? searchInputs.get(1) : searchInputs.first());
+    searchInput.clear();
+    searchInput.sendKeys(searchQuery);
+  };
+
   this.get = function() {
     browser.get(LIBRARY_URL_SUFFIX);
     return waitFor.pageToFullyLoad();
@@ -150,14 +164,12 @@ var LibraryPage = function(){
 
   this.findExploration = function(explorationTitle) {
     waitFor.pageToFullyLoad();
-    searchInput.clear();
-    searchInput.sendKeys(explorationTitle);
+    _submitSearchQuery(explorationTitle);
   };
 
   this.findCollection = function(collectionTitle) {
     waitFor.pageToFullyLoad();
-    searchInput.clear();
-    searchInput.sendKeys(collectionTitle);
+    _submitSearchQuery(collectionTitle);
   };
 };
 
diff --git a/core/tests/protractor_utils/general.js b/core/tests/protractor_utils/general.js
--- a/core/tests/protractor_utils/general.js
+++ b/core/tests/protractor_utils/general.js
@@ -50,6 +50,13 @@ var checkForConsoleErrors = function(errorsToIgnore) {
   });
 };
 
+var isInDevMode = function() {
+  browser.get('/library');
+  waitFor.pageToFullyLoad();
+  var devModeElement = element(by.css('.oppia-dev-mode'));
+  return devModeElement.isPresent();
+};
+
 var SERVER_URL_PREFIX = 'http://localhost:9001';
 var EDITOR_URL_SLICE = '/create/';
 var PLAYER_URL_SLICE = '/explore/';
@@ -174,6 +181,7 @@ var checkConsoleErrorsExist = function(expectedErrors) {
 exports.acceptAlert = acceptAlert;
 exports.scrollToTop = scrollToTop;
 exports.checkForConsoleErrors = checkForConsoleErrors;
+exports.isInDevMode = isInDevMode;
 
 exports.SERVER_URL_PREFIX = SERVER_URL_PREFIX;
 exports.USER_PREFERENCES_URL = USER_PREFERENCES_URL;
diff --git a/core/tests/protractor_utils/users.js b/core/tests/protractor_utils/users.js
--- a/core/tests/protractor_utils/users.js
+++ b/core/tests/protractor_utils/users.js
@@ -90,6 +90,17 @@ var createAndLoginAdminUser = function(email, username) {
   adminPage.updateRole(username, 'admin');
 };
 
+var createAdminMobile = function(email, username) {
+  createAndLoginAdminUserMobile(email, username);
+  logout();
+};
+
+var createAndLoginAdminUserMobile = function(email, username) {
+  login(email, true);
+  _completeSignup(username);
+};
+
+
 exports.login = login;
 exports.logout = logout;
 exports.createUser = createUser;
@@ -97,3 +108,5 @@ exports.createAndLoginUser = createAndLoginUser;
 exports.createModerator = createModerator;
 exports.createAdmin = createAdmin;
 exports.createAndLoginAdminUser = createAndLoginAdminUser;
+exports.createAdminMobile = createAdminMobile;
+exports.createAndLoginAdminUserMobile = createAndLoginAdminUserMobile;
diff --git a/core/tests/protractor_utils/waitFor.js b/core/tests/protractor_utils/waitFor.js
--- a/core/tests/protractor_utils/waitFor.js
+++ b/core/tests/protractor_utils/waitFor.js
@@ -18,7 +18,11 @@
  */
 
 var until = protractor.ExpectedConditions;
-var DEFAULT_WAIT_TIME_MSECS = 10000;
+// When running tests on mobile via browserstack, the localhost
+// might take some time to establish a connection with the
+// server since the mobile tests are run on a real
+// mobile device.
+var DEFAULT_WAIT_TIME_MSECS = browser.isMobile ? 20000 : 10000;
 
 var alertToBePresent = function() {
   return browser.wait(
diff --git a/data/collections/learner_flow_test_collection.yaml b/data/collections/learner_flow_test_collection.yaml
new file mode 100644
--- /dev/null
+++ b/data/collections/learner_flow_test_collection.yaml
@@ -0,0 +1,16 @@
+category: Welcome
+next_skill_id: 1
+nodes:
+- acquired_skill_ids:
+  - skill0
+  exploration_id: '24'
+  prerequisite_skill_ids: []
+language_code: en
+objective: To introduce collections using demo explorations.
+schema_version: 4
+skills:
+  skill0:
+    name: Dummy Exploration
+    question_ids: []
+tags: []
+title: Test Collection
diff --git a/data/explorations/exploration_player_test.yaml b/data/explorations/exploration_player_test.yaml
new file mode 100644
--- /dev/null
+++ b/data/explorations/exploration_player_test.yaml
@@ -0,0 +1,67 @@
+author_notes: ''
+blurb: ''
+category: Astronomy
+init_state_name: Start
+language_code: en
+objective: To test the exploration player
+param_changes: []
+param_specs: {}
+schema_version: 17
+states:
+  END:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Congratulations, you have finished!
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        recommendedExplorationIds:
+          value: []
+      default_outcome: null
+      hints: []
+      id: EndExploration
+      solution: null
+    param_changes: []
+  Mid:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Hello, I am Oppia!
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        buttonText:
+          value: Continue
+      default_outcome:
+        dest: END
+        feedback: []
+        param_changes: []
+      hints: []
+      id: Continue
+      solution: null
+    param_changes: []
+  Start:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Test exploration
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        buttonText:
+          value: Continue
+      default_outcome:
+        dest: Mid
+        feedback: []
+        param_changes: []
+      hints: []
+      id: Continue
+      solution: null
+    param_changes: []
+states_schema_version: 13
+tags: []
+title: Exploration Player Test
diff --git a/data/explorations/learner_flow_test.yaml b/data/explorations/learner_flow_test.yaml
new file mode 100644
--- /dev/null
+++ b/data/explorations/learner_flow_test.yaml
@@ -0,0 +1,67 @@
+author_notes: ''
+blurb: ''
+category: Astronomy
+init_state_name: Start
+language_code: en
+objective: To expand the horizon of the minds!
+param_changes: []
+param_specs: {}
+schema_version: 17
+states:
+  END:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Congratulations, you have finished!
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        recommendedExplorationIds:
+          value: []
+      default_outcome: null
+      hints: []
+      id: EndExploration
+      solution: null
+    param_changes: []
+  Mid:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Hello, I am Oppia!
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        buttonText:
+          value: Continue
+      default_outcome:
+        dest: END
+        feedback: []
+        param_changes: []
+      hints: []
+      id: Continue
+      solution: null
+    param_changes: []
+  Start:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Test exploration
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        buttonText:
+          value: Continue
+      default_outcome:
+        dest: Mid
+        feedback: []
+        param_changes: []
+      hints: []
+      id: Continue
+      solution: null
+    param_changes: []
+states_schema_version: 13
+tags: []
+title: Dummy Exploration
diff --git a/data/explorations/protractor_mobile_test_exploration.yaml b/data/explorations/protractor_mobile_test_exploration.yaml
new file mode 100644
--- /dev/null
+++ b/data/explorations/protractor_mobile_test_exploration.yaml
@@ -0,0 +1,29 @@
+author_notes: ''
+blurb: ''
+category: Algorithms
+init_state_name: Start
+language_code: en
+objective: To learn testing
+param_changes: []
+param_specs: {}
+schema_version: 17
+states:
+  Start:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Test exploration
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        recommendedExplorationIds:
+          value: []
+      default_outcome: null
+      hints: []
+      id: EndExploration
+      solution: null
+    param_changes: []
+states_schema_version: 13
+tags: []
+title: Test Exploration
diff --git a/data/explorations/rating_test.yaml b/data/explorations/rating_test.yaml
new file mode 100644
--- /dev/null
+++ b/data/explorations/rating_test.yaml
@@ -0,0 +1,29 @@
+author_notes: ''
+blurb: ''
+category: Business
+init_state_name: Start
+language_code: en
+objective: this is an objective
+param_changes: []
+param_specs: {}
+schema_version: 17
+states:
+  Start:
+    classifier_model_id: null
+    content:
+      audio_translations: {}
+      html: Test exploration
+    interaction:
+      answer_groups: []
+      confirmed_unclassified_answers: []
+      customization_args:
+        recommendedExplorationIds:
+          value: []
+      default_outcome: null
+      hints: []
+      id: EndExploration
+      solution: null
+    param_changes: []
+states_schema_version: 13
+tags: []
+title: RatingTest
diff --git a/scripts/run_e2e_tests.sh b/scripts/run_e2e_tests.sh
--- a/scripts/run_e2e_tests.sh
+++ b/scripts/run_e2e_tests.sh
@@ -22,6 +22,8 @@
 #   bash scripts/run_e2e_tests.sh
 #
 # Optional arguments:
+#   --browserstack Run the tests on browserstack using the
+#         protractor-browserstack.conf.js file.
 #   --skip-install=true/false If true, skips installing dependencies. The
 #         default value is false.
 #   --sharding=true/false Disables/Enables parallelization of protractor tests.
@@ -112,12 +114,19 @@ trap cleanup EXIT
 
 # Argument passed to feconf.py to help choose production templates folder.
 FORCE_PROD_MODE=False
+RUN_ON_BROWSERSTACK=False
 for arg in "$@"; do
   # Used to emulate running Oppia in a production environment.
   if [ "$arg" == "--prod_env" ]; then
     FORCE_PROD_MODE=True
     echo "  Generating files for production mode..."
   fi
+
+  # Used to run the e2e tests on browserstack.
+  if [ "$arg" == "--browserstack" ]; then
+    RUN_ON_BROWSERSTACK=True
+    echo "  Running the tests on browserstack..."
+  fi
 done
 
 if [[ "$FORCE_PROD_MODE" == "True" ]]; then
@@ -182,6 +191,10 @@ for j in "$@"; do
     shift
     ;;
 
+    --browserstack*)
+    shift
+    ;;
+
     *)
     echo "Error: Unknown command line option: $j"
     ;;
@@ -193,8 +206,16 @@ done
 # Isolated tests do not work properly unless no sharding parameters are passed
 # in at all.
 # TODO(bhenning): Figure out if this is a bug with protractor.
-if [ "$SHARDING" = "false" ] || [ "$SHARD_INSTANCES" = "1" ]; then
-  $NODE_MODULE_DIR/.bin/protractor core/tests/protractor.conf.js --suite "$SUITE"
+if [ "$RUN_ON_BROWSERSTACK" == "False" ]; then
+  if [ "$SHARDING" = "false" ] || [ "$SHARD_INSTANCES" = "1" ]; then
+    $NODE_MODULE_DIR/.bin/protractor core/tests/protractor.conf.js --suite "$SUITE"
+  else
+    $NODE_MODULE_DIR/.bin/protractor core/tests/protractor.conf.js --capabilities.shardTestFiles="$SHARDING" --capabilities.maxInstances=$SHARD_INSTANCES --suite "$SUITE"
+  fi
 else
-  $NODE_MODULE_DIR/.bin/protractor core/tests/protractor.conf.js --capabilities.shardTestFiles="$SHARDING" --capabilities.maxInstances=$SHARD_INSTANCES --suite "$SUITE"
+  if [ "$SHARDING" = "false" ] || [ "$SHARD_INSTANCES" = "1" ]; then
+    $NODE_MODULE_DIR/.bin/protractor core/tests/protractor-browserstack.conf.js --suite "$SUITE"
+  else
+    $NODE_MODULE_DIR/.bin/protractor core/tests/protractor-browserstack.conf.js --capabilities.shardTestFiles="$SHARDING" --capabilities.maxInstances=$SHARD_INSTANCES --suite "$SUITE"
+  fi
 fi