XCUITest set running on both macOS and iOS - ios

We are building an app using Catalyst and I need to run UI tests on both iOS and macOS. However, in the iOS/iPadOS there are gestures (swipes, long press etc.), which are not present on macOS. So, when I try to build tests on macOS, it fails with the errors like "Value of type 'XCUIApplication' has no member 'swipeDown'".
Does anyone have any ideas about what to do to keep one set of tests for both macOS and iOS?
Thanks in advance.

Swipes are unavailable for macOS, because macOS doesn’t run on touchscreen devices.
You can still reuse your code if you separate elements, steps and tests code with ScreenObject (aka PageObject) pattern.
Search the internet for more documentation. You can also check out my implementation of this pattern here
https://github.com/rzakhar/xctest-assignment
Your steps can be multiplatform if you use #available or #if APIs
#if os(iOS)
cell.swipeLeft()
cell.buttons["Delete"].tap()
#else
cell.buttons["More"].click()
cell.buttons["Delete"].click()
#endif

Related

Xamarin.UITests - testing on real device - iOS - app permissions popups issue

I've iOS app that needs some privileges (GPS, Push notifications).
When app starts for a first time iOS asks user if they're ok with granting those permissions to application.
I've written some UITests and want to automate running them on locally connected iPhone.
The problem is that I cannot override permissions questions and my tests fails.
I found out that application deployed by IDE (Xamarin Studio) will ask for permissions, but application deployed via UITests will not.
So I tried with .AppBundle(path_to_app) but it says this is only valid for deploying to Simulator.
SetUp : System.Exception : This app bundle is not valid for running on
a simulator. To fix this issue please ensure that your target device
is a simulator. DTPlatformName is 'iphoneos', not 'iphonesimulator' in
the apps Info.plist.
Like it's trying to deploy iPhone app to Simulator. But Target in Xamarin Studio is set to real device.
I tried to add .DeviceIdentifier. When Used with .InstalledApp it was starting up (still asking for permissions).
But when I used DeviceIdentifier and AppBundle there was the same error as above.
My tests works fine on Test Cloud. They work fine on Simulator.
They work fine when I deploy to device manually, start app and approve permissions then run UI tests.
What I cannot achieve is to make UITests override permissions questions on real device.
Anyone made this work?
Last thing is that I found is in documentation for AppBundle method
"Will force a run on simulator"
https://developer.xamarin.com/api/member/Xamarin.UITest.Configuration.iOSAppConfigurator.AppBundle/p/System.String/
So I may be doomed with the task but maybe someone knows a workaround?
You can dismiss system dialogs with UITest by using InvokeUIA. The test below works by tapping the "OK" button of an iOS system alert:
[Test]
public void AppLaunches ()
{
app.Screenshot ("First screen.");
app.InvokeUia ("uia.query('[:view {:marked \"OK\"}]')");
app.InvokeUia ("uia.tapMark(\"OK\")");
}
A working sample app & UITest is also here:
https://github.com/King-of-Spades/InvokeUia-for-System-Dialogs
Warning about system dialogs in Test Cloud
The reason that you don't see this issue in Test Cloud is because Test Cloud automatically dismisses the system alerts; so usually you don't have to worry about it. However, if your alert launches too soon; so that it appears before the automation has fully started your app, then it will be unable to detect & dismiss the alert and cause your test to fail.
So you want to make sure that when running your app in Test Cloud that the request for permissions are delayed, or you can even deactivate them if they aren't explicitly needed for a particular test. More information is available in this Calabash guide: https://github.com/calabash/calabash-ios/wiki/Managing-Privacy-Alerts%3A--Location-Services%2C-APNS%2C-Contacts
(Even though it's Calabash, you can use the same strategy in UITest; albeit with a C# syntax.)
Update for Xcode 8 / iOS 10
Xcode 8 / iOS 10 removed UIAutomation, so the InvokeUIA workaround will only continue to be possible if you're using Xcode 7 and iOS 7-9. References:
UITest: https://developer.xamarin.com/guides/testcloud/UITest/xcode7/
Calabash: https://developer.xamarin.com/guides/testcloud/calabash/xcode7/#Automation_API
For real devices you dont need any of those.
{
app = ConfigureApp
.iOSAppBundle
.StartApp();
}
this piece of code is good enough, if you are connecting real device to the system, then select that before running.
We use this to execute the UI tests on iPhones:
ConfigureApp.iOS.InstalledApp("com.appcenter.UITestDemo").StartApp();
InstalledApp requires you to build an IPA using the Debug
configuration & a valid provisioning profile, and preinstalling it on
the target device.
https://github.com/microsoft/appcenter-Xamarin.UITest-Demo/blob/main/UITestDemo.UITest/AppInitializer.cs
To confirm system dialogs we use this:
private Query ConfirmLocalNetworkPermissionDialogButton
=> AppInitializer.Platform == Platform.iOS
? new Query(x => x.ClassFull("_UIAlertControllerActionView").Marked("OK"))
: x => x.Class("AppCompatButton").Marked("button1");

Xcode UI Testing: lldb error "only available on iOS 9.0 or newer"

Background
I have a fairly detailed (around 2000 lines of swift code) automation framework built with Xcode 7.3 and Swift for our iOS app.
Up till a recent point, I was able to use lldb to build my framework i.e
insert breakpoint and run code up till that point
use po XCUIApplication().debugDescription and expr bla bla to build the logic
repeat
Problem
Pretty much all of a sudden, I now encounter the following error whenever running anything inside lldb's console:
(lldb) po XCUIApplication()
error: <EXPR>:2:1: error: 'XCUIApplication' is only available on iOS 9.0 or newer
XCUIApplication()
^
<EXPR>:2:1: note: add 'if #available' version check
XCUIApplication()
^
<EXPR>:2:1: note: add #available attribute to enclosing instance method
XCUIApplication()
^
<EXPR>:2:1: note: add #available attribute to enclosing extension
XCUIApplication()
^
<EXPR>:10:9: warning: initialization of variable '$__lldb_error_result' was never used; consider replacing with assignment to '_' or removing it
var $__lldb_error_result = __lldb_tmp_error
~~~~^~~~~~~~~~~~~~~~~~~~
Notes
Googling and research has not gotten me anywhere significant.
The one relevant thread I found was in the fastlane project. Unlike that comment, mine is a UI Test target.
Also, the test target's "iOS Deployment Target" is set to iOS 9.2 (in case that helps).
To the extent I remember, I haven't changed anything significantly in recent times.
My device is (and has always been) iOS 9+.
What could be going wrong?
Update 20 May 2016
Some exploration based on the answer below: https://stackoverflow.com/a/37335950/682912
The issue happens only on real devices. Simulators do not face this problem.
On real device (iPhone 6S+, iOS 9.2.1), I did a full reset of Content and Settings. This did not fix the issue.
This is a bug in the debug agent installed on your device. These are bound to the iOS version, so it probably happened when you upgraded your device. Anyway, please file a bug with http://bugreporter.apple.com.
If I'm right about the problem, it should only happen when debugging to the device, not on the simulator. That might allow you a temporary workaround till the bug gets fixed.
Double check your .xcconfig with your test target or any other means that may set your IPHONEOS_DEPLOYMENT_TARGET to lower than 9.0.
Since UITest is only available on iOS9.0 or later, change IPHONEOS_DEPLOYMENT_TARGET to 9.0+ should fix the issue.

How to define code for different iOS versions

I am working on an iOS app. I want it to support iOS 7 and 8. It is going pretty nicely, however there are lots of different parts of the app which use Apple APIs. Some of these APIs work in both iOS 8 and 7. However, some of them are deprecated in iOS 8. So I therefore went to the Apple developer site to see what to replace them with (new methods/etc....).
However, I now have the problem that the app will work on iOS 8 fine, but certain parts of it don't work properly on iOS 7 as I'm trying to use an iOS 8 API...... (lol).
So I just wanted to know, what is the best way to implement code which works on iOS 8 and 7. I had a few ideas (below), but I'm not sure which is best:
IDEA 1
Whenever I have code which doesn't work on both OS's, I use an if function (which calls a macro) like so:
if (SYSTEM_VERSION_LESS_THAN(#"8.0")) {
// iOS 7 device. Use iOS 7 apis.
}
else {
// iOS 8 (or higher) - use iOS 8 apis.
}
IDEA 2
I was thinking about using ifdef definitions all around the app like so:
#ifdef __IPHONE_8_0
// iOS 8 code here....
#else
// iOS 7 code here....
#endif
Which way is better? I would have thought that the second idea is much faster and uses less resources right?
Or are both my ideas rubbish? Is there a much better way about solving this problem?
Thanks for your time, Dan.
I don't suggest checking the Version and writing code based on that. Instead you need to check whether that API is available or not.
For checking a class available or not:
Class checkClass = NSClassFromString(#"CheckingClass");
if (checkClass)
{
// Available
}
else
{
// Not Available
}
If you need to check a feature/function available;
if ([checkClass respondsToSelector:#selector(yourMethod:)])
{
// Feature/ Method Available
}
else
{
// Feature/ Method Not Available
}
NOTE:
Deprecated API's doesn't mean that you shouldn't use that in current version. It means, it won't work from next version onwards, and only work till current version.
The ifdef-way won't work, because preprocessor statements are evaluated at compile-time; but only at runtime we know which ios-version we have to deal with.
You would use macros for example if you wanted to support Mac OS X and iOS with the same code, because you know at compile-time if the binary will be for Mac OS or iOS.
So you need in this case approach 1 - or, even better, you should use respondsToSelector: to check for availability instead of testing the iOS version if possible.
However, because you are only dealing with deprecation warnings, you don't have to do anything and should simply continue using the deprecated APIs until the app no longer needs to support ios7.

Developing apps for multiple iOS version

I am testing an app on an iPhone 4 with iOS 5.1 and an iPad 4 with iOS 6.0. I looked around and surprisingly did not find similar questions:
1- My app has some methods that have been deprecated in iOS 6.0 so I believe I have to build some if/then conditions to test for system version using: [[UIDevice currentDevice] systemVersion], and then use the version appropriate methods. Is that the most efficient way?
2- My understanding is, with only one target, the "project" Deployment Target and the "Targets" deployment target serve the same purpose. And I need to have one of them or both as iOS 5.1 since that is the minimum iOS supported. What is confusing is that if the target is built based on iOS 5.1, how would it run on the iPad4 with iOS 6.0:
Does the iPad OS checks for target versions before running or just tries to run the code and it happens that the iOS 5.1 target does not have any code that the 6.0 is incompatible with?
Even if that is the case though, how could a 5.1 target support 6.0 methods that I built to conditionally replace deprecated methods?
Many thanks!
Deprecated methods
Deprecated methods can be used if you are targetting iOS versions that were released before those methods were deprecated. But assuming your deployment target is set correctly, you won't get any compiler errors unless those deprecated methods were always deprecated for the versions you are targetting. In other words, if you are seeing deprecation warnings in your code you need to fix them or check that your deployment target setting is correct. Do not ignore them!
Xcode setting levels
You mention the fact that you can define the deployment target setting at both the target and project level. Xcode build settings at the target level will override project settings. So define the deployment target at one of these levels only, then go to the other and hit delete so you don't have duplicate values. If you only have one target then it doesn't really matter if you define it at the target or project level.
Backwards and forwards compatibility
Finally, there are many factors that come into play for backwards and forwards compatibility. Sometimes there will be new iOS 6 methods like supportedInterfaceOrientations which will simply be ignored on older iOS versions. Other times you need to add explicit checks:
If you are calling a method on an object and that method was only introducted with iOS 6, you will need to add a respondsToSelector: check like this:
// only available on iOS 6
if ([locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)]) {
locationManager.pausesLocationUpdatesAutomatically = YES;
}
If you want to check if a particular class exists on the current iOS version, you can check the return value of the +class method like this:
// Only available on iOS 6
if ([UICollectionView class]) {
// ...
} else {
// class doesn't exist in this iOS version
}
If you want to check if a particular function is available, do a simple if statement on it:
// Only available in iOS 6
if (ABAddressBookCreateWithOptions) {
ABAddressBookCreateWithOptions(...);
} else {
ABAddressBookCreate(...);
}
Finally, if you want to check if a constant is available, check it's address:
// Only available in iOS 4
if (&UIApplicationProtectedDataDidBecomeAvailable) {
// subscribe to notification
}
Your Base SDK setting should always be set to "latest".
If you follow all these guidelines you will be able to solve most of your problems without having to add explicit version checks. Checking the iOS version or device ID is very brittle and is likely to cause your app to break in future versions. You really want to avoid it.
You can use [[UIDevice currentDevice] systemVersion] to detect the OS version, that would work. Rather than detecting the OS version, you could use respondsToSelector: to see what methods are present, NSClassFromString() != nil to see if classes are present, and fallback if that method is not available.
iOS is backward compatible, so iOS 6 will run iOS 5 apps just fine. However, if you want to use iOS 6 specific feature but still support iOS 5.1, you have to :
Compile using the 6.0 SDK so that the compilers knows new APIs
Set your target version to the lowest supported, aka. 5.1. Any system higher than that number will run the code. Any lower will not try.
Use one of the method described in 1. to make sure that each device execute codes the OS support.
I hope that's clear, don't hesitate if you have more questions
What you must do is detect functionality, not iOS version, please check out this thread:
Conditional support of iOS 6 features in an iOS 5 app
You shouldn't check against the system version but rather check against the functionality you are trying to use. On that note remember that deprecated does not mean removed so whatever you are trying to do may very well be there for iOS6. To check against a specific functionality you would do something like:
if([TheClassInQuestion class] != nil)
{
//use it
}
iOS is backwards compatible. If you compile your app for iOS 5.1 it will be perfectly run on iOS 6 as well. No need to re-implement deprecated classes.
But if you change your deployment version from 5.0 to 6.0, Xcode will show you warnings if you use deprecated methods
If you want to use methods, which are available only in iOS 6, you can use this check:
if ([self respondsToSelector:#selector(doSomething:)]) {
[self doSomething];
}

Setting multiple UIBarButtonItems fails on device, but not on simulator

I'm consistently running into an invalid selector exception when I attempt to set multiple UIBarButtonItems in MonoTouch.
In the simulator, there are no issues at all. Each button I add works correctly. Only when I deploy to a test device (iPhone 3G with iOS 4.2.1 installed) does it throw this error:
-[UINavigationItem setRightBarButtonItems:animated:]: unrecognized selector sent to instance 0xef7af0
I have tried using the RightBarButtonItems property and the explicit SetRightBarButtonItems method, passing a UIBarButtonItem[] instance in each case.
Maybe the way to get around this is to use a ToolBar with a custom view, but I'd rather just use the API as advertised, especially if it works correctly in the simulator.
Anyone run into this?
I assume you're using MonoTouch 5.0 and the latest iOS SDK (5) and Xcode 4.2.
The setRightBarButtonItems:animated: selector is new in iOS5. This means it will work in the simulator, which likely defaults to 5.0. However this won't work in older releases of iOS (e.g. like 4.2.1).
At this stage you have two choices:
avoid iOS5 features; or
detect, at runtime, which version of iOS is being used and adjust your application to what's available.
Check this out: http://osmorphis.blogspot.com/2009/05/multiple-buttons-on-navigation-bar.html
It works if you want to add multiple right buttons for iOS < 5.0

Resources