Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m
Original file line number Diff line number Diff line change
Expand Up @@ -374,26 +374,18 @@ - (BOOL)fb_scrollAncestorScrollViewByVectorWithinScrollViewFrame:(CGVector)vecto
error:(NSError **)error
{
CGRect scrollingFrame = self.scrollingFrame;
CGRect anchorFrame = anchorElement.frame;
// wdFrame matches scrollingFrame's coordinate space; raw .frame can be pre-scaled or
// dimension-swapped and drift out of sync with it (appium/appium#16185).
CGRect anchorFrame = anchorElement.wdFrame;
if (CGRectIsEmpty(scrollingFrame) || CGRectIsEmpty(anchorFrame)) {
return [[[FBErrorBuilder builder]
withDescriptionFormat:@"Cannot compute a scroll gesture for '%@': its frame is empty", self.fb_description]
buildError:error];
}

// Compute the touch-down/up points within the (possibly clipped) scrolling frame as
// before, then express them as fractions of the anchor element's own frame instead of
// raw points, which XCTest never rescales for compatibility-mode windows
// (appium/appium#16185). When scrollingFrame == anchorFrame this resolves to the exact
// same absolute point as before; it only differs once XCTest itself rescales anchorFrame.
CGVector proportion = [self fb_normalizedHitPointOffsetForScrollingVector:vector];
CGPoint startPoint = CGPointMake((CGFloat)floor(scrollingFrame.origin.x + scrollingFrame.size.width * proportion.dx),
(CGFloat)floor(scrollingFrame.origin.y + scrollingFrame.size.height * proportion.dy));
CGPoint endPoint = CGPointMake((CGFloat)floor(startPoint.x + vector.dx), (CGFloat)floor(startPoint.y + vector.dy));
CGVector startOffset = CGVectorMake((startPoint.x - anchorFrame.origin.x) / anchorFrame.size.width,
(startPoint.y - anchorFrame.origin.y) / anchorFrame.size.height);
CGVector endOffset = CGVectorMake((endPoint.x - anchorFrame.origin.x) / anchorFrame.size.width,
(endPoint.y - anchorFrame.origin.y) / anchorFrame.size.height);
CGVector startOffset, endOffset;
FBScrollGestureOffsets(scrollingFrame, anchorFrame, proportion, vector, &startOffset, &endOffset);
XCUICoordinate *startCoordinate = [anchorElement coordinateWithNormalizedOffset:startOffset];
XCUICoordinate *endCoordinate = [anchorElement coordinateWithNormalizedOffset:endOffset];

Expand Down
25 changes: 25 additions & 0 deletions WebDriverAgentLib/Utilities/FBMathUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,29 @@ XCUICoordinate * _Nullable FBCoordinateWithAnchorOffset(XCUIElement *element,
NSError **error);
#endif

/*!
Computes the normalized (0.0-1.0) start/end offsets of a scroll drag gesture whose
touch-down/up points fall within scrollingFrame, expressed relative to anchorFrame -
the coordinate space the resulting offsets get resolved against (e.g. via
-[XCUIElement coordinateWithNormalizedOffset:]). scrollingFrame and anchorFrame are
usually the same rect, but scrollingFrame may be clipped to a visible sub-region, and/or
the two may come from frame sources XCTest doesn't keep in sync (see appium/appium#16185)
- passing mismatched frames here reproduces that bug rather than fixing it.

@param scrollingFrame the (possibly clipped) frame to compute the touch-down/up points within
@param anchorFrame the frame startOffset/endOffset get normalized against
@param proportion normalized touch-down position within scrollingFrame, e.g. from
-fb_normalizedHitPointOffsetForScrollingVector:
@param vector the scroll vector, in scrollingFrame's coordinate space
@param startOffset populated with the normalized start offset; untouched if NO is returned
@param endOffset populated with the normalized end offset; untouched if NO is returned
@return NO if either frame is empty
*/
BOOL FBScrollGestureOffsets(CGRect scrollingFrame,
CGRect anchorFrame,
CGVector proportion,
CGVector vector,
CGVector *startOffset,
CGVector *endOffset);

NS_ASSUME_NONNULL_END
21 changes: 21 additions & 0 deletions WebDriverAgentLib/Utilities/FBMathUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,24 @@ This verification is just to make sure the bug is still there (since height is n
return [element coordinateWithNormalizedOffset:normalizedOffset];
}
#endif

BOOL FBScrollGestureOffsets(CGRect scrollingFrame,
CGRect anchorFrame,
CGVector proportion,
CGVector vector,
CGVector *startOffset,
CGVector *endOffset)
{
if (CGRectIsEmpty(scrollingFrame) || CGRectIsEmpty(anchorFrame)) {
return NO;
}

CGPoint startPoint = CGPointMake((CGFloat)floor(scrollingFrame.origin.x + scrollingFrame.size.width * proportion.dx),
(CGFloat)floor(scrollingFrame.origin.y + scrollingFrame.size.height * proportion.dy));
CGPoint endPoint = CGPointMake((CGFloat)floor(startPoint.x + vector.dx), (CGFloat)floor(startPoint.y + vector.dy));
*startOffset = CGVectorMake((startPoint.x - anchorFrame.origin.x) / anchorFrame.size.width,
(startPoint.y - anchorFrame.origin.y) / anchorFrame.size.height);
*endOffset = CGVectorMake((endPoint.x - anchorFrame.origin.x) / anchorFrame.size.width,
(endPoint.y - anchorFrame.origin.y) / anchorFrame.size.height);
return YES;
}
38 changes: 38 additions & 0 deletions WebDriverAgentTests/UnitTests/FBMathUtilsTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,42 @@ - (void)testSizeInversion
XCTAssertTrue(FBSizeFuzzyEqualToSize(screenSizeLandscape, FBAdjustDimensionsForApplication(screenSizeLandscape, UIInterfaceOrientationLandscapeRight), t));
}

- (void)testScrollGestureOffsetsWithMatchingFrames
{
CGRect frame = CGRectMake(20, 200, 300, 400);
CGVector proportion = CGVectorMake(0.5, 0.75);
CGVector vector = CGVectorMake(0, -200);
CGVector startOffset, endOffset;
XCTAssertTrue(FBScrollGestureOffsets(frame, frame, proportion, vector, &startOffset, &endOffset));
XCTAssertTrue(FBVectorFuzzyEqualToVector(startOffset, CGVectorMake(0.5, 0.75), 0.01));
XCTAssertTrue(FBVectorFuzzyEqualToVector(endOffset, CGVectorMake(0.5, 0.25), 0.01));
}

- (void)testScrollGestureOffsetsWithRescaledAnchorFrame
{
// Simulates a compatibility-mode window: anchorFrame is scrollingFrame scaled by ~2.19x,
// same origin - offsets should still land within [0, 1] instead of drifting outside it.
CGRect scrollingFrame = CGRectMake(20, 202, 335, 420);
CGRect anchorFrame = CGRectMake(20, 202, 733, 920);
CGVector proportion = CGVectorMake(0.5, 0.75);
CGVector vector = CGVectorMake(0, -250);
CGVector startOffset, endOffset;
XCTAssertTrue(FBScrollGestureOffsets(scrollingFrame, anchorFrame, proportion, vector, &startOffset, &endOffset));
XCTAssertTrue(startOffset.dx >= 0 && startOffset.dx <= 1);
XCTAssertTrue(startOffset.dy >= 0 && startOffset.dy <= 1);
XCTAssertTrue(endOffset.dx >= 0 && endOffset.dx <= 1);
XCTAssertTrue(endOffset.dy >= 0 && endOffset.dy <= 1);
}

- (void)testScrollGestureOffsetsWithEmptyFrame
{
CGVector startOffset = CGVectorMake(-1, -1);
CGVector endOffset = CGVectorMake(-1, -1);
XCTAssertFalse(FBScrollGestureOffsets(CGRectZero, CGRectMake(0, 0, 100, 100), CGVectorMake(0.5, 0.5), CGVectorMake(0, -50), &startOffset, &endOffset));
XCTAssertFalse(FBScrollGestureOffsets(CGRectMake(0, 0, 100, 100), CGRectZero, CGVectorMake(0.5, 0.5), CGVectorMake(0, -50), &startOffset, &endOffset));
// Untouched on failure
XCTAssertTrue(startOffset.dx == -1 && startOffset.dy == -1);
XCTAssertTrue(endOffset.dx == -1 && endOffset.dy == -1);
}

@end
Loading