// Copyright (c) 2012 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 #include "base/bind.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "base/values.h" #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/chrome/stub_chrome.h" #include "chrome/test/chromedriver/chrome/stub_web_view.h" #include "chrome/test/chromedriver/chrome/web_view.h" #include "chrome/test/chromedriver/command_listener_proxy.h" #include "chrome/test/chromedriver/commands.h" #include "chrome/test/chromedriver/element_commands.h" #include "chrome/test/chromedriver/session.h" #include "chrome/test/chromedriver/session_commands.h" #include "chrome/test/chromedriver/window_commands.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/webdriver/atoms.h" namespace { void OnGetStatus(const Status& status, scoped_ptr value, const std::string& session_id) { ASSERT_EQ(kOk, status.code()); base::DictionaryValue* dict; ASSERT_TRUE(value->GetAsDictionary(&dict)); base::Value* unused; ASSERT_TRUE(dict->Get("os.name", &unused)); ASSERT_TRUE(dict->Get("os.version", &unused)); ASSERT_TRUE(dict->Get("os.arch", &unused)); ASSERT_TRUE(dict->Get("build.version", &unused)); } } // namespace TEST(CommandsTest, GetStatus) { base::DictionaryValue params; ExecuteGetStatus(params, std::string(), base::Bind(&OnGetStatus)); } namespace { void ExecuteStubQuit( int* count, const base::DictionaryValue& params, const std::string& session_id, const CommandCallback& callback) { if (*count == 0) { EXPECT_STREQ("id", session_id.c_str()); } else { EXPECT_STREQ("id2", session_id.c_str()); } (*count)++; callback.Run(Status(kOk), scoped_ptr(), session_id); } void OnQuitAll(const Status& status, scoped_ptr value, const std::string& session_id) { ASSERT_EQ(kOk, status.code()); ASSERT_FALSE(value.get()); } } // namespace TEST(CommandsTest, QuitAll) { SessionThreadMap map; Session session("id"); Session session2("id2"); map[session.id] = make_linked_ptr(new base::Thread("1")); map[session2.id] = make_linked_ptr(new base::Thread("2")); int count = 0; Command cmd = base::Bind(&ExecuteStubQuit, &count); base::DictionaryValue params; base::MessageLoop loop; ExecuteQuitAll(cmd, &map, params, std::string(), base::Bind(&OnQuitAll)); ASSERT_EQ(2, count); } namespace { Status ExecuteSimpleCommand( const std::string& expected_id, base::DictionaryValue* expected_params, base::Value* value, Session* session, const base::DictionaryValue& params, scoped_ptr* return_value) { EXPECT_EQ(expected_id, session->id); EXPECT_TRUE(expected_params->Equals(¶ms)); return_value->reset(value->DeepCopy()); session->quit = true; return Status(kOk); } void OnSimpleCommand(base::RunLoop* run_loop, const std::string& expected_session_id, base::Value* expected_value, const Status& status, scoped_ptr value, const std::string& session_id) { ASSERT_EQ(kOk, status.code()); ASSERT_TRUE(expected_value->Equals(value.get())); ASSERT_EQ(expected_session_id, session_id); run_loop->Quit(); } } // namespace TEST(CommandsTest, ExecuteSessionCommand) { SessionThreadMap map; linked_ptr thread(new base::Thread("1")); ASSERT_TRUE(thread->Start()); std::string id("id"); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); map[id] = thread; base::DictionaryValue params; params.SetInteger("param", 5); base::FundamentalValue expected_value(6); SessionCommand cmd = base::Bind( &ExecuteSimpleCommand, id, ¶ms, &expected_value); base::MessageLoop loop; base::RunLoop run_loop; ExecuteSessionCommand( &map, "cmd", cmd, false, params, id, base::Bind(&OnSimpleCommand, &run_loop, id, &expected_value)); run_loop.Run(); } namespace { Status ShouldNotBeCalled( Session* session, const base::DictionaryValue& params, scoped_ptr* value) { EXPECT_TRUE(false); return Status(kOk); } void OnNoSuchSession(const Status& status, scoped_ptr value, const std::string& session_id) { EXPECT_EQ(kNoSuchSession, status.code()); EXPECT_FALSE(value.get()); } void OnNoSuchSessionIsOk(const Status& status, scoped_ptr value, const std::string& session_id) { EXPECT_EQ(kOk, status.code()); EXPECT_FALSE(value.get()); } } // namespace TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSession) { SessionThreadMap map; base::DictionaryValue params; ExecuteSessionCommand(&map, "cmd", base::Bind(&ShouldNotBeCalled), false, params, "session", base::Bind(&OnNoSuchSession)); } TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSessionWhenItExpectsOk) { SessionThreadMap map; base::DictionaryValue params; ExecuteSessionCommand(&map, "cmd", base::Bind(&ShouldNotBeCalled), true, params, "session", base::Bind(&OnNoSuchSessionIsOk)); } namespace { void OnNoSuchSessionAndQuit(base::RunLoop* run_loop, const Status& status, scoped_ptr value, const std::string& session_id) { run_loop->Quit(); EXPECT_EQ(kNoSuchSession, status.code()); EXPECT_FALSE(value.get()); } } // namespace TEST(CommandsTest, ExecuteSessionCommandOnJustDeletedSession) { SessionThreadMap map; linked_ptr thread(new base::Thread("1")); ASSERT_TRUE(thread->Start()); std::string id("id"); map[id] = thread; base::MessageLoop loop; base::RunLoop run_loop; ExecuteSessionCommand(&map, "cmd", base::Bind(&ShouldNotBeCalled), false, base::DictionaryValue(), "session", base::Bind(&OnNoSuchSessionAndQuit, &run_loop)); run_loop.Run(); } namespace { enum TestScenario { kElementExistsQueryOnce = 0, kElementExistsQueryTwice, kElementNotExistsQueryOnce, kElementExistsTimeout }; class FindElementWebView : public StubWebView { public: FindElementWebView(bool only_one, TestScenario scenario) : StubWebView("1"), only_one_(only_one), scenario_(scenario), current_count_(0) { switch (scenario_) { case kElementExistsQueryOnce: case kElementExistsQueryTwice: case kElementExistsTimeout: { if (only_one_) { base::DictionaryValue element; element.SetString("ELEMENT", "1"); result_.reset(element.DeepCopy()); } else { base::DictionaryValue element1; element1.SetString("ELEMENT", "1"); base::DictionaryValue element2; element2.SetString("ELEMENT", "2"); base::ListValue list; list.Append(element1.DeepCopy()); list.Append(element2.DeepCopy()); result_.reset(list.DeepCopy()); } break; } case kElementNotExistsQueryOnce: { if (only_one_) result_.reset(base::Value::CreateNullValue()); else result_.reset(new base::ListValue()); break; } } } virtual ~FindElementWebView() {} void Verify(const std::string& expected_frame, const base::ListValue* expected_args, const base::Value* actrual_result) { EXPECT_EQ(expected_frame, frame_); std::string function; if (only_one_) function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT); else function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS); EXPECT_EQ(function, function_); ASSERT_TRUE(args_.get()); EXPECT_TRUE(expected_args->Equals(args_.get())); ASSERT_TRUE(actrual_result); EXPECT_TRUE(result_->Equals(actrual_result)); } // Overridden from WebView: virtual Status CallFunction(const std::string& frame, const std::string& function, const base::ListValue& args, scoped_ptr* result) OVERRIDE { ++current_count_; if (scenario_ == kElementExistsTimeout || (scenario_ == kElementExistsQueryTwice && current_count_ == 1)) { // Always return empty result when testing timeout. if (only_one_) result->reset(base::Value::CreateNullValue()); else result->reset(new base::ListValue()); } else { switch (scenario_) { case kElementExistsQueryOnce: case kElementNotExistsQueryOnce: { EXPECT_EQ(1, current_count_); break; } case kElementExistsQueryTwice: { EXPECT_EQ(2, current_count_); break; } default: { break; } } result->reset(result_->DeepCopy()); frame_ = frame; function_ = function; args_.reset(args.DeepCopy()); } return Status(kOk); } private: bool only_one_; TestScenario scenario_; int current_count_; std::string frame_; std::string function_; scoped_ptr args_; scoped_ptr result_; }; } // namespace TEST(CommandsTest, SuccessfulFindElement) { FindElementWebView web_view(true, kElementExistsQueryTwice); Session session("id"); session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id1", std::string()); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); scoped_ptr result; ASSERT_EQ(kOk, ExecuteFindElement(1, &session, &web_view, params, &result).code()); base::DictionaryValue param; param.SetString("id", "a"); base::ListValue expected_args; expected_args.Append(param.DeepCopy()); web_view.Verify("frame_id1", &expected_args, result.get()); } TEST(CommandsTest, FailedFindElement) { FindElementWebView web_view(true, kElementNotExistsQueryOnce); Session session("id"); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); scoped_ptr result; ASSERT_EQ(kNoSuchElement, ExecuteFindElement(1, &session, &web_view, params, &result).code()); } TEST(CommandsTest, SuccessfulFindElements) { FindElementWebView web_view(false, kElementExistsQueryTwice); Session session("id"); session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id2", std::string()); base::DictionaryValue params; params.SetString("using", "name"); params.SetString("value", "b"); scoped_ptr result; ASSERT_EQ( kOk, ExecuteFindElements(1, &session, &web_view, params, &result).code()); base::DictionaryValue param; param.SetString("name", "b"); base::ListValue expected_args; expected_args.Append(param.DeepCopy()); web_view.Verify("frame_id2", &expected_args, result.get()); } TEST(CommandsTest, FailedFindElements) { Session session("id"); FindElementWebView web_view(false, kElementNotExistsQueryOnce); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); scoped_ptr result; ASSERT_EQ( kOk, ExecuteFindElements(1, &session, &web_view, params, &result).code()); base::ListValue* list; ASSERT_TRUE(result->GetAsList(&list)); ASSERT_EQ(0U, list->GetSize()); } TEST(CommandsTest, SuccessfulFindChildElement) { FindElementWebView web_view(true, kElementExistsQueryTwice); Session session("id"); session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id3", std::string()); base::DictionaryValue params; params.SetString("using", "tag name"); params.SetString("value", "div"); std::string element_id = "1"; scoped_ptr result; ASSERT_EQ( kOk, ExecuteFindChildElement( 1, &session, &web_view, element_id, params, &result).code()); base::DictionaryValue locator_param; locator_param.SetString("tag name", "div"); base::DictionaryValue root_element_param; root_element_param.SetString("ELEMENT", element_id); base::ListValue expected_args; expected_args.Append(locator_param.DeepCopy()); expected_args.Append(root_element_param.DeepCopy()); web_view.Verify("frame_id3", &expected_args, result.get()); } TEST(CommandsTest, FailedFindChildElement) { Session session("id"); FindElementWebView web_view(true, kElementNotExistsQueryOnce); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); std::string element_id = "1"; scoped_ptr result; ASSERT_EQ( kNoSuchElement, ExecuteFindChildElement( 1, &session, &web_view, element_id, params, &result).code()); } TEST(CommandsTest, SuccessfulFindChildElements) { FindElementWebView web_view(false, kElementExistsQueryTwice); Session session("id"); session.implicit_wait = base::TimeDelta::FromSeconds(1); session.SwitchToSubFrame("frame_id4", std::string()); base::DictionaryValue params; params.SetString("using", "class name"); params.SetString("value", "c"); std::string element_id = "1"; scoped_ptr result; ASSERT_EQ( kOk, ExecuteFindChildElements( 1, &session, &web_view, element_id, params, &result).code()); base::DictionaryValue locator_param; locator_param.SetString("class name", "c"); base::DictionaryValue root_element_param; root_element_param.SetString("ELEMENT", element_id); base::ListValue expected_args; expected_args.Append(locator_param.DeepCopy()); expected_args.Append(root_element_param.DeepCopy()); web_view.Verify("frame_id4", &expected_args, result.get()); } TEST(CommandsTest, FailedFindChildElements) { Session session("id"); FindElementWebView web_view(false, kElementNotExistsQueryOnce); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); std::string element_id = "1"; scoped_ptr result; ASSERT_EQ( kOk, ExecuteFindChildElements( 1, &session, &web_view, element_id, params, &result).code()); base::ListValue* list; ASSERT_TRUE(result->GetAsList(&list)); ASSERT_EQ(0U, list->GetSize()); } TEST(CommandsTest, TimeoutInFindElement) { Session session("id"); FindElementWebView web_view(true, kElementExistsTimeout); session.implicit_wait = base::TimeDelta::FromMilliseconds(2); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); params.SetString("id", "1"); scoped_ptr result; ASSERT_EQ(kNoSuchElement, ExecuteFindElement(1, &session, &web_view, params, &result).code()); } namespace { class ErrorCallFunctionWebView : public StubWebView { public: explicit ErrorCallFunctionWebView(StatusCode code) : StubWebView("1"), code_(code) {} virtual ~ErrorCallFunctionWebView() {} // Overridden from WebView: virtual Status CallFunction(const std::string& frame, const std::string& function, const base::ListValue& args, scoped_ptr* result) OVERRIDE { return Status(code_); } private: StatusCode code_; }; } // namespace TEST(CommandsTest, ErrorFindElement) { Session session("id"); ErrorCallFunctionWebView web_view(kUnknownError); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); scoped_ptr value; ASSERT_EQ(kUnknownError, ExecuteFindElement(1, &session, &web_view, params, &value).code()); ASSERT_EQ(kUnknownError, ExecuteFindElements(1, &session, &web_view, params, &value).code()); } TEST(CommandsTest, ErrorFindChildElement) { Session session("id"); ErrorCallFunctionWebView web_view(kStaleElementReference); base::DictionaryValue params; params.SetString("using", "id"); params.SetString("value", "a"); std::string element_id = "1"; scoped_ptr result; ASSERT_EQ( kStaleElementReference, ExecuteFindChildElement( 1, &session, &web_view, element_id, params, &result).code()); ASSERT_EQ( kStaleElementReference, ExecuteFindChildElements( 1, &session, &web_view, element_id, params, &result).code()); } namespace { class MockCommandListener : public CommandListener { public: MockCommandListener() : called_(false) {} virtual ~MockCommandListener() {} virtual Status BeforeCommand(const std::string& command_name) OVERRIDE { called_ = true; EXPECT_STREQ("cmd", command_name.c_str()); return Status(kOk); } void VerifyCalled() { EXPECT_TRUE(called_); } void VerifyNotCalled() { EXPECT_FALSE(called_); } private: bool called_; }; Status ExecuteAddListenerToSessionCommand( CommandListener* listener, Session* session, const base::DictionaryValue& params, scoped_ptr* return_value) { session->command_listeners.push_back(listener); return Status(kOk); } Status ExecuteQuitSessionCommand( Session* session, const base::DictionaryValue& params, scoped_ptr* return_value) { session->quit = true; return Status(kOk); } void OnSessionCommand( base::RunLoop* run_loop, const Status& status, scoped_ptr value, const std::string& session_id) { ASSERT_EQ(kOk, status.code()); run_loop->Quit(); } } // namespace TEST(CommandsTest, SuccessNotifyingCommandListeners) { SessionThreadMap map; linked_ptr thread(new base::Thread("1")); ASSERT_TRUE(thread->Start()); std::string id("id"); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); map[id] = thread; base::DictionaryValue params; scoped_ptr listener(new MockCommandListener()); CommandListenerProxy* proxy = new CommandListenerProxy(listener.get()); // We add |proxy| to the session instead of adding |listener| directly so that // after the session is destroyed by ExecuteQuitSessionCommand, we can still // verify the listener was called. The session owns and will destroy |proxy|. SessionCommand cmd = base::Bind(&ExecuteAddListenerToSessionCommand, proxy); base::MessageLoop loop; base::RunLoop run_loop_addlistener; // |CommandListener|s are notified immediately before commands are run. // Here, the command adds |listener| to the session, so |listener| // should not be notified since it will not have been added yet. ExecuteSessionCommand( &map, "cmd", cmd, false, params, id, base::Bind(&OnSessionCommand, &run_loop_addlistener)); run_loop_addlistener.Run(); listener->VerifyNotCalled(); base::RunLoop run_loop_testlistener; cmd = base::Bind(&ExecuteQuitSessionCommand); // |listener| was added to |session| by ExecuteAddListenerToSessionCommand // and should be notified before the next command, ExecuteQuitSessionCommand. ExecuteSessionCommand( &map, "cmd", cmd, false, params, id, base::Bind(&OnSessionCommand, &run_loop_testlistener)); run_loop_testlistener.Run(); listener->VerifyCalled(); } namespace { class FailingCommandListener : public CommandListener { public: FailingCommandListener() {} virtual ~FailingCommandListener() {} virtual Status BeforeCommand(const std::string& command_name) OVERRIDE { return Status(kUnknownError); } }; void AddListenerToSessionIfSessionExists(CommandListener* listener) { Session* session = GetThreadLocalSession(); if (session) { session->command_listeners.push_back(listener); } } void OnFailBecauseErrorNotifyingListeners( base::RunLoop* run_loop, const Status& status, scoped_ptr value, const std::string& session_id) { EXPECT_EQ(kUnknownError, status.code()); EXPECT_FALSE(value.get()); run_loop->Quit(); } void VerifySessionWasDeleted() { ASSERT_FALSE(GetThreadLocalSession()); } } // namespace TEST(CommandsTest, ErrorNotifyingCommandListeners) { SessionThreadMap map; linked_ptr thread(new base::Thread("1")); ASSERT_TRUE(thread->Start()); std::string id("id"); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); map[id] = thread; // In SuccessNotifyingCommandListenersBeforeCommand, we verified BeforeCommand // was called before (as opposed to after) command execution. We don't need to // verify this again, so we can just add |listener| with PostTask. CommandListener* listener = new FailingCommandListener(); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&AddListenerToSessionIfSessionExists, listener)); base::DictionaryValue params; // The command should never be executed if BeforeCommand fails for a listener. SessionCommand cmd = base::Bind(&ShouldNotBeCalled); base::MessageLoop loop; base::RunLoop run_loop; ExecuteSessionCommand( &map, "cmd", cmd, false, params, id, base::Bind(&OnFailBecauseErrorNotifyingListeners, &run_loop)); run_loop.Run(); thread->message_loop()->PostTask( FROM_HERE, base::Bind(&VerifySessionWasDeleted)); }