I'm very new to swift and xcuitest. I recently came across addUIInterruptionMonitor for handling alerts that can pop up. What I would like to know is how I can verify that the alert happened and that the handler was dealt with. Take the following for example
addUIInterruptionMonitorWithDescription("Location Services") {
(alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Request Location"].tap()
app.tap() // need to interact with the app again for the handler to fire
// after this if the handler never gets called I want the test to fail
I would like to test that the alert actually happens, but as far as I understand, after my last tap() if the alert never gets triggered my handler wont be called. I need to test that the alert actually happened and then maybe add some assertions to the contents of the handler
I seem to have answered my own question on further investigation. For asynchronous testing with xcuitest I can use a XCTestExpectation which will when created, cause the test to wait until the expectation is fulfilled, or fail after a certain timeout. With that my above code becomes:
let expectation = XCTestExpectation(description: "Location Service Alert")
let locationMonitorToken = addUIInterruptionMonitorWithDescription("Location Services") {
(alert) -> Bool in
alert.buttons["Allow"].tap()
expectation.fulfill() // test waits for this before passing
return true
}
app.buttons["Request Location"].tap()
app.tap() // alert triggered here
wait(for: [expectation], timeout: 3.0)
removeUIInterruptionMonitor(locationMonitorToken)
Update: forgot to put in the wait(for: [expectation], timeout: 3.0) after alert triggered to ensure handler is called.
Related
I"m using a method that checks if the user's email is verified every 5 seconds, the checkIsEmailVerified() is asynchronous since it needs to send a request to check if the user has verified his email.
void checkEveryDurationUserVerification() {
Timer.periodic(
Duration(seconds: 5),
(timer) async {
if (isUserVerified) {
timer.cancel();
}
print("another check");
await checkIsEmailVerified();
},
);
}
so here is what happens, when this method is executing, it shows "another check" every 5 seconds as I want, and so the checkIsEmailVerified().
so what I have ? is a fully working email verification checker every 5 seconds, and the email is not verified yet.
when I open my email inbox and verify my email, In the app it works the listener I gave to the isUserVerified is working because it's true, and it navigates me to the home page as I want, but the Timer is not canceled even the isUserVerified is true
The "another check" is still printing in the debug console, even if the isUserVerified is true and so it should cancel that Timer**
what I think:
that this behavior is related to making the Timer's callback asynchronous
what I expect:
I want the when the isUserVerified is true the Timer to be canceled, so it stopped working after verifying the user
Some of my users experience crash when they finish recording video. I setup everything correctly and this is happening only for %0.2 of the users. The crash happens when finishWriting is called.
I finally decide to check AVAssetWriter's status before I call below code;
videoInput?.markAsFinished()
videoWriter?.finishWriting
now it is;
if videoWriter?.status == .writing {
videoInput?.markAsFinished()
videoWriter?.finishWriting
// some ui configurations
}
But what about the other cases .cancelled & .failed ? Is it necessary to call the following code again? What should I do if videoWriter?.status == .cancelled or videoWriter?.status == .failed?
videoInput?.markAsFinished()
videoWriter?.finishWriting
As the title, is it necessary to call disposed(by:) in any case? If yes, why?
Consider a simple example like this:
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
button.rx.tap.bind(onNext: { _ in
print("Button tapped!")
})
// Does this make any retain cycle here?
}
}
No, it is not necessary to call .disposed(by:) in every case.
If you have an Observable that you know will eventually send a stop event, and you know that you want to keep listening to that observable until it does so, then there is no reason/need to dispose the subscription and therefore no need to insert the disposable into a dispose bag.
The reason .subscribe and its ilk return a Disposable is so that the calling code can end the subscription before the observable has completed. The calling code ends the subscription by calling dispose() on the disposable returned. Otherwise, the subscription will continue until the source observable sends a stop event (either completed or error.)
If the calling code doesn't dispose the subscription, and the source observable doesn't send a stop event, then the subscription will continue to operate even if all other code involved has lost all references to the objects involved in the subscription.
So for example, if you put this in a viewDidLoad:
_ = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.subscribe(onNext: { print($0) })
The code above will continue to print values long after the view controller that created it ceases to exist.
In the example you presented, the UIButton object will emit a completed event when it is deinitialized, so if you want to listen to the button right up until that happens, putting the disposable in a dispose bag isn't necessary.
Ignoring disposables means you need to be very cognizant as to which Observables complete and which ones don't, but if you have that understanding, you can ignore them. Just remember that the next developer down the line, or future you, won't have as good an understanding of the code as you do.
I currently have this:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
...
}
and that works when I don't quit the app, it simply is in the background, but when I quit it, the code inside there that is suppoused to check for a time, doesn't work.
I also tried putting it in
.onAppear() {
but that doesn't workout either :/
How can I once the app has been closed, then opened, as soon as it opens run an action?
you could try adding the following code to perform an action when your app starts:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
// do your action here
....
}
I want to test my macOS application. It uses your Macbook's camera, and want to handle this in my UITest. However I cannot get it working. Here is my NOT working code. This code triggers to notification, and I'm presented an alert to allow access to my camera, but the closure is not getting called. Thanks fo any help.
There are many solutions for iOS, but I need it on macOS.
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
XCTAssertTrue(startButton.waitForExistence(timeout: 1.0))
startButton.click()
XCTAssertTrue(recordButton.waitForExistence(timeout: 20.0))
recordButton.click()
wait(for: 8)
recordButton.click()
removeUIInterruptionMonitor(alertHandler)
}
I managed to make interruption monitor work on macOS by adding an extra interaction after the interaction that triggers the system dialog (be it camera access or else). So in your example I would add an action after startButton.click() (if that is what triggers the camera access dialog).
Example:
func testCamera() {
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
useCameraButton.click()
// try to interact with the app by clicking on the app's window
app.windows.first().click()
// at this point the handler should intercept the system interruption
// and blocks further execution until handler does return
// try to use the camera again
useCameraButton.click()
removeUIInterruptionMonitor(alertHandler)
}
Hint about this behaviour in Apple's documentation:
When an alert or other modal UI is an expected part of the
test workflow, don't write a UI interruption monitor. The test won’t
use the monitor because the modal UI isn’t blocking the test. A UI
test only tries its UI interruption monitors if the elements it needs
to interact with to complete the test are blocked by an interruption
from an unrelated UI.
https://developer.apple.com/documentation/xctest/xctestcase/handling_ui_interruptions