blob: 4d41ff17eadfd615050a3ff696d73892e648a1c2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
#define CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
#include <fstream>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/types/strong_alias.h"
#include "base/values.h"
#include "chrome/browser/ui/browser.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/browser_test_utils.h"
#include "services/network/public/cpp/network_switches.h"
namespace content {
class RenderFrameHost;
class WebContents;
} // namespace content
namespace captured_sites_test_utils {
// The amount of time to wait for an action to complete, or for a page element
// to appear. The Captured Site Automation Framework uses this timeout to break
// out of wait loops in the event that
// 1. A page load error occurred and the page does not have a page element
// the test expects. Test should stop waiting.
// 2. A page contains persistent animation (such as a flash sale count down
// timer) that causes the RenderFrameHost count to never diminish completely.
// Test should stop waiting if a sufficiently large time has expired for the
// page to load or for the page to respond to the last user action.
const base::TimeDelta default_action_timeout = base::Seconds(30);
// The amount of time to wait for a page to trigger a paint in response to a
// an action. The Captured Site Automation Framework uses this timeout to
// break out of a wait loop after a hover action.
const base::TimeDelta visual_update_timeout = base::Seconds(20);
// The amount of time to do a precheck on the page before going to a click
// fallback action.
const base::TimeDelta click_fallback_timeout = base::Seconds(5);
// When we cause a scroll event, make sure we give the page a moment to react
// before continuing.
const base::TimeDelta scroll_wait_timeout = base::Seconds(2);
// Some times, tests tend to need a break that can't be read from the elements
// play status.
const base::TimeDelta cool_off_action_timeout = base::Seconds(1);
// The time to wait between checks for a page to become idle or active based on
// the loading status and then the render frame count.
const base::TimeDelta wait_for_idle_loop_length = base::Milliseconds(500);
std::string FilePathToUTF8(const base::FilePath::StringType& str);
enum ExpectedResult { kPass, kFail };
struct CapturedSiteParams {
std::string scenario_dir;
std::string site_name;
std::optional<int> bug_number;
ExpectedResult expectation = kPass;
bool is_disabled = false;
base::FilePath capture_file_path;
base::FilePath recipe_file_path;
base::FilePath refresh_file_path;
CapturedSiteParams();
~CapturedSiteParams();
CapturedSiteParams(const CapturedSiteParams& other);
};
std::ostream& operator<<(std::ostream& os, const CapturedSiteParams& data);
std::vector<CapturedSiteParams> GetCapturedSites(
const base::FilePath& replay_files_dir_path);
struct GetParamAsString {
template <class ParamType>
std::string operator()(const testing::TestParamInfo<ParamType>& info) const {
if (info.param.scenario_dir.empty())
return info.param.site_name;
return base::StrCat({info.param.scenario_dir, "_", info.param.site_name});
}
};
std::optional<base::FilePath> GetCommandFilePath();
// Prints tips on how to run captured-site tests.
// |test_file_name| should be without the .cc suffix.
void PrintInstructions(const char* test_file_name);
// IFrameWaiter
//
// IFrameWaiter is an waiter object that waits for an iframe befitting a
// criteria to appear. The criteria can be the iframe's 'name' attribute,
// the iframe's origin, or the iframe's full url.
class IFrameWaiter : public content::WebContentsObserver {
public:
explicit IFrameWaiter(content::WebContents* webcontents);
IFrameWaiter(const IFrameWaiter&) = delete;
IFrameWaiter& operator=(const IFrameWaiter&) = delete;
~IFrameWaiter() override;
content::RenderFrameHost* WaitForFrameMatchingName(
const std::string& name,
const base::TimeDelta timeout = default_action_timeout);
content::RenderFrameHost* WaitForFrameMatchingOrigin(
const GURL origin,
const base::TimeDelta timeout = default_action_timeout);
content::RenderFrameHost* WaitForFrameMatchingUrl(
const GURL url,
const base::TimeDelta timeout = default_action_timeout);
private:
enum QueryType { NAME, ORIGIN, URL };
static bool FrameHasOrigin(const GURL& origin,
content::RenderFrameHost* frame);
// content::WebContentsObserver
void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override;
void FrameNameChanged(content::RenderFrameHost* render_frame_host,
const std::string& name) override;
QueryType query_type_;
base::RunLoop run_loop_;
raw_ptr<content::RenderFrameHost> target_frame_;
std::string frame_name_;
GURL origin_;
GURL url_;
};
// WebPageReplayServerWrapper
// WebPageReplayServerWrapper is a helper wrapper that controls the configuring
// and running the WebPageReplay Server instance.
class WebPageReplayServerWrapper {
public:
explicit WebPageReplayServerWrapper(const bool start_as_replay,
int hostHttpPort = 8080,
int hostHttpsPort = 8081);
WebPageReplayServerWrapper(const WebPageReplayServerWrapper&) = delete;
WebPageReplayServerWrapper& operator=(const WebPageReplayServerWrapper&) =
delete;
~WebPageReplayServerWrapper();
bool Start(const base::FilePath& capture_file_path);
bool Stop();
private:
bool RunWebPageReplayCmdAndWaitForExit(
const std::vector<std::string>& args,
const base::TimeDelta& timeout = base::Seconds(5));
bool RunWebPageReplayCmd(const std::vector<std::string>& args);
std::string cmd_name() { return start_as_replay_ ? "replay" : "record"; }
// The Web Page Replay server that serves the captured sites.
base::Process web_page_replay_server_;
int host_http_port_;
int host_https_port_;
bool start_as_replay_;
};
class ProfileDataController {
public:
ProfileDataController();
~ProfileDataController();
const autofill::CreditCard& credit_card() const { return card_; }
const autofill::AutofillProfile& profile() { return profile_; }
bool AddAutofillProfileInfo(const std::string& field_type,
const std::string& field_value);
std::optional<std::u16string> cvc() const { return cvc_; }
private:
// If a CVC is available in the Action Recorder receipt, this test uses a
// server card to autofill the payment form. So the "Enter CVC" dialog will
// pop up for card autofill. Otherwise, this test uses a local card to
// autofill the payment form.
std::optional<std::u16string> cvc_;
autofill::AutofillProfile profile_;
autofill::CreditCard card_;
};
// TestRecipeReplayChromeFeatureActionExecutor
//
// TestRecipeReplayChromeFeatureActionExecutor is a helper interface. A
// TestRecipeReplayChromeFeatureActionExecutor class implements functions
// that automate Chrome feature behavior. TestRecipeReplayer calls
// TestRecipeReplayChromeFeatureActionExecutor functions to execute actions
// that involves a Chrome feature - such as Chrome Autofill or Chrome
// Password Manager. Executing a Chrome feature action typically require
// using private or protected hooks defined inside that feature's
// InProcessBrowserTest class. By implementing this interface an
// InProcessBrowserTest exposes its feature to captured site automation.
class TestRecipeReplayChromeFeatureActionExecutor {
public:
TestRecipeReplayChromeFeatureActionExecutor(
const TestRecipeReplayChromeFeatureActionExecutor&) = delete;
TestRecipeReplayChromeFeatureActionExecutor& operator=(
const TestRecipeReplayChromeFeatureActionExecutor&) = delete;
// Chrome Autofill feature methods.
// Triggers Chrome Autofill in the specified input element on the specified
// document.
virtual bool AutofillForm(
const std::string& focus_element_css_selector,
const std::vector<std::string>& iframe_path,
const int attempts,
content::RenderFrameHost* frame,
std::optional<autofill::FieldType> triggered_field_type);
virtual bool AddAutofillProfileInfo(const std::string& field_type,
const std::string& field_value);
virtual bool SetupAutofillProfile();
// Chrome Password Manager feature methods.
virtual bool AddCredential(const std::string& origin,
const std::string& username,
const std::string& password);
virtual bool SavePassword();
virtual bool UpdatePassword();
virtual bool WaitForSaveFallback();
virtual bool IsChromeShowingPasswordGenerationPrompt();
virtual bool HasChromeShownSavePasswordPrompt();
virtual bool HasChromeStoredCredential(const std::string& origin,
const std::string& username,
const std::string& password);
protected:
TestRecipeReplayChromeFeatureActionExecutor();
~TestRecipeReplayChromeFeatureActionExecutor();
};
// TestRecipeReplayer
//
// The TestRecipeReplayer object drives Captured Site Automation by
// 1. Providing a set of functions that help an InProcessBrowserTest to
// configure, start and stop a Web Page Replay (WPR) server. A WPR server
// is a local server that intercepts and responds to Chrome requests with
// pre-recorded traffic. Using a captured site archive file, WPR can
// mimick the site server and provide the test with deterministic site
// behaviors.
// 2. Providing a function that deserializes and replays a Test Recipe. A Test
// Recipe is a JSON formatted file containing instructions on how to run a
// Chrome test against a live or captured site. These instructions include
// the starting URL for the test, and a list of user actions (clicking,
// typing) that drives the test. One may sample some example Test Recipes
// under the src/chrome/test/data/autofill/captured_sites directory.
class TestRecipeReplayer {
public:
static constexpr int kHostHttpPort = 8080;
static constexpr int kHostHttpsPort = 8081;
static constexpr int kHostHttpRecordPort = 8082;
static constexpr int kHostHttpsRecordPort = 8083;
enum DomElementReadyState {
kReadyStatePresent = 0,
kReadyStateVisible = 1 << 0,
kReadyStateEnabled = 1 << 1,
kReadyStateOnTop = 1 << 2
};
TestRecipeReplayer(
Browser* browser,
TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor);
TestRecipeReplayer(const TestRecipeReplayer&) = delete;
TestRecipeReplayer& operator=(const TestRecipeReplayer&) = delete;
~TestRecipeReplayer();
// Replay a test by:
// 1. Starting a WPR server using the specified capture file.
// 2. Replaying the specified Test Recipe file.
bool ReplayTest(const base::FilePath& capture_file_path,
const base::FilePath& recipe_file_path,
const std::optional<base::FilePath>& command_file_path);
const std::vector<testing::AssertionResult> GetValidationFailures() const;
static void SetUpCommandLine(base::CommandLine* command_line);
static void SetUpHostResolverRules(base::CommandLine* command_line);
static bool ScrollElementIntoView(const std::string& element_xpath,
content::RenderFrameHost* frame);
static bool PlaceFocusOnElement(const std::string& element_xpath,
const std::vector<std::string> iframe_path,
content::RenderFrameHost* frame);
static bool GetBoundingRectOfTargetElement(
const std::string& target_element_xpath,
const std::vector<std::string> iframe_path,
content::RenderFrameHost* frame,
gfx::Rect* output_rect);
static bool SimulateLeftMouseClickAt(
const gfx::Point& point,
content::RenderFrameHost* render_frame_host);
static bool SimulateMouseHoverAt(content::RenderFrameHost* render_frame_host,
const gfx::Point& point);
private:
using IgnoreCase = base::StrongAlias<struct IgnoreCaseTag, bool>;
static bool GetIFrameOffsetFromIFramePath(
const std::vector<std::string>& iframe_path,
content::RenderFrameHost* frame,
gfx::Vector2d* offset);
static bool GetBoundingRectOfTargetElement(
const std::string& target_element_xpath,
content::RenderFrameHost* frame,
gfx::Rect* output_rect);
Browser* browser();
TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor();
WebPageReplayServerWrapper* web_page_replay_server_wrapper();
content::WebContents* GetWebContents();
void CleanupSiteData();
bool StartWebPageReplayServer(base::Process* web_page_replay_server,
const base::FilePath& capture_file_path,
const bool start_as_replay = true);
bool StopWebPageReplayServer(base::Process* web_page_replay_server);
bool ReplayRecordedActions(
const base::FilePath& recipe_file_path,
const std::optional<base::FilePath>& command_file_path);
bool InitializeBrowserToExecuteRecipe(base::Value::Dict& recipe);
bool ExecuteAutofillAction(base::Value::Dict action);
bool ExecuteClickAction(base::Value::Dict action);
bool ExecuteClickIfNotSeenAction(base::Value::Dict action);
bool ExecuteCoolOffAction(base::Value::Dict action);
bool ExecuteCloseTabAction(base::Value::Dict action);
bool ExecuteHoverAction(base::Value::Dict action);
bool ExecuteForceLoadPage(base::Value::Dict action);
bool ExecutePressEnterAction(base::Value::Dict action);
bool ExecutePressEscapeAction(base::Value::Dict action);
bool ExecutePressSpaceAction(base::Value::Dict action);
bool ExecuteRunCommandAction(base::Value::Dict action);
bool ExecuteSavePasswordAction(base::Value::Dict action);
bool ExecuteSelectDropdownAction(base::Value::Dict action);
bool ExecuteTypeAction(base::Value::Dict action);
bool ExecuteTypePasswordAction(base::Value::Dict action);
bool ExecuteUpdatePasswordAction(base::Value::Dict action);
bool ExecuteValidateFieldValueAction(base::Value::Dict action);
bool ExecuteValidateNoSavePasswordPromptAction(base::Value::Dict action);
bool ExecuteValidatePasswordGenerationPromptAction(base::Value::Dict action);
bool ExecuteValidateSaveFallbackAction(base::Value::Dict action);
bool ExecuteWaitForStateAction(base::Value::Dict action);
bool GetTargetHTMLElementXpathFromAction(const base::Value::Dict& action,
std::string* xpath);
bool GetTargetFrameFromAction(const base::Value::Dict& action,
content::RenderFrameHost** frame);
bool GetIFramePathFromAction(const base::Value::Dict& action,
std::vector<std::string>* iframe_path);
bool GetTargetHTMLElementVisibilityEnumFromAction(
const base::Value::Dict& action,
int* visibility_enum_val);
bool ExtractFrameAndVerifyElement(const base::Value::Dict& action,
std::string* xpath,
content::RenderFrameHost** frame,
bool set_focus = false,
bool relaxed_visibility = false,
bool ignore_failure = false);
void ValidatePasswordGenerationPromptState(
const content::ToRenderFrameHost& frame,
const std::string& element_xpath,
bool expect_to_be_shown);
bool WaitForElementToBeReady(const std::string& xpath,
const int visibility_enum_val,
content::RenderFrameHost* frame,
bool ignore_failure = false);
bool WaitForStateChange(
content::RenderFrameHost* frame,
const std::vector<std::string>& state_assertions,
const base::TimeDelta& timeout = default_action_timeout,
bool ignore_failure = false);
bool AllAssertionsPassed(const content::ToRenderFrameHost& frame,
const std::vector<std::string>& assertions);
bool ExecuteJavaScriptOnElementByXpath(
const content::ToRenderFrameHost& frame,
const std::string& element_xpath,
const std::string& execute_function_body,
const base::TimeDelta& time_to_wait_for_element = default_action_timeout);
bool GetElementProperty(const content::ToRenderFrameHost& frame,
const std::string& element_xpath,
const std::string& get_property_function_body,
std::string* property);
bool ExpectElementPropertyEqualsAnyOf(
const content::ToRenderFrameHost& frame,
const std::string& element_xpath,
const std::string& get_property_function_body,
const std::vector<std::string>& expected_value,
const std::string& validation_field,
IgnoreCase ignore_case = IgnoreCase(false));
void SimulateKeyPressWrapper(content::WebContents* web_contents,
ui::DomKey key);
bool HasChromeStoredCredential(const base::Value::Dict& action,
bool* stored_cred);
bool OverrideAutofillClock(const base::FilePath capture_file_path);
bool SetupSavedAutofillProfile(
base::Value::List saved_autofill_profile_container);
bool SetupSavedPasswords(base::Value::List saved_password_list_container);
// Wait until Chrome finishes loading a page and updating the page's visuals.
// If Chrome finishes loading a page but continues to paint every half
// second, exit after |continuous_paint_timeout| expires since Chrome
// finished loading the page.
void WaitTillPageIsIdle(
base::TimeDelta continuous_paint_timeout = default_action_timeout);
// Wait until Chrome makes at least 1 visual update, or until timeout
// expires. Returns false if no visual update is observed before the given
// timeout elapses.
bool WaitForVisualUpdate(base::TimeDelta timeout = visual_update_timeout);
raw_ptr<Browser> browser_;
raw_ptr<TestRecipeReplayChromeFeatureActionExecutor> feature_action_executor_;
// The Web Page Replay server that serves the captured sites.
std::unique_ptr<captured_sites_test_utils::WebPageReplayServerWrapper>
web_page_replay_server_wrapper_ =
std::make_unique<WebPageReplayServerWrapper>(true);
std::vector<testing::AssertionResult> validation_failures_;
// Overrides the AutofillClock to use the recorded date.
autofill::TestAutofillClock test_clock_;
};
} // namespace captured_sites_test_utils
#endif // CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_