Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop scrolling on UITableView at last during XCUI TestCase

In my one of UITableView have more then 10 rows. I want to scroll till last row while UITestCase running.

I have written below code to scroll till last row.

-(void)scrollToElement:(XCUIElement *)element application:(XCUIApplication *)app{
    while ([self visible:element withApplication:app]) {
        XCUIElement *searchResultTableView = app.tables[@"searchResultView"];
        XCUICoordinate *startCoord = [searchResultTableView coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
        XCUICoordinate *endCoord = [startCoord coordinateWithOffset:CGVectorMake(0.0, -262)];
        [startCoord pressForDuration:0.01 thenDragToCoordinate:endCoord];
    }
}
-(BOOL)visible:(XCUIElement *)element withApplication:(XCUIApplication *)app{
    if (element.exists && !CGRectIsEmpty(element.frame) && element.isHittable) {
        return CGRectContainsRect([app.windows elementBoundByIndex:0].frame, element.frame);
    } else {
        return FALSE;
    }
}

An i have called above method in my one of UITestCase method by below code

XCUIElement *searchResultTableView = app.tables[@"searchResultView"];
[self waitForElementToAppear:searchResultTableView withTimeout:30];


XCUIElement *table = [app.tables elementBoundByIndex:0];
XCUIElement *lastCell = [table.cells elementBoundByIndex:table.cells.count - 1];
[self scrollToElement:lastCell application:app];

By this code i can scroll to last row but after reaching last row, it continue doing scroll means can't stop scrolling.

Please help me to scroll to only last row and then it should stop to scroll so that i can perform next action event.

I have refer StackOverFlow answer but none of them meet my requirement.

Thanks in advance.

like image 656
Yagnesh Dobariya Avatar asked Jan 24 '18 09:01

Yagnesh Dobariya


People also ask

Should I use a scroll view or a UITableView?

For example, many developers make their life harder using a scroll view when a UITableView would be a better choice. Finally, architecture is crucial for table views. The code of the table view data source often ends inside view controllers when it should go into a separate class.

How does a table view scroll back to a specific row?

When the user scrolls back to a row, the table view will request its data again. The tableView(_:numberOfRowsInSection:) method tells the table view how many elements a section contains. The table view uses this information to prepare its scrolling area and display a scroll bar of the appropriate size.

Should I use auto layout for scroll views?

In a scroll view, you need to figure the frame of each subview by yourself. You can use Auto Layout, but that still means you will need a lot of code to set all the constraints. In a scroll view, that’s not as easy to get right as it is in a regular UIView.

Why use a UITableView instead of a Tableview?

Table views are more versatile than you might think. For example, many developers make their life harder using a scroll view when a UITableView would be a better choice. Finally, architecture is crucial for table views.


1 Answers

I faced similar issue in one of my project. In that I wanted to test "Load More" feature by TestKit framework.

Here is some workaround to achieve the same scenario.

//app           : is your current instance of appliaction
//listTable     : is a Table which you've found via accessibility identifier
//loadMoreTest  : is a parameter to determine whether code should perform test for loadmore feature or not

- (void)testScrollableTableForApplication:(XCUIApplication *)app
                                 forTable:(XCUIElement *)listTable
                         withLoadMoreTest:(BOOL)loadMoreTest {

    [listTable accessibilityScroll:UIAccessibilityScrollDirectionUp];
    [listTable swipeUp];

    if (loadMoreTest) {
        __block BOOL isLoadMoreCalled;
        __block XCUIElement *lastCell;
        __block __weak void (^load_more)();
        void (^loadMoreCall)();
        load_more = loadMoreCall = ^() {
            XCUIElementQuery *tablesQuery = app.tables;
            XCUIElementQuery *cellQuery = [tablesQuery.cells containingType:XCUIElementTypeCell identifier:@"LoadMoreCell"];
            lastCell = cellQuery.element;
            if ([lastCell elementIsWithinWindowForApplication:app]) {
                [self waitForElementToAppear:lastCell withTimeout:2];
                [lastCell tap];
                isLoadMoreCalled = true;
                [self wait:2];
            }
            [listTable swipeUp];
            if (!isLoadMoreCalled) {
                load_more();
            }
        };
        loadMoreCall();
    }
}


- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];

    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == 1"];
    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

create one category for XCUIElement XCUIElement+Helper.m and import it into your respective Test class.

#import <XCTest/XCTest.h>

@interface XCUIElement (Helper)

/// Check whether current XCUIElement is within current window or not
- (BOOL)elementIsWithinWindowForApplication:(XCUIApplication *)app ;

@end

@implementation XCUIElement (Helper)

/// Check whether current XCUIElement is within current window or not
/*
@description: we need to check particular element's frame and window's frame is intersecting or not, to get perfectly outcome whether element is currently visible on screen or not, because if element has not yet appeared on screen then also the flag frame, exists and hittable can become true
*/
- (BOOL)elementIsWithinWindowForApplication:(XCUIApplication *)app {
    if (self.exists && !CGRectIsEmpty(self.frame) && self.hittable)
        return CGRectContainsRect(app.windows.allElementsBoundByIndex[0].frame, self.frame);
    else
        return false;
}

@end

To get the "Load More" cell, i've given the

cell.accessibilityIdentifier = @"LoadMoreCell";

Rest of the code is, recursive function in testScrollableTableForApplication to make Tableview scroll to reach to bottom so i can have the access of load more cell(in your case last cell). Then i am performing the action Tap to fetch new records from server. Then again i am scrolling the Table to verify if the new records has been fetched from server or not.

Tip : you can replace recursive function with do while or while loop to achieve the same.

Hope this helps!

Happy Coding!!

like image 92
Dhruvik Avatar answered Sep 21 '22 23:09

Dhruvik