web-dev-qa-db-ja.com

Xcode 7 UIテスト:コードで一連のシステムアラートを非表示にする方法

新しいXcode 7 UIテスト機能を使用してUIテストケースを作成しています。アプリのある時点で、ユーザーにカメラアクセスとプッシュ通知の許可を求めます。そのため、2つのiOSポップアップが表示されます:"MyApp Would Like to Access the Camera"ポップアップと"MyApp Would Like to Send You Notifications" 現れる。テストで両方のポップアップを閉じたいのですが。

UIの記録により、次のコードが生成されました。

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

しかしながら、 [app.alerts[@"cameraAccessTitle"] exists]はfalseに解決され、上記のコードはエラーを生成します:Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202"

それでは、テストでシステムアラートのスタックを却下する最良の方法は何でしょうか?システムのポップアップはアプリのフローを中断し、通常のUIテストケースにすぐに失敗します。実際、通常のフローのテストを再開できるようにシステムアラートをバイパスする方法に関する推奨事項はありがたいです。

この質問は、これに関連する可能性がありますSO投稿にも回答がない: Xcode7 | Xcode UIテスト|位置情報サービスアラートを処理する方法?

前もって感謝します。

51
SeaJelly

Xcode 7.1

Xcode 7.1はついにシステムアラートの問題を修正しました。ただし、2つの小さな落とし穴があります。

最初に、アラートを表示する前に「UI Interuption Handler」をセットアップする必要があります。これは、表示されたときにアラートを処理する方法をフレームワークに伝える方法です。

第二に、アラートを提示した後、インターフェイスを操作する必要があります。アプリをタップするだけで問題ありませんが、必須です。

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

「ロケーションダイアログ」は、開発者がアクセスされたハンドラを識別するための単なる文字列であり、アラートのタイプに固有ではありません。

ハンドラーからtrueを返すと、それが「完了」とマークされ、再度呼び出されることはありません。あなたの状況では、falseを返して、2番目のアラートがハンドラーを再びトリガーするようにします。

Xcode 7.0

以下は、Xcode 7 Beta 6の単一の「システムアラート」を却下します。

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

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

ベータ6では、UIテストに多数の修正が導入されましたが、これはそのうちの1つだと思います。

また、-element-alertsを直接呼び出していることにも注意してください。 XCUIElementQuery-elementを呼び出すと、フレームワークは画面上で「唯一の」一致する要素を選択します。これは、一度に1つしか表示できないアラートに最適です。ただし、ラベルに対してこれを試行し、2つのラベルがある場合、フレームワークは例外を発生させます。

48
Joe Masilotti

まあ。意図的に「許可」をタップすると言っても、常に「許可しない」をタップします

少なくとも

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

次のテストに進むことができます。

目的-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"];
}

Swift

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

特定のシステムダイアログの特定の説明を探している人(私がしたように)はありません:)文字列はテスター追跡目的のためだけです。関連Apple document link: https://developer.Apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor


更新:xcode 9.2

メソッドは時々トリガーされ、時にはトリガーされません。私にとっての最善の回避策は、システムアラートがあることを知っているときです:

sleep(2)
app.tap()

そしてシステムアラートは消えました

1
ergunkocak

これを確実に修正したことがわかったのは、アラートを処理するために2つの個別のテストを設定することだけでした。最初のテストでは、app.tap()を呼び出し、それ以外は何もしません。 2番目のテストでは、app.tap()を再度呼び出してから、実際の作業を行います。

1
Kevin London

神! XCTestがUIViewアラートを処理するのに最も時間がかかるのは嫌です。私は2つのアラートを受け取るアプリを持っています最初の1つは「許可」を選択してアプリの許可のためのロケーションサービスを有効にし、スプラッシュページでユーザーは「場所をオンにする」というUIButtonを押す必要があり、最後にUIViewAlertでSMSアラートを通知し、ユーザーは「OK」を選択する必要があります。私たちが抱えていた問題は、システムアラートと対話することができなかっただけでなく、画面上の動作とその外観がタイミングが悪い競合状態でもありました。 _alert.element.buttons["whateverText"].tap_を使用する場合、XCTestのロジックは、テストの時間がなくなるまで押し続けます。したがって、基本的には、すべてのシステムアラートが表示されなくなるまで、画面上の何かを押し続けます。

これはハックですが、これが私にとってうまくいったことです。

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

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

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

文字列「Allow」は完全に無視され、app.tap()へのロジックはevreytimeと呼ばれ、アラートが表示され、最後に到達したいボタン["Turn On Location"]にアクセスでき、テストに合格します。

〜完全に混乱しています、Appleに感謝します。

1
Laser Hawk

xcode 9.1では、テストデバイスにiOS 11がある場合にのみアラートが処理されます。 10.3などの古いiOSバージョンでは動作しません。参照: https://forums.developer.Apple.com/thread/86989

アラートを処理するには、これを使用します。

//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

@Joe Masilottiの答えは正解であり、そのおかげで、私を大いに助けてくれました:)

一つだけ指摘したいのは、UIInterruptionMonitorキャッチall連続して表示されるシステムアラート- [〜#〜] together [〜#〜]。これにより、完了ハンドラーで適用するアクションがすべてのアラートに適用されます( "許可しない"または "OK")。アラートアクションを異なる方法で処理する場合は、完了ハンドラー内で、現在表示されているアラートを確認する必要があります。静的テキストをチェックすると、そのアラートにのみアクションが適用されます。

次に、2つのアラートで「許可しない」アクションを3つのアラートのシリーズで適用するための小さなコードスニペットと "OK"残り2つのアクション:

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