chromium/chrome/test/base/ash/web_ui_browser_test_browsertest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/test_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/base/ash/web_ui_browser_test.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-spi.h"

using content::WebUIMessageHandler;

namespace {
const GURL& DummyUrl() {
  static GURL url(content::GetWebUIURLString("DummyURL"));
  return url;
}
}  // namespace

// According to the interface for EXPECT_FATAL_FAILURE
// (https://github.com/google/googletest/blob/main/docs/advanced.md#catching-failures)
// the statement must be statically available. Therefore, we make a static
// global s_test_ which should point to |this| for the duration of the test run
// and be cleared afterward.
class WebUIBrowserExpectFailTest : public WebUIBrowserTest {
 public:
  WebUIBrowserExpectFailTest() {
    EXPECT_FALSE(s_test_);
    s_test_ = this;
  }

 protected:
  ~WebUIBrowserExpectFailTest() override {
    EXPECT_TRUE(s_test_);
    s_test_ = nullptr;
  }

  static void RunJavascriptTestNoReturn(const std::string& testname) {
    EXPECT_TRUE(s_test_);
    s_test_->RunJavascriptTest(testname);
  }

  static void RunJavascriptAsyncTestNoReturn(const std::string& testname) {
    EXPECT_TRUE(s_test_);
    s_test_->RunJavascriptAsyncTest(testname);
  }

 private:
  static WebUIBrowserTest* s_test_;
};

WebUIBrowserTest* WebUIBrowserExpectFailTest::s_test_ = nullptr;

IN_PROC_BROWSER_TEST_F(WebUIBrowserExpectFailTest, TestFailsFast) {
  AddLibrary(base::FilePath(FILE_PATH_LITERAL("sample_downloads.js")));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(chrome::kChromeUIDownloadsURL)));
  EXPECT_FATAL_FAILURE(RunJavascriptTestNoReturn("DISABLED_BogusFunctionName"),
                       "result.is_bool()");
}

// Test that bogus javascript fails fast - no timeout waiting for result.
IN_PROC_BROWSER_TEST_F(WebUIBrowserExpectFailTest, TestRuntimeErrorFailsFast) {
  AddLibrary(base::FilePath(FILE_PATH_LITERAL("runtime_error.js")));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), DummyUrl()));
  EXPECT_FATAL_FAILURE(RunJavascriptTestNoReturn("TestRuntimeErrorFailsFast"),
                       "result.is_bool()");
}

// Test times out in debug builds: https://crbug.com/902310
#if !defined(NDEBUG)
#define MAYBE_TestFailsAsyncFast DISABLED_TestFailsAsyncFast
#else
#define MAYBE_TestFailsAsyncFast TestFailsAsyncFast
#endif

// Test that bogus javascript fails async test fast as well - no timeout waiting
// for result.
IN_PROC_BROWSER_TEST_F(WebUIBrowserExpectFailTest, MAYBE_TestFailsAsyncFast) {
  AddLibrary(base::FilePath(FILE_PATH_LITERAL("sample_downloads.js")));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(chrome::kChromeUIDownloadsURL)));
  EXPECT_FATAL_FAILURE(
      RunJavascriptAsyncTestNoReturn("DISABLED_BogusFunctionName"),
      "result.is_bool()");
}

// Tests that the async framework works.
class WebUIBrowserAsyncTest : public WebUIBrowserTest {
 public:
  WebUIBrowserAsyncTest(const WebUIBrowserAsyncTest&) = delete;
  WebUIBrowserAsyncTest& operator=(const WebUIBrowserAsyncTest&) = delete;
  // Calls the testDone() function from test_api.js
  void TestDone() {
    RunJavascriptFunction("testDone");
  }

  // Starts a failing test.
  void RunTestFailsAssert() {
    RunJavascriptFunction("runAsync", base::Value("testFailsAssert"));
  }

  // Starts a passing test.
  void RunTestPasses() {
    RunJavascriptFunction("runAsync", base::Value("testPasses"));
  }

 protected:
  WebUIBrowserAsyncTest() {}

  // Class to synchronize asynchronous javascript activity with the tests.
  class AsyncWebUIMessageHandler : public WebUIMessageHandler {
   public:
    AsyncWebUIMessageHandler() = default;
    AsyncWebUIMessageHandler(const AsyncWebUIMessageHandler&) = delete;
    AsyncWebUIMessageHandler& operator=(const AsyncWebUIMessageHandler&) =
        delete;

    MOCK_METHOD1(HandleTestContinues, void(const base::Value::List&));
    MOCK_METHOD1(HandleTestFails, void(const base::Value::List&));
    MOCK_METHOD1(HandleTestPasses, void(const base::Value::List&));

   private:
    void RegisterMessages() override {
      web_ui()->RegisterMessageCallback(
          "startAsyncTest",
          base::BindRepeating(&AsyncWebUIMessageHandler::HandleStartAsyncTest,
                              base::Unretained(this)));
      web_ui()->RegisterMessageCallback(
          "testContinues",
          base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestContinues,
                              base::Unretained(this)));
      web_ui()->RegisterMessageCallback(
          "testFails",
          base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestFails,
                              base::Unretained(this)));
      web_ui()->RegisterMessageCallback(
          "testPasses",
          base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestPasses,
                              base::Unretained(this)));
    }

    // Starts the test in |list_value|[0] with the runAsync wrapper.
    void HandleStartAsyncTest(const base::Value::List& list_value) {
      const base::Value& test_name = list_value[0];
      web_ui()->CallJavascriptFunctionUnsafe("runAsync", test_name);
    }
  };

  // Handler for this object.
  ::testing::StrictMock<AsyncWebUIMessageHandler> message_handler_;

 private:
  // Provide this object's handler.
  WebUIMessageHandler* GetMockMessageHandler() override {
    return &message_handler_;
  }

  // Set up and browse to DummyUrl() for all tests.
  void SetUpOnMainThread() override {
    WebUIBrowserTest::SetUpOnMainThread();
    AddLibrary(base::FilePath(FILE_PATH_LITERAL("async.js")));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), DummyUrl()));
  }
};

// Test that assertions fail immediately after assertion fails (no testContinues
// message). (Sync version).
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestSyncOkTestFail) {
  ASSERT_FALSE(RunJavascriptTest("testFailsAssert"));
}

// Test that assertions fail immediately after assertion fails (no testContinues
// message). (Async version).
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestAsyncFailsAssert) {
  EXPECT_CALL(message_handler_, HandleTestFails(::testing::_));
  ASSERT_FALSE(
      RunJavascriptAsyncTest("startAsyncTest", base::Value("testFailsAssert")));
}

// Test that test continues and passes. (Sync version).
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestSyncPasses) {
  EXPECT_CALL(message_handler_, HandleTestContinues(::testing::_));
  ASSERT_TRUE(RunJavascriptTest("testPasses"));
}

// Test that test continues and passes. (Async version).
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestAsyncPasses) {
  ::testing::InSequence s;
  EXPECT_CALL(message_handler_, HandleTestContinues(::testing::_));
  EXPECT_CALL(message_handler_, HandleTestPasses(::testing::_))
      .WillOnce(::testing::InvokeWithoutArgs(
          this, &WebUIBrowserAsyncTest::TestDone));
  ASSERT_TRUE(
      RunJavascriptAsyncTest("startAsyncTest", base::Value("testPasses")));
}

// Test that two tests pass.
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestAsyncPassPass) {
  ::testing::InSequence s;
  EXPECT_CALL(message_handler_, HandleTestContinues(::testing::_));
  EXPECT_CALL(message_handler_, HandleTestPasses(::testing::_))
      .WillOnce(::testing::InvokeWithoutArgs(
          this, &WebUIBrowserAsyncTest::RunTestPasses));
  EXPECT_CALL(message_handler_, HandleTestContinues(::testing::_));
  EXPECT_CALL(message_handler_, HandleTestPasses(::testing::_))
      .WillOnce(::testing::InvokeWithoutArgs(
          this, &WebUIBrowserAsyncTest::TestDone));
  ASSERT_TRUE(
      RunJavascriptAsyncTest("startAsyncTest", base::Value("testPasses")));
}

// Test that first test passes; second fails.
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestAsyncPassThenFail) {
  ::testing::InSequence s;
  EXPECT_CALL(message_handler_, HandleTestContinues(::testing::_));
  EXPECT_CALL(message_handler_, HandleTestPasses(::testing::_))
      .WillOnce(::testing::InvokeWithoutArgs(
          this, &WebUIBrowserAsyncTest::RunTestFailsAssert));
  EXPECT_CALL(message_handler_, HandleTestFails(::testing::_));
  ASSERT_FALSE(
      RunJavascriptAsyncTest("startAsyncTest", base::Value("testPasses")));
}

// Test that calling testDone during RunJavascriptAsyncTest still completes
// when waiting for async result. This is similar to the previous test, but call
// testDone directly and expect pass result.
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestTestDoneEarlyPassesAsync) {
  ASSERT_TRUE(RunJavascriptAsyncTest("testDone"));
}

// Test that calling testDone during RunJavascriptTest still completes when
// waiting for async result.
IN_PROC_BROWSER_TEST_F(WebUIBrowserAsyncTest, TestTestDoneEarlyPasses) {
  ASSERT_TRUE(RunJavascriptTest("testDone"));
}

class WebUICoverageTest : public WebUIBrowserTest {
 protected:
  base::ScopedTempDir tmp_dir_;

 private:
  void SetUpOnMainThread() override {
    WebUIBrowserTest::SetUpOnMainThread();
    AddLibrary(base::FilePath(FILE_PATH_LITERAL("async.js")));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), DummyUrl()));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    CHECK(tmp_dir_.CreateUniqueTempDir());
    command_line->AppendSwitchPath(switches::kDevtoolsCodeCoverage,
                                   tmp_dir_.GetPath());
    WebUIBrowserTest::SetUpCommandLine(command_line);
  }
};

IN_PROC_BROWSER_TEST_F(WebUICoverageTest, TestCoverageEmits) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  ASSERT_TRUE(base::IsDirectoryEmpty(tmp_dir_.GetPath()));
  ASSERT_TRUE(RunJavascriptTest("testDone"));

  CollectCoverage("foo");
  ASSERT_FALSE(base::IsDirectoryEmpty(tmp_dir_.GetPath()));

  // Scripts and tests are special directories under the WebUI specific
  // directory, ensure they have been created and are not empty.
  base::FilePath coverage_dir =
      tmp_dir_.GetPath().AppendASCII("webui_javascript_code_coverage");
  ASSERT_FALSE(base::IsDirectoryEmpty(coverage_dir.AppendASCII("scripts")));
  ASSERT_FALSE(base::IsDirectoryEmpty(coverage_dir.AppendASCII("tests")));
}