web-dev-qa-db-fra.com

Faites défiler jusqu'à ce que l'élément soit visible iOS UI Automation avec xcode7

Ainsi, avec la nouvelle mise à jour de xcode Apple a remanié notre façon de faire les tests d'interface utilisateur. Dans les instruments, nous avons utilisé Java fonction de script "isVisible" pour déterminer si notre élément ciblé est visible.

J'essaie de reproduire cela dans l'objectif C mais je n'arrive pas à trouver l'équivalent de cela. J'ai une vue de table, une cellule prototype avec deux étiquettes dessus. Disons que ce prototype de cellule est réutilisé 50 fois.

J'essaye de faire défiler jusqu'à ce que la dernière cellule soit visible, je l'ai fait en faisant ceci:

if (![[[[[[XCUIApplication alloc] init].tables childrenMatchingType:XCUIElementTypeCell] matchingIdentifier:@"cell"] elementBoundByIndex:49].staticTexts[@"text"] exists]) {
        [[[[[[XCUIApplication alloc] init].tables childrenMatchingType:XCUIElementTypeCell] matchingIdentifier:@"cell"] elementBoundByIndex:0].staticTexts[@"text"] swipeUp];
}

Mais cela ne glissera pas puisque l'élément existe lorsque la vue est chargée. S'il vous plaît, aidez-moi car cela me rend fou.

32
CodeGeass

Vous devez étendre la liste des méthodes de XCUIElement. La première méthode (scrollToElement:) sera appelé sur la tableView, la deuxième méthode d'extension vous aide à décider si l'élément est sur la fenêtre principale.

extension XCUIElement {

    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
    }

    func visible() -> Bool {
        guard self.exists && !CGRectIsEmpty(self.frame) else { return false }
        return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, self.frame)
    }

}

Le code de défilement doit ressembler à ceci (par exemple, faire défiler jusqu'à la dernière cellule):

func testScrollTable() {
    let app = XCUIApplication()
    let table = app.tables.elementBoundByIndex(0)
    let lastCell = table.cells.elementBoundByIndex(table.cells.count-1)
    table.scrollToElement(lastCell)
}

Swift 3:

extension XCUIElement {
    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
    }

    func visible() -> Bool {
        guard self.exists && !self.frame.isEmpty else { return false }
        return XCUIApplication().windows.element(boundBy: 0).frame.contains(self.frame)
    }
}
47
Kádi

Toutes les réponses précédentes ne sont pas 100% infaillibles. Le problème auquel j'étais confronté est que swipeUp () a un décalage plus important et je n'ai pas pu trouver un moyen d'arrêter le défilement lorsque j'ai l'élément dans le port d'affichage. Parfois, l'élément est défilé en raison du défilement excessif et, en conséquence, le cas de test échoue. Cependant, j'ai réussi à contrôler le défilement en utilisant le morceau de code suivant.

/**
Scrolls to a particular element until it is rendered in the visible rect
- Parameter elememt: the element we want to scroll to
*/
func scrollToElement(element: XCUIElement)
{
    while element.visible() == false
    {
        let app = XCUIApplication()
        let startCoord = app.collectionViews.element.coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: 0.5))
        let endCoord = startCoord.coordinateWithOffset(CGVector(dx: 0.0, dy: -262));
        startCoord.pressForDuration(0.01, thenDragToCoordinate: endCoord)
    }
}

func visible() -> Bool
{
    guard self.exists && self.hittable && !CGRectIsEmpty(self.frame) else
    {
        return false
    }

    return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, self.frame)
}

Remarque: veuillez utiliser app.tables si votre vue est basée sur une vue de table

20
ravisekahrp

Les solutions utilisant swipeUp() et swipeDown() ne sont pas idéales car elles peuvent potentiellement défiler au-delà de l'élément cible en raison de l'élan du balayage. Après beaucoup de recherches et de frustration, j'ai trouvé une méthode magique sur XCUICoordinate:

func press(forDuration duration: TimeInterval, thenDragTo otherCoordinate: XCUICoordinate)

Nous pouvons donc faire quelque chose comme:

let topCoordinate = XCUIApplication().statusBars.firstMatch.coordinate(withNormalizedOffset: .zero)
let myElement = XCUIApplication().staticTexts["My Element"].coordinate(withNormalizedOffset: .zero)
// drag from element to top of screen (status bar)
myElement.press(forDuration: 0.1, thenDragTo: topCoordinate)

Pour vérifier si quelque chose est visible, vous voulez utiliser isHittable en conjonction avec exists. voir scrollDownToElement dans l'extension ci-dessous

Voici une extension pratique qui défilera jusqu'à ce qu'un élément soit à l'écran, puis fera défiler cet élément vers le haut de l'écran :)

extension XCUIApplication {
    private struct Constants {
        // Half way accross the screen and 10% from top
        static let topOffset = CGVector(dx: 0.5, dy: 0.1)

        // Half way accross the screen and 90% from top
        static let bottomOffset = CGVector(dx: 0.5, dy: 0.9)
    }

    var screenTopCoordinate: XCUICoordinate {
        return windows.firstMatch.coordinate(withNormalizedOffset: Constants.topOffset)
    }

    var screenBottomCoordinate: XCUICoordinate {
        return windows.firstMatch.coordinate(withNormalizedOffset: Constants.bottomOffset)
    }

    func scrollDownToElement(element: XCUIElement, maxScrolls: Int = 5) {
        for _ in 0..<maxScrolls {
            if element.exists && element.isHittable { element.scrollToTop(); break }
            scrollDown()
        }
    }

    func scrollDown() {
        screenBottomCoordinate.press(forDuration: 0.1, thenDragTo: screenTopCoordinate)
    }
}

extension XCUIElement {
    func scrollToTop() {
        let topCoordinate = XCUIApplication().screenTopCoordinate
        let elementCoordinate = coordinate(withNormalizedOffset: .zero)

        // Adjust coordinate so that the drag is straight up, otherwise
        // an embedded horizontal scrolling element will get scrolled instead
        let delta = topCoordinate.screenPoint.x - elementCoordinate.screenPoint.x
        let deltaVector = CGVector(dx: delta, dy: 0.0)

        elementCoordinate.withOffset(deltaVector).press(forDuration: 0.1, thenDragTo: topCoordinate)
    }
}

Gist over ici avec des méthodes scrollUp ajoutées

8
RyanM

Développer sur réponse de @ Kade , dans mon cas, devait tenir compte de la barre de tabulations dans scrollToElement, sinon un bouton de la barre de tabulations pourrait être utilisé si la vue était sous la barre de tabulations:

    func scrollToElement(element: XCUIElement) {
        while !element.visible() {
            swipeUp()
        }
        // Account for tabBar
        let tabBar = XCUIApplication().tabBars.element(boundBy: 0)
        if (tabBar.visible()) {
            while element.frame.intersects(tabBar.frame) {
                swipeUp()
           }
       }
    }
4
Justin Zealand

Voici ma version qui je pense est à l'épreuve des balles (Swift 4.0):

import XCTest

enum TestSwipeDirections {
    case up
    case down
    case left
    case right
}

fileprivate let min = 0.05
fileprivate let mid = 0.5
fileprivate let max = 0.95

fileprivate let leftPoint = CGVector(dx: min, dy: mid)
fileprivate let rightPoint = CGVector(dx: max, dy: mid)
fileprivate let topPoint = CGVector(dx: mid, dy: min)
fileprivate let bottomPoint = CGVector(dx: mid, dy: max)

extension TestSwipeDirections {
    var vector: (begin: CGVector, end: CGVector) {
        switch self {
        case .up:
            return (begin: bottomPoint,
                    end:   topPoint)
        case .down:
            return (begin: topPoint,
                    end:   bottomPoint)
        case .left:
            return (begin: rightPoint,
                    end:   leftPoint)
        case .right:
            return (begin: leftPoint,
                    end:   rightPoint)
        }
    }
}

extension XCUIElement {
    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      until: () -> Bool) -> Bool {
        XCTAssert(exists)

        let begining = coordinate(withNormalizedOffset: direction.vector.begin)
        let ending = coordinate(withNormalizedOffset: direction.vector.end)

        var swipesRemaining = swipeLimit
        while !until() && swipesRemaining > 0 {
            begining.press(forDuration: swipeDuration, thenDragTo: ending)
            swipesRemaining = swipesRemaining - 1
        }
        return !until()
    }

    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      untilHittable element: XCUIElement) -> Bool {
        return swipeOnIt(direction, swipeLimit: swipeLimit, swipeDuration: swipeDuration) { element.isHittable }
    }

    @discardableResult func swipeOnIt(_ direction: TestSwipeDirections,
                                      swipeLimit: Int = 6,
                                      swipeDuration: TimeInterval = 1.0,
                                      untilExists element: XCUIElement) -> Bool {
        return swipeOnIt(direction, swipeLimit: swipeLimit, swipeDuration: swipeDuration) { element.exists }
    }
}

Il faut tenir compte du fait que l'article peut ne pas être trouvé (dans ce cas, il ne doit pas se bloquer). Le défilement est également effectué par étapes de taille de l'élément afin que l'élément de recherche ne passe pas à travers la zone visible, ce qui est possible en cas de balayage.

3
Marek R

Malheureusement .exists ne confirme pas qu'un élément est actuellement visible - quelque chose comme ça n'est toujours pas parfait, mais il fournira une validation plus fiable en travaillant avec des cellules de vue de tableau ou de collection:

extension XCUIElement {
    var displayed: Bool {
        guard self.exists && !CGRectIsEmpty(frame) else { return false }
        return CGRectContainsRect(XCUIApplication().windows.elementBoundByIndex(0).frame, frame)
    }
}

alors vous pouvez écrire une boucle simple comme:

func scrollDownUntilVisible(element: XCUIElement) {
    while !element.displayed {
        swipeDown()
    }
}
3
Tucker Sherman

vous pouvez faire quelque chose comme ça:

extension XCUIElement {
    internal func scrollToElement(element: XCUIElement) {
        while !element.exists {
            swipeDown()
        }
    }
}

et d'utiliser scrollToElement pour rechercher l'élément

1
Andrey

Mise à jour de la réponse de @ ravisekahrp pour le nouveau Swift:

extension XCUIElement {
    func isVisible() -> Bool {
        if !self.exists || !self.isHittable || self.frame.isEmpty {
            return false
        }

        return XCUIApplication().windows.element(boundBy: 0).frame.contains(self.frame)
    }
}

extension XCTestCase {
    func scrollToElement(_ element: XCUIElement) {
        while !element.isVisible() {
            let app = XCUIApplication()
            let startCoord = app.tables.element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
            let endCoord = startCoord.withOffset(CGVector(dx: 0.0, dy: -262))
            startCoord.press(forDuration: 0.01, thenDragTo: endCoord)
        }
    }
}
0
Sunkas

dans Swift 4.2, si votre élément existe dans le cadre inférieur de la vue du tableau ou le cadre supérieur de la vue du tableau, vous pouvez utiliser cette commande pour faire défiler vers le haut et vers le bas pour trouver l'élément

let app = XCUIApplication()
app.swipeUp()

ou

app.swipeDown()
0
Ashim Dahal