Distinguishing first launch on UI Tests - xcode-ui-testing

I want to use Fastlane Snapshot in order to generate screenshots for my app. But the behavior of the app is different when launch for the first time and launching after that.
How do I manage to get a consistent behavior in order to grab the screenshots?
(this question is also relevant for any UI test desired consistency I presume)

You should be using UserDefaults class to preserve data in your app so that you can stub data in your tests.
If we assume that the Bool key you use to see if it's the first launch is isFirstTime, in order to stub it in your UI test, you should pass it to launchArguments following the value YES or NO (for Bool values). Note that I added - sign before key, this is the way it works:
class FirstTimeLaunchTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "YES"]
app.launch()
}
func testWelcomeScreenShown() {
XCTAssert(app.navigationBars["Welcome"].exists)
}
}
For tests where you'd like to have first start skipped, use this class:
class LaterLaunchesTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "NO"]
app.launch()
}
func testMainAppScreenShown() {
XCTAssert(app.navigationBars["My App"].exists)
}
}
One note though: If you are using SwiftyUserDefaults library, this solution wouldn't work. There is a problem in the current version of the library where converting YES and NO strings to true and false is not working as expected. There was a PR that solved this problem (that is rejected), but to solve this problem, you can look at the solutions #2 and #3 from this answer.

Related

Use reference to captured variable in concurrently-executing code

Update 2: I suspect the question gets upvoted because of the possible solution that I describe. Highlighted it for clarity.
Update 1: This question gets a lot of views. If you think the question can be enhanced with the situation in which you encountered the error yourself, please briefly describe your situation in the comments so we can make this Q&A more valuable. And if you have a solution to your version of the problem, please add it as an answer.
I want to update the UI after doing async background work using Task.detached and an async function.
However, I get a build error Reference to captured var 'a' in concurrently-executing code error during build.
I tried some things and turning the variable into a let constant before updating the UI is the only thing that works. Why do I need to make a let constant before being able to update the UI? Are there alternatives?
class ViewModel: ObservableObject {
#Published var something: String?
init() {
Task.detached(priority: .userInitiated) {
await self.doVariousStuff()
}
}
private func doVariousStuff() async {
var a = "a"
let b = await doSomeAsyncStuff()
a.append(b)
something = a /* Not working,
Gives
- runtime warning `Publishing changes from
background threads is not allowed; make sure to
publish values from the main thread (via operators
like receive(on:)) on model updates.`
or, if `something` is #MainActor:
- buildtime error `Property 'something' isolated
to global actor 'MainActor' can not be mutated
from this context`
*/
await MainActor.run {
something = a
} /* Not working,
Gives buildtime error "Reference to captured var 'a'
in concurrently-executing code" error during build
*/
DispatchQueue.main.async {
self.something = a
} /* Not working,
Gives buildtime error "Reference to captured var 'a'
in concurrently-executing code" error during build
*/
/*
This however, works!
*/
let c = a
await MainActor.run {
something = c
}
}
private func doSomeAsyncStuff() async -> String {
return "b"
}
}
Make your observable object as main actor, like
#MainActor // << here !!
class ViewModel: ObservableObject {
#Published var something: String?
init() {
Task.detached(priority: .userInitiated) {
await self.doVariousStuff()
}
}
private func doVariousStuff() async {
var a = "a"
let b = await doSomeAsyncStuff()
a.append(b)
something = a // << now this works !!
}
private func doSomeAsyncStuff() async -> String {
return "b"
}
}
Tested with Xcode 13 / iOS 15
In short, something has to be modified from the main thread and only Sendable types can be passed from one actor to another. Let's dig in the details.
something has to be modified from the main thread. This is because #Published properties in an ObservableObject have to be modified from the main thread. The documentation for this is lacking (if anyone finds a link to the official documentation I'll update this answer). But as the subscriber of an ObservableObject is probably a SwiftUI View, it makes sense. Apple could have decided that a View subscribes and receives events on the main thread, but this would hide the fact that it is dangerous to send UI update events from multiple threads.
Only Sendable types can be passed from one actor to another. There are two ways to solve this. First we can make a Sendable. Second we can make sure not to pass a across actor boundaries and have all code run on the same actor (in this case it has to be the Main Actor as it is guaranteed to run on the main thread).
Let's see how to make a sendable and study the case of:
await MainActor.run {
something = a
}
The code in doVariousStuff() function can run from any actor; let's call it Actor A. a belongs to Actor A and it has to be sent to the Main Actor. As a does not conform to Sendable, the compiler does not see any guarantee that a will not be changed while a is read on the Main Actor. This is not allowed in the Swift concurrency model. To give the compiler that guarantee, a has to be Sendable. One way to do that is to make it constant. Which is why this works:
let c = a
await MainActor.run {
something = c
}
Even if it could be improved to:
await MainActor.run { [a] in
something = a
}
Which captures a as a constant. There are other Sendable types, details can be found here https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID649.
The other way to solve this is to make all code run on the same actor. The easiest way to do that is to mark ViewModel with #MainActor as suggested by Asperi. This will guarantee that doVariousStuff() runs from the Main Actor, so it can set something. As a side note, a then belongs to the Main Actor so (even if it is pointless) await MainActor.run { something = a } would work.
Note that actors are not threads. Actor A can run from any thread. It can start on one thread and then continue on another after any await. It could even run partially on the main thread. What is important is that one actor can only ever run from one thread at a time. The only exception to the rule that any actor can run from any thread is for the Main Actor which only runs on the main thread.
You can use #State and .task as follows:
struct ContentView: View {
#State var result = ""
var body: some View {
HStack {
Text(result)
}
.task {
result = await Something.doSomeAsyncStuff()
}
}
}
The task is started when view appears and is cancelled when it disappears. Also if you use .task(id:) it will restart (also cancelling previous task) when the value of id changes.
The async func can go in a few different places, usually somewhere so it can tested independently.
struct Something {
static func doSomeAsyncStuff() async -> String {
return "b"
}
}

Xcode XCTestCase setting values for UserDefaults

I am trying to run a UITest in a XCTestCase class, and also set a certain value in UserDefaults to true. (There is nothing in my func setUP() other than the super call so I did not post that part.) But when I run the test and set the breakpoints in the given ViewController, the value for the default key is not set to the desired value.
The code looks something like this:
func testSomething() {
let app = XCUIApplication()
app.launch()
UserDefaults.standard.set(true, forKey: "someKey")
}

Xcode 10 and super.tearDown

Since Xcode 10.1(maybe 10) when I create a Unit test file I don't have calls super.tearDown() and super.setUp() .
I've not seen such changes in release notes.
In documentation https://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods are still here.
So my question should I still write super.tearDown() and super.setUp()?
class SomethingTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
For a direct subclass of XCTestCase, there never was any change of behavior for not calling super.setUp(). That's because setUp and tearDown are template methods with empty implementations at the top level.
Though there's no change in behavior, omitting the calls to super means that if you create a test hierarchy with more than one level, you'll have to add them back.
When would you ever have more than one level? There are two cases:
When you want to reuse the same tests for different scenarios.
When you subclass XCTestCase to make a customized helper.
These don't happen every day. But they do happen. Deciding "I need it here, but I don't need it there" is perilous. So I'd just call super all the time.

Swift 3 dispatch_async or dispatch_sync (dispatch_get_main_queue()) dont work. [duplicate]

I have lots of code in Swift 2.x (or even 1.x) projects that looks like this:
// Move to a background thread to do some long running work
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let image = self.loadOrGenerateAnImage()
// Bounce back to the main thread to update the UI
dispatch_async(dispatch_get_main_queue()) {
self.imageView.image = image
}
}
Or stuff like this to delay execution:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
print("test")
}
Or any of all kinds of other uses of the Grand Central Dispatch API...
Now that I've opened my project in Xcode 8 (beta) for Swift 3, I get all kinds of errors. Some of them offer to fix my code, but not all of the fixes produce working code. What do I do about this?
Since the beginning, Swift has provided some facilities for making ObjC and C more Swifty, adding more with each version. Now, in Swift 3, the new "import as member" feature lets frameworks with certain styles of C API -- where you have a data type that works sort of like a class, and a bunch of global functions to work with it -- act more like Swift-native APIs. The data types import as Swift classes, their related global functions import as methods and properties on those classes, and some related things like sets of constants can become subtypes where appropriate.
In Xcode 8 / Swift 3 beta, Apple has applied this feature (along with a few others) to make the Dispatch framework much more Swifty. (And Core Graphics, too.) If you've been following the Swift open-source efforts, this isn't news, but now is the first time it's part of Xcode.
Your first step on moving any project to Swift 3 should be to open it in Xcode 8 and choose Edit > Convert > To Current Swift Syntax... in the menu. This will apply (with your review and approval) all of the changes at once needed for all the renamed APIs and other changes. (Often, a line of code is affected by more than one of these changes at once, so responding to error fix-its individually might not handle everything right.)
The result is that the common pattern for bouncing work to the background and back now looks like this:
// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
let image = self.loadOrGenerateAnImage()
// Bounce back to the main thread to update the UI
DispatchQueue.main.async {
self.imageView.image = image
}
}
Note we're using .userInitiated instead of one of the old DISPATCH_QUEUE_PRIORITY constants. Quality of Service (QoS) specifiers were introduced in OS X 10.10 / iOS 8.0, providing a clearer way for the system to prioritize work and deprecating the old priority specifiers. See Apple's docs on background work and energy efficiency for details.
By the way, if you're keeping your own queues to organize work, the way to get one now looks like this (notice that DispatchQueueAttributes is an OptionSet, so you use collection-style literals to combine options):
class Foo {
let queue = DispatchQueue(label: "com.example.my-serial-queue",
attributes: [.serial, .qosUtility])
func doStuff() {
queue.async {
print("Hello World")
}
}
}
Using dispatch_after to do work later? That's a method on queues, too, and it takes a DispatchTime, which has operators for various numeric types so you can just add whole or fractional seconds:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // in half a second...
print("Are we there yet?")
}
You can find your way around the new Dispatch API by opening its interface in Xcode 8 -- use Open Quickly to find the Dispatch module, or put a symbol (like DispatchQueue) in your Swift project/playground and command-click it, then brouse around the module from there. (You can find the Swift Dispatch API in Apple's spiffy new API Reference website and in-Xcode doc viewer, but it looks like the doc content from the C version hasn't moved into it just yet.)
See the Migration Guide for more tips.
In Xcode 8 beta 4 does not work...
Use:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
print("Are we there yet?")
}
for async two ways:
DispatchQueue.main.async {
print("Async1")
}
DispatchQueue.main.async( execute: {
print("Async2")
})
This one is good example for Swift 4 about async:
DispatchQueue.global(qos: .background).async {
// Background Thread
DispatchQueue.main.async {
// Run UI Updates or call completion block
}
}
in Xcode 8 use:
DispatchQueue.global(qos: .userInitiated).async { }
Swift 5.2, 4 and later
Main and Background Queues
let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread")
Working with async and sync threads!
background.async { //async tasks here }
background.sync { //sync tasks here }
Async threads will work along with the main thread.
Sync threads will block the main thread while executing.
Swift 4.1 and 5. We use queues in many places in our code. So, I created Threads class with all queues. If you don't want to use Threads class you can copy the desired queue code from class methods.
class Threads {
static let concurrentQueue = DispatchQueue(label: "AppNameConcurrentQueue", attributes: .concurrent)
static let serialQueue = DispatchQueue(label: "AppNameSerialQueue")
// Main Queue
class func performTaskInMainQueue(task: #escaping ()->()) {
DispatchQueue.main.async {
task()
}
}
// Background Queue
class func performTaskInBackground(task:#escaping () throws -> ()) {
DispatchQueue.global(qos: .background).async {
do {
try task()
} catch let error as NSError {
print("error in background thread:\(error.localizedDescription)")
}
}
}
// Concurrent Queue
class func perfromTaskInConcurrentQueue(task:#escaping () throws -> ()) {
concurrentQueue.async {
do {
try task()
} catch let error as NSError {
print("error in Concurrent Queue:\(error.localizedDescription)")
}
}
}
// Serial Queue
class func perfromTaskInSerialQueue(task:#escaping () throws -> ()) {
serialQueue.async {
do {
try task()
} catch let error as NSError {
print("error in Serial Queue:\(error.localizedDescription)")
}
}
}
// Perform task afterDelay
class func performTaskAfterDealy(_ timeInteval: TimeInterval, _ task:#escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: (.now() + timeInteval)) {
task()
}
}
}
Example showing the use of main queue.
override func viewDidLoad() {
super.viewDidLoad()
Threads.performTaskInMainQueue {
//Update UI
}
}

Call to swift method from JavaScript hangs xcode and application

I am writing an iOS App (using xcode 7.3 and swift 2.2) using JavascriptCode framework. Calling javascript methods from swift works perfect, but when I call the swift method from javascript, xcode simply shows a "loading" type of symbol and nothing happens. I need to "force quit" xcode to get out of this state.
I have followed https://www.raywenderlich.com/124075/javascriptcore-tutorial and http://nshipster.com/javascriptcore/ and I am trying pretty simple calls.
Has anyone faced this kind of issue?
My swift code is as follows:
#objc protocol WindowJSExports : JSExport {
var name: String { get set }
func getName() -> String
static func createWindowWithName(name: String) -> WindowJS
}
#objc class WindowJS : NSObject, WindowJSExports {
dynamic var name: String
init(name: String) {
self.name = name
}
class func createWindowWithName(name: String) -> WindowJS {
return WindowJS(name: name)
}
func getName() -> String {
NSLog("getName called from JS context")
return "\(name)"
}
}
I am initializing the context as follows:
runContext = JSContext()
runContext.name = "test_Context"
windowToJs = WindowJS(name: "test")
runContext.setObject(windowToJs.self, forKeyedSubscript: "WindowJS")
If I replace the last two lines in above code with below code without instantiating it, the code simply fails to load.
runContext.setObject(WindowJS.self, forKeyedSubscript: "WindowJS")
And the javascript code is as simple as
function check() {
return WindowJS.getName()
}
I do see the breakpoint being hit in the JS function check and when the WindowJS.getName gets called, xcode simply becomes unresponsive.
The setTimeout could be solved by adding following piece of code to my swift function.
let setTimeout: #convention(block) (JSValue, Int) -> () =
{ callback, timeout in
let timeVal = Int64(timeout)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeVal), dispatch_get_main_queue(), { callback.callWithArguments(nil)})
}
To expose this native code to the JS context, I also added following.
runContext.setObject(unsafeBitCast(setTimeout, AnyObject.self), forKeyedSubscript: "setTimeout")
Things then worked fine.
You're creating a deadlock since you are calling from Swift to JavaScript back to Swift. I'm not sure exactly why it is a deadlock but I had a similar issue with WKWebView on Mac recently.
You need to decouple this and make the communication asynchronous. This obviously means you cannot simply return a value from your JS function in this case.
To decouple, you can break the deadlock by deferring the work the JavaScript function needs to do out of the current runloop iteration using setTimeout:
function myFunction() {
setTimeout(function() {
// The actual work is done here.
// Call the Swift part here.
}, 0);
}
The whole native ↔︎ JavaScript communication is very, very tricky. Avoid it if you can. There's a project called XWebView that may be able to help you as it tries to ease bridging between the two worlds.

Resources