UI automation testing with multilanguage in iOS - ios

I've written some UI tests in Xcode 7 and when I need to refer to a button I use its accessibility.identifier. This logic worked correctly for all the languages.
app.buttons["signin"].tap()
With Xcode 7.3 when I try to launch this code the test fails because the button cannot be found if the simulator language is not English. I've also tried to record the navigation to check how Xcode reads this button when language is different by English and I found out that it use the translations as key... it doesn't make any sense at all!
Those test were really useful to create screenshots... but obviously with this issue I cannot run tests (and create screens) for all the languages.
How can I point to a button in absolute way if it cannot be recognized by identifier!?
----EDIT
I found the main issue. The company that did the translation has translated the labelidentifier fields :/
I'm trying to get the element using app.buttons.elementBoundByIndex(1) but it doesn't seem to work correctly

Accessibility identifiers should not be localized strings, otherwise they will change when you are running your app in a different language.
Just hard-code the accessibility identifiers to make them persistent regardless of language.
button.accessibilityIdentifier = "myButton"

You can have actual accessibilityIdentifier assigned to button and then try accessing it. Accessing buttons with text is always a bad idea as text may change anytime.
E.g. : XCUIApplication().buttons["AppSignInIdentifier"]

I hope your project is maintaining the localization file. I faced the same issue. If so then localization doesn't work directly with UIAutomation yet.
There is a workaround for this, I am sharing the code snippet with you.
You need to find out the xcodeproj file in your bundle first. As these files are not bundled in the UIAutomation target.
- (NSString *)getLocalizedString:(NSString *)string withComments:(NSString *)comments {
NSString *userLocale = [[NSLocale currentLocale] localeIdentifier];
NSString *userLanguage = [userLocale substringToIndex:2];
NSString *path = [self getProjectPath];
path = [path stringByAppendingPathComponent:[NSString stringWithFormat:#"/YourProjectFileName/Resources/Localization/%#.lproj",userLanguage]];
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSString *localizedString = NSLocalizedStringFromTableInBundle(string, #"Localizable", bundle, comments);
return localizedString;
}
- (NSString *)getProjectPath {
NSString *currentSourceFilePath = [[NSString stringWithCString:__FILE__ encoding:NSUTF8StringEncoding] stringByDeletingLastPathComponent];
NSString *currentPath = [currentSourceFilePath copy];
BOOL foundIt = NO;
do {
NSString *testPath = [currentPath stringByAppendingPathComponent:#"XYZ.xcodeproj"];
if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) {
// found it
foundIt = YES;
break;
}
if ([currentPath isEqualToString:#"/"]) {
// cannot go further up
break;
}
currentPath = [currentPath stringByDeletingLastPathComponent];
} while ([currentPath length]);
if (!foundIt) {
return nil;
}
return currentPath;
}
And to use this //
NSString *loginStr = [self getLocalizedString:#"Login" withComments:#""];
And moreover for the better use of Automation, please set the accessibility labels for your controls, so that it would be easy for the automation process to find out that, which is less overhead on processor and less prone to crashes and issues.

Related

Protect iOS app from runtime hooking

iOS application when running in a device gets attached to many runtime libraries. How can we protect our iOS app from any other debugger getting attacked to the application process.
Like using GDB we can hack into the application process and manipulate runtime. Is there a way we can stop in using any setting or code?
Or is there a way to check if any other runtime libraries are getting attached to the process?
Can we quite the app in such scenarios?
We can check the modification date of the Info.plist file and Appname and compare it with the package modified date. If there is a miss match found we can conclude that the app binary was modified.
//Check date of modifications in files (if different - app cracked)
NSString* path = [NSString stringWithFormat:#"%#/Info.plist", bundlePath];
NSString* path2 = [NSString stringWithFormat:#"%#/AppName", bundlePath];
NSDate* infoModifiedDate = [[manager attributesOfFileSystemForPath:path error:nil] fileModificationDate];
NSDate* infoModifiedDate2 = [[manager attributesOfFileSystemForPath:path2 error:nil] fileModificationDate];
NSDate* pkgInfoModifiedDate = [[manager attributesOfFileSystemForPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"PkgInfo"] error:nil] fileModificationDate];
if([infoModifiedDate timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {
return YES;
}
if([infoModifiedDate2 timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {
return YES;
}
A bit late to the party, hope it helps somebody. In case you like pure, Swift solution, you might want to look at iOSSecuritySuite. It provides some protections against modifications you mentioned, for instance debugging detection:
let amIDebugged: Bool = IOSSecuritySuite.amIDebugged()
Which might save you some lines if you'd wanted to write it on your own.

IOS get installed Apps [duplicate]

Is it possible to programatically find out name of all apps installed on my iOS device ?
Is there any API available for same ?
Thanks for the help
No, on iOS applications has no access to information of/about other applications due to sandboxed environment.
Yes it is possible to get list of all installed app
-(void) allInstalledApp
{
NSDictionary *cacheDict;
NSDictionary *user;
static NSString *const cacheFileName = #"com.apple.mobile.installation.plist";
NSString *relativeCachePath = [[#"Library" stringByAppendingPathComponent: #"Caches"] stringByAppendingPathComponent: cacheFileName];
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent: #"../.."] stringByAppendingPathComponent: relativeCachePath];
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
user = [cacheDict objectForKey: #"User"];
NSDictionary *systemApp=[cacheDict objectForKey:#"System"];
}
systemApp Dictionary contains the list of all system related app
and user Dictionary contains other app information.
Not from the device. However, from the desktop you could peek into the iTunes library.
There are ways to do this without a jailbroken device and not get your app rejected.
1. get a list of currently running processes see this SO answer. You will need to translate from process name to app name.
2. Check to see if any apps have registered a unique URL scheme with UIApplicationDelegate canOpenURL. There are a few sites cataloging known url schemes, this is the best one.
If an app is not currently running and does not register a custom url scheme then it will not be detected by these methods. I am interested in hearing a method that will be allowed in the app store that works better than this.
try this, it will work even with non-jailbroken devices:
#include <objc/runtime.h>
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSLog(#"apps: %#", [workspace performSelector:selectorALL]);//will give you all **Bundle IDS** of user's all installed apps
You can do it by checking whether an application is installed or not by using canOpenURL method or by checking the background processes and matching them with the name of the app you are interested in.
You can use runtime objective c to get the list of all installed apps. It will give you an array of LSApplicationProxy objects.
Following is a code snippet that prints Name of all applications installed in your device.
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:NSSelectorFromString(#"defaultWorkspace")];
NSMutableArray *array = [workspace performSelector:NSSelectorFromString(#"allApplications")];
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
for (id lsApplicationProxy in array) {
if(nil != [lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]){
[mutableArray addObject:[lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]];
}
}
NSLog(#"********* Applications List ************* : \n %#",mutableArray);
Don't forget to include <objc/runtime.h> .

Reading from file works in iPhone 6.1 Simulator, but not in iPhone device (iOS 6.1.3)

I have a "Write" button that takes whatever in a Textfild, and appends to a designated file. I also have a "Read" button that reads from the content of the designated file, and displays it on the screen. The "Write" button calls writeDataToFile:(id)sender, and the "Read" button calls readDataFromFile:(id)sender.
The problem is that the following simple code works fine on iPhone 6.1 Simulator (Xcode 4.6.2), but does not display anything in a read device, e.g., iPod with iOS 6.1.3.
I suspect the problem is something related to encoding:NSUTF8StringEncoding. However, I tried NSUTF16StringEncoding and NSUTF32StringEncoding, and it still didn’t work on the real device (but disply scrambled words on Simulator).
Where did I do wrong? I would appreciate very much if somebody can give me some pointers.
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
_fileName = [documentsDirectory stringByAppendingPathComponent:#"data.txt"];
}
- (IBAction)writeDataToFile:(id)sender {
NSString *str = tfData.text;
str = [str stringByAppendingFormat:#"\n"];
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
[_fileHandle seekToEndOfFile];
//convert NSString to NSData
[_fileHandle writeData: [str dataUsingEncoding:NSUTF8StringEncoding]];
[_fileHandle closeFile];
[tfData resignFirstResponder];
}
- (IBAction)readDataFromFile:(id)sender {
[tfData resignFirstResponder];
//lbOut is a UILabel
lbOut.text = [NSString stringWithContentsOfFile:_fileName encoding:NSUTF8StringEncoding error:NULL];
}
Try to diagnose whether the file exists when you try to read from it. You can do this with NSFileManager. Also check whether the file has the right size (or at least a non-zero size).
Also, have you checked whether stringWithContentsOfFile actually returns a string? If an error occurs it will return nil and not raise an exception. If you get nil you can pass an NSError object to stringWithContentsOfFile and it will let you know what went wrong.
Start by checking your return values. If I had to make a guess I would say that the file at _fileName does not exist. If the file doesn't exist fileHandleForUpdatingAtPath: will return nil. And each subsequent call on the fileHandle will fail silently.
So you should check if fileHandleForUpdatingAtPath: actually returns a fileHandle. If it doesn't, create a fileHandle by using fileHandleForWritingAtPath:.
In a similar way you should check if the conversion to NSData has failed. I used NSParameterAssert, which will crash your app if stringData is nil, because I think there is no way to recover from a failed NSString conversion. If you think there is a way to recover from this error replace the NSParameterAssert with a if (!stringData) statement.
_fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
if (!_fileHandle) {
NSLog(#"File does not exist at path \"%#\". Create it.", _fileName);
_fileHandle = [NSFileHandle fileHandleForWritingAtPath:_fileName];
}
// should have a valid fileHandle here
[_fileHandle seekToEndOfFile];
NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding];
NSParameterAssert(stringData); // will raise an exception if stringData is nil
[_fileHandle writeData:stringData];
I found the problem. The problem is with the binding of _fileHandle. I declare this public variable, and binds it with a NSFileHandle associated with the file I intended to write using
[NSFileHandle fileHandleForWritingAtPath:_fileName]. I intended to keep using the _fileHandle from different ViewControllers without the need to bind it with the file every time I use it. It resulted the problem I described (i.e., works fine on Simulator, but not on real device).
Once I re-bind _fileHandle with NSFileHandle initialization every time I use it, it works fine now.

Using dropbox chooser to import pdf in ipad app

Hello i'm building a iPad app in which i want to use dropbox to get my pdf files in my application. Now i've tried the dropbox sdk but didn't get it working. I've seen they have a special dropbox chooser but only in web. The basic idea is to get a chooser in which i can chose the pdf which i want to read in my custom ipad reader. My question is if anybody has already done this before or if anybody have some suggestions where i can start?
Well you dont need any special chooser module. Dropbox API is very rich in terms of selecting files with its types. Use this code
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata
{
NSMutableArray *pathArray = [[NSMutableArray alloc] init];
for (DBMetadata *child in metadata.contents) {
if (!child.isDirectory) {
NSString *fileExtension = [[child.filename componentsSeparatedByString:#"."] lastObject];
if ([fileExtension compare:#"pdf" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
[pathArray addObject:child];
}
}
else {
[pathArray addObject:child];
}
}
}
This pathArray contains the list of PDF files only. Check it out and let me know if you are not getting anything. Have a Happy Coding.!! Cheers.!!

How to use a prepopulated SQLite database with PhoneGap / Cordova 2.0?

I want to use a pre populated database with my web-app, so that my app works offline. How can i do this with the newest version of PhoneGap / Cordova (2.0)?
I know that this question has been asked before, but all answers seem to be out of date relative to the current version of cordova and iOS
https://github.com/atkinson/phonegap-prepopulate-db has not been updated for two years
https://github.com/davibe/Phonegap-SQLitePlugin has not been updated for 7 months and is for 1.7
I found a post here:
http://www.raymondcamden.com/index.cfm/2012/7/27/Guest-Blog-Post-Shipping-a-populated-SQLite-DB-with-PhoneGap is this the only way?
Also, i should note that i use iOS 6
I have used the following plugin for Cordova 2.7 with iOS6 and I see it was just updated a day ago. https://github.com/pgsqlite/PG-SQLitePlugin-iOS
They also have an Android version here as well.
Part of the problem is PhoneGap changes so often keeping plugins up to date can be time consuming so they lapse. Here is the code for the init method in the AppDelegate.m I use in this project http://www.binpress.com/app/conference-core-ios-for-phonegap/1483
Keep in mind that you sometimes need to wipe the iOS Simulator so it reloads the files related to the application.
- (id)init{
NSHTTPCookieStorage* cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
int cacheSizeMemory = 8 * 1024 * 1024; // 8MB
int cacheSizeDisk = 32 * 1024 * 1024; // 32MB
if __has_feature(objc_arc)
NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
else
NSURLCache* sharedCache = [[[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"] autorelease];
endif
[NSURLCache setSharedURLCache:sharedCache];
databaseName = #"SomeCoolDatabase.db";
NSLog(#"databaseName: %#", databaseName);
databasePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSLog(#"databasePath: %#", databasePath);
databaseFile = [databasePath stringByAppendingPathComponent:databaseName];
NSLog(#"databaseFile: %#", databaseFile);
// Execute the "checkAndCreateDatabase" function
[self checkAndCreateDatabase];
self = [super init];
return self;
}
-(void)checkAndCreateDatabase {
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databaseFile];
// If the database already exists then return without doing anything
if(success){
NSLog(#"Database Present");
return;
} else {
NSLog(#"database not present");
}
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
// Create the database folder structure
[fileManager createDirectoryAtPath:databasePath withIntermediateDirectories:YES attributes:nil error:NULL];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databaseFile error:nil];
[fileManager release];
}
Hope this helps...
Thank you! I wouldn't have been able to do this without starting here!
That said, this wasn't working for me out of the box, so I updated it, and I'm by no means 'good' with native code for iOS, so I'm VERY open to suggestions.
I added the suggested code to the AppDelegate.m file, got a heap of errors.
So, I moved over to the AppDelegate.h file:
#interface AppDelegate : NSObject <UIApplicationDelegate>{} --Looks like this to start.
Make it look like this:
#interface AppDelegate : NSObject <UIApplicationDelegate>{
NSString* databaseName;
NSString* databasePath;
NSString* databaseFile;
}
I just had to declare the variables in there.
I also removed this line:
[fileManager release];
As, according to this answer, my project seemingly automatically releases? I hope?
Now I have to work on getting the DB file into the correct location for xcode to find it.
Hope this helps!
Though this question is old... thought it might help someone to post my solution - found in 2017. (Since PhoneGap has changed, older solutions no longer will work or be accepted on the Android and Apple app markets.) I worked on this problem for a long time - the issue is you want to import a pre-populated database - which there are not alot of easy solutions for. I found the best and easiest - and currently just about the ONLY - solution to be LiteHelpers CORDOVA-SQLITE-EXT (https://github.com/litehelpers/cordova-sqlite-ext)
I have added this in to my app and it is working well. I had to switch away from PhoneGap though - and use CORDOVA - but it is working for me.

Resources