From 2f74277963841ef8eac6689e8fa89aa49f046821 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 26 May 2025 09:00:04 +0200 Subject: [PATCH] feat: Use another snapshotting mechanism for the hittable attribute calculation --- .../Categories/XCUIElement+FBUtilities.h | 17 ++++++++- .../Categories/XCUIElement+FBUtilities.m | 35 +++++++++++++------ .../XCUIElement+FBWebDriverAttributes.m | 4 +++ .../FBElementAttributeTests.m | 3 +- .../IntegrationTests/FBScrollingTests.m | 5 ++- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h index 65ba738b6..c5d4bd7f6 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h +++ b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h @@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN /** Gets the most recent snapshot of the current element. The element will be automatically resolved if the snapshot is not available yet. - Calls to this method mutate the `lastSnapshot` instance property.. + Calls to this method mutate the `lastSnapshot` instance property. The maximum snapshot tree depth is set by `FBConfiguration.snapshotMaxDepth` Snapshot specifics: @@ -49,6 +49,21 @@ NS_ASSUME_NONNULL_BEGIN */ - (id)fb_customSnapshot; +/** + Gets the most recent snapshot of the current element. The element will be + automatically resolved if the snapshot is not available yet. + Calls to this method mutate the `lastSnapshot` instance property. + The maximum snapshot tree depth is set by `FBConfiguration.snapshotMaxDepth` + + Snapshot specifics: + - Less performant in comparison to the standard one + - The `hittable` property calculation is aligned with the native calculation logic + + @return The recent snapshot of the element + @throws FBStaleElementException if the element is not present in DOM and thus no snapshot could be made + */ +- (id)fb_nativeSnapshot; + /** Extracts the cached element snapshot from its query. No requests to the accessiblity framework is made. diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m index 2a86ea764..6d6df732f 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +++ b/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m @@ -53,16 +53,7 @@ @implementation XCUIElement (FBUtilities) ? [self.fb_query fb_uniqueSnapshotWithError:&error] : (id)[self snapshotWithError:&error]; if (nil == snapshot) { - NSString *hintText = @"Make sure the application UI has the expected state"; - if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) { - hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText]; - } - NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@", - self.description, hintText]; - if (nil != error) { - reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription]; - } - @throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}]; + [self fb_raiseStaleElementExceptionWithError:error]; } } self.lastSnapshot = snapshot; @@ -79,6 +70,16 @@ @implementation XCUIElement (FBUtilities) return [self fb_takeSnapshot:YES]; } +- (id)fb_nativeSnapshot +{ + NSError *error = nil; + BOOL isSuccessful = [self resolveOrRaiseTestFailure:NO error:&error]; + if (nil == self.lastSnapshot || !isSuccessful) { + [self fb_raiseStaleElementExceptionWithError:error]; + } + return self.lastSnapshot; +} + - (id)fb_cachedSnapshot { return [self.query fb_cachedSnapshot]; @@ -153,4 +154,18 @@ - (void)fb_waitUntilStableWithTimeout:(NSTimeInterval)timeout FBConfiguration.waitForIdleTimeout = previousTimeout; } +- (void)fb_raiseStaleElementExceptionWithError:(NSError *)error __attribute__((noreturn)) +{ + NSString *hintText = @"Make sure the application UI has the expected state"; + if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) { + hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText]; + } + NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@", + self.description, hintText]; + if (nil != error) { + reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription]; + } + @throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}]; +} + @end diff --git a/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m b/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m index 8bb068709..d5860113a 100644 --- a/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m +++ b/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m @@ -29,6 +29,10 @@ @implementation XCUIElement (WebDriverAttributesForwarding) - (id)fb_snapshotForAttributeName:(NSString *)name { + // https://github.com/appium/appium-xcuitest-driver/pull/2565 + if ([name isEqualToString:FBStringify(XCUIElement, isWDHittable)]) { + return [self fb_nativeSnapshot]; + } // https://github.com/appium/appium-xcuitest-driver/issues/2552 BOOL isValueRequest = [name isEqualToString:FBStringify(XCUIElement, wdValue)]; if ([self isKindOfClass:XCUIApplication.class] && !isValueRequest) { diff --git a/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m b/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m index db956eeab..1591d12af 100644 --- a/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m @@ -16,7 +16,6 @@ #import "XCUIElement+FBAccessibility.h" #import "XCUIElement+FBIsVisible.h" #import "XCUIElement+FBWebDriverAttributes.h" -#import "FBXCodeCompatibility.h" @interface FBElementAttributeTests : FBIntegrationTestCase @end @@ -157,7 +156,7 @@ - (void)testSwitchAttributes XCTAssertNil(element.wdPlaceholderValue); XCTAssertEqualObjects(element.wdValue, @"1"); XCTAssertFalse(element.wdSelected); - XCTAssertTrue(element.wdHittable); + XCTAssertEqual(element.wdHittable, element.hittable); [element tap]; XCTAssertEqualObjects(element.wdValue, @"0"); XCTAssertFalse(element.wdSelected); diff --git a/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m b/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m index f1c5e15a0..0f6272b9b 100644 --- a/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m +++ b/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m @@ -15,7 +15,6 @@ #import "FBMacros.h" #import "XCUIElement+FBIsVisible.h" #import "XCUIElement+FBScrolling.h" - #import "XCUIElement+FBClassChain.h" #import "FBXCodeCompatibility.h" @@ -43,8 +42,12 @@ - (void)testCellVisibility { FBAssertVisibleCell(@"0"); FBAssertVisibleCell(@"10"); + XCUIElement *cell10 = FBCellElementWithLabel(@"10"); + XCTAssertEqual([cell10 isWDHittable], [cell10 isHittable]); FBAssertInvisibleCell(@"30"); FBAssertInvisibleCell(@"50"); + XCUIElement *cell50 = FBCellElementWithLabel(@"50"); + XCTAssertEqual([cell50 isWDHittable], [cell50 isHittable]); } - (void)testSimpleScroll