| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- /**
- * Copyright (c) 2018-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 "FBXCElementSnapshotWrapper+Helpers.h"
- #import "FBFindElementCommands.h"
- #import "FBRunLoopSpinner.h"
- #import "FBLogger.h"
- #import "FBXCElementSnapshot.h"
- #import "FBXCAXClientProxy.h"
- #import "XCTestDriver.h"
- #import "XCTestPrivateSymbols.h"
- #import "XCUIElement.h"
- #import "XCUIElement+FBWebDriverAttributes.h"
- #import "XCUIHitPointResult.h"
- inline static BOOL isSnapshotTypeAmongstGivenTypes(id<FBXCElementSnapshot> snapshot,
- NSArray<NSNumber *> *types);
- @implementation FBXCElementSnapshotWrapper (Helpers)
- - (NSString *)fb_description
- {
- NSString *result = [NSString stringWithFormat:@"%@", self.wdType];
- if (nil != self.wdName) {
- result = [NSString stringWithFormat:@"%@ (%@)", result, self.wdName];
- }
- return result;
- }
- - (NSArray<id<FBXCElementSnapshot>> *)fb_descendantsMatchingType:(XCUIElementType)type
- {
- return [self descendantsByFilteringWithBlock:^BOOL(id<FBXCElementSnapshot> snapshot) {
- return snapshot.elementType == type;
- }];
- }
- - (id<FBXCElementSnapshot>)fb_parentMatchingType:(XCUIElementType)type
- {
- NSArray *acceptedParents = @[@(type)];
- return [self fb_parentMatchingOneOfTypes:acceptedParents];
- }
- - (id<FBXCElementSnapshot>)fb_parentMatchingOneOfTypes:(NSArray<NSNumber *> *)types
- {
- return [self fb_parentMatchingOneOfTypes:types filter:^(id<FBXCElementSnapshot> snapshot) {
- return YES;
- }];
- }
- - (id<FBXCElementSnapshot>)fb_parentMatchingOneOfTypes:(NSArray<NSNumber *> *)types
- filter:(BOOL(^)(id<FBXCElementSnapshot> snapshot))filter
- {
- id<FBXCElementSnapshot> snapshot = self.parent;
- while (snapshot && !(isSnapshotTypeAmongstGivenTypes(snapshot, types) && filter(snapshot))) {
- snapshot = snapshot.parent;
- }
- return snapshot;
- }
- - (id)fb_attributeValue:(NSString *)attribute
- {
- NSDictionary *result = [FBXCAXClientProxy.sharedClient attributesForElement:[self accessibilityElement]
- attributes:@[attribute]];
- return result[attribute];
- }
- inline static BOOL areValuesEqual(id value1, id value2);
- inline static BOOL areValuesEqualOrBlank(id value1, id value2);
- inline static BOOL isNilOrEmpty(id value);
- - (BOOL)fb_framelessFuzzyMatchesElement:(id<FBXCElementSnapshot>)snapshot
- {
- // Pure payload-based comparison sometimes yield false negatives, therefore relying on it only if all of the identifying properties are blank
- if (isNilOrEmpty(self.identifier)
- && isNilOrEmpty(self.title)
- && isNilOrEmpty(self.label)
- && isNilOrEmpty(self.value)
- && isNilOrEmpty(self.placeholderValue)) {
- return [self.wdUID isEqualToString:([FBXCElementSnapshotWrapper ensureWrapped:snapshot].wdUID ?: @"")];
- }
-
- // Sometimes value and placeholderValue of a correct match from different snapshots are not the same (one is nil and one is a blank string)
- // Therefore taking it into account when comparing
- return self.elementType == snapshot.elementType &&
- areValuesEqual(self.identifier, snapshot.identifier) &&
- areValuesEqual(self.title, snapshot.title) &&
- areValuesEqual(self.label, snapshot.label) &&
- areValuesEqualOrBlank(self.value, snapshot.value) &&
- areValuesEqualOrBlank(self.placeholderValue, snapshot.placeholderValue);
- }
- - (NSArray<id<FBXCElementSnapshot>> *)fb_descendantsCellSnapshots
- {
- NSArray<id<FBXCElementSnapshot>> *cellSnapshots = [self fb_descendantsMatchingType:XCUIElementTypeCell];
-
- if (cellSnapshots.count == 0) {
- // For the home screen, cells are actually of type XCUIElementTypeIcon
- cellSnapshots = [self fb_descendantsMatchingType:XCUIElementTypeIcon];
- }
-
- if (cellSnapshots.count == 0) {
- // In some cases XCTest will not report Cell Views. In that case grab all descendants and try to figure out scroll directon from them.
- cellSnapshots = self._allDescendants;
- }
-
- return cellSnapshots;
- }
- - (NSArray<id<FBXCElementSnapshot>> *)fb_ancestors
- {
- NSMutableArray<id<FBXCElementSnapshot>> *ancestors = [NSMutableArray array];
- id<FBXCElementSnapshot> parent = self.parent;
- while (parent) {
- [ancestors addObject:parent];
- parent = parent.parent;
- }
- return ancestors.copy;
- }
- - (id<FBXCElementSnapshot>)fb_parentCellSnapshot
- {
- id<FBXCElementSnapshot> targetCellSnapshot = self.snapshot;
- // XCUIElementTypeIcon is the cell type for homescreen icons
- NSArray<NSNumber *> *acceptableElementTypes = @[
- @(XCUIElementTypeCell),
- @(XCUIElementTypeIcon),
- ];
- if (self.elementType != XCUIElementTypeCell && self.elementType != XCUIElementTypeIcon) {
- targetCellSnapshot = [self fb_parentMatchingOneOfTypes:acceptableElementTypes];
- }
- return targetCellSnapshot;
- }
- - (CGRect)fb_visibleFrameWithFallback
- {
- CGRect thisVisibleFrame = [self visibleFrame];
- if (!CGRectIsEmpty(thisVisibleFrame)) {
- return thisVisibleFrame;
- }
-
- NSDictionary *visibleFrameDict = (NSDictionary*)[self fb_attributeValue:@"XC_kAXXCAttributeVisibleFrame"];
- if (visibleFrameDict == nil) {
- return thisVisibleFrame;
- }
-
- id x = [visibleFrameDict objectForKey:@"X"];
- id y = [visibleFrameDict objectForKey:@"Y"];
- id height = [visibleFrameDict objectForKey:@"Height"];
- id width = [visibleFrameDict objectForKey:@"Width"];
- if (x != nil && y != nil && height != nil && width != nil) {
- return CGRectMake([x doubleValue], [y doubleValue], [width doubleValue], [height doubleValue]);
- }
-
- return thisVisibleFrame;
- }
- - (NSValue *)fb_hitPoint
- {
- NSError *error;
- XCUIHitPointResult *result = [self hitPoint:&error];
- if (nil != error) {
- [FBLogger logFmt:@"Failed to fetch hit point for %@ - %@", self.fb_description, error.localizedDescription];
- return nil;
- }
- return [NSValue valueWithCGPoint:result.hitPoint];
- }
- @end
- inline static BOOL isSnapshotTypeAmongstGivenTypes(id<FBXCElementSnapshot> snapshot, NSArray<NSNumber *> *types)
- {
- for (NSUInteger i = 0; i < types.count; i++) {
- if([@(snapshot.elementType) isEqual: types[i]] || [types[i] isEqual: @(XCUIElementTypeAny)]){
- return YES;
- }
- }
- return NO;
- }
- inline static BOOL areValuesEqual(id value1, id value2)
- {
- return value1 == value2 || [value1 isEqual:value2];
- }
- inline static BOOL areValuesEqualOrBlank(id value1, id value2)
- {
- return areValuesEqual(value1, value2) || (isNilOrEmpty(value1) && isNilOrEmpty(value2));
- }
- inline static BOOL isNilOrEmpty(id value)
- {
- if ([value isKindOfClass:NSString.class]) {
- return [(NSString*)value length] == 0;
- }
- return value == nil;
- }
|