XCUITest an iOS app phone call
Background
Recently (2017-10) I talked with a an iOS developer.
In his company's app, the user is able to start a phone call.
Currently they test some of this function manually.
I decided to create an automated test.
Programmatically starting a call was easy, ending the call took more work!
XCUITest recorder
In Xcode I started the UI test recorder and manually operated the iPhone.
I started my app, made a phone call and ended it.
Unfortunately the recorded test didn't play to completion.
I edited the test script to reference "mobilephone.app", that didn't work.
I tried several alternative approaches before I found a simple solution via springboard app.
XCUITest with Springboard to tap End call button
Recently Apple added ability to XCUITest multiple apps.
Test Flow
- The XCUITest starts the Phoney app on the iOS device
- The test taps the app "Call Now" button
- iOS shows an alert
- Use springboard app to tap "Call"
- iOS starts the phone call and navigates to the view with the red end call button
- iOS calls callObserver callChanged with call.hasConnected, fulfills a test expectation
- Use springboard app to tap "End call"
- iOS ends the call
- iOS calls callObserver callChanged with call.hasEnded, fulfills a test expectation
XCUITest Code Snippet
let app = XCUIApplication()
let springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")
app.launch()
let appCallButton = app.buttons["Call Now"]
if appCallButton.waitForExistence(timeout: 10) {
appCallButton.tap()
springboardApp.buttons["Call"].tap()
let delayBeforeEndCallSeconds: UInt32 = 12
sleep(delayBeforeEndCallSeconds)
springboardApp.buttons["End call"].tap()
Alternative approaches to end call
CXCallObserver
A test can use CXCallObserver delegate method callObserver(_ callObserver: callChanged:)
Then the test can verify that a call hasConnected or hasEnded.
But it's only an observer, doesn't have a method to end a call.
XCUITest Code Snippet
Set test expectations and delegate.
// make expectations properties so that delegate methods can reference them
weak var expectCallHasConnected: XCTestExpectation?
weak var expectCallHasEnded: XCTestExpectation?
func testCallTapped() {
expectCallHasConnected = self.expectation(description: "expect call hasConnected")
expectCallHasEnded = self.expectation(description: "expect call hasEnded")
let callObserver = CXCallObserver()
callObserver.setDelegate(self, queue: nil)
Then the test taps buttons to start and end call.
In callback, fulfill expectations.
extension PhoneyUITests: CXCallObserverDelegate {
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
// This callback comes from iOS.
// It accurately represents that a phone call connected or ended.
if call.isOutgoing && call.hasConnected && !call.hasEnded {
print("outgoing call hasConnected")
if expectCallHasConnected != nil {
expectCallHasConnected?.fulfill()
// set nil to avoid error from calling multiple times
// https://jeremywsherman.com/blog/2016/03/19/xctestexpectation-gotchas/#kaboom-calling-twice
expectCallHasConnected = nil
}
}
if call.isOutgoing && call.hasEnded {
print("outgoing call hasEnded")
if self.expectCallHasEnded != nil {
expectCallHasEnded?.fulfill()
// set nil to avoid error from calling multiple times
expectCallHasEnded = nil
}
}
}
}
CXProvider
I briefly tried using CXProvider in the test to end a call.
It didn't work, maybe because the call was started by the Phoney.app, not the test.
Accessibility Switch Control
This approach is very general, could be applied to many actions.
I may prototype it later.
For more info see Using a Raspberry Pi for iPhone Switch Control
Raspberry Pi and headphone breakout switch
A user can end a phone call by clicking the headphone switch.
A program can end a phone call by closing a headphone breakout switch.
I made a prototype.
For more info see Using a Raspberry Pi to end an iPhone phone call
Potential improvements
Add text-to-speech or mp3 playback to test phone customer support options
I experimented calling AVSpeechSynthesizer within XCUITest. It works on simulator but not on device.
But simulator can't make a phone call!
The problem may be due to test running in background while a phone call is running in foreground
Could use a Raspberry Pi web service to speak to the phone.Add speech recognition via Siri or another service
References
Phoney
Phoney is an iOS application to experiment with testing phone calls.
https://github.com/beepscore/Phoney
master branch
Uses XCUITest with Springboard to tap End call button.
request_web_service_end_call branch
Uses XCUITest to make an http request to an external web server, asks it to end call by closing switch.
XCUITest phone call with help from springboard
https://stackoverflow.com/questions/46322758/call-button-on-ui-testing
Build a universal translator (Raspberry Pi)
https://www.daveconroy.com/turn-raspberry-pi-translator-speech-recognition-playback-60-languages/
Speech Synthesis on the Raspberry Pi
https://learn.adafruit.com/speech-synthesis-on-the-raspberry-pi/introduction