// 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.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/core/common/autofill_payments_features.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/authentication_egtest_util.h"
#import "ios/chrome/browser/autofill/ui_bundled/authentication/card_unmask_authentication_selection_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h"
#import "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_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/testing/earl_grey/matchers.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"
// The masked phone number associated with the OTP card unmask option.
NSString* const kUnmaskOptionMaskedPhoneNumber = @"*******1234";
// The masked email associated with the email card unmask option.
NSString* const kUnmaskOptionMaskedEmailAddress = @"a***[email protected]";
// The length of the cvc challenge as shown in the challenge message.
NSString* const kUnmaskOptionCvcLength = @"3";
// Matcher for a navigation bar with the "Verification" title.
id<GREYMatcher> CardUnmaskPromptNavigationBarTitle() {
return chrome_test_util::NavigationBarTitleWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_PROMPT_NAVIGATION_TITLE_VERIFICATION);
}
// Matcher for the text message challenge option label.
id<GREYMatcher> CardUnmaskTextMessageChallengeOptionLabel() {
return chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_AUTHENTICATION_MODE_GET_TEXT_MESSAGE);
}
// Matcher for the CVC challenge option label.
id<GREYMatcher> CardUnmaskCvcChallengeOptionLabel() {
return chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_AUTHENTICATION_MODE_SECURITY_CODE);
}
// Matcher for the "Send" button.
id<GREYMatcher> CardUnmaskAuthenticationSelectionSendButton() {
return grey_allOf(
chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND),
grey_accessibilityID(
kCardUnmaskAuthenticationSelectionAcceptanceButtonAccessibilityIdentifier),
nil);
}
// Matcher for the "Cancel" button.
id<GREYMatcher> CardUnmaskAuthenticationSelectionCancelButton() {
return grey_allOf(
chrome_test_util::CancelButton(),
grey_accessibilityID(
kCardUnmaskAuthenticationSelectionCancelButtonAccessibilityIdentifier),
nil);
}
@interface CardUnmaskAuthenticationSelectionEgtest : ChromeTestCase
@end
@implementation CardUnmaskAuthenticationSelectionEgtest {
NSString* _enrolledCardNameAndLastFour;
}
#pragma mark - Setup
- (AppLaunchConfiguration)appConfigurationForTestCase {
AppLaunchConfiguration config;
config.features_enabled.push_back(
autofill::features::kAutofillEnableVirtualCards);
return config;
}
- (void)setUp {
[super setUp];
[AutofillAppInterface setUpFakeCreditCardServer];
_enrolledCardNameAndLastFour =
[AutofillAppInterface saveMaskedCreditCardEnrolledInVirtualCard];
[self setUpServer];
[self setUpTestPage];
}
- (void)setUpServer {
net::test_server::RegisterDefaultHandlers(self.testServer);
GREYAssertTrue(self.testServer->Start(), @"Failed to start test server.");
}
- (void)setUpTestPage {
[ChromeEarlGrey loadURL:self.testServer->GetURL(kCreditCardUrl)];
[ChromeEarlGrey waitForWebStateContainingText:kAutofillTestString];
[AutofillAppInterface considerCreditCardFormSecureForTesting];
}
- (void)tearDown {
[AutofillAppInterface clearAllServerDataForTesting];
[AutofillAppInterface tearDownFakeCreditCardServer];
[super tearDown];
}
- (void)showAuthenticationSelection {
// Tap on the card name field in the web content.
[[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
performAction:chrome_test_util::TapWebElementWithId(kFormCardName)];
// Wait for the payments bottom sheet to appear.
id<GREYMatcher> paymentsBottomSheetVirtualCard = grey_accessibilityID(
[NSString stringWithFormat:@"%@ %@", _enrolledCardNameAndLastFour,
@"Virtual card"]);
[ChromeEarlGrey
waitForUIElementToAppearWithMatcher:paymentsBottomSheetVirtualCard];
[[EarlGrey selectElementWithMatcher:paymentsBottomSheetVirtualCard]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_IOS_PAYMENT_BOTTOM_SHEET_CONTINUE)]
performAction:grey_tap()];
// Wait for the progress dialog to appear.
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_PROGRESS_DIALOG_TITLE)];
// Inject the card unmask response with card unmask options.
[AutofillAppInterface
setPaymentsResponse:kUnmaskCardResponseSuccessOtpAndEmailAndCvc
forRequest:kUnmaskCardRequestUrl
withErrorCode:net::HTTP_OK];
// Wait for the card unmask authentication selection to appear.
[ChromeEarlGrey
waitForUIElementToAppearWithMatcher:CardUnmaskPromptNavigationBarTitle()];
}
- (void)testCardUnmaskAuthenticationSelectionIsShownForVirtualCard {
[self showAuthenticationSelection];
// Verify that the card unmask prompt was shown.
[[EarlGrey
selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_AUTH_SELECTION_DIALOG_TITLE_MULTIPLE_OPTIONS)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey
selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_ISSUER_CONFIRMATION_TEXT)]
assertWithMatcher:grey_sufficientlyVisible()];
// Verify that the card unmask options are shown starting with text message
// (SMS OTP).
[[EarlGrey
selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:chrome_test_util::
StaticTextWithAccessibilityLabel(
kUnmaskOptionMaskedPhoneNumber)]
assertWithMatcher:grey_sufficientlyVisible()];
// Verify that the email OTP unmask option is shown.
[[EarlGrey selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_AUTHENTICATION_MODE_GET_EMAIL)]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:chrome_test_util::
StaticTextWithAccessibilityLabel(
kUnmaskOptionMaskedEmailAddress)]
assertWithMatcher:grey_sufficientlyVisible()];
// Verify that the CVC unmask option is shown.
[[EarlGrey selectElementWithMatcher:CardUnmaskCvcChallengeOptionLabel()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey
selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabel(l10n_util::GetNSStringF(
IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_CVC_CHALLENGE_INFO,
base::SysNSStringToUTF16(kUnmaskOptionCvcLength),
l10n_util::GetStringUTF16(
IDS_AUTOFILL_CARD_UNMASK_PROMPT_SECURITY_CODE_POSITION_BACK_OF_CARD)))]
assertWithMatcher:grey_sufficientlyVisible()];
}
- (void)testCardUnmaskAuthenticationSelectionCancel {
[self showAuthenticationSelection];
// Tap the cancel button.
[[EarlGrey
selectElementWithMatcher:CardUnmaskAuthenticationSelectionCancelButton()]
performAction:grey_tap()];
// Expect the card unmask authentication selection view to disappear.
[ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
CardUnmaskPromptNavigationBarTitle()];
}
- (void)testCardUnmaskAuthenticationSelectionAcceptanceButtonIsSetInitially {
[self showAuthenticationSelection];
// Ensure the "Send" button is present (since the first option, OTP, is pre
// selected).
[[EarlGrey
selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()]
assertWithMatcher:grey_allOf(grey_enabled(), grey_sufficientlyVisible(),
nil)];
}
- (void)testCardUnmaskAuthenticationSelectionAcceptanceButtonLabel {
[self showAuthenticationSelection];
// Verify selecting text message sets the acceptance button label to "Send".
[[EarlGrey
selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()]
performAction:grey_tap()];
[[EarlGrey
selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()]
assertWithMatcher:grey_sufficientlyVisible()];
// Verify selecting CVC sets the acceptance button label to "Continue".
[[EarlGrey selectElementWithMatcher:CardUnmaskCvcChallengeOptionLabel()]
performAction:grey_tap()];
[[EarlGrey
selectElementWithMatcher:
grey_allOf(
chrome_test_util::ButtonWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_CONTINUE),
grey_accessibilityID(
kCardUnmaskAuthenticationSelectionAcceptanceButtonAccessibilityIdentifier),
nil)] assertWithMatcher:grey_sufficientlyVisible()];
}
- (void)testCardUnmaskAuthenticationSelectionShowsActivityIndicatorView {
[self showAuthenticationSelection];
// Select the text message otp challenge option.
[[EarlGrey
selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()]
performAction:grey_tap()];
[[EarlGrey
selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()]
performAction:grey_tap()];
// Verify the activity indicator has been set.
[[EarlGrey
selectElementWithMatcher:grey_kindOfClassName(@"UIActivityIndicatorView")]
assertWithMatcher:grey_sufficientlyVisible()];
}
- (void)testDismissInputViaSwipe {
[self showAuthenticationSelection];
// The initial access token has been used up, set another fake access token.
[AutofillAppInterface setAccessToken];
// Set a fake response for the select challenge option request.
[AutofillAppInterface
setPaymentsResponse:kSelectChallengeOptionResponseSuccess
forRequest:kSelectChallengeOptionRequestUrl
withErrorCode:net::HTTP_OK];
// Select the text message otp challenge option.
[[EarlGrey
selectElementWithMatcher:CardUnmaskTextMessageChallengeOptionLabel()]
performAction:grey_tap()];
[[EarlGrey
selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()]
performAction:grey_tap()];
// Swipe the input sheet down to close it.
[[EarlGrey selectElementWithMatcher:
chrome_test_util::StaticTextWithAccessibilityLabelId(
IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE)]
performAction:grey_swipeFastInDirection(kGREYDirectionDown)];
}
@end