Commit 2ebd2c83 authored by Eric Aleshire's avatar Eric Aleshire Committed by Commit Bot

Load pre-recorded autofill profile data from the test recipe.

This is based on Yiming's CL here: https://chromium-review.googlesource.com/c/chromium/src/+/1241756
The idea is to store the autofill profile data in each test recipe at record time,
then load the data from the recipe when testing. This makes recording the tests
less fragile, and running the tests more self-contained.

Note that, as per Yiming, validateField still takes explicit information as to
what to validate, because websites may do some formatting on the autofilled data,
so a profile field such as "2018" might be validated as "18".

Bug: 881096
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: Ia87e4d3ee0419264a3f37c18f6e19ee7ae5d6bf3
Reviewed-on: https://chromium-review.googlesource.com/c/1257855
Commit-Queue: ericale <ericale@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604771}
parent 12ba1c55
...@@ -326,79 +326,9 @@ using web::test::ElementSelector; ...@@ -326,79 +326,9 @@ using web::test::ElementSelector;
@implementation AutomationActionAutofill @implementation AutomationActionAutofill
static const char PROFILE_NAME_FULL[] = "Milton C. Waddams";
static const char PROFILE_NAME_FIRST[] = "Milton";
static const char PROFILE_NAME_MIDDLE[] = "C.";
static const char PROFILE_NAME_LAST[] = "Waddams";
static const char PROFILE_HOME_LINE1[] = "4120 Freidrich Lane";
static const char PROFILE_HOME_LINE2[] = "Apt 8";
static const char PROFILE_HOME_CITY[] = "Austin";
static const char PROFILE_HOME_STATE[] = "Texas";
static const char PROFILE_COMPANY_NAME[] = "Initech";
static const char PROFILE_EMAIL_ADDRESS[] = "red.swingline@initech.com";
static const char PROFILE_HOME_ZIP[] = "78744";
static const char PROFILE_PHONE_HOME_CITY_CODE[] = "512";
static const char PROFILE_PHONE_HOME_WHOLE[] = "5125551234";
static const char PROFILE_CREDIT_CARD_NUMBER[] = "9621327911759602";
static const char PROFILE_CREDIT_CARD_NAME_FULL[] = "Milton Waddams";
static const char PROFILE_CREDIT_CARD_EXP_MONTH[] = "5";
static const char PROFILE_CREDIT_CARD_EXP_4_DIGIT_YEAR[] = "2027";
// Loads the predefined autofill profile into the personal data manager, so that
// autofill actions will be suggested when tapping on an autofillable form.
- (void)prepareAutofillProfileWithWebState:(web::WebState*)web_state {
web::WebFrame* main_frame = web::GetMainWebFrame(web_state);
autofill::AutofillManager* autofill_manager =
autofill::AutofillDriverIOS::FromWebStateAndWebFrame(web_state,
main_frame)
->autofill_manager();
autofill::PersonalDataManager* personal_data_manager =
autofill_manager->client()->GetPersonalDataManager();
autofill::AutofillProfile profile(base::GenerateGUID(),
"https://www.example.com/");
profile.SetRawInfo(autofill::NAME_FULL, base::UTF8ToUTF16(PROFILE_NAME_FULL));
profile.SetRawInfo(autofill::NAME_FIRST,
base::UTF8ToUTF16(PROFILE_NAME_FIRST));
profile.SetRawInfo(autofill::NAME_MIDDLE,
base::UTF8ToUTF16(PROFILE_NAME_MIDDLE));
profile.SetRawInfo(autofill::NAME_LAST, base::UTF8ToUTF16(PROFILE_NAME_LAST));
profile.SetRawInfo(autofill::ADDRESS_HOME_LINE1,
base::UTF8ToUTF16(PROFILE_HOME_LINE1));
profile.SetRawInfo(autofill::ADDRESS_HOME_LINE2,
base::UTF8ToUTF16(PROFILE_HOME_LINE2));
profile.SetRawInfo(autofill::ADDRESS_HOME_CITY,
base::UTF8ToUTF16(PROFILE_HOME_CITY));
profile.SetRawInfo(autofill::ADDRESS_HOME_STATE,
base::UTF8ToUTF16(PROFILE_HOME_STATE));
profile.SetRawInfo(autofill::COMPANY_NAME,
base::UTF8ToUTF16(PROFILE_COMPANY_NAME));
profile.SetRawInfo(autofill::EMAIL_ADDRESS,
base::UTF8ToUTF16(PROFILE_EMAIL_ADDRESS));
profile.SetRawInfo(autofill::ADDRESS_HOME_ZIP,
base::UTF8ToUTF16(PROFILE_HOME_ZIP));
profile.SetRawInfo(autofill::PHONE_HOME_CITY_CODE,
base::UTF8ToUTF16(PROFILE_PHONE_HOME_CITY_CODE));
profile.SetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER,
base::UTF8ToUTF16(PROFILE_PHONE_HOME_WHOLE));
personal_data_manager->SaveImportedProfile(profile);
autofill::CreditCard credit_card(base::GenerateGUID(),
"https://www.example.com/");
credit_card.SetRawInfo(autofill::CREDIT_CARD_NUMBER,
base::UTF8ToUTF16(PROFILE_CREDIT_CARD_NUMBER));
credit_card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL,
base::UTF8ToUTF16(PROFILE_CREDIT_CARD_NAME_FULL));
credit_card.SetRawInfo(autofill::CREDIT_CARD_EXP_MONTH,
base::UTF8ToUTF16(PROFILE_CREDIT_CARD_EXP_MONTH));
credit_card.SetRawInfo(
autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR,
base::UTF8ToUTF16(PROFILE_CREDIT_CARD_EXP_4_DIGIT_YEAR));
personal_data_manager->AddCreditCard(credit_card);
}
- (void)execute { - (void)execute {
web::WebState* web_state = chrome_test_util::GetCurrentWebState(); // The autofill profile is configured in
[self prepareAutofillProfileWithWebState:web_state]; // automation_egtest::prepareAutofillProfileWithValues.
web::test::ElementSelector selector = [self selectorForTarget]; web::test::ElementSelector selector = [self selectorForTarget];
[self tapOnTarget:selector]; [self tapOnTarget:selector];
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/guid.h"
#include "base/json/json_reader.h" #include "base/json/json_reader.h"
#include "base/values.h" #include "base/values.h"
#import "ios/chrome/browser/autofill/automation/automation_action.h" #import "ios/chrome/browser/autofill/automation/automation_action.h"
...@@ -14,6 +15,25 @@ ...@@ -14,6 +15,25 @@
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h" #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h" #import "ios/chrome/test/earl_grey/chrome_test_case.h"
#include "base/guid.h"
#include "base/mac/foundation_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/ios/browser/autofill_driver_ios.h"
#import "ios/chrome/browser/autofill/form_suggestion_label.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/web/public/test/earl_grey/web_view_actions.h"
#import "ios/web/public/test/earl_grey/web_view_matchers.h"
#include "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/js_test_util.h"
#include "ios/web/public/web_state/web_frame_util.h"
#import "ios/web/public/web_state/web_frames_manager.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
...@@ -26,6 +46,8 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation"; ...@@ -26,6 +46,8 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation";
// captured web site. It gets the script from the command line. // captured web site. It gets the script from the command line.
@interface AutofillAutomationTestCase : ChromeTestCase { @interface AutofillAutomationTestCase : ChromeTestCase {
NSMutableArray<AutomationAction*>* actions_; NSMutableArray<AutomationAction*>* actions_;
std::map<const std::string, autofill::ServerFieldType>
string_to_field_type_map_;
} }
@end @end
...@@ -71,6 +93,108 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation"; ...@@ -71,6 +93,108 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation";
return base::DictionaryValue::From(std::move(value)); return base::DictionaryValue::From(std::move(value));
} }
// Converts a string (from the test recipe) to the autofill ServerFieldType it
// represents.
- (autofill::ServerFieldType)serverFieldTypeFromString:(const std::string&)str {
// Lazily init the string to autofill field type map on the first call.
// The test recipe can contain both server and html field types, as when
// creating the recipe either type can be returned from predictions.
// Therefore, we store both in this map.
if (string_to_field_type_map_.empty()) {
for (size_t i = autofill::NO_SERVER_DATA;
i < autofill::MAX_VALID_FIELD_TYPE; ++i) {
autofill::ServerFieldType field_type =
static_cast<autofill::ServerFieldType>(i);
string_to_field_type_map_[autofill::AutofillType(field_type).ToString()] =
field_type;
}
for (size_t i = autofill::HTML_TYPE_UNSPECIFIED;
i < autofill::HTML_TYPE_UNRECOGNIZED; ++i) {
autofill::AutofillType field_type(static_cast<autofill::HtmlFieldType>(i),
autofill::HTML_MODE_NONE);
string_to_field_type_map_[field_type.ToString()] =
field_type.GetStorableType();
}
}
if (string_to_field_type_map_.count(str) == 0) {
NSString* errorStr = [NSString
stringWithFormat:@"Unable to recognize autofill field type %@!",
base::SysUTF8ToNSString(str)];
GREYAssert(false, errorStr);
}
return string_to_field_type_map_[str];
}
// Loads the defined autofill profile into the personal data manager, so that
// autofill actions will be suggested when tapping on an autofillable form.
// The autofill profile should be pulled from the test recipe, and consists of
// a list of dictionaries, each mapping one autofill type to one value, like so:
// "autofillProfile": [
// { "type": "NAME_FIRST", "value": "Satsuki" },
// { "type": "NAME_LAST", "value": "Yumizuka" },
// ],
- (void)prepareAutofillProfileWithValues:(const base::Value*)autofillProfile {
autofill::AutofillProfile profile(base::GenerateGUID(),
"https://www.example.com/");
autofill::CreditCard credit_card(base::GenerateGUID(),
"https://www.example.com/");
const base::Value::ListStorage& profile_entries_list =
autofillProfile->GetList();
// For each type-value dictionary in the autofill profile list, validate it,
// then add it to the appropriate profile.
for (base::ListValue::const_iterator it_entry = profile_entries_list.begin();
it_entry != profile_entries_list.end(); ++it_entry) {
const base::DictionaryValue* entry;
GREYAssert(it_entry->GetAsDictionary(&entry),
@"Failed to extract an entry!");
const base::Value* type_container = entry->FindKey("type");
GREYAssert(base::Value::Type::STRING == type_container->type(),
@"Type is not a string!");
const std::string field_type = type_container->GetString();
const base::Value* value_container = entry->FindKey("value");
GREYAssert(base::Value::Type::STRING == value_container->type(),
@"Value is not a string!");
const std::string field_value = value_container->GetString();
autofill::ServerFieldType type =
[self serverFieldTypeFromString:field_type];
// TODO(crbug.com/895968): Autofill profile and credit card info should be
// loaded from separate fields in the recipe, instead of being grouped
// together. However, we need to make sure this change is also performed on
// desktop automation.
if (base::StartsWith(field_type, "HTML_TYPE_CREDIT_CARD_",
base::CompareCase::INSENSITIVE_ASCII) ||
base::StartsWith(field_type, "CREDIT_CARD_",
base::CompareCase::INSENSITIVE_ASCII)) {
credit_card.SetRawInfo(type, base::UTF8ToUTF16(field_value));
} else {
profile.SetRawInfo(type, base::UTF8ToUTF16(field_value));
}
}
// Save the profile and credit card generated to the personal data manager.
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
web::WebFrame* main_frame = web::GetMainWebFrame(web_state);
autofill::AutofillManager* autofill_manager =
autofill::AutofillDriverIOS::FromWebStateAndWebFrame(web_state,
main_frame)
->autofill_manager();
autofill::PersonalDataManager* personal_data_manager =
autofill_manager->client()->GetPersonalDataManager();
personal_data_manager->AddCreditCard(credit_card);
personal_data_manager->SaveImportedProfile(profile);
}
- (void)setUp { - (void)setUp {
[super setUp]; [super setUp];
...@@ -78,6 +202,12 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation"; ...@@ -78,6 +202,12 @@ static const char kAutofillAutomationSwitch[] = "autofillautomation";
std::unique_ptr<base::DictionaryValue> recipeRoot = std::unique_ptr<base::DictionaryValue> recipeRoot =
[[self class] parseRecipeAtPath:recipePath]; [[self class] parseRecipeAtPath:recipePath];
const base::Value* autofillProfile =
recipeRoot->FindKeyOfType("autofillProfile", base::Value::Type::LIST);
if (autofillProfile) {
[self prepareAutofillProfileWithValues:autofillProfile];
}
// Extract the starting URL. // Extract the starting URL.
base::Value* startUrlValue = base::Value* startUrlValue =
recipeRoot->FindKeyOfType("startingURL", base::Value::Type::STRING); recipeRoot->FindKeyOfType("startingURL", base::Value::Type::STRING);
......
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