chromium/chrome/browser/ash/accessibility/spoken_feedback_integration_test.cc

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

#include "ash/ash_element_identifiers.h"
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/chromevox_panel.h"
#include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/test/base/chromeos/crosier/ash_integration_test.h"
#include "chrome/test/base/chromeos/crosier/upstart.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "extensions/browser/process_manager.h"
#include "ui/base/interaction/interactive_test.h"
#include "ui/base/test/ui_controls.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"

namespace ash {

namespace {
constexpr char kGoogleTtsJobName[] = "googletts";
constexpr char kGoogleTestSupportPath[] =
    "crosier_js_helpers/google_tts_test_support.js";
}  // namespace

// Integration tests for the ChromeVox screen reader. These tests run on
// physical devices and VMs running a complete ChromeOS image.
class SpokenFeedbackIntegrationTest : public AshIntegrationTest {
 public:
  SpokenFeedbackIntegrationTest() = default;
  ~SpokenFeedbackIntegrationTest() override = default;
  SpokenFeedbackIntegrationTest(const SpokenFeedbackIntegrationTest&) = delete;
  SpokenFeedbackIntegrationTest& operator=(
      const SpokenFeedbackIntegrationTest&) = delete;
};

// TODO(crbug.com/362289622): Re-enable this test after fallback for chrome
// screen ai has been implemented.
IN_PROC_BROWSER_TEST_F(SpokenFeedbackIntegrationTest,
                       DISABLED_KeyboardShortcut) {
  SetupContextWidget();
  RunTestSequence(Log("Enabling ChromeVox with the keyboard shortcut"), Do([] {
                    ui_controls::SendKeyPress(/*window=*/nullptr, ui::VKEY_Z,
                                              /*control=*/true, /*shift=*/false,
                                              /*alt=*/true, /*command=*/false);
                  }),
                  Log("Waiting for the ChromeVox panel to show"),
                  WaitForShow(kChromeVoxPanelElementId));
}

// Integration tests for the Google TTS engine on ChromeOS. These tests run on
// physical devices and VMs running a complete ChromeOS image.
class GoogleTtsIntegrationTest : public AshIntegrationTest {
 public:
  GoogleTtsIntegrationTest() {
    // Google TTS files are mounted by upstart when the UI job is started.
    // However, Crosier (which is the UI job in this context) is started by a
    // test script, not upstart. The result is that Google TTS will never get
    // mounted because upstart doesn't know that the UI job has started.
    // Therefore, we need to manually start googletts so that files are
    // available at runtime.
    upstart::StartJob(kGoogleTtsJobName);
    upstart::WaitForJobStatus(kGoogleTtsJobName, upstart::Goal::kStart,
                              upstart::State::kRunning,
                              upstart::WrongGoalPolicy::kReject);
  }

  ~GoogleTtsIntegrationTest() override {
    // Since googletts is manually started in `SetUpOnMainThread`, we need to
    // manually stop it.
    upstart::StopJob(kGoogleTtsJobName);
    upstart::WaitForJobStatus(kGoogleTtsJobName, upstart::Goal::kStop,
                              upstart::State::kWaiting,
                              upstart::WrongGoalPolicy::kReject);
    TestSudoHelperClient().RunCommand("rm -rf /tmp/tts/");
  }
  GoogleTtsIntegrationTest(const GoogleTtsIntegrationTest&) = delete;
  GoogleTtsIntegrationTest& operator=(const GoogleTtsIntegrationTest&) = delete;

  void EnableGoogleTts() {
    base::RunLoop loop{base::RunLoop::Type::kNestableTasksAllowed};
    Profile* profile = AccessibilityManager::Get()->profile();
    extensions::ExtensionHostTestHelper host_helper(
        profile, extension_misc::kGoogleSpeechSynthesisExtensionId);
    TtsExtensionEngine::GetInstance()->LoadBuiltInTtsEngine(profile);
    extensions::ProcessManager::Get(profile)->WakeEventPage(
        extension_misc::kGoogleSpeechSynthesisExtensionId,
        base::BindLambdaForTesting([&loop](bool success) { loop.Quit(); }));
    loop.Run();
    host_helper.WaitForHostCompletedFirstLoad();
  }

  void InjectTestSupportScript() {
    base::ScopedAllowBlockingForTesting allow_blocking;
    base::FilePath source_dir;
    base::FilePath test_support_path(kGoogleTestSupportPath);
    std::string script;
    ASSERT_TRUE(base::ReadFileToString(test_support_path, &script))
        << test_support_path;
    ExecuteGoogleTtsScript(script);
  }

  void EnsureTtsEngineLoaded() {
    ExecuteGoogleTtsScript("googleTtsTestSupport.ensureLoaded();");
  }

  void SendSpeechRequest(const std::string& utterance) {
    std::string script = base::StringPrintf("googleTtsTestSupport.speak(`%s`)",
                                            utterance.c_str());
    ExecuteGoogleTtsScript(script);
  }

  void ConsumeUtterance(const std::string& utterance) {
    std::string script = base::StringPrintf(
        "googleTtsTestSupport.consume(`%s`)", utterance.c_str());
    ExecuteGoogleTtsScript(script);
  }

 private:
  void ExecuteGoogleTtsScript(const std::string& script) {
    extensions::browsertest_util::ExecuteScriptInBackgroundPage(
        /*context=*/AccessibilityManager::Get()->profile(),
        /*extension_id=*/extension_misc::kGoogleSpeechSynthesisExtensionId,
        /*script=*/script);
  }
};

IN_PROC_BROWSER_TEST_F(GoogleTtsIntegrationTest, Speak) {
  SetupContextWidget();
  WaitForAshFullyStarted();
  RunTestSequence(Log("Setting up Google TTS extension"), Do([this] {
                    EnableGoogleTts();
                    InjectTestSupportScript();
                    EnsureTtsEngineLoaded();
                  }),
                  Log("Requesting and verifying speech"), Do([this] {
                    SendSpeechRequest("Test");
                    ConsumeUtterance("Test");
                  }));
}

}  // namespace ash