1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/extension_apitest.h"
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/extensions/api/test/test_api.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_system.h"
14 #include "chrome/browser/extensions/unpacked_installer.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/extensions/application_launch.h"
18 #include "chrome/test/base/ui_test_utils.h"
19 #include "content/public/browser/notification_registrar.h"
20 #include "content/public/browser/notification_service.h"
21 #include "extensions/common/extension.h"
22 #include "extensions/common/extension_set.h"
23 #include "net/base/escape.h"
24 #include "net/base/net_util.h"
25 #include "net/test/embedded_test_server/embedded_test_server.h"
26 #include "net/test/embedded_test_server/http_request.h"
27 #include "net/test/embedded_test_server/http_response.h"
28 #include "net/test/spawned_test_server/spawned_test_server.h"
32 const char kTestCustomArg
[] = "customArg";
33 const char kTestServerPort
[] = "testServer.port";
34 const char kTestDataDirectory
[] = "testDataDirectory";
35 const char kTestWebSocketPort
[] = "testWebSocketPort";
36 const char kSpawnedTestServerPort
[] = "spawnedTestServer.port";
38 scoped_ptr
<net::test_server::HttpResponse
> HandleServerRedirectRequest(
39 const net::test_server::HttpRequest
& request
) {
40 if (!StartsWithASCII(request
.relative_url
, "/server-redirect?", true))
41 return scoped_ptr
<net::test_server::HttpResponse
>();
43 size_t query_string_pos
= request
.relative_url
.find('?');
44 std::string redirect_target
=
45 request
.relative_url
.substr(query_string_pos
+ 1);
47 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
48 new net::test_server::BasicHttpResponse
);
49 http_response
->set_code(net::HTTP_MOVED_PERMANENTLY
);
50 http_response
->AddCustomHeader("Location", redirect_target
);
51 return http_response
.PassAs
<net::test_server::HttpResponse
>();
54 scoped_ptr
<net::test_server::HttpResponse
> HandleEchoHeaderRequest(
55 const net::test_server::HttpRequest
& request
) {
56 if (!StartsWithASCII(request
.relative_url
, "/echoheader?", true))
57 return scoped_ptr
<net::test_server::HttpResponse
>();
59 size_t query_string_pos
= request
.relative_url
.find('?');
60 std::string header_name
=
61 request
.relative_url
.substr(query_string_pos
+ 1);
63 std::string header_value
;
64 std::map
<std::string
, std::string
>::const_iterator it
= request
.headers
.find(
66 if (it
!= request
.headers
.end())
67 header_value
= it
->second
;
69 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
70 new net::test_server::BasicHttpResponse
);
71 http_response
->set_code(net::HTTP_OK
);
72 http_response
->set_content(header_value
);
73 return http_response
.PassAs
<net::test_server::HttpResponse
>();
76 scoped_ptr
<net::test_server::HttpResponse
> HandleSetCookieRequest(
77 const net::test_server::HttpRequest
& request
) {
78 if (!StartsWithASCII(request
.relative_url
, "/set-cookie?", true))
79 return scoped_ptr
<net::test_server::HttpResponse
>();
81 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
82 new net::test_server::BasicHttpResponse
);
83 http_response
->set_code(net::HTTP_OK
);
85 size_t query_string_pos
= request
.relative_url
.find('?');
86 std::string cookie_value
=
87 request
.relative_url
.substr(query_string_pos
+ 1);
89 std::vector
<std::string
> cookies
;
90 base::SplitString(cookie_value
, '&', &cookies
);
92 for (size_t i
= 0; i
< cookies
.size(); i
++)
93 http_response
->AddCustomHeader("Set-Cookie", cookies
[i
]);
95 return http_response
.PassAs
<net::test_server::HttpResponse
>();
98 scoped_ptr
<net::test_server::HttpResponse
> HandleSetHeaderRequest(
99 const net::test_server::HttpRequest
& request
) {
100 if (!StartsWithASCII(request
.relative_url
, "/set-header?", true))
101 return scoped_ptr
<net::test_server::HttpResponse
>();
103 size_t query_string_pos
= request
.relative_url
.find('?');
104 std::string escaped_header
=
105 request
.relative_url
.substr(query_string_pos
+ 1);
108 net::UnescapeURLComponent(escaped_header
,
109 net::UnescapeRule::NORMAL
|
110 net::UnescapeRule::SPACES
|
111 net::UnescapeRule::URL_SPECIAL_CHARS
);
113 size_t colon_pos
= header
.find(':');
114 if (colon_pos
== std::string::npos
)
115 return scoped_ptr
<net::test_server::HttpResponse
>();
117 std::string header_name
= header
.substr(0, colon_pos
);
118 // Skip space after colon.
119 std::string header_value
= header
.substr(colon_pos
+ 2);
121 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
122 new net::test_server::BasicHttpResponse
);
123 http_response
->set_code(net::HTTP_OK
);
124 http_response
->AddCustomHeader(header_name
, header_value
);
125 return http_response
.PassAs
<net::test_server::HttpResponse
>();
130 ExtensionApiTest::ExtensionApiTest() {
131 embedded_test_server()->RegisterRequestHandler(
132 base::Bind(&HandleServerRedirectRequest
));
133 embedded_test_server()->RegisterRequestHandler(
134 base::Bind(&HandleEchoHeaderRequest
));
135 embedded_test_server()->RegisterRequestHandler(
136 base::Bind(&HandleSetCookieRequest
));
137 embedded_test_server()->RegisterRequestHandler(
138 base::Bind(&HandleSetHeaderRequest
));
141 ExtensionApiTest::~ExtensionApiTest() {}
143 ExtensionApiTest::ResultCatcher::ResultCatcher()
144 : profile_restriction_(NULL
),
146 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_PASSED
,
147 content::NotificationService::AllSources());
148 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_TEST_FAILED
,
149 content::NotificationService::AllSources());
152 ExtensionApiTest::ResultCatcher::~ResultCatcher() {
155 bool ExtensionApiTest::ResultCatcher::GetNextResult() {
156 // Depending on the tests, multiple results can come in from a single call
157 // to RunMessageLoop(), so we maintain a queue of results and just pull them
158 // off as the test calls this, going to the run loop only when the queue is
160 if (results_
.empty()) {
162 content::RunMessageLoop();
166 if (!results_
.empty()) {
167 bool ret
= results_
.front();
168 results_
.pop_front();
169 message_
= messages_
.front();
170 messages_
.pop_front();
178 void ExtensionApiTest::ResultCatcher::Observe(
179 int type
, const content::NotificationSource
& source
,
180 const content::NotificationDetails
& details
) {
181 if (profile_restriction_
&&
182 content::Source
<Profile
>(source
).ptr() != profile_restriction_
) {
187 case chrome::NOTIFICATION_EXTENSION_TEST_PASSED
:
188 VLOG(1) << "Got EXTENSION_TEST_PASSED notification.";
189 results_
.push_back(true);
190 messages_
.push_back(std::string());
192 base::MessageLoopForUI::current()->Quit();
195 case chrome::NOTIFICATION_EXTENSION_TEST_FAILED
:
196 VLOG(1) << "Got EXTENSION_TEST_FAILED notification.";
197 results_
.push_back(false);
198 messages_
.push_back(*(content::Details
<std::string
>(details
).ptr()));
200 base::MessageLoopForUI::current()->Quit();
208 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
209 DCHECK(!test_config_
.get()) << "Previous test did not clear config state.";
210 test_config_
.reset(new base::DictionaryValue());
211 test_config_
->SetString(kTestDataDirectory
,
212 net::FilePathToFileURL(test_data_dir_
).spec());
213 test_config_
->SetInteger(kTestWebSocketPort
, 0);
214 extensions::TestGetConfigFunction::set_test_config_state(
218 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
219 extensions::TestGetConfigFunction::set_test_config_state(NULL
);
220 test_config_
.reset(NULL
);
223 bool ExtensionApiTest::RunExtensionTest(const std::string
& extension_name
) {
224 return RunExtensionTestImpl(
225 extension_name
, std::string(), NULL
, kFlagEnableFileAccess
);
228 bool ExtensionApiTest::RunExtensionTestIncognito(
229 const std::string
& extension_name
) {
230 return RunExtensionTestImpl(extension_name
,
233 kFlagEnableIncognito
| kFlagEnableFileAccess
);
236 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
237 const std::string
& extension_name
) {
238 return RunExtensionTestImpl(
239 extension_name
, std::string(), NULL
, kFlagIgnoreManifestWarnings
);
242 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
243 const std::string
& extension_name
) {
244 return RunExtensionTestImpl(
248 kFlagEnableFileAccess
| kFlagAllowOldManifestVersions
);
251 bool ExtensionApiTest::RunComponentExtensionTest(
252 const std::string
& extension_name
) {
253 return RunExtensionTestImpl(extension_name
,
256 kFlagEnableFileAccess
| kFlagLoadAsComponent
);
259 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
260 const std::string
& extension_name
) {
261 return RunExtensionTestImpl(extension_name
, std::string(), NULL
, kFlagNone
);
264 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
265 const std::string
& extension_name
) {
266 return RunExtensionTestImpl(
267 extension_name
, std::string(), NULL
, kFlagEnableIncognito
);
270 bool ExtensionApiTest::RunExtensionSubtest(const std::string
& extension_name
,
271 const std::string
& page_url
) {
272 return RunExtensionSubtest(extension_name
, page_url
, kFlagEnableFileAccess
);
275 bool ExtensionApiTest::RunExtensionSubtest(const std::string
& extension_name
,
276 const std::string
& page_url
,
278 DCHECK(!page_url
.empty()) << "Argument page_url is required.";
279 // See http://crbug.com/177163 for details.
280 #if defined(OS_WIN) && !defined(NDEBUG)
281 LOG(WARNING
) << "Workaround for 177163, prematurely returning";
284 return RunExtensionTestImpl(extension_name
, page_url
, NULL
, flags
);
288 bool ExtensionApiTest::RunPageTest(const std::string
& page_url
) {
289 return RunExtensionSubtest(std::string(), page_url
);
292 bool ExtensionApiTest::RunPageTest(const std::string
& page_url
,
294 return RunExtensionSubtest(std::string(), page_url
, flags
);
297 bool ExtensionApiTest::RunPlatformAppTest(const std::string
& extension_name
) {
298 return RunExtensionTestImpl(
299 extension_name
, std::string(), NULL
, kFlagLaunchPlatformApp
);
302 bool ExtensionApiTest::RunPlatformAppTestWithArg(
303 const std::string
& extension_name
, const char* custom_arg
) {
304 return RunExtensionTestImpl(
305 extension_name
, std::string(), custom_arg
, kFlagLaunchPlatformApp
);
308 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
309 const std::string
& extension_name
, int flags
) {
310 return RunExtensionTestImpl(
311 extension_name
, std::string(), NULL
, flags
| kFlagLaunchPlatformApp
);
314 // Load |extension_name| extension and/or |page_url| and wait for
315 // PASSED or FAILED notification.
316 bool ExtensionApiTest::RunExtensionTestImpl(const std::string
& extension_name
,
317 const std::string
& page_url
,
318 const char* custom_arg
,
320 bool load_as_component
= (flags
& kFlagLoadAsComponent
) != 0;
321 bool launch_platform_app
= (flags
& kFlagLaunchPlatformApp
) != 0;
322 bool use_incognito
= (flags
& kFlagUseIncognito
) != 0;
324 if (custom_arg
&& custom_arg
[0])
325 test_config_
->SetString(kTestCustomArg
, custom_arg
);
327 ResultCatcher catcher
;
328 DCHECK(!extension_name
.empty() || !page_url
.empty()) <<
329 "extension_name and page_url cannot both be empty";
331 const extensions::Extension
* extension
= NULL
;
332 if (!extension_name
.empty()) {
333 base::FilePath extension_path
= test_data_dir_
.AppendASCII(extension_name
);
334 if (load_as_component
) {
335 extension
= LoadExtensionAsComponent(extension_path
);
337 int browser_test_flags
= ExtensionBrowserTest::kFlagNone
;
338 if (flags
& kFlagEnableIncognito
)
339 browser_test_flags
|= ExtensionBrowserTest::kFlagEnableIncognito
;
340 if (flags
& kFlagEnableFileAccess
)
341 browser_test_flags
|= ExtensionBrowserTest::kFlagEnableFileAccess
;
342 if (flags
& kFlagIgnoreManifestWarnings
)
343 browser_test_flags
|= ExtensionBrowserTest::kFlagIgnoreManifestWarnings
;
344 if (flags
& kFlagAllowOldManifestVersions
) {
345 browser_test_flags
|=
346 ExtensionBrowserTest::kFlagAllowOldManifestVersions
;
348 extension
= LoadExtensionWithFlags(extension_path
, browser_test_flags
);
351 message_
= "Failed to load extension.";
356 // If there is a page_url to load, navigate it.
357 if (!page_url
.empty()) {
358 GURL url
= GURL(page_url
);
360 // Note: We use is_valid() here in the expectation that the provided url
361 // may lack a scheme & host and thus be a relative url within the loaded
363 if (!url
.is_valid()) {
364 DCHECK(!extension_name
.empty()) <<
365 "Relative page_url given with no extension_name";
367 url
= extension
->GetResourceURL(page_url
);
371 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url
);
373 ui_test_utils::NavigateToURL(browser(), url
);
374 } else if (launch_platform_app
) {
375 AppLaunchParams
params(browser()->profile(),
377 extensions::LAUNCH_CONTAINER_NONE
,
379 params
.command_line
= *CommandLine::ForCurrentProcess();
380 OpenApplication(params
);
383 if (!catcher
.GetNextResult()) {
384 message_
= catcher
.message();
391 // Test that exactly one extension is loaded, and return it.
392 const extensions::Extension
* ExtensionApiTest::GetSingleLoadedExtension() {
393 ExtensionService
* service
= extensions::ExtensionSystem::Get(
394 browser()->profile())->extension_service();
396 const extensions::Extension
* extension
= NULL
;
397 for (extensions::ExtensionSet::const_iterator it
=
398 service
->extensions()->begin();
399 it
!= service
->extensions()->end(); ++it
) {
400 // Ignore any component extensions. They are automatically loaded into all
401 // profiles and aren't the extension we're looking for here.
402 if ((*it
)->location() == extensions::Manifest::COMPONENT
)
405 if (extension
!= NULL
) {
406 // TODO(yoz): this is misleading; it counts component extensions.
407 message_
= base::StringPrintf(
408 "Expected only one extension to be present. Found %u.",
409 static_cast<unsigned>(service
->extensions()->size()));
413 extension
= it
->get();
417 message_
= "extension pointer is NULL.";
423 bool ExtensionApiTest::StartEmbeddedTestServer() {
424 if (!embedded_test_server()->InitializeAndWaitUntilReady())
427 // Build a dictionary of values that tests can use to build URLs that
428 // access the test server and local file system. Tests can see these values
429 // using the extension API function chrome.test.getConfig().
430 test_config_
->SetInteger(kTestServerPort
,
431 embedded_test_server()->port());
436 bool ExtensionApiTest::StartWebSocketServer(
437 const base::FilePath
& root_directory
) {
438 websocket_server_
.reset(new net::SpawnedTestServer(
439 net::SpawnedTestServer::TYPE_WS
,
440 net::SpawnedTestServer::kLocalhost
,
443 if (!websocket_server_
->Start())
446 test_config_
->SetInteger(kTestWebSocketPort
,
447 websocket_server_
->host_port_pair().port());
452 bool ExtensionApiTest::StartSpawnedTestServer() {
453 if (!test_server()->Start())
456 // Build a dictionary of values that tests can use to build URLs that
457 // access the test server and local file system. Tests can see these values
458 // using the extension API function chrome.test.getConfig().
459 test_config_
->SetInteger(kSpawnedTestServerPort
,
460 test_server()->host_port_pair().port());
465 void ExtensionApiTest::SetUpCommandLine(CommandLine
* command_line
) {
466 ExtensionBrowserTest::SetUpCommandLine(command_line
);
467 test_data_dir_
= test_data_dir_
.AppendASCII("api_test");