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.
8 #include "base/callback.h"
9 #include "base/compiler_specific.h"
10 #include "base/files/file_path.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "base/synchronization/lock.h"
15 #include "base/threading/thread.h"
16 #include "base/values.h"
17 #include "chrome/test/chromedriver/chrome/status.h"
18 #include "chrome/test/chromedriver/chrome/stub_chrome.h"
19 #include "chrome/test/chromedriver/chrome/stub_web_view.h"
20 #include "chrome/test/chromedriver/chrome/web_view.h"
21 #include "chrome/test/chromedriver/command_listener_proxy.h"
22 #include "chrome/test/chromedriver/commands.h"
23 #include "chrome/test/chromedriver/element_commands.h"
24 #include "chrome/test/chromedriver/session.h"
25 #include "chrome/test/chromedriver/session_commands.h"
26 #include "chrome/test/chromedriver/window_commands.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "third_party/webdriver/atoms.h"
32 void OnGetStatus(const Status
& status
,
33 scoped_ptr
<base::Value
> value
,
34 const std::string
& session_id
) {
35 ASSERT_EQ(kOk
, status
.code());
36 base::DictionaryValue
* dict
;
37 ASSERT_TRUE(value
->GetAsDictionary(&dict
));
39 ASSERT_TRUE(dict
->Get("os.name", &unused
));
40 ASSERT_TRUE(dict
->Get("os.version", &unused
));
41 ASSERT_TRUE(dict
->Get("os.arch", &unused
));
42 ASSERT_TRUE(dict
->Get("build.version", &unused
));
47 TEST(CommandsTest
, GetStatus
) {
48 base::DictionaryValue params
;
49 ExecuteGetStatus(params
, std::string(), base::Bind(&OnGetStatus
));
56 const base::DictionaryValue
& params
,
57 const std::string
& session_id
,
58 const CommandCallback
& callback
) {
60 EXPECT_STREQ("id", session_id
.c_str());
62 EXPECT_STREQ("id2", session_id
.c_str());
65 callback
.Run(Status(kOk
), scoped_ptr
<base::Value
>(), session_id
);
68 void OnQuitAll(const Status
& status
,
69 scoped_ptr
<base::Value
> value
,
70 const std::string
& session_id
) {
71 ASSERT_EQ(kOk
, status
.code());
72 ASSERT_FALSE(value
.get());
77 TEST(CommandsTest
, QuitAll
) {
79 Session
session("id");
80 Session
session2("id2");
81 map
[session
.id
] = make_linked_ptr(new base::Thread("1"));
82 map
[session2
.id
] = make_linked_ptr(new base::Thread("2"));
85 Command cmd
= base::Bind(&ExecuteStubQuit
, &count
);
86 base::DictionaryValue params
;
87 base::MessageLoop loop
;
88 ExecuteQuitAll(cmd
, &map
, params
, std::string(), base::Bind(&OnQuitAll
));
94 Status
ExecuteSimpleCommand(
95 const std::string
& expected_id
,
96 base::DictionaryValue
* expected_params
,
99 const base::DictionaryValue
& params
,
100 scoped_ptr
<base::Value
>* return_value
) {
101 EXPECT_EQ(expected_id
, session
->id
);
102 EXPECT_TRUE(expected_params
->Equals(¶ms
));
103 return_value
->reset(value
->DeepCopy());
104 session
->quit
= true;
108 void OnSimpleCommand(base::RunLoop
* run_loop
,
109 const std::string
& expected_session_id
,
110 base::Value
* expected_value
,
111 const Status
& status
,
112 scoped_ptr
<base::Value
> value
,
113 const std::string
& session_id
) {
114 ASSERT_EQ(kOk
, status
.code());
115 ASSERT_TRUE(expected_value
->Equals(value
.get()));
116 ASSERT_EQ(expected_session_id
, session_id
);
122 TEST(CommandsTest
, ExecuteSessionCommand
) {
123 SessionThreadMap map
;
124 linked_ptr
<base::Thread
> thread(new base::Thread("1"));
125 ASSERT_TRUE(thread
->Start());
126 std::string
id("id");
127 thread
->message_loop()->PostTask(
129 base::Bind(&internal::CreateSessionOnSessionThreadForTesting
, id
));
132 base::DictionaryValue params
;
133 params
.SetInteger("param", 5);
134 base::FundamentalValue
expected_value(6);
135 SessionCommand cmd
= base::Bind(
136 &ExecuteSimpleCommand
, id
, ¶ms
, &expected_value
);
138 base::MessageLoop loop
;
139 base::RunLoop run_loop
;
140 ExecuteSessionCommand(
147 base::Bind(&OnSimpleCommand
, &run_loop
, id
, &expected_value
));
153 Status
ShouldNotBeCalled(
155 const base::DictionaryValue
& params
,
156 scoped_ptr
<base::Value
>* value
) {
161 void OnNoSuchSession(const Status
& status
,
162 scoped_ptr
<base::Value
> value
,
163 const std::string
& session_id
) {
164 EXPECT_EQ(kNoSuchSession
, status
.code());
165 EXPECT_FALSE(value
.get());
168 void OnNoSuchSessionIsOk(const Status
& status
,
169 scoped_ptr
<base::Value
> value
,
170 const std::string
& session_id
) {
171 EXPECT_EQ(kOk
, status
.code());
172 EXPECT_FALSE(value
.get());
177 TEST(CommandsTest
, ExecuteSessionCommandOnNoSuchSession
) {
178 SessionThreadMap map
;
179 base::DictionaryValue params
;
180 ExecuteSessionCommand(&map
,
182 base::Bind(&ShouldNotBeCalled
),
186 base::Bind(&OnNoSuchSession
));
189 TEST(CommandsTest
, ExecuteSessionCommandOnNoSuchSessionWhenItExpectsOk
) {
190 SessionThreadMap map
;
191 base::DictionaryValue params
;
192 ExecuteSessionCommand(&map
,
194 base::Bind(&ShouldNotBeCalled
),
198 base::Bind(&OnNoSuchSessionIsOk
));
203 void OnNoSuchSessionAndQuit(base::RunLoop
* run_loop
,
204 const Status
& status
,
205 scoped_ptr
<base::Value
> value
,
206 const std::string
& session_id
) {
208 EXPECT_EQ(kNoSuchSession
, status
.code());
209 EXPECT_FALSE(value
.get());
214 TEST(CommandsTest
, ExecuteSessionCommandOnJustDeletedSession
) {
215 SessionThreadMap map
;
216 linked_ptr
<base::Thread
> thread(new base::Thread("1"));
217 ASSERT_TRUE(thread
->Start());
218 std::string
id("id");
221 base::MessageLoop loop
;
222 base::RunLoop run_loop
;
223 ExecuteSessionCommand(&map
,
225 base::Bind(&ShouldNotBeCalled
),
227 base::DictionaryValue(),
229 base::Bind(&OnNoSuchSessionAndQuit
, &run_loop
));
236 kElementExistsQueryOnce
= 0,
237 kElementExistsQueryTwice
,
238 kElementNotExistsQueryOnce
,
239 kElementExistsTimeout
242 class FindElementWebView
: public StubWebView
{
244 FindElementWebView(bool only_one
, TestScenario scenario
)
245 : StubWebView("1"), only_one_(only_one
), scenario_(scenario
),
248 case kElementExistsQueryOnce
:
249 case kElementExistsQueryTwice
:
250 case kElementExistsTimeout
: {
252 base::DictionaryValue element
;
253 element
.SetString("ELEMENT", "1");
254 result_
.reset(element
.DeepCopy());
256 base::DictionaryValue element1
;
257 element1
.SetString("ELEMENT", "1");
258 base::DictionaryValue element2
;
259 element2
.SetString("ELEMENT", "2");
260 base::ListValue list
;
261 list
.Append(element1
.DeepCopy());
262 list
.Append(element2
.DeepCopy());
263 result_
.reset(list
.DeepCopy());
267 case kElementNotExistsQueryOnce
: {
269 result_
.reset(base::Value::CreateNullValue());
271 result_
.reset(new base::ListValue());
276 ~FindElementWebView() override
{}
278 void Verify(const std::string
& expected_frame
,
279 const base::ListValue
* expected_args
,
280 const base::Value
* actrual_result
) {
281 EXPECT_EQ(expected_frame
, frame_
);
282 std::string function
;
284 function
= webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT
);
286 function
= webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS
);
287 EXPECT_EQ(function
, function_
);
288 ASSERT_TRUE(args_
.get());
289 EXPECT_TRUE(expected_args
->Equals(args_
.get()));
290 ASSERT_TRUE(actrual_result
);
291 EXPECT_TRUE(result_
->Equals(actrual_result
));
294 // Overridden from WebView:
295 Status
CallFunction(const std::string
& frame
,
296 const std::string
& function
,
297 const base::ListValue
& args
,
298 scoped_ptr
<base::Value
>* result
) override
{
300 if (scenario_
== kElementExistsTimeout
||
301 (scenario_
== kElementExistsQueryTwice
&& current_count_
== 1)) {
302 // Always return empty result when testing timeout.
304 result
->reset(base::Value::CreateNullValue());
306 result
->reset(new base::ListValue());
309 case kElementExistsQueryOnce
:
310 case kElementNotExistsQueryOnce
: {
311 EXPECT_EQ(1, current_count_
);
314 case kElementExistsQueryTwice
: {
315 EXPECT_EQ(2, current_count_
);
323 result
->reset(result_
->DeepCopy());
325 function_
= function
;
326 args_
.reset(args
.DeepCopy());
333 TestScenario scenario_
;
336 std::string function_
;
337 scoped_ptr
<base::ListValue
> args_
;
338 scoped_ptr
<base::Value
> result_
;
343 TEST(CommandsTest
, SuccessfulFindElement
) {
344 FindElementWebView
web_view(true, kElementExistsQueryTwice
);
345 Session
session("id");
346 session
.implicit_wait
= base::TimeDelta::FromSeconds(1);
347 session
.SwitchToSubFrame("frame_id1", std::string());
348 base::DictionaryValue params
;
349 params
.SetString("using", "id");
350 params
.SetString("value", "a");
351 scoped_ptr
<base::Value
> result
;
353 ExecuteFindElement(1, &session
, &web_view
, params
, &result
).code());
354 base::DictionaryValue param
;
355 param
.SetString("id", "a");
356 base::ListValue expected_args
;
357 expected_args
.Append(param
.DeepCopy());
358 web_view
.Verify("frame_id1", &expected_args
, result
.get());
361 TEST(CommandsTest
, FailedFindElement
) {
362 FindElementWebView
web_view(true, kElementNotExistsQueryOnce
);
363 Session
session("id");
364 base::DictionaryValue params
;
365 params
.SetString("using", "id");
366 params
.SetString("value", "a");
367 scoped_ptr
<base::Value
> result
;
368 ASSERT_EQ(kNoSuchElement
,
369 ExecuteFindElement(1, &session
, &web_view
, params
, &result
).code());
372 TEST(CommandsTest
, SuccessfulFindElements
) {
373 FindElementWebView
web_view(false, kElementExistsQueryTwice
);
374 Session
session("id");
375 session
.implicit_wait
= base::TimeDelta::FromSeconds(1);
376 session
.SwitchToSubFrame("frame_id2", std::string());
377 base::DictionaryValue params
;
378 params
.SetString("using", "name");
379 params
.SetString("value", "b");
380 scoped_ptr
<base::Value
> result
;
383 ExecuteFindElements(1, &session
, &web_view
, params
, &result
).code());
384 base::DictionaryValue param
;
385 param
.SetString("name", "b");
386 base::ListValue expected_args
;
387 expected_args
.Append(param
.DeepCopy());
388 web_view
.Verify("frame_id2", &expected_args
, result
.get());
391 TEST(CommandsTest
, FailedFindElements
) {
392 Session
session("id");
393 FindElementWebView
web_view(false, kElementNotExistsQueryOnce
);
394 base::DictionaryValue params
;
395 params
.SetString("using", "id");
396 params
.SetString("value", "a");
397 scoped_ptr
<base::Value
> result
;
400 ExecuteFindElements(1, &session
, &web_view
, params
, &result
).code());
401 base::ListValue
* list
;
402 ASSERT_TRUE(result
->GetAsList(&list
));
403 ASSERT_EQ(0U, list
->GetSize());
406 TEST(CommandsTest
, SuccessfulFindChildElement
) {
407 FindElementWebView
web_view(true, kElementExistsQueryTwice
);
408 Session
session("id");
409 session
.implicit_wait
= base::TimeDelta::FromSeconds(1);
410 session
.SwitchToSubFrame("frame_id3", std::string());
411 base::DictionaryValue params
;
412 params
.SetString("using", "tag name");
413 params
.SetString("value", "div");
414 std::string element_id
= "1";
415 scoped_ptr
<base::Value
> result
;
418 ExecuteFindChildElement(
419 1, &session
, &web_view
, element_id
, params
, &result
).code());
420 base::DictionaryValue locator_param
;
421 locator_param
.SetString("tag name", "div");
422 base::DictionaryValue root_element_param
;
423 root_element_param
.SetString("ELEMENT", element_id
);
424 base::ListValue expected_args
;
425 expected_args
.Append(locator_param
.DeepCopy());
426 expected_args
.Append(root_element_param
.DeepCopy());
427 web_view
.Verify("frame_id3", &expected_args
, result
.get());
430 TEST(CommandsTest
, FailedFindChildElement
) {
431 Session
session("id");
432 FindElementWebView
web_view(true, kElementNotExistsQueryOnce
);
433 base::DictionaryValue params
;
434 params
.SetString("using", "id");
435 params
.SetString("value", "a");
436 std::string element_id
= "1";
437 scoped_ptr
<base::Value
> result
;
440 ExecuteFindChildElement(
441 1, &session
, &web_view
, element_id
, params
, &result
).code());
444 TEST(CommandsTest
, SuccessfulFindChildElements
) {
445 FindElementWebView
web_view(false, kElementExistsQueryTwice
);
446 Session
session("id");
447 session
.implicit_wait
= base::TimeDelta::FromSeconds(1);
448 session
.SwitchToSubFrame("frame_id4", std::string());
449 base::DictionaryValue params
;
450 params
.SetString("using", "class name");
451 params
.SetString("value", "c");
452 std::string element_id
= "1";
453 scoped_ptr
<base::Value
> result
;
456 ExecuteFindChildElements(
457 1, &session
, &web_view
, element_id
, params
, &result
).code());
458 base::DictionaryValue locator_param
;
459 locator_param
.SetString("class name", "c");
460 base::DictionaryValue root_element_param
;
461 root_element_param
.SetString("ELEMENT", element_id
);
462 base::ListValue expected_args
;
463 expected_args
.Append(locator_param
.DeepCopy());
464 expected_args
.Append(root_element_param
.DeepCopy());
465 web_view
.Verify("frame_id4", &expected_args
, result
.get());
468 TEST(CommandsTest
, FailedFindChildElements
) {
469 Session
session("id");
470 FindElementWebView
web_view(false, kElementNotExistsQueryOnce
);
471 base::DictionaryValue params
;
472 params
.SetString("using", "id");
473 params
.SetString("value", "a");
474 std::string element_id
= "1";
475 scoped_ptr
<base::Value
> result
;
478 ExecuteFindChildElements(
479 1, &session
, &web_view
, element_id
, params
, &result
).code());
480 base::ListValue
* list
;
481 ASSERT_TRUE(result
->GetAsList(&list
));
482 ASSERT_EQ(0U, list
->GetSize());
485 TEST(CommandsTest
, TimeoutInFindElement
) {
486 Session
session("id");
487 FindElementWebView
web_view(true, kElementExistsTimeout
);
488 session
.implicit_wait
= base::TimeDelta::FromMilliseconds(2);
489 base::DictionaryValue params
;
490 params
.SetString("using", "id");
491 params
.SetString("value", "a");
492 params
.SetString("id", "1");
493 scoped_ptr
<base::Value
> result
;
494 ASSERT_EQ(kNoSuchElement
,
495 ExecuteFindElement(1, &session
, &web_view
, params
, &result
).code());
500 class ErrorCallFunctionWebView
: public StubWebView
{
502 explicit ErrorCallFunctionWebView(StatusCode code
)
503 : StubWebView("1"), code_(code
) {}
504 ~ErrorCallFunctionWebView() override
{}
506 // Overridden from WebView:
507 Status
CallFunction(const std::string
& frame
,
508 const std::string
& function
,
509 const base::ListValue
& args
,
510 scoped_ptr
<base::Value
>* result
) override
{
511 return Status(code_
);
520 TEST(CommandsTest
, ErrorFindElement
) {
521 Session
session("id");
522 ErrorCallFunctionWebView
web_view(kUnknownError
);
523 base::DictionaryValue params
;
524 params
.SetString("using", "id");
525 params
.SetString("value", "a");
526 scoped_ptr
<base::Value
> value
;
527 ASSERT_EQ(kUnknownError
,
528 ExecuteFindElement(1, &session
, &web_view
, params
, &value
).code());
529 ASSERT_EQ(kUnknownError
,
530 ExecuteFindElements(1, &session
, &web_view
, params
, &value
).code());
533 TEST(CommandsTest
, ErrorFindChildElement
) {
534 Session
session("id");
535 ErrorCallFunctionWebView
web_view(kStaleElementReference
);
536 base::DictionaryValue params
;
537 params
.SetString("using", "id");
538 params
.SetString("value", "a");
539 std::string element_id
= "1";
540 scoped_ptr
<base::Value
> result
;
542 kStaleElementReference
,
543 ExecuteFindChildElement(
544 1, &session
, &web_view
, element_id
, params
, &result
).code());
546 kStaleElementReference
,
547 ExecuteFindChildElements(
548 1, &session
, &web_view
, element_id
, params
, &result
).code());
553 class MockCommandListener
: public CommandListener
{
555 MockCommandListener() : called_(false) {}
556 ~MockCommandListener() override
{}
558 Status
BeforeCommand(const std::string
& command_name
) override
{
560 EXPECT_STREQ("cmd", command_name
.c_str());
564 void VerifyCalled() {
565 EXPECT_TRUE(called_
);
568 void VerifyNotCalled() {
569 EXPECT_FALSE(called_
);
576 Status
ExecuteAddListenerToSessionCommand(
577 CommandListener
* listener
,
579 const base::DictionaryValue
& params
,
580 scoped_ptr
<base::Value
>* return_value
) {
581 session
->command_listeners
.push_back(listener
);
585 Status
ExecuteQuitSessionCommand(
587 const base::DictionaryValue
& params
,
588 scoped_ptr
<base::Value
>* return_value
) {
589 session
->quit
= true;
593 void OnSessionCommand(
594 base::RunLoop
* run_loop
,
595 const Status
& status
,
596 scoped_ptr
<base::Value
> value
,
597 const std::string
& session_id
) {
598 ASSERT_EQ(kOk
, status
.code());
604 TEST(CommandsTest
, SuccessNotifyingCommandListeners
) {
605 SessionThreadMap map
;
606 linked_ptr
<base::Thread
> thread(new base::Thread("1"));
607 ASSERT_TRUE(thread
->Start());
608 std::string
id("id");
609 thread
->message_loop()->PostTask(
611 base::Bind(&internal::CreateSessionOnSessionThreadForTesting
, id
));
615 base::DictionaryValue params
;
616 scoped_ptr
<MockCommandListener
> listener(new MockCommandListener());
617 CommandListenerProxy
* proxy
= new CommandListenerProxy(listener
.get());
618 // We add |proxy| to the session instead of adding |listener| directly so that
619 // after the session is destroyed by ExecuteQuitSessionCommand, we can still
620 // verify the listener was called. The session owns and will destroy |proxy|.
621 SessionCommand cmd
= base::Bind(&ExecuteAddListenerToSessionCommand
, proxy
);
622 base::MessageLoop loop
;
623 base::RunLoop run_loop_addlistener
;
625 // |CommandListener|s are notified immediately before commands are run.
626 // Here, the command adds |listener| to the session, so |listener|
627 // should not be notified since it will not have been added yet.
628 ExecuteSessionCommand(
635 base::Bind(&OnSessionCommand
, &run_loop_addlistener
));
636 run_loop_addlistener
.Run();
638 listener
->VerifyNotCalled();
640 base::RunLoop run_loop_testlistener
;
641 cmd
= base::Bind(&ExecuteQuitSessionCommand
);
643 // |listener| was added to |session| by ExecuteAddListenerToSessionCommand
644 // and should be notified before the next command, ExecuteQuitSessionCommand.
645 ExecuteSessionCommand(
652 base::Bind(&OnSessionCommand
, &run_loop_testlistener
));
653 run_loop_testlistener
.Run();
655 listener
->VerifyCalled();
660 class FailingCommandListener
: public CommandListener
{
662 FailingCommandListener() {}
663 ~FailingCommandListener() override
{}
665 Status
BeforeCommand(const std::string
& command_name
) override
{
666 return Status(kUnknownError
);
670 void AddListenerToSessionIfSessionExists(CommandListener
* listener
) {
671 Session
* session
= GetThreadLocalSession();
673 session
->command_listeners
.push_back(listener
);
677 void OnFailBecauseErrorNotifyingListeners(
678 base::RunLoop
* run_loop
,
679 const Status
& status
,
680 scoped_ptr
<base::Value
> value
,
681 const std::string
& session_id
) {
682 EXPECT_EQ(kUnknownError
, status
.code());
683 EXPECT_FALSE(value
.get());
687 void VerifySessionWasDeleted() {
688 ASSERT_FALSE(GetThreadLocalSession());
693 TEST(CommandsTest
, ErrorNotifyingCommandListeners
) {
694 SessionThreadMap map
;
695 linked_ptr
<base::Thread
> thread(new base::Thread("1"));
696 ASSERT_TRUE(thread
->Start());
697 std::string
id("id");
698 thread
->message_loop()->PostTask(
700 base::Bind(&internal::CreateSessionOnSessionThreadForTesting
, id
));
703 // In SuccessNotifyingCommandListenersBeforeCommand, we verified BeforeCommand
704 // was called before (as opposed to after) command execution. We don't need to
705 // verify this again, so we can just add |listener| with PostTask.
706 CommandListener
* listener
= new FailingCommandListener();
707 thread
->message_loop()->PostTask(
709 base::Bind(&AddListenerToSessionIfSessionExists
, listener
));
711 base::DictionaryValue params
;
712 // The command should never be executed if BeforeCommand fails for a listener.
713 SessionCommand cmd
= base::Bind(&ShouldNotBeCalled
);
714 base::MessageLoop loop
;
715 base::RunLoop run_loop
;
717 ExecuteSessionCommand(
724 base::Bind(&OnFailBecauseErrorNotifyingListeners
, &run_loop
));
727 thread
->message_loop()->PostTask(
729 base::Bind(&VerifySessionWasDeleted
));