blob: 76dd2380998bbbf127ed2b0378b9a5ba8354de69 [file] [log] [blame]
// 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.
// This class runs functional tests for lens overlay. These tests spin up a full
// web browser, but allow for inspection and modification of internal state of
// LensOverlayController and other business-logic classes.
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "base/test/run_until.h"
#include "chrome/browser/lens/lens_overlay/lens_overlay_url_builder.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
#include "chrome/browser/ui/tabs/tab_features.h"
#include "chrome/browser/ui/tabs/tab_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/side_panel/lens/lens_overlay_side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_util.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/lens/lens_features.h"
#include "components/permissions/test/permission_request_observer.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/view_utils.h"
namespace {
constexpr char kDocumentWithNamedElement[] = "/select.html";
using State = LensOverlayController::State;
constexpr char kRequestNotificationsScript[] = R"(
new Promise(resolve => {
Notification.requestPermission().then(function (permission) {
resolve(permission);
});
})
)";
// Stubs out network requests.
class LensOverlayControllerFake : public LensOverlayController {
public:
explicit LensOverlayControllerFake(tabs::TabModel* tab_model)
: LensOverlayController(tab_model) {}
};
class TabFeaturesFake : public tabs::TabFeatures {
public:
explicit TabFeaturesFake(tabs::TabModel* tab) : tabs::TabFeatures(tab) {}
protected:
std::unique_ptr<LensOverlayController> CreateLensController(
tabs::TabModel* tab) override {
return std::make_unique<LensOverlayControllerFake>(tab);
}
};
class LensOverlayControllerBrowserTest : public InProcessBrowserTest {
protected:
LensOverlayControllerBrowserTest() {
tabs::TabFeatures::ReplaceTabFeaturesForTesting(base::BindRepeating(
&LensOverlayControllerBrowserTest::CreateTabFeatures,
base::Unretained(this)));
}
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
embedded_test_server()->StartAcceptingConnections();
}
~LensOverlayControllerBrowserTest() override {
tabs::TabFeatures::ReplaceTabFeaturesForTesting(base::NullCallback());
}
std::unique_ptr<tabs::TabFeatures> CreateTabFeatures(tabs::TabModel* tab) {
return std::make_unique<TabFeaturesFake>(tab);
}
content::WebContents* GetOverlayWebContents() {
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
raw_ptr<views::WebView> overlay_web_view =
views::AsViewClass<views::WebView>(
controller->GetOverlayWidgetForTesting()
->GetContentsView()
->children()[0]);
return overlay_web_view->GetWebContents();
}
void SimulateLeftClickDrag(gfx::Point from, gfx::Point to) {
auto* overlay_web_contents = GetOverlayWebContents();
// We should wait for the main frame's hit-test data to be ready before
// sending the click event below to avoid flakiness.
content::WaitForHitTestData(overlay_web_contents->GetPrimaryMainFrame());
content::SimulateMouseEvent(overlay_web_contents,
blink::WebInputEvent::Type::kMouseDown,
blink::WebMouseEvent::Button::kLeft, from);
content::SimulateMouseEvent(overlay_web_contents,
blink::WebInputEvent::Type::kMouseMove,
blink::WebMouseEvent::Button::kLeft, to);
content::SimulateMouseEvent(overlay_web_contents,
blink::WebInputEvent::Type::kMouseUp,
blink::WebMouseEvent::Button::kLeft, to);
content::RunUntilInputProcessed(
overlay_web_contents->GetRenderWidgetHostView()->GetRenderWidgetHost());
}
// Lens overlay takes a screenshot of the tab. In order to take a screenshot
// the tab must not be about:blank and must be painted.
void WaitForPaint() {
const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(base::test::RunUntil([&]() {
return browser()
->tab_strip_model()
->GetActiveTab()
->contents()
->CompletedFirstVisuallyNonEmptyPaint();
}));
}
private:
base::test::ScopedFeatureList feature_list_{lens::features::kLensOverlay};
};
// TODO(https://crbug.com/329708692): Flaky on all platforms.
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
DISABLED_CaptureScreenshot) {
WaitForPaint();
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
// Verify screenshot was captured and stored.
auto screenshot_bitmap = controller->current_screenshot();
ASSERT_FALSE(screenshot_bitmap.empty());
}
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, CreateAndLoadWebUI) {
WaitForPaint();
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
// Assert that the web view was created and loaded WebUI.
GURL webui_url(chrome::kChromeUILensUntrustedURL);
ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
ASSERT_EQ(GetOverlayWebContents()->GetLastCommittedURL(), webui_url);
}
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, ShowSidePanel) {
WaitForPaint();
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
// Now show the side panel.
controller->side_panel_coordinator()->RegisterEntryAndShow();
auto* coordinator =
SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
EXPECT_TRUE(coordinator->IsSidePanelShowing());
EXPECT_EQ(coordinator->GetCurrentEntryId(),
SidePanelEntry::Id::kLensOverlayResults);
}
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
DelayPermissionsPrompt) {
// Navigate to a page so we can request permissions
WaitForPaint();
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(contents);
permissions::PermissionRequestObserver observer(contents);
// Request permission in tab under overlay.
EXPECT_TRUE(content::ExecJs(
contents->GetPrimaryMainFrame(), kRequestNotificationsScript,
content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
// Verify no prompt was shown
observer.Wait();
EXPECT_FALSE(observer.request_shown());
// Close overlay
controller->CloseUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOff; }));
// Verify a prompt was shown
ASSERT_TRUE(base::test::RunUntil([&]() { return observer.request_shown(); }));
}
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
ShowSidePanelAfterManualRegionSelection) {
WaitForPaint();
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
// Simulate mouse events on the overlay for drawing a manual region.
gfx::Point center =
GetOverlayWebContents()->GetContainerBounds().CenterPoint();
gfx::Point off_center = gfx::Point(center);
off_center.Offset(100, 100);
SimulateLeftClickDrag(center, off_center);
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlayAndResults; }));
auto* coordinator =
SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
// Expect the Lens Overlay results panel to open.
ASSERT_TRUE(coordinator->IsSidePanelShowing());
EXPECT_EQ(coordinator->GetCurrentEntryId(),
SidePanelEntry::Id::kLensOverlayResults);
}
// TODO(b/328294794): This browser test should be deleted / modified after text
// requests are implemented from mojo.
IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
ShowSidePanelAfterTextRequest) {
WaitForPaint();
std::string text_query = "Apples";
// State should start in off.
auto* controller = browser()
->tab_strip_model()
->GetActiveTab()
->tab_features()
->lens_overlay_controller();
ASSERT_EQ(controller->state(), State::kOff);
// Showing UI should eventually result in overlay state.
controller->ShowUI();
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlay; }));
ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
// TODO(b/328294794): This function should be replaced when the text selection
// call from mojo is implemented.
controller->IssueTextRequestForTesting(text_query);
ASSERT_TRUE(base::test::RunUntil(
[&]() { return controller->state() == State::kOverlayAndResults; }));
// Expect the Lens Overlay results panel to open.
auto* coordinator =
SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
ASSERT_TRUE(coordinator->IsSidePanelShowing());
EXPECT_EQ(coordinator->GetCurrentEntryId(),
SidePanelEntry::Id::kLensOverlayResults);
}
} // namespace