Commit 831f175d authored by Yiming Zhou's avatar Yiming Zhou Committed by Commit Bot

Refactor captures sites automation framework out of autofill tests.

This change refactors the captured sites automation framework code out
of autofill_captured_sites_interactive_uitest. By completing this
refactor, other Chrome teams can leverage the captures sites
automation framework to test their team's features on real-world sites.

Also included in this change are a few minor fixes:
1. Fixed the captured sites autofill test to work with EventWaiters.
2. Modified a few test recipe files to address test breaks caused by
removing the client name parameter from autofill server queries.

Bug: 866152
Change-Id: Iee47cee8bd038ce0920caf6d63440c90c94b6285
Reviewed-on: https://chromium-review.googlesource.com/1147512
Commit-Queue: Yiming Zhou <uwyiming@google.com>
Reviewed-by: default avatarSebastien Seguin-Gagnon <sebsg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#580278}
parent 90b244eb
......@@ -89,70 +89,6 @@ void AutofillUiTest::TearDownOnMainThread() {
test::ReenableSystemServices();
}
bool AutofillUiTest::TryFillForm(const std::string& focus_element_xpath,
const int attempts) {
content::WebContents* web_contents = GetWebContents();
AutofillManager* autofill_manager =
ContentAutofillDriverFactory::FromWebContents(web_contents)
->DriverForFrame(web_contents->GetMainFrame())
->autofill_manager();
int tries = 0;
while (tries < attempts) {
tries++;
autofill_manager->client()->HideAutofillPopup();
if (!ShowAutofillSuggestion(focus_element_xpath)) {
LOG(WARNING) << "Failed to bring up the autofill suggestion drop down.";
continue;
}
// Press the down key again to highlight the first choice in the autofill
// suggestion drop down.
test_delegate()->Reset();
SendKeyToPopup(ui::DomKey::ARROW_DOWN);
if (!test_delegate()->Wait({ObservedUiEvents::kPreviewFormData},
base::TimeDelta::FromSeconds(5))) {
LOG(WARNING) << "Failed to select an option from the"
<< " autofill suggestion drop down.";
continue;
}
// Press the enter key to invoke autofill using the first suggestion.
test_delegate()->Reset();
SendKeyToPopup(ui::DomKey::ENTER);
if (!test_delegate()->Wait({ObservedUiEvents::kFormDataFilled},
base::TimeDelta::FromSeconds(5))) {
LOG(WARNING) << "Failed to fill the form.";
continue;
}
return true;
}
autofill_manager->client()->HideAutofillPopup();
return false;
}
bool AutofillUiTest::ShowAutofillSuggestion(
const std::string& focus_element_xpath) {
const std::string js(base::StringPrintf(
"try {"
" var element = automation_helper.getElementByXpath(`%s`);"
" while (document.activeElement !== element) {"
" element.focus();"
" }"
"} catch(ex) {}",
focus_element_xpath.c_str()));
if (content::ExecuteScript(GetWebContents(), js)) {
test_delegate()->Reset();
SendKeyToPage(ui::DomKey::ARROW_DOWN);
return test_delegate()->Wait({ObservedUiEvents::kSuggestionShown},
base::TimeDelta::FromSeconds(5));
}
return false;
}
void AutofillUiTest::SendKeyToPage(ui::DomKey key) {
ui::KeyboardCode key_code = ui::NonPrintableDomKeyToKeyboardCode(key);
ui::DomCode code = ui::UsLayoutKeyboardCodeToDomCode(key_code);
......
......@@ -64,10 +64,6 @@ class AutofillUiTest : public InProcessBrowserTest {
void SetUpOnMainThread() override;
void TearDownOnMainThread() override;
bool TryFillForm(const std::string& focus_element_xpath,
const int attempts = 1);
bool ShowAutofillSuggestion(const std::string& focus_element_xpath);
void SendKeyToPage(ui::DomKey key);
void SendKeyToPageAndWait(ui::DomKey key,
std::list<ObservedUiEvents> expected_events);
......@@ -75,6 +71,7 @@ class AutofillUiTest : public InProcessBrowserTest {
ui::DomCode code,
ui::KeyboardCode key_code,
std::list<ObservedUiEvents> expected_events);
void SendKeyToPopup(ui::DomKey key);
// Send key to the render host view's widget if |widget| is null.
void SendKeyToPopupAndWait(ui::DomKey key,
std::list<ObservedUiEvents> expected_events,
......@@ -98,8 +95,6 @@ class AutofillUiTest : public InProcessBrowserTest {
content::RenderWidgetHost::KeyPressEventCallback key_press_event_sink();
private:
void SendKeyToPopup(ui::DomKey key);
AutofillManagerTestDelegateImpl test_delegate_;
// KeyPressEventCallback that serves as a sink to ensure that every key press
......
This diff is collapsed.
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 "base/command_line.h"
#include "base/files/file_path.h"
#include "chrome/browser/ui/browser.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "services/network/public/cpp/network_switches.h"
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 framework's paint-based PageActivityObserver to
// wait indefinitely. 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::TimeDelta::FromSeconds(30);
std::string FilePathToUTF8(const base::FilePath::StringType& str);
// PageActivityObserver
//
// PageActivityObserver is a universal wait-for-page-ready object that ensures
// the current web page finishes responding to user input. The Captured Site
// Automation Framework, specifically the TestRecipeReplayer class, uses
// PageActivityObserver to time delays between two test actions. Without the
// delay, a test may break itself by performing a page action before the page
// is ready to receive the action.
//
// For example, the Amazon.com checkout page runs background scripts after
// loading. While running the background scripts, the checkout page displays
// a spinner. If a user clicks on a link while the spinner is present,
// Amazon.com will dispatch the user to an error page.
//
// Page readiness is hard to determine because on real-world sites, page
// readiness does not correspond to page load events. In the above Amazon.com
// example, the checkout page starts the background scripts after the page
// finishes loading.
//
// The PageActivityObserver defines page ready as the absence of Chrome paint
// events. On real-world sites, if a page is busy loading, the Chrome tab
// should be busy and Chrome should continuously make layout changes and
// repaint the page. If a site is busy doing background work, most pages
// typically display some form of persistent animation such as a progress bar
// or a spinner to tell the user that the page is not ready. Therefore, it
// is reasonable to assume that a page is ready if Chrome finished painting.
class PageActivityObserver : public content::WebContentsObserver {
public:
explicit PageActivityObserver(content::WebContents* web_contents);
explicit PageActivityObserver(content::RenderFrameHost* frame);
~PageActivityObserver() override = default;
// 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);
private:
// PageActivityObserver determines if Chrome stopped painting by checking if
// Chrome hasn't painted for a specific amount of time.
// kPaintEventCheckInterval defines this amount of time.
static constexpr base::TimeDelta kPaintEventCheckInterval =
base::TimeDelta::FromMilliseconds(500);
// content::WebContentsObserver:
void DidCommitAndDrawCompositorFrame() override;
bool paint_occurred_during_last_loop_ = false;
DISALLOW_COPY_AND_ASSIGN(PageActivityObserver);
};
// 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:
// Chrome Autofill feature methods.
// Triggers Chrome Autofill in the specified input element on the specified
// document.
virtual bool AutofillForm(content::WebContents* web_contents,
const std::string& focus_element_css_selector,
int attempts = 1);
protected:
TestRecipeReplayChromeFeatureActionExecutor();
~TestRecipeReplayChromeFeatureActionExecutor();
DISALLOW_COPY_AND_ASSIGN(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 const int kHostHttpPort = 8080;
static const int kHostHttpsPort = 8081;
TestRecipeReplayer(
Browser* browser,
TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor);
~TestRecipeReplayer();
void Setup();
void Cleanup();
// 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);
static void SetUpCommandLine(base::CommandLine* command_line);
private:
TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor();
content::WebContents* GetWebContents();
void CleanupSiteData();
bool StartWebPageReplayServer(const base::FilePath& capture_file_path);
bool StopWebPageReplayServer();
bool InstallWebPageReplayServerRootCert();
bool RemoveWebPageReplayServerRootCert();
bool RunWebPageReplayCmdAndWaitForExit(
const std::string& cmd,
const std::vector<std::string>& args,
const base::TimeDelta& timeout = base::TimeDelta::FromSeconds(5));
bool RunWebPageReplayCmd(const std::string& cmd,
const std::vector<std::string>& args,
base::Process* process);
bool ReplayRecordedActions(const base::FilePath recipe_file_path);
void InitializeBrowserToExecuteRecipe(
std::unique_ptr<base::DictionaryValue>& recipe);
void ExecuteAutofillAction(base::DictionaryValue* action);
void ExecuteClickAction(base::DictionaryValue* action);
void ExecuteSelectDropdownAction(base::DictionaryValue* action);
void ExecuteTypeAction(base::DictionaryValue* action);
void ExecuteValidateFieldValueAction(base::DictionaryValue* action);
void ExecuteWaitForStateAction(base::DictionaryValue* action);
bool GetTargetHTMLElementXpathFromAction(base::DictionaryValue* action,
std::string* xpath);
void WaitForElemementToBeReady(std::string xpath);
bool WaitForStateChange(
const std::vector<std::string>& state_assertions,
const base::TimeDelta& timeout = default_action_timeout);
bool AllAssertionsPassed(const std::vector<std::string>& assertions);
bool ExecuteJavaScriptOnElementByXpath(
const std::string& element_xpath,
const std::string& execute_function_body,
const base::TimeDelta& time_to_wait_for_element = default_action_timeout);
bool ExpectElementPropertyEquals(
const std::string& element_xpath,
const std::string& get_property_function_body,
const std::string& expected_value,
bool ignoreCase = false);
Browser* browser_;
TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor_;
// The Web Page Replay server that serves the captured sites.
base::Process web_page_replay_server_;
DISALLOW_COPY_AND_ASSIGN(TestRecipeReplayer);
};
} // namespace captured_sites_test_utils
#endif // CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
......@@ -5677,6 +5677,8 @@ if (!is_android && !is_fuchsia) {
"../browser/autofill/autofill_uitest.h",
"../browser/autofill/autofill_uitest_util.cc",
"../browser/autofill/autofill_uitest_util.h",
"../browser/autofill/captured_sites_test_utils.cc",
"../browser/autofill/captured_sites_test_utils.h",
"base/interactive_test_utils.cc",
"base/interactive_test_utils.h",
"base/interactive_test_utils_common_views.cc",
......
......@@ -65,7 +65,7 @@
{
"selector": "//*[@id=\"enterAddressPhoneNumber\"]",
"context": [],
"expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
"expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
"expectedValue": "5125551234",
"type": "validateField"
},
......
......@@ -50,7 +50,7 @@
"selectorType": "xpath",
"selector": "//input[@type='text' and @name='phone']",
"context": [],
"expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
"expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
"expectedValue": "5125551234",
"type": "validateField"
},
......
......@@ -65,7 +65,7 @@
{
"selector": "//*[@id=\"AddressForm_PHONE_NUMBER\"]",
"context": [],
"expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
"expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
"expectedValue": "5125551234",
"type": "validateField"
},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment