summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/first_run/drive_first_run_browsertest.cc192
-rw-r--r--chrome/browser/chromeos/first_run/drive_first_run_controller.cc82
-rw-r--r--chrome/browser/chromeos/first_run/drive_first_run_controller.h32
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/test/data/drive_first_run/app.crxbin0 -> 693 bytes
-rw-r--r--chrome/test/data/drive_first_run/app.pem16
-rw-r--r--chrome/test/data/drive_first_run/app/manifest.json22
-rw-r--r--chrome/test/data/drive_first_run/bad/endpoint.html7
-rw-r--r--chrome/test/data/drive_first_run/good/background.html6
-rw-r--r--chrome/test/data/drive_first_run/good/endpoint.html12
10 files changed, 354 insertions, 16 deletions
diff --git a/chrome/browser/chromeos/first_run/drive_first_run_browsertest.cc b/chrome/browser/chromeos/first_run/drive_first_run_browsertest.cc
new file mode 100644
index 0000000..c40cabb
--- /dev/null
+++ b/chrome/browser/chromeos/first_run/drive_first_run_browsertest.cc
@@ -0,0 +1,192 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/chromeos/first_run/drive_first_run_controller.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_test_notification_observer.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/test_utils.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_status_code.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace chromeos {
+
+namespace {
+
+// Directory containing data files for the tests.
+const char kTestDirectory[] = "drive_first_run";
+
+// Directory containing correct hosted app page served by the test server.
+const char kGoodServerDirectory[] = "good";
+
+// Directory containing incorrect hosted app page served by the test server.
+const char kBadServerDirectory[] = "bad";
+
+// Name of the test hosted app .crx file.
+const char kTestAppCrxName[] = "app.crx";
+
+// App id of the test hosted app.
+const char kTestAppId[] = "kipccbklifbfblhpplnmklieangbjnhb";
+
+// The endpoint belonging to the test hosted app.
+const char kTestEndpointUrl[] = "http://example.com/endpoint.html";
+
+} // namespace
+
+class DriveFirstRunTest : public InProcessBrowserTest,
+ public DriveFirstRunController::Observer {
+ protected:
+ DriveFirstRunTest();
+
+ // InProcessBrowserTest overrides:
+ virtual void SetUpOnMainThread() OVERRIDE;
+ virtual void CleanUpOnMainThread() OVERRIDE;
+
+ // DriveFirstRunController::Observer overrides:
+ virtual void OnCompletion(bool success) OVERRIDE;
+ virtual void OnTimedOut() OVERRIDE;
+
+ void InstallApp();
+
+ void InitTestServer(const std::string& directory);
+
+ bool WaitForFirstRunResult();
+
+ void EnableOfflineMode();
+
+ void SetDelays(int initial_delay_secs, int timeout_secs);
+
+ bool timed_out() const { return timed_out_; }
+
+ private:
+ // |controller_| is responsible for its own lifetime.
+ DriveFirstRunController* controller_;
+ scoped_refptr<content::MessageLoopRunner> runner_;
+
+ bool timed_out_;
+ bool waiting_for_result_;
+ bool success_;
+ base::FilePath test_data_dir_;
+ std::string endpoint_url_;
+};
+
+DriveFirstRunTest::DriveFirstRunTest() :
+ timed_out_(false),
+ waiting_for_result_(false),
+ success_(false) {}
+
+void DriveFirstRunTest::SetUpOnMainThread() {
+ InProcessBrowserTest::SetUpOnMainThread();
+ PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
+ test_data_dir_ = test_data_dir_.AppendASCII(kTestDirectory);
+
+ host_resolver()->AddRule("example.com", "127.0.0.1");
+
+ // |controller_| will delete itself when it completes.
+ controller_ = new DriveFirstRunController();
+ controller_->AddObserver(this);
+ controller_->SetDelaysForTest(0, 10);
+ controller_->SetAppInfoForTest(kTestAppId, kTestEndpointUrl);
+}
+
+void DriveFirstRunTest::CleanUpOnMainThread() {
+ InProcessBrowserTest::CleanUpOnMainThread();
+ content::RunAllPendingInMessageLoop();
+}
+
+void DriveFirstRunTest::InitTestServer(const std::string& directory) {
+ embedded_test_server()->ServeFilesFromDirectory(
+ test_data_dir_.AppendASCII(directory));
+ ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
+
+ // Configure the endpoint to use the test server's port.
+ const GURL url(kTestEndpointUrl);
+ GURL::Replacements replacements;
+ std::string port(base::IntToString(embedded_test_server()->port()));
+ replacements.SetPortStr(port);
+ endpoint_url_ = url.ReplaceComponents(replacements).spec();
+ controller_->SetAppInfoForTest(kTestAppId, endpoint_url_);
+}
+
+void DriveFirstRunTest::InstallApp() {
+ ExtensionService* extension_service = extensions::ExtensionSystem::Get(
+ browser()->profile())->extension_service();
+ scoped_refptr<extensions::CrxInstaller> installer =
+ extensions::CrxInstaller::CreateSilent(extension_service);
+
+ installer->InstallCrx(test_data_dir_.AppendASCII(kTestAppCrxName));
+ ExtensionTestNotificationObserver observer(browser());
+ observer.WaitForExtensionLoad();
+
+ ASSERT_TRUE(extension_service->GetExtensionById(kTestAppId, false));
+}
+
+void DriveFirstRunTest::EnableOfflineMode() {
+ controller_->EnableOfflineMode();
+}
+
+void DriveFirstRunTest::SetDelays(int initial_delay_secs, int timeout_secs) {
+ controller_->SetDelaysForTest(initial_delay_secs, timeout_secs);
+}
+
+bool DriveFirstRunTest::WaitForFirstRunResult() {
+ waiting_for_result_ = true;
+ runner_ = new content::MessageLoopRunner;
+ runner_->Run();
+ EXPECT_FALSE(waiting_for_result_);
+ return success_;
+}
+
+void DriveFirstRunTest::OnCompletion(bool success) {
+ EXPECT_TRUE(waiting_for_result_);
+ waiting_for_result_ = false;
+ success_ = success;
+ runner_->Quit();
+
+ // |controller_| will eventually delete itself upon completion, so invalidate
+ // the pointer.
+ controller_ = NULL;
+}
+
+void DriveFirstRunTest::OnTimedOut() {
+ timed_out_ = true;
+}
+
+IN_PROC_BROWSER_TEST_F(DriveFirstRunTest, OfflineEnabled) {
+ InstallApp();
+ InitTestServer(kGoodServerDirectory);
+ EnableOfflineMode();
+ EXPECT_TRUE(WaitForFirstRunResult());
+}
+
+IN_PROC_BROWSER_TEST_F(DriveFirstRunTest, AppNotInstalled) {
+ InitTestServer(kGoodServerDirectory);
+ EnableOfflineMode();
+ EXPECT_FALSE(WaitForFirstRunResult());
+ EXPECT_FALSE(timed_out());
+}
+
+IN_PROC_BROWSER_TEST_F(DriveFirstRunTest, TimedOut) {
+ // Test that the controller times out instead of hanging forever.
+ InstallApp();
+ InitTestServer(kBadServerDirectory);
+ SetDelays(0, 0);
+ EnableOfflineMode();
+ EXPECT_FALSE(WaitForFirstRunResult());
+ EXPECT_TRUE(timed_out());
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/first_run/drive_first_run_controller.cc b/chrome/browser/chromeos/first_run/drive_first_run_controller.cc
index ba523b1..9526948 100644
--- a/chrome/browser/chromeos/first_run/drive_first_run_controller.cc
+++ b/chrome/browser/chromeos/first_run/drive_first_run_controller.cc
@@ -16,6 +16,7 @@
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/extensions/extension_web_contents_observer.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/tab_contents/background_contents.h"
#include "content/public/browser/browser_thread.h"
@@ -36,10 +37,10 @@ namespace chromeos {
namespace {
// The initial time to wait in seconds before starting the opt-in.
-const int kInitialDelaySeconds = 180;
+int kInitialDelaySeconds = 180;
// Time to wait for Drive app background page to come up before giving up.
-const int kWebContentsTimeoutSeconds = 15;
+int kWebContentsTimeoutSeconds = 15;
// Google Drive offline opt-in endpoint.
const char kDriveOfflineEndpointUrl[] = "https://drive.google.com/#offline";
@@ -63,6 +64,8 @@ class DriveWebContentsManager : public content::WebContentsObserver,
typedef base::Callback<void(bool)> CompletionCallback;
DriveWebContentsManager(Profile* profile,
+ const std::string& app_id,
+ const std::string& endpoint_url,
const CompletionCallback& completion_callback);
virtual ~DriveWebContentsManager();
@@ -114,6 +117,8 @@ class DriveWebContentsManager : public content::WebContentsObserver,
const content::NotificationDetails& details) OVERRIDE;
Profile* profile_;
+ const std::string app_id_;
+ const std::string endpoint_url_;
scoped_ptr<content::WebContents> web_contents_;
content::NotificationRegistrar registrar_;
bool started_;
@@ -125,8 +130,12 @@ class DriveWebContentsManager : public content::WebContentsObserver,
DriveWebContentsManager::DriveWebContentsManager(
Profile* profile,
+ const std::string& app_id,
+ const std::string& endpoint_url,
const CompletionCallback& completion_callback)
: profile_(profile),
+ app_id_(app_id),
+ endpoint_url_(endpoint_url),
started_(false),
completion_callback_(completion_callback),
weak_ptr_factory_(this) {
@@ -140,12 +149,14 @@ DriveWebContentsManager::~DriveWebContentsManager() {
void DriveWebContentsManager::StartLoad() {
started_ = true;
- const GURL url(kDriveOfflineEndpointUrl);
+ const GURL url(endpoint_url_);
content::WebContents::CreateParams create_params(
profile_, content::SiteInstance::CreateForURL(profile_, url));
web_contents_.reset(content::WebContents::Create(create_params));
web_contents_->SetDelegate(this);
+ extensions::ExtensionWebContentsObserver::CreateForWebContents(
+ web_contents_.get());
content::NavigationController::LoadURLParams load_params(url);
load_params.transition_type = content::PAGE_TRANSITION_GENERATED;
@@ -183,7 +194,10 @@ void DriveWebContentsManager::DidFailProvisionalLoad(
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) {
- OnOfflineInit(false);
+ if (is_main_frame) {
+ LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
+ OnOfflineInit(false);
+ }
}
void DriveWebContentsManager::DidFailLoad(
@@ -193,7 +207,10 @@ void DriveWebContentsManager::DidFailLoad(
int error_code,
const string16& error_description,
content::RenderViewHost* render_view_host) {
- OnOfflineInit(false);
+ if (is_main_frame) {
+ LOG(WARNING) << "Failed to load WebContents to enable offline mode.";
+ OnOfflineInit(false);
+ }
}
bool DriveWebContentsManager::ShouldCreateWebContents(
@@ -213,7 +230,7 @@ bool DriveWebContentsManager::ShouldCreateWebContents(
extensions::ExtensionSystem::Get(profile_)->extension_service();
const extensions::Extension *extension =
service->GetInstalledApp(target_url);
- if (!extension || extension->id() != kDriveHostedAppId)
+ if (!extension || extension->id() != app_id_)
return true;
// The background contents creation is normally done in Browser, but
@@ -223,7 +240,7 @@ bool DriveWebContentsManager::ShouldCreateWebContents(
// Prevent redirection if background contents already exists.
if (background_contents_service->GetAppBackgroundContents(
- UTF8ToUTF16(kDriveHostedAppId))) {
+ UTF8ToUTF16(app_id_))) {
return false;
}
BackgroundContents* contents = background_contents_service
@@ -231,7 +248,7 @@ bool DriveWebContentsManager::ShouldCreateWebContents(
route_id,
profile_,
frame_name,
- ASCIIToUTF16(kDriveHostedAppId),
+ ASCIIToUTF16(app_id_),
partition_id,
session_storage_namespace);
@@ -253,7 +270,7 @@ void DriveWebContentsManager::Observe(
const std::string app_id = UTF16ToUTF8(
content::Details<BackgroundContentsOpenedDetails>(details)
->application_id);
- if (app_id == kDriveHostedAppId)
+ if (app_id == app_id_)
OnOfflineInit(true);
}
}
@@ -263,7 +280,11 @@ void DriveWebContentsManager::Observe(
DriveFirstRunController::DriveFirstRunController()
: profile_(ProfileManager::GetDefaultProfile()),
- started_(false) {
+ started_(false),
+ initial_delay_secs_(kInitialDelaySeconds),
+ web_contents_timeout_secs_(kWebContentsTimeoutSeconds),
+ drive_offline_endpoint_url_(kDriveOfflineEndpointUrl),
+ drive_hosted_app_id_(kDriveHostedAppId) {
}
DriveFirstRunController::~DriveFirstRunController() {
@@ -274,7 +295,7 @@ void DriveFirstRunController::EnableOfflineMode() {
started_ = true;
initial_delay_timer_.Start(
FROM_HERE,
- base::TimeDelta::FromSeconds(kInitialDelaySeconds),
+ base::TimeDelta::FromSeconds(initial_delay_secs_),
this,
&DriveFirstRunController::EnableOfflineMode);
return;
@@ -289,7 +310,7 @@ void DriveFirstRunController::EnableOfflineMode() {
ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
- if (!extension_service->GetExtensionById(kDriveHostedAppId, false)) {
+ if (!extension_service->GetExtensionById(drive_hosted_app_id_, false)) {
LOG(WARNING) << "Drive app is not installed.";
OnOfflineInit(false);
return;
@@ -298,7 +319,7 @@ void DriveFirstRunController::EnableOfflineMode() {
BackgroundContentsService* background_contents_service =
BackgroundContentsServiceFactory::GetForProfile(profile_);
if (background_contents_service->GetAppBackgroundContents(
- UTF8ToUTF16(kDriveHostedAppId))) {
+ UTF8ToUTF16(drive_hosted_app_id_))) {
LOG(WARNING) << "Background page for Drive app already exists";
OnOfflineInit(false);
return;
@@ -306,18 +327,44 @@ void DriveFirstRunController::EnableOfflineMode() {
web_contents_manager_.reset(new DriveWebContentsManager(
profile_,
+ drive_hosted_app_id_,
+ drive_offline_endpoint_url_,
base::Bind(&DriveFirstRunController::OnOfflineInit,
base::Unretained(this))));
web_contents_manager_->StartLoad();
web_contents_timer_.Start(
FROM_HERE,
- base::TimeDelta::FromSeconds(kWebContentsTimeoutSeconds),
+ base::TimeDelta::FromSeconds(web_contents_timeout_secs_),
this,
&DriveFirstRunController::OnWebContentsTimedOut);
}
+void DriveFirstRunController::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void DriveFirstRunController::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void DriveFirstRunController::SetDelaysForTest(int initial_delay_secs,
+ int timeout_secs) {
+ DCHECK(!started_);
+ initial_delay_secs_ = initial_delay_secs;
+ web_contents_timeout_secs_ = timeout_secs;
+}
+
+void DriveFirstRunController::SetAppInfoForTest(
+ const std::string& app_id,
+ const std::string& endpoint_url) {
+ DCHECK(!started_);
+ drive_hosted_app_id_ = app_id;
+ drive_offline_endpoint_url_ = endpoint_url;
+}
+
void DriveFirstRunController::OnWebContentsTimedOut() {
LOG(WARNING) << "Timed out waiting for web contents to opt-in";
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnTimedOut());
OnOfflineInit(false);
}
@@ -330,8 +377,11 @@ void DriveFirstRunController::CleanUp() {
void DriveFirstRunController::OnOfflineInit(bool success) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
- ash::Shell::GetInstance()->system_tray_notifier()
- ->NotifyDriveOfflineEnabled();
+ if (success) {
+ ash::Shell::GetInstance()->system_tray_notifier()
+ ->NotifyDriveOfflineEnabled();
+ }
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnCompletion(success));
CleanUp();
}
diff --git a/chrome/browser/chromeos/first_run/drive_first_run_controller.h b/chrome/browser/chromeos/first_run/drive_first_run_controller.h
index 65b3a49..47247ee 100644
--- a/chrome/browser/chromeos/first_run/drive_first_run_controller.h
+++ b/chrome/browser/chromeos/first_run/drive_first_run_controller.h
@@ -5,6 +5,7 @@
#define CHROME_BROWSER_CHROMEOS_FIRST_RUN_DRIVE_FIRST_RUN_CONTROLLER_H_
#include "base/basictypes.h"
+#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "chrome/browser/profiles/profile.h"
@@ -18,12 +19,37 @@ class DriveWebContentsManager;
// destroy itself when the initialization succeeds or fails.
class DriveFirstRunController {
public:
+ class Observer {
+ public:
+ // Called when enabling offline mode times out. OnCompletion will be called
+ // immediately afterwards.
+ virtual void OnTimedOut() = 0;
+
+ // Called when the first run flow finishes, informing the observer of
+ // success or failure.
+ virtual void OnCompletion(bool success) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
DriveFirstRunController();
~DriveFirstRunController();
// Starts the process to enable offline mode for the user's Drive account.
void EnableOfflineMode();
+ // Manages observers of the first run flow.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Set delay times for testing purposes.
+ void SetDelaysForTest(int initial_delay_secs, int timeout_secs);
+
+ // Set the app id and endpoint url for testing purposes.
+ void SetAppInfoForTest(const std::string& app_id,
+ const std::string& endpoint_url);
+
private:
// Used as a callback to indicate whether the offline initialization
// succeeds or fails.
@@ -40,6 +66,12 @@ class DriveFirstRunController {
base::OneShotTimer<DriveFirstRunController> web_contents_timer_;
base::OneShotTimer<DriveFirstRunController> initial_delay_timer_;
bool started_;
+ ObserverList<Observer> observer_list_;
+
+ int initial_delay_secs_;
+ int web_contents_timeout_secs_;
+ std::string drive_offline_endpoint_url_;
+ std::string drive_hosted_app_id_;
DISALLOW_COPY_AND_ASSIGN(DriveFirstRunController);
};
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 68e931a..718de52 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1026,6 +1026,7 @@
'browser/chromeos/file_manager/external_filesystem_apitest.cc',
'browser/chromeos/file_manager/file_manager_browsertest.cc',
'browser/chromeos/file_manager/file_manager_jstest.cc',
+ 'browser/chromeos/first_run/drive_first_run_browsertest.cc',
'browser/chromeos/input_method/input_method_engine_ibus_browserttests.cc',
'browser/chromeos/kiosk_mode/mock_kiosk_mode_settings.cc',
'browser/chromeos/kiosk_mode/mock_kiosk_mode_settings.h',
diff --git a/chrome/test/data/drive_first_run/app.crx b/chrome/test/data/drive_first_run/app.crx
new file mode 100644
index 0000000..4142419
--- /dev/null
+++ b/chrome/test/data/drive_first_run/app.crx
Binary files differ
diff --git a/chrome/test/data/drive_first_run/app.pem b/chrome/test/data/drive_first_run/app.pem
new file mode 100644
index 0000000..b6535d6
--- /dev/null
+++ b/chrome/test/data/drive_first_run/app.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALZCHvqUjR5KG7A/2
+UIEdTi1EkinTLqK5toBIgQdUjf33QFkIHVS3ABIMHmfl5T54hpp3P5RPmuSkP0F3i
+f/BEvue0cSw3YKONlRIoYkAtGWyMh9MMN58FM6mP8Ck7nhSCPLL3QLkEr5TlauoG0
+KESY8NwYYVMeJajI1QfohnuwfAgMBAAECgYBt5Y7Cb6Jr2im567X37bI1AFHHB0Hn
+1Wt/lmFJc9iosMdNWG+N7umDLgQ0wftntAkW/jBoFNr7iEPunYQoP8f5b5F8zt3dy
+1Aor8vBqP55GvvQjqOGlzH0bRE67PoCjTFc4K9avCbgVZfyG+Bb41Li5VZBynJCkg
+kRVoazkaAwKQJBAOvgYLiy89arKYghhavxtaGWaSqL1HmrfZBPaKppK2Hy/ycekS8
+S6MqEfSnmDhKdUBdGHNnlmNkzM5GNoCY7cIUCQQDFzrQn/xPw2p2Wj8QX9HzefBCs
+m1nPMVtoceu2jew1x4WvM1Wr7M/0B3dL+cmvOMRBqHUzjPF3mp1Jj0uhbv1TAkANV
+4zBBcZLHzVjMNo5xptKf5KFSJGFLFEW55b5BKfii3cpRE5cBkrKocHeq9eh7+oG1v
+1sydLifkXtdsBXSUdtAkEAxFWjmaNkDodfLWcrMr+4BTjNcBWOMcoCuYuBc6QwlTy
+h40Enwsr9qXCTp3SaC/JjUew70FwP/DAZ+D5jyisZAwJAZ6UeZMnA1matrZusy1VU
+LSjDL+fHEYQXnBVgdcMaV5XimArRz1h5zBzJdLLR+ibSBhlgVXAuYu83O2XQuk8Wc
+g==
+-----END PRIVATE KEY-----
diff --git a/chrome/test/data/drive_first_run/app/manifest.json b/chrome/test/data/drive_first_run/app/manifest.json
new file mode 100644
index 0000000..f714070
--- /dev/null
+++ b/chrome/test/data/drive_first_run/app/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Drive First Trun Test App",
+ "description": "App similar to Google Drive app for testing enabling offline mode on Chrome OS first run.",
+ "version": "1.0",
+ "offline_enabled": true,
+ "manifest_version": 2,
+ "permissions": [
+ "background"
+ ],
+ "background": {
+ "allow_js_access": false
+ },
+ "app": {
+ "launch": {
+ "web_url": "http://example.com/endpoint.html"
+ },
+ "urls": [
+ "*://example.com",
+ "*://example.com:*"
+ ]
+ }
+}
diff --git a/chrome/test/data/drive_first_run/bad/endpoint.html b/chrome/test/data/drive_first_run/bad/endpoint.html
new file mode 100644
index 0000000..b105b49
--- /dev/null
+++ b/chrome/test/data/drive_first_run/bad/endpoint.html
@@ -0,0 +1,7 @@
+<html>
+<title>Bad Test Endpoint</title>
+<body>
+<p> This endpoint is faulty and does not register a background page for the app. </p>
+</script>
+</body>
+</html>
diff --git a/chrome/test/data/drive_first_run/good/background.html b/chrome/test/data/drive_first_run/good/background.html
new file mode 100644
index 0000000..c03552e
--- /dev/null
+++ b/chrome/test/data/drive_first_run/good/background.html
@@ -0,0 +1,6 @@
+<html>
+<title>Test Background Page</title>
+<body>
+<p> If this background page is opened, the test was successful. </p>
+</body>
+</html>
diff --git a/chrome/test/data/drive_first_run/good/endpoint.html b/chrome/test/data/drive_first_run/good/endpoint.html
new file mode 100644
index 0000000..a1045dd
--- /dev/null
+++ b/chrome/test/data/drive_first_run/good/endpoint.html
@@ -0,0 +1,12 @@
+<html>
+<title>Test Endpoint</title>
+<body>
+<p> This endpoint should open and register a background page for the app. </p>
+
+<script type="text/javascript">
+document.addEventListener("DOMContentLoaded", function(event) {
+ window.open("background.html#0", "bg", "background");
+});
+</script>
+</body>
+</html>