When running an XCT UI test it is possible to put the application under test in the background with:
XCUIDevice().pressButton(XCUIDeviceButton.Home)
It it possible in some way to bring the app back to foreground (active state) without relaunching the application?
Update for Xcode 9: Starting in Xcode 9, you can now simply call activate() on any XCUIApplication.
let myApp = XCUIApplication()
myApp.activate() // bring to foreground
https://developer.apple.com/documentation/xctest/xcuiapplication/2873317-activate
Yes, it is. But, you'll need XCUIElement's private headers (which are available via header dump from Facebook here). In order to foreground the app, you need to call resolve which I believe resolves the element's query (which for applications means foregrounding the app).
For Swift, you'll have to import the XCUIElement.h into your bridging header. For Objective-C you'll just need to import XCUIElement.h.
With the app backgrounded:
Swift:
XCUIApplication().resolve()
Objective-C
[[XCUIApplication new] resolve];
If this is the only functionality you need, you could just write a quick ObjC category.
#interface XCUIElement (Tests)
- (void) resolve;
#end
If you need to launch / resolve another app. Facebook has an example of that here by going through the Springboard.
As of Xcode 8.3 and iOS 10.3, you can accomplish this with Siri:
XCUIDevice.shared().press(XCUIDeviceButton.home)
XCUIDevice.shared().siriService.activate(voiceRecognitionText: "Open {appName}")
Include #available(iOS 10.3, *) at the top of your test suite file and you should be good to go!
This is what I have in my XCUITest and it works like a charm (xcode 10.1 and test device is iPhone X 11.0)
func testWhatever() {
// You test steps go here until you need the background foreground to run
XCUIDevice.shared.press(XCUIDevice.Button.home) // To background the app
XCUIApplication().activate() // To bring the app back
// You test continues after background foreground has been done.
}
If somebody needs just move app back from background i have written (based on answer above) category that really works(great thanks to pointing to FB git)
#implementation XCUIApplication(SpringBoard)
+ (instancetype)springBoard
{
XCUIApplication * springboard = [[XCUIApplication alloc] performSelector:#selector(initPrivateWithPath:bundleID:)
withObject:nil
withObject:#"com.apple.springboard"];
[springboard performSelector:#selector(resolve) ];
return springboard;
}
- (void)tapApplicationWithIdentifier:(NSString *)identifier
{
XCUIElement *appElement = [[self descendantsMatchingType:XCUIElementTypeAny]
elementMatchingPredicate:[NSPredicate predicateWithFormat:#"identifier = %#", identifier]
];
[appElement tap];
}
#end
For Swift, you need to declare the XCUIApplication private methods interface in Bridging-Header.h like this:
#interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
#end
Then call resolve() in your test cases to bring the app back:
XCUIApplication().resolve()
Since Xcode 13 we got several errors that the app was not in foreground state after returning to the app.
applying this code to our "goToSpringboardAndBack()" works
XCUIDevice.shared.press(XCUIDevice.Button.home)
if XCUIApplication().wait(for: .runningBackground, timeout: 5.0) {
XCUIApplication().activate()
}
_ = XCUIApplication().wait(for: .runningForeground, timeout: 5.0)
´´´
Related
I found an issue, I have a project which has both Swift and objective - C files. I have added NSnotificationcenter addobserver, which works fine in debug mode, but when I generate IPA (release mode) then it does not work. I have added observer in Objective-C class and posting it from Swift class.
Swift Class: On a button click
#IBAction func btnBackTapped(_ sender: Any) {
let testManager = TestManager.init(window: appdel?.window, andApplicationViewController: splitViewController!)
NotificationCenter.default.post(name: NSNotification.Name(rawValue:"test"), object: nil)}
> Objectice - Class:
-(id)initWithWindow:(UIWindow *)w andApplicationViewController:(UIViewController *)avc {
self = [super init];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(testMethod:) name:#"test" object:nil];
}
return self;
}
-(void)testMethod:(NSNotification *)notification {
NSLog(#"testMethod called ");
}
Notes: I have noticed one more thing once I work with Xcode 11 version then it works in both cases. (Release and debug mode works fine). The issue only I faced in Xcode 12 versions, debug mode only. I found the issue and fixed now working fine even in debug mode.
Issue: I have created an object of class TestManager in the local method. So class destroys at the time notification fire. That's why the other class notification method does not call.
Fixes: I have created the object of class TestManager as Global instead of local method. And now working fine.
I have posted this question for two reasons: First one: if someone facing the same issue then can try, as I searched a couple of hours then I was not able to find the issue, because as this issue was only in debug mode, So I was not aware of how to run release mode in Xcode and set breakpoint. I found a way by which you can run from Xcode in release mode as well, no need to create IPA and install it every time. Go to edit scheme > Select run and info tab > Build confirmation > change it debug mode.
Second: May please someone tell me why it works in debug mode and why does not work in release mode when I created another class object in the local method instead of global. Any suggestion will be great, Thanks in advance. Happy coding.
I'm working on developing tests using KIF for a project. I want to know if it's possible to have KIF simulate a tap on the home button? Is it also possible to simulate other actions at that point, such as bringing up the command center or the notification center?
At least a partial answer for you, take a look at deactivateAppForDuration in KIFTestActor.h:
/*!
#abstract Backgrounds app using UIAutomation command, simulating pressing the Home button
#param duration Amount of time for a background event before the app becomes active again
*/
- (void)deactivateAppForDuration:(NSTimeInterval)duration;
For swift 3 and Xcode 8, this function of KIFSystemTestActor works:
system().deactivateApp(forDuration: 3)
extension XCTestCase {
func system(_ file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
}
}
With WatchKit you have your app that runs on the phone, and the watch app that runs as an extension.
If you create a library that contains common code to be used in both the phone app and the watch extension, is there a way to tell if the code is running in the phone app or the watch extension?
I.e.
if ([self isRunningInWatchExtension]) {
NSLog(#"this is running on watch");
} else {
NSLog(#"this is running on phone app");
}
- (BOOL)isRunningInWatchExtension {
???
}
In target conditionals there are some conditionals that may help you,
#if TARGET_OS_WATCH
//do something for watch
#else
//do something for ios ==> assuming you only support two platforms
#endif
I've accomplished this by checking the bundle identifier:
if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:kAppBundleIdentifier]) {
// Running in main app
}
else if ([[[NSBundle mainBundle] bundleIdentifier] isEqualToString:kWatchBundleIdentifier]) {
// Running in extension
}
This can be easy if you are calling any custom methods in your common framework class. You just need to add additional method parameters to method. And if you are calling this method from iOS app or Watchkit app then add appropriate key-value pair to dictionary for parameters. And compare this in your framework methods.
To determine this from init or any other method then you can still get to know by this code,
NSLog(#"%#",[NSThread callStackSymbols]);
So, you need to parse this string and get appropriate target names. If it is called by iOS app then you will get 'UIKit' string and from watch kit app extension you will get 'YourApp WatchKit Extension' string somewhere. You can also refer this SO answer for parsing this string and compare it - https://stackoverflow.com/a/9603733/602997
I'm trying to add a Today Extension to a project I've been working on for quite some time. In fact the app is in the AppStore already and I'm looking to enhance it with a Today Extension.
The problem is that the Extension won't launch at all. Not on the device nor on the simulator.
EDIT: just skip the next sections and read on at the last EDIT as I think I found the problem. I just not sure how to fix it.
I've done a test project following a tutorial and it works just fine. The environment seem(!) to be identical. Xcode 6.1.1, iOS 8.1 on the device and simulator.
My project is Objective-C based. For the Extension I’ve tried both Objective-C and Swift targets. On both occasions all three (four with obj-C) files were created as expected (storyboard, viewController and PLIST).
Having done nothing more (as with the example project) I'm trying to launch the widget with the widget scheme selected. With the test projects the widget would launch while it won't with the actual project.
I put a println()/NSLog in the viewDidLoad of the widgets viewController to see if anything happens but nothing.
Happy to provide code or settings but at this pointing time I've no idea where to start.
I just realised that with the test project the today view would launch/appear automatically when the widget gets run from Xcode. With my actual project I'm just getting the HomeScreen and have to pull down the Today view myself. So, really nothing at all happens regarding the widget while everything looks identical compared to the test project.
Any help is appreciated.
EDIT: Here is something I came across which might constitute the problem. The widget never gets launched really and gets stuck at ´Waiting to Attach´ in Xcode's Debug navigator. While other seemed to have had the same problem all potential solutions I found so far did't work for me.
EDIT: I noticed that when I add a Today widget as a target the binary is named .app. All test projects I did the binary gets created as .appex. All the information on the web suggests that it should be named .appex really. Where does this come from and how do I alter this?
I had the same problem.
The following steps helped:
selected target Today Extortion -> Build Settings -> line Wrapper Extension add (change) value to appex
See:
Same problem happened today when I created a Notification Content extension in an old project.(2016, Xcode8 iOS10)
Finally I found the cause:
"Wrapper Extension" in Build Settings of the project was “app”, and when the new target of extension was created, "Wrapper Extension” inherited from the project settings as “app”.
Clearing the project setting before adding an extention target will make Xcode creat an extention as “appex” automatically.
I am herewith sharing the step and source code.
Step 1:- App extension must have a containing app - you can't just create an app extension to be downloaded from the store, first create a regular app to contain the app extension. For the sake of this demonstration just create a new single view project and leave it untouched. Go to File-> New-> Project and select Single view application under iOS -> Applications call it 'ExtendableApp'.
Step 2:- If you want to create your custom experience simply set your ExtensionViewController to inherit from UIViewController, Once your extension is activated all the regular viewDidLoad, viewDidAppear, etc will be called.
Step 3:- In your controller storyboard create outlets for button, I am herewith describing 3 buttons.
Step 4:- In ExtensionViewController.m write
- (void)viewDidLoad {
[super viewDidLoad];
self.preferredContentSize = CGSizeMake(self.view.frame.size.width, 60.0f);
// Do any additional setup after loading the view from its nib.
}
Step 5:- I am assuming that you have set the outlets and IB Action of your buttons in extension storyboard
- (IBAction) mActionButtonTapped :(UIButton *) sender {
switch (sender.tag) {
case 0: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_1://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 1: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_2://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
case 2: {
NSURL *url = [NSURL URLWithString:#"IDENTIFIER_3://"];
[self.extensionContext openURL:url completionHandler:nil];
}
break;
default:
break;
}
}
Step 6:- In your project write these code in appDelete.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
[self appExtensionCallBack:url.absoluteString];
return YES;
}
- (void) appExtensionCallBack :(NSString *)urlString {
if ([urlString isEqualToString:#"IDENTIFIER_1://"]) {
[self.tabBarController setSelectedIndex:0];
} else if ([urlString isEqualToString:#"IDENTIFIER_2://"]) {
[self.tabBarController setSelectedIndex:1];
} else if ([urlString isEqualToString:#"IDENTIFIER_3://"]) {
[self.tabBarController setSelectedIndex:2];
}
}
Note :- I am using Tab Bar Controller in my project, You can give own respected controller.
- (void) moveToControllerScene {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORY_BOARD_IDENTIFIER bundle:nil];
YOUR_CONTROLLER_OBJECT *obj = [storyboard instantiateViewControllerWithIdentifier:#"YOUR_CONTROLLER_OBJECT"];
[navController pushViewController:obj animated:YES];
}
Step 7:- For testing the Extension in real device you have to make a separate App ID and Provisioning profile. Delete appropriate provisioning profile in extension and ur project.
UPDATE: As of iOS 5 and Xcode 4.1 is is now possible to test location in the simulator and even define routes. See http://developer.apple.com for more details.
Legacy Question
Is there anyway to test CoreLocation on the iPhone Simulator?
All I require is to be able to set the location myself and have CoreLocation return it.
Here is my simple hack that forces the CLLocationMager to return
the geocoords of Powell's Tech Bookstore only on the simulator:
#ifdef TARGET_IPHONE_SIMULATOR
#interface CLLocationManager (Simulator)
#end
#implementation CLLocationManager (Simulator)
-(void)startUpdatingLocation {
CLLocation *powellsTech = [[[CLLocation alloc] initWithLatitude:45.523450 longitude:-122.678897] autorelease];
[self.delegate locationManager:self
didUpdateToLocation:powellsTech
fromLocation:powellsTech];
}
#end
#endif // TARGET_IPHONE_SIMULATOR
Thanks for the great feedback, it has prompted me to find a robust solution.
All the code can be found here:
http://code.google.com/p/dlocation/
It is very messy but as I use it it will be become much better.
The solution was to subclass CLLocationManager and define a new delegate #protocol, called DLocationManagerDelegate.
It is designed to be a simple drop-in replacement for CLLocationManagerDelegate that compiles down to a very thin layer when deployed on an actual device.
When running on the device it will return data as normal using CoreLocation, but in the simulator it will read latitude and longitude from a text file (defined in the DLocationManager.h file).
I hope this helps, the implementation is on the simple side and you have to startUpdatingLocation and stopUpdatingLocation to update the display.
Comments and feedback will be gratefully received.
Use a filtering function to swap in a test instance when running on the simulator. Wherever you previously received the location (delegate call, etc), pass it through this:
+ (CLLocation *) wakkawakka: (CLLocation*) loc {
#ifdef TARGET_IPHONE_SIMULATOR
/* replace with a test instance */
return [[CLLocation alloc] initWithLatitude:10.0 longitude:20.0];
#else
return loc;
#endif
}
Memory management issues aside...
I think there's another (better IMHO) approach here than subclassing CLLocationManager like in
http://code.google.com/p/dlocation/
In ObjectiveC it seems to be possible to replace an existing method from a class without overriding it. This is often called "method swizzling" : you define your own category for an existing class an implement an existing method in it.
From the client perspective, everything is transparent : he has the feeling he's dealing with the real CLLocationManager but actually, you "took the control from it". So that he doesn't need to deal with any special subclass or any special delegate protocol : he keeps on using the same class / protocol as the one from CoreLocation.
Here's an example to took the control over the delegate a client would inject :
#implementation CLLocationManager (simulator)
-(void) setDelegate:(id)delegate {
//your own implementation of the setDelegate...
}
-(id)delegate {
//your own implementation of the delegate....
}
-(void)startUpdatingLocation {
}
-(void)stopUpdatingLocation {
}
//....
//same for the rest of any method available in the standard CLLocationManager
#end
Then in this implementation, you're free to deal with a pre defined set of coordinates (coming from a file of whatever) that will be "pushed" to the delegate using the standard CLLocationManagerDelegate protocol.
Trigger the Core Location callbacks from a test class, if you need to set a location other than the one the simulator gives you.
the locationManager:didUpdateToLocation and locationManager:didFailedWithError overloaded callbacks are never called in the iphone simulator, that's kinda strange, all i get is 0.0000 for lat., and 0.0000 for lon. as the position. In the situation you develop something, that's kinda hard to implement all the possible situations that can occur during the location handling, using only simulator environment.
If you're interested in updating the blue userLocation dot in a MKMapView with the simulated location updates, check out my FTLocationSimulator at http://github.com/futuretap/FTLocationSimulator
It reads a KML file generated by Google Earth to provide continuous location updates.
Testing CoreLocation on iPhone Simulator
1) To test the location in simulator,best way is to use GPX files,just go to Files -> New -> Resource -> GPX File.
2) After Adding the GPX file update the location coordinates as desired.
3) once the GPX file is added to the project,Select the Scheme -> Edit Scheme -> Run -> Allow Location Simulation.tick the location simulation and select the name of the GPX file you just created.
this way simulator will always pick your desired coordinates,that we have added in our GPX File.