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/extensions/extension_service.h"
11 #include "chrome/browser/extensions/unpacked_installer.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/extensions/application_launch.h"
15 #include "chrome/test/base/ui_test_utils.h"
16 #include "extensions/browser/api/test/test_api.h"
17 #include "extensions/browser/extension_system.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/extension_set.h"
20 #include "extensions/test/result_catcher.h"
21 #include "net/base/escape.h"
22 #include "net/base/filename_util.h"
23 #include "net/test/embedded_test_server/embedded_test_server.h"
24 #include "net/test/embedded_test_server/http_request.h"
25 #include "net/test/embedded_test_server/http_response.h"
26 #include "net/test/spawned_test_server/spawned_test_server.h"
30 const char kTestCustomArg
[] = "customArg";
31 const char kTestServerPort
[] = "testServer.port";
32 const char kTestDataDirectory
[] = "testDataDirectory";
33 const char kTestWebSocketPort
[] = "testWebSocketPort";
34 const char kFtpServerPort
[] = "ftpServer.port";
35 const char kSpawnedTestServerPort
[] = "spawnedTestServer.port";
37 scoped_ptr
<net::test_server::HttpResponse
> HandleServerRedirectRequest(
38 const net::test_server::HttpRequest
& request
) {
39 if (!StartsWithASCII(request
.relative_url
, "/server-redirect?", true))
42 size_t query_string_pos
= request
.relative_url
.find('?');
43 std::string redirect_target
=
44 request
.relative_url
.substr(query_string_pos
+ 1);
46 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
47 new net::test_server::BasicHttpResponse
);
48 http_response
->set_code(net::HTTP_MOVED_PERMANENTLY
);
49 http_response
->AddCustomHeader("Location", redirect_target
);
50 return http_response
.Pass();
53 scoped_ptr
<net::test_server::HttpResponse
> HandleEchoHeaderRequest(
54 const net::test_server::HttpRequest
& request
) {
55 if (!StartsWithASCII(request
.relative_url
, "/echoheader?", true))
58 size_t query_string_pos
= request
.relative_url
.find('?');
59 std::string header_name
=
60 request
.relative_url
.substr(query_string_pos
+ 1);
62 std::string header_value
;
63 std::map
<std::string
, std::string
>::const_iterator it
= request
.headers
.find(
65 if (it
!= request
.headers
.end())
66 header_value
= it
->second
;
68 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
69 new net::test_server::BasicHttpResponse
);
70 http_response
->set_code(net::HTTP_OK
);
71 http_response
->set_content(header_value
);
72 return http_response
.Pass();
75 scoped_ptr
<net::test_server::HttpResponse
> HandleSetCookieRequest(
76 const net::test_server::HttpRequest
& request
) {
77 if (!StartsWithASCII(request
.relative_url
, "/set-cookie?", true))
80 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
81 new net::test_server::BasicHttpResponse
);
82 http_response
->set_code(net::HTTP_OK
);
84 size_t query_string_pos
= request
.relative_url
.find('?');
85 std::string cookie_value
=
86 request
.relative_url
.substr(query_string_pos
+ 1);
88 std::vector
<std::string
> cookies
;
89 base::SplitString(cookie_value
, '&', &cookies
);
91 for (size_t i
= 0; i
< cookies
.size(); i
++)
92 http_response
->AddCustomHeader("Set-Cookie", cookies
[i
]);
94 return http_response
.Pass();
97 scoped_ptr
<net::test_server::HttpResponse
> HandleSetHeaderRequest(
98 const net::test_server::HttpRequest
& request
) {
99 if (!StartsWithASCII(request
.relative_url
, "/set-header?", true))
102 size_t query_string_pos
= request
.relative_url
.find('?');
103 std::string escaped_header
=
104 request
.relative_url
.substr(query_string_pos
+ 1);
107 net::UnescapeURLComponent(escaped_header
,
108 net::UnescapeRule::NORMAL
|
109 net::UnescapeRule::SPACES
|
110 net::UnescapeRule::URL_SPECIAL_CHARS
);
112 size_t colon_pos
= header
.find(':');
113 if (colon_pos
== std::string::npos
)
114 return scoped_ptr
<net::test_server::HttpResponse
>();
116 std::string header_name
= header
.substr(0, colon_pos
);
117 // Skip space after colon.
118 std::string header_value
= header
.substr(colon_pos
+ 2);
120 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
121 new net::test_server::BasicHttpResponse
);
122 http_response
->set_code(net::HTTP_OK
);
123 http_response
->AddCustomHeader(header_name
, header_value
);
124 return http_response
.Pass();
129 ExtensionApiTest::ExtensionApiTest() {
130 embedded_test_server()->RegisterRequestHandler(
131 base::Bind(&HandleServerRedirectRequest
));
132 embedded_test_server()->RegisterRequestHandler(
133 base::Bind(&HandleEchoHeaderRequest
));
134 embedded_test_server()->RegisterRequestHandler(
135 base::Bind(&HandleSetCookieRequest
));
136 embedded_test_server()->RegisterRequestHandler(
137 base::Bind(&HandleSetHeaderRequest
));
140 ExtensionApiTest::~ExtensionApiTest() {}
142 void ExtensionApiTest::SetUpInProcessBrowserTestFixture() {
143 DCHECK(!test_config_
.get()) << "Previous test did not clear config state.";
144 test_config_
.reset(new base::DictionaryValue());
145 test_config_
->SetString(kTestDataDirectory
,
146 net::FilePathToFileURL(test_data_dir_
).spec());
147 test_config_
->SetInteger(kTestWebSocketPort
, 0);
148 extensions::TestGetConfigFunction::set_test_config_state(
152 void ExtensionApiTest::TearDownInProcessBrowserTestFixture() {
153 extensions::TestGetConfigFunction::set_test_config_state(NULL
);
154 test_config_
.reset(NULL
);
157 bool ExtensionApiTest::RunExtensionTest(const std::string
& extension_name
) {
158 return RunExtensionTestImpl(
159 extension_name
, std::string(), NULL
, kFlagEnableFileAccess
);
162 bool ExtensionApiTest::RunExtensionTestIncognito(
163 const std::string
& extension_name
) {
164 return RunExtensionTestImpl(extension_name
,
167 kFlagEnableIncognito
| kFlagEnableFileAccess
);
170 bool ExtensionApiTest::RunExtensionTestIgnoreManifestWarnings(
171 const std::string
& extension_name
) {
172 return RunExtensionTestImpl(
173 extension_name
, std::string(), NULL
, kFlagIgnoreManifestWarnings
);
176 bool ExtensionApiTest::RunExtensionTestAllowOldManifestVersion(
177 const std::string
& extension_name
) {
178 return RunExtensionTestImpl(
182 kFlagEnableFileAccess
| kFlagAllowOldManifestVersions
);
185 bool ExtensionApiTest::RunComponentExtensionTest(
186 const std::string
& extension_name
) {
187 return RunExtensionTestImpl(extension_name
,
190 kFlagEnableFileAccess
| kFlagLoadAsComponent
);
193 bool ExtensionApiTest::RunExtensionTestNoFileAccess(
194 const std::string
& extension_name
) {
195 return RunExtensionTestImpl(extension_name
, std::string(), NULL
, kFlagNone
);
198 bool ExtensionApiTest::RunExtensionTestIncognitoNoFileAccess(
199 const std::string
& extension_name
) {
200 return RunExtensionTestImpl(
201 extension_name
, std::string(), NULL
, kFlagEnableIncognito
);
204 bool ExtensionApiTest::RunExtensionSubtest(const std::string
& extension_name
,
205 const std::string
& page_url
) {
206 return RunExtensionSubtest(extension_name
, page_url
, kFlagEnableFileAccess
);
209 bool ExtensionApiTest::RunExtensionSubtest(const std::string
& extension_name
,
210 const std::string
& page_url
,
212 DCHECK(!page_url
.empty()) << "Argument page_url is required.";
213 // See http://crbug.com/177163 for details.
214 #if defined(OS_WIN) && !defined(NDEBUG)
215 LOG(WARNING
) << "Workaround for 177163, prematurely returning";
218 return RunExtensionTestImpl(extension_name
, page_url
, NULL
, flags
);
223 bool ExtensionApiTest::RunPageTest(const std::string
& page_url
) {
224 return RunExtensionSubtest(std::string(), page_url
);
227 bool ExtensionApiTest::RunPageTest(const std::string
& page_url
,
229 return RunExtensionSubtest(std::string(), page_url
, flags
);
232 bool ExtensionApiTest::RunPlatformAppTest(const std::string
& extension_name
) {
233 return RunExtensionTestImpl(
234 extension_name
, std::string(), NULL
, kFlagLaunchPlatformApp
);
237 bool ExtensionApiTest::RunPlatformAppTestWithArg(
238 const std::string
& extension_name
, const char* custom_arg
) {
239 return RunExtensionTestImpl(
240 extension_name
, std::string(), custom_arg
, kFlagLaunchPlatformApp
);
243 bool ExtensionApiTest::RunPlatformAppTestWithFlags(
244 const std::string
& extension_name
, int flags
) {
245 return RunExtensionTestImpl(
246 extension_name
, std::string(), NULL
, flags
| kFlagLaunchPlatformApp
);
249 // Load |extension_name| extension and/or |page_url| and wait for
250 // PASSED or FAILED notification.
251 bool ExtensionApiTest::RunExtensionTestImpl(const std::string
& extension_name
,
252 const std::string
& page_url
,
253 const char* custom_arg
,
255 bool load_as_component
= (flags
& kFlagLoadAsComponent
) != 0;
256 bool launch_platform_app
= (flags
& kFlagLaunchPlatformApp
) != 0;
257 bool use_incognito
= (flags
& kFlagUseIncognito
) != 0;
259 if (custom_arg
&& custom_arg
[0])
260 test_config_
->SetString(kTestCustomArg
, custom_arg
);
262 extensions::ResultCatcher catcher
;
263 DCHECK(!extension_name
.empty() || !page_url
.empty()) <<
264 "extension_name and page_url cannot both be empty";
266 const extensions::Extension
* extension
= NULL
;
267 if (!extension_name
.empty()) {
268 base::FilePath extension_path
= test_data_dir_
.AppendASCII(extension_name
);
269 if (load_as_component
) {
270 extension
= LoadExtensionAsComponent(extension_path
);
272 int browser_test_flags
= ExtensionBrowserTest::kFlagNone
;
273 if (flags
& kFlagEnableIncognito
)
274 browser_test_flags
|= ExtensionBrowserTest::kFlagEnableIncognito
;
275 if (flags
& kFlagEnableFileAccess
)
276 browser_test_flags
|= ExtensionBrowserTest::kFlagEnableFileAccess
;
277 if (flags
& kFlagIgnoreManifestWarnings
)
278 browser_test_flags
|= ExtensionBrowserTest::kFlagIgnoreManifestWarnings
;
279 if (flags
& kFlagAllowOldManifestVersions
) {
280 browser_test_flags
|=
281 ExtensionBrowserTest::kFlagAllowOldManifestVersions
;
283 extension
= LoadExtensionWithFlags(extension_path
, browser_test_flags
);
286 message_
= "Failed to load extension.";
291 // If there is a page_url to load, navigate it.
292 if (!page_url
.empty()) {
293 GURL url
= GURL(page_url
);
295 // Note: We use is_valid() here in the expectation that the provided url
296 // may lack a scheme & host and thus be a relative url within the loaded
298 if (!url
.is_valid()) {
299 DCHECK(!extension_name
.empty()) <<
300 "Relative page_url given with no extension_name";
302 url
= extension
->GetResourceURL(page_url
);
306 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), url
);
308 ui_test_utils::NavigateToURL(browser(), url
);
309 } else if (launch_platform_app
) {
310 AppLaunchParams
params(browser()->profile(),
312 extensions::LAUNCH_CONTAINER_NONE
,
314 params
.command_line
= *CommandLine::ForCurrentProcess();
315 OpenApplication(params
);
318 if (!catcher
.GetNextResult()) {
319 message_
= catcher
.message();
326 // Test that exactly one extension is loaded, and return it.
327 const extensions::Extension
* ExtensionApiTest::GetSingleLoadedExtension() {
328 ExtensionService
* service
= extensions::ExtensionSystem::Get(
329 browser()->profile())->extension_service();
331 const extensions::Extension
* extension
= NULL
;
332 for (extensions::ExtensionSet::const_iterator it
=
333 service
->extensions()->begin();
334 it
!= service
->extensions()->end(); ++it
) {
335 // Ignore any component extensions. They are automatically loaded into all
336 // profiles and aren't the extension we're looking for here.
337 if ((*it
)->location() == extensions::Manifest::COMPONENT
)
340 if (extension
!= NULL
) {
341 // TODO(yoz): this is misleading; it counts component extensions.
342 message_
= base::StringPrintf(
343 "Expected only one extension to be present. Found %u.",
344 static_cast<unsigned>(service
->extensions()->size()));
348 extension
= it
->get();
352 message_
= "extension pointer is NULL.";
358 bool ExtensionApiTest::StartEmbeddedTestServer() {
359 if (!embedded_test_server()->InitializeAndWaitUntilReady())
362 // Build a dictionary of values that tests can use to build URLs that
363 // access the test server and local file system. Tests can see these values
364 // using the extension API function chrome.test.getConfig().
365 test_config_
->SetInteger(kTestServerPort
,
366 embedded_test_server()->port());
371 bool ExtensionApiTest::StartWebSocketServer(
372 const base::FilePath
& root_directory
) {
373 websocket_server_
.reset(new net::SpawnedTestServer(
374 net::SpawnedTestServer::TYPE_WS
,
375 net::SpawnedTestServer::kLocalhost
,
378 if (!websocket_server_
->Start())
381 test_config_
->SetInteger(kTestWebSocketPort
,
382 websocket_server_
->host_port_pair().port());
387 bool ExtensionApiTest::StartFTPServer(const base::FilePath
& root_directory
) {
388 ftp_server_
.reset(new net::SpawnedTestServer(
389 net::SpawnedTestServer::TYPE_FTP
,
390 net::SpawnedTestServer::kLocalhost
,
393 if (!ftp_server_
->Start())
396 test_config_
->SetInteger(kFtpServerPort
,
397 ftp_server_
->host_port_pair().port());
402 bool ExtensionApiTest::StartSpawnedTestServer() {
403 if (!test_server()->Start())
406 // Build a dictionary of values that tests can use to build URLs that
407 // access the test server and local file system. Tests can see these values
408 // using the extension API function chrome.test.getConfig().
409 test_config_
->SetInteger(kSpawnedTestServerPort
,
410 test_server()->host_port_pair().port());
415 void ExtensionApiTest::SetUpCommandLine(CommandLine
* command_line
) {
416 ExtensionBrowserTest::SetUpCommandLine(command_line
);
417 test_data_dir_
= test_data_dir_
.AppendASCII("api_test");