web-dev-qa-db-fra.com

Xcode 7 UI Testing: comment ignorer une série d'alertes système dans le code

J'écris des scénarios de test d'interface utilisateur à l'aide de la nouvelle fonctionnalité de test d'interface utilisateur Xcode 7. À un moment donné de mon application, je demande à l'utilisateur l'autorisation d'accès à la caméra et de notification Push. Donc, deux popups iOS vont apparaître: "MyApp Would Like to Access the Camera" popup et "MyApp Would Like to Send You Notifications" popup. Je voudrais que mon test rejette les deux popups.

L'enregistrement d'interface utilisateur a généré le code suivant pour moi: 

[app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap];

Cependant, [app.alerts[@"cameraAccessTitle"] exists] est résolu en false et le code ci-dessus génère une erreur: Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202"

Alors, quel est le meilleur moyen de rejeter une pile d’alertes système à l’essai? Les fenêtres contextuelles du système interrompent le flux de mes applications et échouent immédiatement dans mes cas de test d’interface utilisateur normaux. En fait, toutes les recommandations concernant la manière de contourner les alertes système afin de pouvoir reprendre le test du flux habituel sont appréciées. 

Cette question pourrait être liée à cet article SO qui n'a pas non plus de réponse: Xcode7 | Tests d'interface utilisateur Xcode | Comment gérer l'alerte de service de localisation?

Merci d'avance.

49
SeaJelly

Xcode 7.1

Xcode 7.1 a finalement résolu le problème des alertes système. Il y a cependant deux petites prises.

Tout d'abord, vous devez configurer un "Gestionnaire d'interruption d'interface utilisateur" avant de présenter l'alerte. C’est notre manière de dire au framework comment gérer une alerte quand elle apparaît.

Deuxièmement, après avoir présenté l'alerte, vous devez interagir avec l'interface. Il suffit de taper sur l'application, mais cela est nécessaire.

addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
    alert.buttons["Allow"].tap()
    return true
}

app.buttons["Request Location"].tap()
app.tap() // need to interact with the app for the handler to fire

La "boîte de dialogue d'emplacement" est simplement une chaîne destinée à aider le développeur à identifier le gestionnaire auquel il a accédé. Elle n'est pas spécifique au type d'alerte.

Je pense que renvoyer true du gestionnaire le marque comme "complet", ce qui signifie qu'il ne sera plus appelé. Dans votre cas, j'essaierais de renvoyer false afin que la deuxième alerte déclenche à nouveau le gestionnaire.

Xcode 7.0

Les éléments suivants ignoreront une seule "alerte système" dans Xcode 7 Beta 6:

let app = XCUIApplication()
app.launch()
// trigger location permission dialog

app.alerts.element.collectionViews.buttons["Allow"].tap()

La bêta 6 a introduit une série de correctifs pour les tests d'interface utilisateur et je crois que c'était l'un d'entre eux.

Notez également que j'appelle -element directement sur -alerts. L'appel de -element sur une XCUIElementQuery oblige la structure à choisir le "seul et unique" élément correspondant à l'écran. Cela fonctionne très bien pour les alertes dans lesquelles vous ne pouvez avoir qu'une seule visibilité à la fois. Cependant, si vous essayez ceci pour une étiquette et que vous avez deux étiquettes, la structure générera une exception.

47
Joe Masilotti

Gosh. Il tape toujours sur "Ne pas autoriser" même si je dis délibérément que je tape sur "Autoriser"

Au moins 

if app.alerts.element.collectionViews.buttons["Allow"].exists {
    app.tap()
}

me permet d'aller de l'avant et de faire d'autres tests.

3

Objectif c

-(void) registerHandlerforDescription: (NSString*) description {

    [self addUIInterruptionMonitorWithDescription:description handler:^BOOL(XCUIElement * _Nonnull interruptingElement) {

        XCUIElement *element = interruptingElement;
        XCUIElement *allow = element.buttons[@"Allow"];
        XCUIElement *ok = element.buttons[@"OK"];

        if ([ok exists]) {
            [ok tap];
            return YES;
        }

        if ([allow exists]) {
            [allow tap];
            return YES;
        }

        return NO;
    }];
}

-(void)setUp {

    [super setUp];

    self.continueAfterFailure = NO;
    self.app = [[XCUIApplication alloc] init];
    [self.app launch];

    [self registerHandlerforDescription:@"“MyApp” would like to make data available to nearby Bluetooth devices even when you're not using app."];
    [self registerHandlerforDescription:@"“MyApp” Would Like to Access Your Photos"];
    [self registerHandlerforDescription:@"“MyApp” Would Like to Access the Camera"];
}

Rapide

addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
    alert.buttons["Allow"].tap()
    alert.buttons["OK"].tap()
    return true
}
3

Pour ceux qui recherchent des descriptions spécifiques pour des dialogues système spécifiques (comme je l'ai fait), il n'y en a aucune :) la chaîne est uniquement destinée au suivi des testeurs. Lien vers les documents Apple associés: https://developer.Apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor


Mise à jour: xcode 9.2

La méthode est parfois déclenchée, parfois non. La meilleure solution pour moi est quand je sais qu’il y aura une alerte système, j’ajoute: 

sleep(2)
app.tap()

et l'alerte système est parti

1
ergunkocak

La seule chose que j'ai trouvé qui soit corrigée de manière fiable était de configurer deux tests distincts pour gérer les alertes. Lors du premier test, j'appelle app.tap() et ne fais rien d'autre. Dans le deuxième test, j'appelle à nouveau app.tap() et effectue ensuite le travail réel.

1
Kevin London

Dieu! Je déteste le pire temps que XCTest ait à gérer avec les alertes UIView. J'ai une application pour laquelle je reçois 2 alertes. La première demande que je sélectionne "Autoriser" pour activer les services de localisation pour les autorisations d'application. Ensuite, sur une page de démarrage, l'utilisateur doit appuyer sur un bouton UIB appelé "Activer la localisation". notification sms alert dans UIViewAlert et l'utilisateur doit sélectionner "OK". Le problème que nous rencontrions était de ne pas pouvoir interagir avec les alertes du système, mais aussi d'une situation de concurrence critique dans laquelle le comportement et son apparence à l'écran étaient inopportuns. Il semble que si vous utilisez le alert.element.buttons["whateverText"].tap, la logique de XCTest est de continuer à appuyer jusqu'à la fin du test. Continuez donc d'appuyer sur tout ce qui est à l'écran jusqu'à ce que toutes les alertes du système soient bien visibles.

C'est un bidouillage mais c'est ce qui a fonctionné pour moi.

    func testGetPastTheStupidAlerts(){
    let app = XCUIApplication()
    app.launch()

    if app.alerts.element.collectionViews.buttons["Allow"].exists {
        app.tap()
    }

    app.buttons["TURN ON MY LOCATION"].tap()
}

La chaîne "Autoriser" est complètement ignorée et la logique de app.tap() est appelée une heure d'alerte. Enfin, le bouton que je souhaitais atteindre ["Activer la localisation"] est accessible et le test réussit. 

~ Totalement confus, merci Apple.

1
JJacquet

Sur xcode 9.1, les alertes ne sont traitées que si le périphérique de test dispose d'iOS 11. Ne fonctionne pas sur les anciennes versions iOS, par exemple 10.3, etc. Référence: https://forums.developer.Apple.com/thread/86989

Pour gérer les alertes, utilisez ceci:

//Use this before the alerts appear. I am doing it before app.launch()

let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow'")
//1st alert
_ = addUIInterruptionMonitor(withDescription: "Allow to access your location?") { (alert) -> Bool in
    let alwaysAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch
    if alwaysAllowButton.exists {
        alwaysAllowButton.tap()
        return true
    }
    return false
}
//Copy paste if there are more than one alerts to handle in the app
0
Hasaan Ali

La réponse de @Joe Masilotti est correcte et merci pour cela, cela m'a beaucoup aidé :)

Je voudrais juste souligner une chose, à savoir le UIInterruptionMonitor saisit all les alertes système présentées en sérieAINSI QUEensemble, afin que l'action que vous appliquez dans le gestionnaire d'achèvement reçoive appliqué à chaque alerte ("Ne pas autoriser" ou "OK"). Si vous souhaitez gérer les actions d'alerte différemment, vous devez vérifier, dans le gestionnaire d'achèvement, quelle alerte est actuellement présentée, par exemple. en vérifiant son texte statique, puis l'action ne sera appliquée que sur cette alerte.

Voici un petit extrait de code permettant d'appliquer l'action "Ne pas autoriser" à la deuxième alerte, en série de trois alertes, et l'action "OK" aux deux autres:

addUIInterruptionMonitor(withDescription: "Access to sound recording") { (alert) -> Bool in
        if alert.staticTexts["MyApp would like to use your microphone for recording your sound."].exists {
            alert.buttons["Don’t Allow"].tap()
        } else {
            alert.buttons["OK"].tap()
        }
        return true
    }
app.tap()
0
bra.Scene