chromium/chrome/browser/lacros/desk_template_client_lacros_browsertest.cc

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

#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_future.h"
#include "chrome/browser/lacros/desk_template_client_lacros.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/crosapi/mojom/desk_template.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_info.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "ui/gfx/range/range.h"
#include "ui/platform_window/platform_window.h"
#include "ui/views/test/widget_show_state_waiter.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h"
#include "url/gurl.h"

namespace {

constexpr char kTestName[] = "chrome version";
constexpr char kChromeVersionUrl[] = "chrome://version/";
constexpr char kGoogleURL[] = "www.google.com";
constexpr char kAboutBlankURL[] = "about:blank";

// Asserts that tab groups are correct based on `expected_state`.
void AssertTabGroupsCorrect(
    TabStripModel* browser_tab_model,
    const crosapi::mojom::DeskTemplateStatePtr& expected_state) {
  TabGroupModel* group_model = browser_tab_model->group_model();
  std::vector<tab_groups::TabGroupId> tab_group_id_list =
      group_model->ListTabGroups();

  EXPECT_TRUE(expected_state->groups.has_value());
  std::vector<tab_groups::TabGroupInfo>& expected_groups =
      expected_state->groups.value();

  for (const auto& group_id : tab_group_id_list) {
    TabGroup* current_group = group_model->GetTabGroup(group_id);

    tab_groups::TabGroupInfo current_group_info = tab_groups::TabGroupInfo(
        current_group->ListTabs(),
        tab_groups::TabGroupVisualData(*current_group->visual_data()));
    auto it = std::find(expected_groups.begin(), expected_groups.end(),
                        current_group_info);

    EXPECT_FALSE(it == expected_groups.end());
  }
}

// Assertst that `browser` was created correctly based on `expected_state` and
// `initial_bounds`.
void AssertBrowserCreatedCorrectly(
    Browser* browser,
    const crosapi::mojom::DeskTemplateStatePtr& expected_state,
    const gfx::Rect& initial_bounds) {
  EXPECT_TRUE(browser);

  EXPECT_EQ(initial_bounds, browser->create_params().initial_bounds);

  if (expected_state->browser_app_name.has_value()) {
    EXPECT_EQ(browser->create_params().type, Browser::Type::TYPE_APP);
  } else {
    EXPECT_EQ(browser->create_params().type, Browser::Type::TYPE_NORMAL);
  }

  EXPECT_EQ(browser->creation_source(), Browser::CreationSource::kDeskTemplate);

  TabStripModel* browser_tab_model = browser->tab_strip_model();
  EXPECT_TRUE(browser_tab_model);

  EXPECT_EQ(expected_state->urls.size(),
            static_cast<uint32_t>(browser_tab_model->count()));

  // We expect order of the browser's tabs to be equivalent to the tabs given.
  int tab_index = 0;
  for (const auto& expected_url : expected_state->urls) {
    EXPECT_EQ(expected_url,
              browser_tab_model->GetWebContentsAt(tab_index)->GetURL());
    ++tab_index;
  }

  if (browser_tab_model->SupportsTabGroups() &&
      expected_state->groups.has_value()) {
    AssertTabGroupsCorrect(browser_tab_model, expected_state);
  }

  EXPECT_EQ(
      expected_state->first_non_pinned_index,
      static_cast<uint32_t>(browser_tab_model->IndexOfFirstNonPinnedTab()));
}

}  // namespace

class DeskTemplateClientLacrosBrowserTest : public InProcessBrowserTest {
 public:
  // Returns the correct GURL object for "www.google.com/test" which is used
  // as a testing URL.
  GURL GetGoogleTestURL() {
    return embedded_test_server()->GetURL(kGoogleURL, "/test");
  }

  // Returns a maximal `DeskTemplateStatePtr` mojom for testing without pinned
  // tabs and tab groups.
  crosapi::mojom::DeskTemplateStatePtr
  MakeTestStateWithoutPinnedTabsOrTabGroup() {
    crosapi::mojom::DeskTemplateStatePtr state =
        crosapi::mojom::DeskTemplateState::New();

    state->urls = std::vector<GURL>(
        {GURL(kChromeVersionUrl), GURL(kAboutBlankURL), GetGoogleTestURL(),
         GURL(kChromeVersionUrl), GURL(kChromeVersionUrl)});
    state->active_index = 0;

    return state;
  }

  // Returns a DeskTemplateStatePtr where all tabs are pinned.  This tests for
  // regressions wherein all tabs become unpinned on launch.
  crosapi::mojom::DeskTemplateStatePtr MakeTestMojomWithOnlyPinnedTabs() {
    crosapi::mojom::DeskTemplateStatePtr state =
        MakeTestStateWithoutPinnedTabsOrTabGroup();

    state->first_non_pinned_index = 5;

    return state;
  }

  // Returns a DeskTemplateStatePtr where some tabs are pinned and the rest are
  // normal. This tests for regressions wherein all tabs become unpinned on
  // launch.
  crosapi::mojom::DeskTemplateStatePtr MakeTestMojomWithSomePinnedTabs() {
    crosapi::mojom::DeskTemplateStatePtr state =
        MakeTestStateWithoutPinnedTabsOrTabGroup();

    state->first_non_pinned_index = 3;

    return state;
  }

  // Returns a maximal `DeskTemplateStatePtr` mojom for testing.
  crosapi::mojom::DeskTemplateStatePtr MakeTestMojom() {
    crosapi::mojom::DeskTemplateStatePtr state =
        MakeTestStateWithoutPinnedTabsOrTabGroup();

    // Add in tab groups and pinned tabs.
    state->first_non_pinned_index = 1;
    state->groups =
        std::vector<tab_groups::TabGroupInfo>({tab_groups::TabGroupInfo(
            gfx::Range(2, 4),
            tab_groups::TabGroupVisualData(
                u"tab group one", tab_groups::TabGroupColorId::kGreen))});

    return state;
  }

  // Returns a maximal `DeskTemplateStatePtr` mojom for testing where the tab
  // group in question reaches to the end of the tab strip.  This ensures that
  // the browser launches with the entirety of the tab group intact.
  crosapi::mojom::DeskTemplateStatePtr MakeTestMojomWithTabGroupAtEndOfStrip() {
    crosapi::mojom::DeskTemplateStatePtr state =
        MakeTestStateWithoutPinnedTabsOrTabGroup();

    // Add in tab groups and pinned tabs.
    state->first_non_pinned_index = 1;
    state->groups =
        std::vector<tab_groups::TabGroupInfo>({tab_groups::TabGroupInfo(
            gfx::Range(2, 5),
            tab_groups::TabGroupVisualData(
                u"tab group one", tab_groups::TabGroupColorId::kGreen))});

    return state;
  }

  // Returns a PWA `DeskTemplateStatePtr` mojom for testing app launches work in
  // lacros.
  crosapi::mojom::DeskTemplateStatePtr MakeAppTestMojom() {
    crosapi::mojom::DeskTemplateStatePtr state =
        crosapi::mojom::DeskTemplateState::New();

    state->urls = std::vector<GURL>({GURL(kChromeVersionUrl)});
    state->active_index = 0;
    state->browser_app_name = kTestName;

    return state;
  }

  // Returns the result of MakeTestMojom with additional invalid tab groups.
  // The result of launching a browser with this should pass
  // AssertBrowserCreated correctly with the standard MakeTestMojom object
  // passed as the mojom to assert against.
  crosapi::mojom::DeskTemplateStatePtr MakeInvalidTabGroupTestMojom() {
    crosapi::mojom::DeskTemplateStatePtr state = MakeTestMojom();

    EXPECT_TRUE(state->groups.has_value());

    // First group violates the upper bound of the tab range.
    state->groups->emplace_back(tab_groups::TabGroupInfo(
        gfx::Range(4, 6),
        tab_groups::TabGroupVisualData(u"tab_group_two",
                                       tab_groups::TabGroupColorId::kBlue)));

    return state;
  }

  // Returns the result of MakeTestStateWithoutPinnedTabsOrTabGroup with an
  // invalid first non pinned index The result of launching a browser with this
  // should pass AssertBrowserCreated correctley with the standard
  // MakeTestStateWithoutPinnedTabsOrTabGroup object passed as the mojom to
  // assert against.
  crosapi::mojom::DeskTemplateStatePtr MakeInvalidPinnedTabTestMojom() {
    crosapi::mojom::DeskTemplateStatePtr state =
        MakeTestStateWithoutPinnedTabsOrTabGroup();

    // first non pinned index in this case will be larger than the count of
    // tabs./
    state->first_non_pinned_index = 6;

    return state;
  }

  // Request handler method, just returns a simple plaintext response when
  // invoked.  We're not testing the underlying behavior of the browser, rather
  // simply that normal URLs invoked on template launch are handled correctly.
  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) {
    // We're not interested in the contents of the response for this test
    // any response should be valid.
    auto http_response =
        std::make_unique<net::test_server::BasicHttpResponse>();
    http_response->set_code(net::HTTP_OK);
    http_response->set_content("hello");
    http_response->set_content_type("text/plain");
    return http_response;
  }

  // SetUp for test base class, initializes the embedded test server.
  void SetUp() override {
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
    InProcessBrowserTest::SetUp();
  }

  // Sets Up embedded test server to listen on IO thread.
  void SetUpOnMainThread() override {
    host_resolver()->AddRule(kGoogleURL, "127.0.0.1");

    embedded_test_server()->RegisterRequestHandler(
        base::BindRepeating(&DeskTemplateClientLacrosBrowserTest::HandleRequest,
                            base::Unretained(this)));
    embedded_test_server()->StartAcceptingConnections();
  }

  // Helper function that modifies the test's browser() object to match the
  // representation produced in the `MakeTestMojom` function.
  void MakeTestBrowser() {
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 0, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 2, GetGoogleTestURL(),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 3, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 4, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));

    TabStripModel* strip_model = browser()->tab_strip_model();

    // May not be necessary but guarantees that the correct tab is active.
    strip_model->ActivateTabAt(0);

    // Assert we haven't moved the pinned tab.
    EXPECT_EQ(0, strip_model->SetTabPinned(0, /*pinned=*/true));
    tab_groups::TabGroupId group_id_one = strip_model->AddToNewGroup({2, 3});

    TabGroupModel* group_model = strip_model->group_model();
    TabGroup* group_one = group_model->GetTabGroup(group_id_one);
    group_one->SetVisualData(tab_groups::TabGroupVisualData(
        u"tab group one", tab_groups::TabGroupColorId::kGreen));
  }

  // Helper function that modifies the test's browser() object to match the
  // representation produced in the `MakeTestMojomWithTabGroupAtEndOfStrip`
  // function. This helps to ensure that the entirety of Tab Groups are captured
  // properly.
  void MakeTestBrowserWithTabGroupAtEndOfStrip() {
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 0, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 2, GetGoogleTestURL(),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 3, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 4, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));

    TabStripModel* strip_model = browser()->tab_strip_model();

    // May not be necessary but guarantees that the correct tab is active.
    strip_model->ActivateTabAt(0);

    // Assert we haven't moved the pinned tab.
    EXPECT_EQ(0, strip_model->SetTabPinned(0, /*pinned=*/true));
    tab_groups::TabGroupId group_id_one = strip_model->AddToNewGroup({2, 3, 4});

    TabGroupModel* group_model = strip_model->group_model();
    TabGroup* group_one = group_model->GetTabGroup(group_id_one);
    group_one->SetVisualData(tab_groups::TabGroupVisualData(
        u"tab group one", tab_groups::TabGroupColorId::kGreen));
  }

  void MakeTestAppBrowser() {
    Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();

    Browser::CreateParams create_params = Browser::CreateParams::CreateForApp(
        kTestName, /*trusted_source=*/true, {0, 0, 256, 256}, profile,
        /*user_gesture=*/false);
    Browser::Create(create_params);

    // Close default test browser, we will set browser to the browser created
    // by this method.
    CloseBrowserSynchronously(browser());
    SelectFirstBrowser();

    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 0, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
  }

  // Instantiates a browser that when captured should assert equivalence with
  // MakeTestStateWithoutPinnedTabsOrTabGroups.
  void MakeBrowserWithoutTabgroupsOrPinnedTabs() {
    Profile* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();

    Browser::CreateParams create_params(profile, /*user_gesture=*/false);
    create_params.initial_bounds = gfx::Rect(0, 0, 256, 256);
    create_params.are_tab_groups_enabled = false;
    Browser::Create(create_params);

    // Close default test browser, we will set browser to the browser created
    // by this method.
    CloseBrowserSynchronously(browser());
    SelectFirstBrowser();

    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 0, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 1, GURL(kAboutBlankURL),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 2, GetGoogleTestURL(),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 3, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));
    EXPECT_TRUE(
        AddTabAtIndexToBrowser(browser(), 4, GURL(kChromeVersionUrl),
                               ui::PageTransition::PAGE_TRANSITION_LINK));

    TabStripModel* strip_model = browser()->tab_strip_model();

    // May not be necessary but guarantees that the correct tab is active.
    strip_model->ActivateTabAt(0);
  }
};

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state = MakeTestMojom();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters = MakeTestMojom();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserCorrectlyAndMinimized) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state = MakeTestMojom();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters = MakeTestMojom();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(
      expected_bounds, ui::SHOW_STATE_MINIMIZED, std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
  // Test that the browser gets correctly minimized.
  views::test::WaitForWidgetShowState(
      BrowserView::GetBrowserViewForBrowser(browser())->GetWidget(),
      ui::SHOW_STATE_MINIMIZED);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserWithOnlyPinnedTabsCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state =
      MakeTestMojomWithOnlyPinnedTabs();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeTestMojomWithOnlyPinnedTabs();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserWithSomePinnedTabsCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state =
      MakeTestMojomWithSomePinnedTabs();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeTestMojomWithSomePinnedTabs();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserWithTabGroupAtEndOfStripCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state =
      MakeTestMojomWithTabGroupAtEndOfStrip();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeTestMojomWithTabGroupAtEndOfStrip();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesBrowserAppCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state = MakeAppTestMojom();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters = MakeAppTestMojom();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       IgnoresInvalidTabGroups) {
  // When launched the invalid tab groups should be removed before being
  // attached to the browser, so we assert against the regular test mojom.
  crosapi::mojom::DeskTemplateStatePtr expected_state = MakeTestMojom();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeInvalidTabGroupTestMojom();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       LaunchesOldBrowserCorrectly) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state =
      MakeTestStateWithoutPinnedTabsOrTabGroup();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeTestStateWithoutPinnedTabsOrTabGroup();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       IgnoresInvalidPinnedTab) {
  // State pointers don't supply a Clone operation.  Therefore we will create
  // two semantically identical states to test against.
  crosapi::mojom::DeskTemplateStatePtr expected_state =
      MakeTestStateWithoutPinnedTabsOrTabGroup();
  crosapi::mojom::DeskTemplateStatePtr launch_parameters =
      MakeInvalidPinnedTabTestMojom();
  gfx::Rect expected_bounds(0, 0, 256, 256);

  DeskTemplateClientLacros client;

  client.CreateBrowserWithRestoredData(expected_bounds, ui::SHOW_STATE_DEFAULT,
                                       std::move(launch_parameters));

  // Close default test browser, we will set browser to the browser created
  // by the method under test.
  CloseBrowserSynchronously(browser());
  SelectFirstBrowser();

  AssertBrowserCreatedCorrectly(browser(), expected_state, expected_bounds);
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       CapturesBrowserCorrectly) {
  MakeTestBrowser();
  DeskTemplateClientLacros client;
  std::string window_id = views::DesktopWindowTreeHostLacros::From(
                              browser()->window()->GetNativeWindow()->GetHost())
                              ->platform_window()
                              ->GetWindowUniqueId();

  base::test::TestFuture<uint32_t, const std::string&,
                         crosapi::mojom::DeskTemplateStatePtr>
      future;
  client.GetBrowserInformation(/*serial=*/0, window_id, future.GetCallback());
  auto [serial, out_window_id, out_state] = future.Take();

  crosapi::mojom::DeskTemplateStatePtr test_mojom = MakeTestMojom();
  EXPECT_EQ(out_state->urls, test_mojom->urls);
  EXPECT_EQ(out_state->active_index, test_mojom->active_index);
  EXPECT_EQ(out_state->first_non_pinned_index,
            test_mojom->first_non_pinned_index);
  EXPECT_TRUE(test_mojom->groups.has_value());
  EXPECT_TRUE(out_state->groups.has_value());

  // We don't care about the order of tab groups.
  EXPECT_THAT(out_state->groups.value(),
              testing::UnorderedElementsAreArray(test_mojom->groups.value()));
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       CapturesBrowserWithTabGroupAtEndOfStripCorrectly) {
  MakeTestBrowserWithTabGroupAtEndOfStrip();
  DeskTemplateClientLacros client;
  std::string window_id = views::DesktopWindowTreeHostLacros::From(
                              browser()->window()->GetNativeWindow()->GetHost())
                              ->platform_window()
                              ->GetWindowUniqueId();

  base::test::TestFuture<uint32_t, const std::string&,
                         crosapi::mojom::DeskTemplateStatePtr>
      future;
  client.GetBrowserInformation(/*serial=*/0, window_id, future.GetCallback());
  auto [serial, out_window_id, out_state] = future.Take();

  crosapi::mojom::DeskTemplateStatePtr test_mojom =
      MakeTestMojomWithTabGroupAtEndOfStrip();
  EXPECT_EQ(out_state->urls, test_mojom->urls);
  EXPECT_EQ(out_state->active_index, test_mojom->active_index);
  EXPECT_EQ(out_state->first_non_pinned_index,
            test_mojom->first_non_pinned_index);
  ASSERT_TRUE(test_mojom->groups.has_value());
  EXPECT_TRUE(out_state->groups.has_value());

  // We don't care about the order of tab groups.
  EXPECT_THAT(out_state->groups.value(),
              testing::UnorderedElementsAreArray(test_mojom->groups.value()));
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       CapturesBrowserWithoutPinnedTabsOrTabGroupsCorrectly) {
  MakeBrowserWithoutTabgroupsOrPinnedTabs();
  DeskTemplateClientLacros client;
  std::string window_id = views::DesktopWindowTreeHostLacros::From(
                              browser()->window()->GetNativeWindow()->GetHost())
                              ->platform_window()
                              ->GetWindowUniqueId();

  base::test::TestFuture<uint32_t, const std::string&,
                         crosapi::mojom::DeskTemplateStatePtr>
      future;
  client.GetBrowserInformation(/*serial=*/0, window_id, future.GetCallback());
  auto [serial, out_window_id, out_state] = future.Take();

  crosapi::mojom::DeskTemplateStatePtr test_mojom =
      MakeTestStateWithoutPinnedTabsOrTabGroup();
  EXPECT_EQ(out_state->urls, test_mojom->urls);
  EXPECT_EQ(out_state->active_index, test_mojom->active_index);

  // first_non_pinned_index defaults to zero so it should be zero when unset.
  // This value would be ignored if launched as is asserted in tests above.
  EXPECT_EQ(out_state->first_non_pinned_index, 0u);

  // This state should not contain tab groups.
  EXPECT_FALSE(out_state->groups.has_value());
}

IN_PROC_BROWSER_TEST_F(DeskTemplateClientLacrosBrowserTest,
                       CapturesBrowserAppCorrectly) {
  MakeTestAppBrowser();
  DeskTemplateClientLacros client;
  std::string window_id = views::DesktopWindowTreeHostLacros::From(
                              browser()->window()->GetNativeWindow()->GetHost())
                              ->platform_window()
                              ->GetWindowUniqueId();

  base::test::TestFuture<uint32_t, const std::string&,
                         crosapi::mojom::DeskTemplateStatePtr>
      future;
  client.GetBrowserInformation(/*serial=*/0, window_id, future.GetCallback());
  auto [serial, out_window_id, out_state] = future.Take();

  crosapi::mojom::DeskTemplateStatePtr test_mojom = MakeAppTestMojom();
  EXPECT_EQ(out_state->urls, test_mojom->urls);
  EXPECT_EQ(out_state->browser_app_name, test_mojom->browser_app_name);
}