| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- /**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
- #import "FBAlert.h"
- #import "FBConfiguration.h"
- #import "FBErrorBuilder.h"
- #import "FBLogger.h"
- #import "FBXCElementSnapshotWrapper+Helpers.h"
- #import "FBXCodeCompatibility.h"
- #import "XCUIApplication.h"
- #import "XCUIApplication+FBAlert.h"
- #import "XCUIElement+FBClassChain.h"
- #import "XCUIElement+FBTyping.h"
- #import "XCUIElement+FBUtilities.h"
- #import "XCUIElement+FBWebDriverAttributes.h"
- @interface FBAlert ()
- @property (nonatomic, strong) XCUIApplication *application;
- @property (nonatomic, strong, nullable) XCUIElement *element;
- @end
- @implementation FBAlert
- + (instancetype)alertWithApplication:(XCUIApplication *)application
- {
- FBAlert *alert = [FBAlert new];
- alert.application = application;
- return alert;
- }
- + (instancetype)alertWithElement:(XCUIElement *)element
- {
- FBAlert *alert = [FBAlert new];
- alert.element = element;
- alert.application = element.application;
- return alert;
- }
- - (BOOL)isPresent
- {
- @try {
- if (nil == self.alertElement) {
- return NO;
- }
- [self.alertElement fb_takeSnapshot];
- return YES;
- } @catch (NSException *) {
- return NO;
- }
- }
- - (BOOL)notPresentWithError:(NSError **)error
- {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"No alert is open"]
- buildError:error];
- }
- + (BOOL)isSafariWebAlertWithSnapshot:(id<FBXCElementSnapshot>)snapshot
- {
- if (snapshot.elementType != XCUIElementTypeOther) {
- return NO;
- }
- FBXCElementSnapshotWrapper *snapshotWrapper = [FBXCElementSnapshotWrapper ensureWrapped:snapshot];
- id<FBXCElementSnapshot> application = [snapshotWrapper fb_parentMatchingType:XCUIElementTypeApplication];
- return nil != application && [application.label isEqualToString:FB_SAFARI_APP_NAME];
- }
- - (NSString *)text
- {
- if (!self.isPresent) {
- return nil;
- }
- NSMutableArray<NSString *> *resultText = [NSMutableArray array];
- id<FBXCElementSnapshot> snapshot = self.alertElement.lastSnapshot;
- BOOL isSafariAlert = [self.class isSafariWebAlertWithSnapshot:snapshot];
- [snapshot enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
- XCUIElementType elementType = descendant.elementType;
- if (!(elementType == XCUIElementTypeTextView || elementType == XCUIElementTypeStaticText)) {
- return;
- }
-
- FBXCElementSnapshotWrapper *descendantWrapper = [FBXCElementSnapshotWrapper ensureWrapped:descendant];
- if (elementType == XCUIElementTypeStaticText
- && nil != [descendantWrapper fb_parentMatchingType:XCUIElementTypeButton]) {
- return;
- }
- NSString *text = descendantWrapper.wdLabel ?: descendantWrapper.wdValue;
- if (isSafariAlert && nil != descendant.parent) {
- FBXCElementSnapshotWrapper *descendantParentWrapper = [FBXCElementSnapshotWrapper ensureWrapped:descendant.parent];
- NSString *parentText = descendantParentWrapper.wdLabel ?: descendantParentWrapper.wdValue;
- if ([parentText isEqualToString:text]) {
- // Avoid duplicated texts on Safari alerts
- return;
- }
- }
- if (nil != text) {
- [resultText addObject:[NSString stringWithFormat:@"%@", text]];
- }
- }];
- return [resultText componentsJoinedByString:@"\n"];
- }
- - (BOOL)typeText:(NSString *)text error:(NSError **)error
- {
- if (!self.isPresent) {
- return [self notPresentWithError:error];
- }
- NSPredicate *textCollectorPredicate = [NSPredicate predicateWithFormat:@"elementType IN {%lu,%lu}",
- XCUIElementTypeTextField, XCUIElementTypeSecureTextField];
- NSArray<XCUIElement *> *dstFields = [[self.alertElement descendantsMatchingType:XCUIElementTypeAny]
- matchingPredicate:textCollectorPredicate].allElementsBoundByIndex;
- if (dstFields.count > 1) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The alert contains more than one input field"]
- buildError:error];
- }
- if (0 == dstFields.count) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"The alert contains no input fields"]
- buildError:error];
- }
- return [dstFields.firstObject fb_typeText:text
- shouldClear:YES
- error:error];
- }
- - (NSArray *)buttonLabels
- {
- if (!self.isPresent) {
- return nil;
- }
- NSMutableArray<NSString *> *labels = [NSMutableArray array];
- [self.alertElement.lastSnapshot enumerateDescendantsUsingBlock:^(id<FBXCElementSnapshot> descendant) {
- if (descendant.elementType != XCUIElementTypeButton) {
- return;
- }
- NSString *label = [FBXCElementSnapshotWrapper ensureWrapped:descendant].wdLabel;
- if (nil != label) {
- [labels addObject:[NSString stringWithFormat:@"%@", label]];
- }
- }];
- return labels.copy;
- }
- - (BOOL)acceptWithError:(NSError **)error
- {
- if (!self.isPresent) {
- return [self notPresentWithError:error];
- }
- id<FBXCElementSnapshot> alertSnapshot = self.alertElement.lastSnapshot;
- XCUIElement *acceptButton = nil;
- if (FBConfiguration.acceptAlertButtonSelector.length) {
- NSString *errorReason = nil;
- @try {
- acceptButton = [[self.alertElement fb_descendantsMatchingClassChain:FBConfiguration.acceptAlertButtonSelector
- shouldReturnAfterFirstMatch:YES] firstObject];
- } @catch (NSException *ex) {
- errorReason = ex.reason;
- }
- if (nil == acceptButton) {
- [FBLogger logFmt:@"Cannot find any match for Accept alert button using the class chain selector '%@'",
- FBConfiguration.acceptAlertButtonSelector];
- if (nil != errorReason) {
- [FBLogger logFmt:@"Original error: %@", errorReason];
- }
- [FBLogger log:@"Will fallback to the default button location algorithm"];
- }
- }
- if (nil == acceptButton) {
- NSArray<XCUIElement *> *buttons = [self.alertElement.fb_query
- descendantsMatchingType:XCUIElementTypeButton].allElementsBoundByIndex;
- acceptButton = (alertSnapshot.elementType == XCUIElementTypeAlert || [self.class isSafariWebAlertWithSnapshot:alertSnapshot])
- ? buttons.lastObject
- : buttons.firstObject;
- }
- if (nil == acceptButton) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Failed to find accept button for alert: %@", self.alertElement]
- buildError:error];
- }
- [acceptButton tap];
- return YES;
- }
- - (BOOL)dismissWithError:(NSError **)error
- {
- if (!self.isPresent) {
- return [self notPresentWithError:error];
- }
- id<FBXCElementSnapshot> alertSnapshot = self.alertElement.lastSnapshot;
- XCUIElement *dismissButton = nil;
- if (FBConfiguration.dismissAlertButtonSelector.length) {
- NSString *errorReason = nil;
- @try {
- dismissButton = [[self.alertElement fb_descendantsMatchingClassChain:FBConfiguration.dismissAlertButtonSelector
- shouldReturnAfterFirstMatch:YES] firstObject];
- } @catch (NSException *ex) {
- errorReason = ex.reason;
- }
- if (nil == dismissButton) {
- [FBLogger logFmt:@"Cannot find any match for Dismiss alert button using the class chain selector '%@'",
- FBConfiguration.dismissAlertButtonSelector];
- if (nil != errorReason) {
- [FBLogger logFmt:@"Original error: %@", errorReason];
- }
- [FBLogger log:@"Will fallback to the default button location algorithm"];
- }
- }
- if (nil == dismissButton) {
- NSArray<XCUIElement *> *buttons = [self.alertElement.fb_query
- descendantsMatchingType:XCUIElementTypeButton].allElementsBoundByIndex;
- dismissButton = (alertSnapshot.elementType == XCUIElementTypeAlert || [self.class isSafariWebAlertWithSnapshot:alertSnapshot])
- ? buttons.firstObject
- : buttons.lastObject;
- }
- if (nil == dismissButton) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Failed to find dismiss button for alert: %@", self.alertElement]
- buildError:error];
- }
- [dismissButton tap];
- return YES;
- }
- - (BOOL)clickAlertButton:(NSString *)label error:(NSError **)error
- {
- if (!self.isPresent) {
- return [self notPresentWithError:error];
- }
- NSPredicate *predicate = [NSPredicate predicateWithFormat:@"label == %@", label];
- XCUIElement *requestedButton = [[self.alertElement descendantsMatchingType:XCUIElementTypeButton]
- matchingPredicate:predicate].allElementsBoundByIndex.firstObject;
- if (!requestedButton) {
- return [[[FBErrorBuilder builder]
- withDescriptionFormat:@"Failed to find button with label '%@' for alert: %@", label, self.alertElement]
- buildError:error];
- }
- [requestedButton tap];
- return YES;
- }
- - (XCUIElement *)alertElement
- {
- if (nil == self.element) {
- XCUIApplication *systemApp = XCUIApplication.fb_systemApplication;
- if ([systemApp fb_isSameAppAs:self.application]) {
- self.element = systemApp.fb_alertElement;
- } else {
- self.element = systemApp.fb_alertElement ?: self.application.fb_alertElement;
- }
- }
- return self.element;
- }
- @end
|