Commit b35a35a3 authored by Maria Kazinova's avatar Maria Kazinova Committed by Chromium LUCI CQ

[iOS, Passwords] EarlGrey tests for password saving/updating.

Added tests for standard flows for saving a new credential or
updating a password for an already existing credential on a
successful password form submission.

Change-Id: I448fc801984e9462f212c3ae9a643325343fbd59
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2627279
Commit-Queue: Maria Kazinova <kazinova@google.com>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#844601}
parent 58450005
......@@ -204,16 +204,21 @@ source_set("eg2_tests") {
"//build/config/ios:xctest_config",
]
testonly = true
sources = [ "password_controller_egtest.mm" ]
deps = [
":eg_test_support+eg2",
"//base",
"//base/test:test_support",
"//components/password_manager/core/common",
"//ios/chrome/app/strings:ios_strings_grit",
"//ios/chrome/browser/ui/infobars/banners:public",
"//ios/chrome/test:eg_test_support+eg2",
"//ios/chrome/test/earl_grey:eg_test_support+eg2",
"//ios/testing/earl_grey:eg_test_support+eg2",
"//ios/third_party/earl_grey2:test_lib",
"//ios/web/public/test/http_server",
"//net:test_support",
"//ui/base:base",
]
frameworks = [ "UIKit.framework" ]
}
......@@ -232,6 +237,7 @@ source_set("eg_app_support+eg2") {
deps = [
":passwords",
"//base",
"//base/test:test_support",
"//components/autofill/core/common",
"//components/keyed_service/core",
"//components/password_manager/core/browser",
......
// Copyright 2020 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.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#include <memory>
#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/passwords/password_manager_app_interface.h"
#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_constants.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_actions.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/web_http_server_chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#if defined(CHROME_EARL_GREY_2)
// TODO(crbug.com/1015113): The EG2 macro is breaking indexing for some reason
// without the trailing semicolon. For now, disable the extra semi warning
// so Xcode indexing works for the egtest.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++98-compat-extra-semi"
GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(PasswordManagerAppInterface);
#endif // defined(CHROME_EARL_GREY_2)
constexpr char kFormUsername[] = "un";
constexpr char kFormPassword[] = "pw";
NSString* const kSavedCredentialLabel = @"Eguser, Hidden, Password";
namespace {
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using base::test::ios::kWaitForActionTimeout;
id<GREYMatcher> PasswordInfobar(int prompt_id) {
NSString* bannerLabel =
[NSString stringWithFormat:@"%@, %@", l10n_util::GetNSString(prompt_id),
kSavedCredentialLabel];
return grey_allOf(grey_accessibilityID(kInfobarBannerViewIdentifier),
grey_accessibilityLabel(bannerLabel), nil);
}
id<GREYMatcher> PasswordInfobarButton(int button_id) {
return chrome_test_util::ButtonWithAccessibilityLabelId(button_id);
}
} // namespace
@interface PasswordControllerEGTest : WebHttpServerChromeTestCase
@end
@implementation PasswordControllerEGTest
- (void)setUp {
[super setUp];
// Set up server.
net::test_server::RegisterDefaultHandlers(self.testServer);
GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
}
- (void)tearDown {
[PasswordManagerAppInterface clearCredentials];
[super tearDown];
}
#pragma mark - Helper methods
// Loads simple page on localhost.
- (void)loadLoginPage {
// Loads simple page. It is on localhost so it is considered a secure context.
[ChromeEarlGrey loadURL:self.testServer->GetURL("/simple_login_form.html")];
[ChromeEarlGrey waitForWebStateContainingText:"Login form."];
}
#pragma mark - Tests
// Tests that save password prompt is shown on new login.
- (void)testSavePromptAppearsOnFormSubmission {
[self loadLoginPage];
// Simulate user interacting with fields.
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId(kFormUsername)];
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId(kFormPassword)];
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId("submit_button")];
// Wait until the save password prompt becomes visible.
[ChromeEarlGrey
waitForUIElementToAppearWithMatcher:
PasswordInfobar(IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT)];
[[EarlGrey selectElementWithMatcher:PasswordInfobarButton(
IDS_IOS_PASSWORD_MANAGER_SAVE_BUTTON)]
performAction:grey_tap()];
// Wait until the save password infobar disappears.
[ChromeEarlGrey
waitForUIElementToDisappearWithMatcher:
PasswordInfobar(IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_PROMPT)];
int credentialsCount = [PasswordManagerAppInterface storedCredentialsCount];
GREYAssertEqual(1, credentialsCount, @"Wrong number of stored credentials.");
}
// Tests that update password prompt is shown on submitting the new password
// for an already stored login.
- (void)testUpdatePromptAppearsOnFormSubmission {
// Load the page the first time an store credentials.
[self loadLoginPage];
[PasswordManagerAppInterface storeCredentialWithUsername:@"Eguser"
password:@"OldPass"];
int credentialsCount = [PasswordManagerAppInterface storedCredentialsCount];
GREYAssertEqual(1, credentialsCount, @"Wrong number of initial credentials.");
// Load the page again and have a new password value to save.
[self loadLoginPage];
// Simulate user interacting with fields.
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId(kFormUsername)];
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId(kFormPassword)];
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId("submit_button")];
// Wait until the update password prompt becomes visible.
[ChromeEarlGrey
waitForUIElementToAppearWithMatcher:
PasswordInfobar(IDS_IOS_PASSWORD_MANAGER_UPDATE_PASSWORD)];
[[EarlGrey
selectElementWithMatcher:PasswordInfobarButton(
IDS_IOS_PASSWORD_MANAGER_UPDATE_BUTTON)]
performAction:grey_tap()];
// Wait until the update password infobar disappears.
[ChromeEarlGrey
waitForUIElementToDisappearWithMatcher:
PasswordInfobar(IDS_IOS_PASSWORD_MANAGER_UPDATE_PASSWORD)];
credentialsCount = [PasswordManagerAppInterface storedCredentialsCount];
GREYAssertEqual(1, credentialsCount, @"Wrong number of final credentials.");
}
@end
......@@ -16,9 +16,8 @@
// Clears any credentials that were stored during a test run.
+ (void)clearCredentials;
// Executes the javascript to fetch credentials in a background tab. There must
// be two tabs open before calling this method.
+ (void)getCredentialsInTabAtIndex:(int)index;
// Returns the number of stored credentials.
+ (int)storedCredentialsCount;
@end
......
......@@ -6,9 +6,11 @@
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/ios/wait_util.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/tab_test_util.h"
......@@ -19,6 +21,35 @@
#error "This file requires ARC support."
#endif
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using password_manager::PasswordForm;
using password_manager::PasswordStore;
using password_manager::PasswordStoreConsumer;
class PasswordStoreConsumerHelper : public PasswordStoreConsumer {
public:
PasswordStoreConsumerHelper() {}
void OnGetPasswordStoreResults(
std::vector<std::unique_ptr<PasswordForm>> results) override {
result_.swap(results);
}
std::vector<std::unique_ptr<PasswordForm>> WaitForResult() {
bool unused = WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return result_.size() > 0;
});
(void)unused;
return std::move(result_);
}
private:
std::vector<std::unique_ptr<PasswordForm>> result_;
DISALLOW_COPY_AND_ASSIGN(PasswordStoreConsumerHelper);
};
@implementation PasswordManagerAppInterface
+ (NSError*)storeCredentialWithUsername:(NSString*)username
......@@ -58,14 +89,21 @@
base::OnceClosure());
}
+ (void)getCredentialsInTabAtIndex:(int)index {
// Get WebState for the original tab.
web::WebState* webState =
chrome_test_util::GetWebStateAtIndexInCurrentMode(index);
+ (int)storedCredentialsCount {
// Obtain a PasswordStore.
scoped_refptr<PasswordStore> passwordStore =
IOSChromePasswordStoreFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState(),
ServiceAccessType::IMPLICIT_ACCESS)
.get();
PasswordStoreConsumerHelper consumer;
passwordStore->GetAllLogins(&consumer);
std::vector<std::unique_ptr<PasswordForm>> credentials =
consumer.WaitForResult();
// Execute JavaScript from inactive tab.
webState->ExecuteJavaScript(
base::UTF8ToUTF16("typeof navigator.credentials.get({password: true})"));
return credentials.size();
}
@end
......@@ -129,6 +129,12 @@ id ExecuteJavaScript(NSString* javascript, NSError** out_error);
// Waits for the matcher to return an element that is sufficiently visible.
- (void)waitForSufficientlyVisibleElementWithMatcher:(id<GREYMatcher>)matcher;
// Waits for the matcher to return an element.
- (void)waitForUIElementToAppearWithMatcher:(id<GREYMatcher>)matcher;
// Waits for the matcher to not return any elements.
- (void)waitForUIElementToDisappearWithMatcher:(id<GREYMatcher>)matcher;
// Waits for there to be |count| number of non-incognito tabs within a timeout,
// or a GREYAssert is induced.
- (void)waitForMainTabCount:(NSUInteger)count;
......
......@@ -358,6 +358,40 @@ GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(ChromeEarlGreyAppInterface)
EG_TEST_HELPER_ASSERT_TRUE(matchedElement, errorDescription);
}
- (void)waitForUIElementToAppearWithMatcher:(id<GREYMatcher>)matcher {
NSString* errorDescription = [NSString
stringWithFormat:@"Failed waiting for element with matcher %@ to appear",
matcher];
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
};
bool matched =
WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition);
GREYAssert(matched, errorDescription);
}
- (void)waitForUIElementToDisappearWithMatcher:(id<GREYMatcher>)matcher {
NSString* errorDescription = [NSString
stringWithFormat:
@"Failed waiting for element with matcher %@ to disappear", matcher];
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_nil()
error:&error];
return error == nil;
};
bool matched =
WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition);
GREYAssert(matched, errorDescription);
}
- (NSString*)currentTabTitle {
return [ChromeEarlGreyAppInterface currentTabTitle];
}
......
......@@ -129,6 +129,7 @@ bundle_data("http_server_bundle_data") {
"data/http_server_files/readonly_form.html",
"data/http_server_files/redchair.usdz",
"data/http_server_files/redirect_refresh.html",
"data/http_server_files/simple_login_form.html",
"data/http_server_files/single_page_wide.pdf",
"data/http_server_files/state_operations.html",
"data/http_server_files/state_operations.js",
......
<!DOCTYPE html>
<html><body>
Login form.
<form name='login_form' id='signup_form' action='https://google.com/'>
<input type='text' name='username' id='un' value='Eguser'>
<input type='password' name='password' id='pw' value='Egpass'>
<button id='submit_button' value='Submit'>SubForm</button>
</form>
</body></html>
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