We are building an iOS app that connects to a device using Bluetooth. To test unhappy flow scenarios for this app, we'd like to power cycle the device we are connecting to by using an IoT power switch that connects to the local network using WiFi (a Shelly Plug-S).
In my test code on iOS13, I was able to do a local HTTP call to the IP address of the power switch and trigger a power cycle using its REST interface. In iOS 14 this is no longer possible, probably due to new restrictions regarding local network usage without permissions (see:
https://developer.apple.com/videos/play/wwdc2020/10110
).
When running the test and trying a local network call to the power switch in iOS14, I get the following error:
Code Block Task <D206B326-1820-43CA-A54C-5B470B4F1A79>.<2> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x2833f34b0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <D206B326-1820-43CA-A54C-5B470B4F1A79>.<2>, _NSURLErrorRelatedURLSessionTaskErrorKey=("LocalDataTask <D206B326-1820-43CA-A54C-5B470B4F1A79>.<2>"), NSLocalizedDescription=The internet connection appears to be offline., NSErrorFailingURLStringKey=http://192.168.22.57/relay/0?turn=on, NSErrorFailingURLKey=http://192.168.22.57/relay/0?turn=on, _kCFStreamErrorDomainKey=1} |
An external network call (to google.com) works just fine in the test.
I have tried fixing this by adding the following entries to the Info.plist of my UI test target:
Code Block <key>NSLocalNetworkUsageDescription</key> |
<string>Local network access is needed for tests</string> |
<key>NSBonjourServices</key> |
<array> |
<string>_http._tcp</string> |
</array> |
<key>NSAppTransportSecurity</key> |
<dict> |
<key>NSAllowsArbitraryLoads</key> |
<true/> |
</dict> |
However, this has no effect.
I have also tried adding these entries to the Info.plist of my app target to see if that makes a difference, but it doesn't. I'd also rather not add these entries to my app's Info.plist, because the app does not need local network access. Only the test does.
Does anyone know how to enable local network access during an iOS UI test in iOS14?
Up-thread, ncreated posted a bug number,
FB8983382
. I just checked on the status of that bug and it was reported as fixed years ago. However, I suspect it was fixed in general but not fixed for the specific case of local network privacy. So…
If you’re having problems with local network privacy in your test suite with modern versions of Xcode (so, Xcode 15 or later, iOS 17 or later), please do
file a bug
about that. Make sure you reference
FB8983382
and explain that whatever fix was applied there isn’t solving your problem.
Please post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
I have also tried adding these entries to the Info.plist of my app target to see if that makes a difference, but it doesn't.
Right. You cannot override local network privacy with
NSAllowsArbitraryLoads
being true.
Does anyone know how to enable local network access during an iOS UI test in iOS14?
I just setup a XCTest for UI Testing in a main app that triggers local network privacy and it does prompt the user but there is no way to automatically allow or do not allow this prompt. In this case you will need to manually allow or do not allow this prompt. If you are looking for a way to manually interact with this prompt for use in an XCTest
only
I would submit an
enhancement request.
Please respond back with the Feedback ID.
I should also note that just like the normal usage of the local network privacy prompt, that once you allow or do not allow the first time you local network is accessed then this value will be cached. So you could seed you tests with this value if this is an option for your workflow.
Matt Eaton
DTS Engineering, CoreOS
[email protected]
Hi Matt,
Thank you for your response.
I have tried only setting the NSLocalNetworkUsageDescription key in my test target's Info.plist (no more entries for NSBonjourServices or NSAllowsArbitraryLoads). My test code now simply executes a URLSession.dataTask to a local endpoint via http. However, I do not get the local network usage prompt when running the test, and the network call fails with the same error as before. I removed the test app before running to make sure the value was not cached.
Can you tell me what code you wrote in your test that triggered the prompt? And just to be sure: the prompt will be triggered by the test app, not the actual app under test, right? Also, the Info.plist of the app under test can remain untouched?
Kind regards,
Ejnar
Ejnar,
I didn't set the
NSLocalNetworkUsageDescription
in the testing target. However, it looks like we may have run different tests.
Regarding:
Can you tell me what code you wrote in your test that triggered the prompt? And just to
be sure: the prompt will be triggered by the test app, not the actual app under test, right?
My original test was to find a button in the app being tested, tap that button, and that tap would send the dataTask request via URLSession over the local network. This would trigger the prompt and this was the test I ran yesterday.
This morning I refactored that XCTest and tried to run the dataTask inside the XCTest case and did not receive the local network privacy prompt. Open a bug report on this one just to make sure that not receiving the local network privacy prompt from a XCTest case is the intended behavior. Please please responde with the Feedback ID.
Matt Eaton
DTS Engineering, CoreOS
[email protected]
Hello Matt,
I step across the same issue. I want to make an HTTP call to the server running in my local network. I added the
NSLocalNetworkUsageDescription
key both to my app target's
Info.plist
and my UITest target's
Info.plist
.
When running on physical device connected to the same network as the server:
-
Sending the
URLSession.dataTask()
from the app brings the local network privacy prompt. If approved, the request is send and the approval is cached so it doesn't appear next time. All works fine - as expected.
-
Sending the
URLSession.dataTask()
from the UITest runner's
XCTestCase
does not bring the local network privacy prompt.
The request never reaches the local server and
URLSession.dataTask()
results with the
Error Domain=NSURLErrorDomain Code=-1009 "The internet connection appears to be offline."
as reported by Ejnar.
I opened a ticket in Feedback Assistant:
FB8983382
(Local Network Privacy Prompt is not received from an XCTestCase)
Would love to hear Apple Engineer's reply on this.
Is there any workaround for sending local requests from UITest runner?
I've also encountered this issue, here are my findings:
When firing a local network request from the application:
When firing a local network request from a Unit Test target:
When firing a local network request from a UI Test target:
-
Permission prompt is
not
displayed;
-
Even after allowing local network access (manually from Settings), requests fail;
-
Works on simulator;
-
Fails
on real device;
I've tested now with Xcode 12.5 beta 3 and it seems that the issue has been fixed! 🥳
Great news. I did also try invoking a datagram message over the local network from the context of an
XCTestCase
today in Xcode 13 Beta on iOS 14.5 and I did receive the prompt here as well and did see the message received on the listening side.
Matt Eaton
DTS Engineering, CoreOS
[email protected]
Today I did some testing on iOS 14.8 and iOS 15.2 trying to enable the Local Network Privacy Prompt in my UI XCTestCase and here is what I found:
I setup a test case in the main app like so:
class ViewController: UIViewController {
@IBOutlet weak var testButton: UIButton!
var networkSessionManager: NetworkSessionManager?
override func viewDidLoad() {
super.viewDidLoad()
networkSessionManager = NetworkSessionManager()
networkSessionManager?.delegate = self
testButton.accessibilityIdentifier = "myButton"
@IBAction func testRequest() {
instruction.text = "Sending Network Request"
// Hits redirect
networkSessionManager?.dataTask(with: "http://192.168.***.***:8000")
protocol NetworkManagerDelegate: AnyObject {
func networkSessionManager(networkSession: NetworkSessionManager, response: String, error: Error?)
class NetworkSessionManager: NSObject {
private var urlSession: URLSession?
private var config: URLSessionConfiguration = URLSessionConfiguration.default
weak var delegate: NetworkManagerDelegate?
override init() {
super.init()
config.waitsForConnectivity = true
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: .main)
func dataTask(with url: String) {
guard let url = URL(string: url),
let unwrappedURLSession = urlSession else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
let task = unwrappedURLSession.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
task.resume()
extension NetworkSessionManager: URLSessionDelegate {
func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
Then in the XCTestCase I set it up like so:
import XCTest
@testable import URLSessionExample
class URLSessionExampleUITests: XCTestCase {
var networkSessionManager: NetworkSessionManager?
let expectation = XCTestExpectation(description: "Download from local network home page")
override func setUpWithError() throws {
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
networkSessionManager = NetworkSessionManager()
networkSessionManager?.delegate = self
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
let button = app.buttons["myButton"]
button.tap()
wait(for: [expectation], timeout: 10.0)
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
extension URLSessionExampleUITests: NetworkManagerDelegate {
func networkSessionManager(networkSession: NetworkSessionManager, response: String, error: Error?) {
XCTAssertNotNil(response, "No data was downloaded.")
// Fulfill the expectation to indicate that the background task has finished successfully.
expectation.fulfill()
Now, on Xcode 12.5 with iOS 14.8 I was able to run testExample()
and have the Local Network Privacy Prompt show up. On Xcode 13.1 with iOS 15.2 I did not see the Local Network Privacy Prompt. I am still doing some digging to see what is the correct path here, but I've had a few folks ask about the test I ran from this example, so I wanted to post it here.
Matt Eaton
DTS Engineering, CoreOS
[email protected]
The solution I've found in the Xcode 15.3 working with iOS 17.4.1 made me SICK! I couldn't find any doc about it, but I'm sure it works. You have to bring your xctrunner app to front, this way OS asks for local network permission in UI test target.
Try this in your UI test target without need to specify NSLocalNetworkUsageDescription and bonjour services, etc.
func testXCTRunnerToAskForLocalNetworkPermission() {
// without bringing xctrunner to front, permission alert won't be shown.
let app = XCUIApplication(bundleIdentifier: "{YOUR_APP_BUNDLE_ID}UITests.xctrunner")
app.activate()
connectToLocalNetwork()
waitFor(aSecond: 1)
let springBoard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
// Allows local network permission.
let button = springBoard.alerts.firstMatch.buttons["Allow"]
if button.exists {
button.tap()
// local network is allowed in current running ui test.
connectToLocalNetwork() // try to connect, as it's allowed now.
// Keep XCUITest running forever, though sometimes it stops automatically
wait(for: [expectation(description: "keep running")], timeout: .infinity)
waiter helper function:
func waitFor(aSecond seconds: TimeInterval) {
let expectation = expectation(description: "waiting for \(seconds)")
let timer = Timer(timeInterval: seconds, repeats: false) { _ in
expectation.fulfill()
wait(for: [expectation], timeout: seconds)
local network function:
func connectToLocalNetwork() {
URLSession.shared.dataTask(with: URLRequest(url: URL(string: "http://192.168.2.101:5055")!)) { data, response, error in
if let data {
print("Data: \(String(data: data, encoding: .utf8) ?? "")")
}.resume()
Up-thread, ncreated posted a bug number, FB8983382
. I just checked on the status of that bug and it was reported as fixed years ago. However, I suspect it was fixed in general but not fixed for the specific case of local network privacy. So…
If you’re having problems with local network privacy in your test suite with modern versions of Xcode (so, Xcode 15 or later, iOS 17 or later), please do file a bug about that. Make sure you reference FB8983382
and explain that whatever fix was applied there isn’t solving your problem.
Please post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
This site contains user submitted content, comments and opinions and is for informational purposes only. Apple disclaims any and all liability for the acts, omissions and conduct of any third parties in connection with or related to your use of the site. All postings and use of the content on this site are subject to the
Apple Developer Forums Participation Agreement and Apple provided code is subject to the
Apple Sample Code License.
Forums
Apple Developer Program
Apple Developer Enterprise Program
App Store Small Business Program
MFi Program
News Partner Program
Video Partner Program
Security Bounty Program
Security Research Device Program