How to view app logging when ui testing - xctest

When running UI tests on my iOS app on our Jenkins CI slave, the UI is incorrect and my tests fail. Locally, the same UI is as expected and the tests pass.
I haven't been able to figure this out and I cannot debug it as it is on a remote machine. So, I would like to add logging to see what decisions are being made by the app to figure out why the UI is displaying incorrectly in the remote Jenkins slave.
The test log output does not contain log output from the app (via NSLog), nor can I find any of the strings from the appNSLog calls in any .xcactivitylog file in the derived data output folders.
Where can I find my logging output from my app when it is being run by the UI tests? This is with Xcode 8.2 running a iOS 9.3 simulated device.

The following solution is a bit hacky but allows you to get the logs even when running on the device.
Logger.h
#define Debug(...) _Debug(__VA_ARGS__)
void _Debug(NSString *format, ...);
Logger.m
void _Debug(NSString *format, ...)
{
format = [NSString stringWithFormat:#"[Debug]: %#", format];
va_list args;
va_start(args, format);
NSLogv(format, args); // logs to the console
NSString *logMessage = [[NSString alloc] initWithFormat:format arguments:arguments];
UIApplication *application = [UIApplication sharedApplication];
UIWindow *window = [application keyWindow];
window.accessibilityValue = [NSString stringWithFormat:#"%#\n%#", window.accessibilityValue, logMessage];
va_end(args);
}
Then from the UI Test, I can access the logs like this
+ (NSString *)logs
{
XCUIElementQuery *windowQuery = [[[XCUIApplication alloc] init] windows];
return [windowQuery elementAtIndex:0].value;
}

Related

How to launch Finder Sync Extension on launching the main app?

In my Cocoa application, I have a finder sync extension.
On launching the application, my finder sync extension doesn't start automatically.
I need to go to System Preferences -> Extensions and enable it.
How do i make sure that on launch of my main application (.app) file the finder sync extension is launched and is enabled?
Checkout https://blog.codecentric.de/en/2018/09/finder-sync-extension/
There is a section Restarting FinderSyncExtension on app launch with instructions on how to restart FinderSyncExtension on app launch and thus make it more reliable:
+ (void) restart
{
NSString* bundleID = NSBundle.mainBundle.bundleIdentifier;
NSString* extBundleID = [NSString stringWithFormat:#"%#.FinderSyncExt", bundleID];
NSArray<NSRunningApplication*>* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:extBundleID];
ASTEach(apps, ^(NSRunningApplication* app) {
NSString* killCommand = [NSString stringWithFormat:#"kill -s 9 %d", app.processIdentifier];
system(killCommand.UTF8String);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString* runCommand = [NSString stringWithFormat:#"pluginkit -e use -i %#", extBundleID];
system(runCommand.UTF8String);
});
}

I try to find a API can instead of AuthorizationExecuteWithPrivileges? [duplicate]

Since updating to OSX 10.7 Lion, Xcode tells me that AuthorizationExecuteWithPrivileges is deprecated.
Can anyone suggest a way my application can write to a directory it doesn't have permission for?
I know it sounds crazy, but this actually works:
NSDictionary *error = [NSDictionary new];
NSString *script = #"do shell script \"whoami > /tmp/me\" with administrator privileges";
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
if ([appleScript executeAndReturnError:&error]) {
NSLog(#"success!");
} else {
NSLog(#"failure!");
}
I'm executing an Applescript from Objective C. The only disadvantage is that you cannot gain permanent root privileges with this. It will ask for the password each time you run this.
In fact, AuthorizationExecuteWithPrivileges() has been deprecated for a very long time, it's only recently that the header file has caught up with this fact.
You can create a privileged helper tool as part of your application. You can use ServiceManagement.framework's SMJobBless() function to have the helper deployed into the system launchd context: then when you need to perform privileged tasks, you just message the privileged helper to do that work.
There's a little bit of hidden complexity, in that the app and the helper must each declare the signing identity of the other before SMJobBless() believes they're supposed to be used together, and you need to get the linker to write the helper tool's Info.plist file into the binary. That's all covered by Apple's Documentation and Apple have provided a sample project, too.
I wrote an example application that uses SMJobBless() to deploy its privileged helper.
Based on a great find by user950473 I've implemented his/her discovery as a method; thought I'd share the code in case it's helpful.
- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath
withArguments:(NSArray *)arguments
output:(NSString **)output
errorDescription:(NSString **)errorDescription {
NSString * allArgs = [arguments componentsJoinedByString:#" "];
NSString * fullScript = [NSString stringWithFormat:#"'%#' %#", scriptPath, allArgs];
NSDictionary *errorInfo = [NSDictionary new];
NSString *script = [NSString stringWithFormat:#"do shell script \"%#\" with administrator privileges", fullScript];
NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];
// Check errorInfo
if (! eventResult)
{
// Describe common errors
*errorDescription = nil;
if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
{
NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
if ([errorNumber intValue] == -128)
*errorDescription = #"The administrator password is required to do this.";
}
// Set error message from provided message
if (*errorDescription == nil)
{
if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
*errorDescription = (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
}
return NO;
}
else
{
// Set output to the AppleScript's output
*output = [eventResult stringValue];
return YES;
}
}
Usage example:
NSString * output = nil;
NSString * processErrorDescription = nil;
BOOL success = [self runProcessAsAdministrator:#"/usr/bin/id"
withArguments:[NSArray arrayWithObjects:#"-un", nil]
output:&output
errorDescription:&processErrorDescription];
if (!success) // Process failed to run
{
// ...look at errorDescription
}
else
{
// ...process output
}
It's very slightly hacky, but IMHO is a satisfactory solution.
AuthorizationExecuteWithPrivileges is indeed deprecated.
But fortunately, there is a new recommended way to proceed.
As of 10.6 there is the new API and it is recommended to install a helper tool that will perform the privileged operation. Apple provide a code sample that clearly demonstrate how to manage it.
Make sure you check out their readme.txt since contrarily to other code sample there is more to do than just downloading the project and running it.
From The SMJobBless example introduction
SMJobBless demonstrates how to securely install a helper tool that performs a privileged operation and how to associate the tool
with an application that invokes it.
As of Snow Leopard, this is the preferred method of managing privilege
escalation on Mac OS X and should be used instead of earlier
approaches such as BetterAuthorizationSample or directly calling
AuthorizationExecuteWithPrivileges.
SMJobBless uses ServiceManagement.framework that was introduced in Mac
OS X v10.6 Snow Leopard.
Source: Apple SMJobBless code sample

Global events, the Mac App Store, and the sandbox

I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (It's a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.
Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.
The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.
I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.
In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
NSString *appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );
[self restartApp];
}
}
- (void)restartApp{
NSTask *task = [[NSTask alloc] init];
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-c"];
[args addObject:[NSString stringWithFormat:#"sleep %d; open \"%#\"", 3, [[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:#"/bin/sh"];
[task setArguments:args];
[task launch];
[NSApp terminate:nil];
}
Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.
The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."
If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.
In summary, my specific questions are:
Is there an error in my sample code about how to get the
authentication prompt to appear?
In order to get the authentication prompt to appear, am I required
to use the Authorization Services API?
Is it possible, or not possible, to have a sandboxed app that has
access to global events?
First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:
NSDictionary *options = #{(id) kAXTrustedCheckOptionPrompt : #YES};
AXIsProcessTrustedWithOptions((CFDictionaryRef) options);
Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox):
The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.
To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:
+ (BOOL)makeTrustedWithError:(NSError **)error {
NSString *label = FMTStr(#"%#.%#", kShiftItAppBundleId, #"mktrusted");
NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:#"mktrusted"];
AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;
if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
// this is actually important - if from any reason the job was not removed, it won't relaunch
// to check for the running jobs use: sudo launchctl list
// the sudo is important since this job runs under root
SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
// this is actually the launchd plist for a new process
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
NSDictionary *plist = #{
#"Label" : label,
#"RunAtLoad" : #YES,
#"ProgramArguments" : #[command],
#"Debug" : #YES
};
BOOL ret;
if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
FMTLogDebug(#"Executed %#", command);
ret = YES;
} else {
FMTLogError(#"Failed to execute %# as priviledged process: %#", command, *error);
ret = NO;
}
// From whatever reason this did not work very well
// seems like it removed the job before it was executed
// SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
AuthorizationFree(auth, 0);
return ret;
} else {
FMTLogError(#"Unable to create authorization object");
return NO;
}
}
As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:
+ (void) relaunch {
NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:#"relaunch" ofType:#""];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *pid = FMTStr(#"%d", [[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:#[path, pid]];
[NSApp terminate:self];
}
Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:
NSString *sqlite = #"/usr/bin/sqlite3";
NSString *sql = FMTStr(#"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%#', 1, 1, 1, NULL);", MY_BUNDLE_ID);
NSArray *args = #[#"/Library/Application Support/com.apple.TCC/TCC.db", sql];
NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];
This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.
Basically, MAS restrictions will require you to the route of having tge user turning on AX for all.
I found a potential solution on GitHub.
https://github.com/K8TIY/CW-Station
It has an auxiliary application which would be run at root to request access for the main application. It is a little outdated and is using some functions which have been deprecated so I am working on modernizing it. It looks like a good starting point.

NSTask does not receive stdout outside of Xcode 4.3.3

this is my first post, so let me send me many thanks to all the posting guys
outside there (I use SO extensively passively - great!)
I'm working on an video exporting tool for Mac OS X using the good old Quicktime API.
Brief:
I cut frames from multiple input movies an arrange them (scaled) to a new output
movie (kind of media-kiosk).
As many of the needed QT functionality (e.g. writing timecode ...) need to be
nested in a 32-bit Application, I decided to do this offline using a 32 bit command line
tool. The tool renders frame by frame (offline) and prints the current progress
in values between 0.0 and 1.0
It is invoked by the main application (Cocoa, GUI, the modern stuff) via
NSTask. The stout is caught by a NSPipe.
I took a look at some examples and 'll give you quick overview over my code:
NSTask *task;
NSPipe *pipe;
float progress;
// prepare the offline process
//
//
-(void) prepareOfflineExport {
task = [[NSTask alloc] init];
pipe = [[NSPipe alloc] init];
[task setLaunchPath:pathToRenderer];
[task setStandardOutput:pipe];
}
// arguments are passed outside
// invoke the process
//
-(void) startOfflineExport {
progress = 0.0f;
NSArray *argv = [NSArray arrayWithObjects: /* command line args */, nil];
[task setArguments:argv];
NSFileHandle *fh = [pipe fileHandleForReading];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataReady:) name:NSFileHandleReadCompletionNotification object:fh];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskTerminated:) name:NSTaskDidTerminateNotification object:task];
[task launch];
[fh readInBackgroundAndNotify];
}
// called when data ready
//
//
-(void) dataReady:(NSNotification*)n {
NSData *d = [[n userInfo] valueForKey:NSFileHandleNotificationData];
if([d length]) {
NSString *s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
progress = [s floatValue];
}
}
// called when process exits
//
//
-(void) taskTerminated:(NSNotification*)n {
task = nil;
progress = 1.0f;
}
Now the Problem:
When launching the application inside Xcode (via "run"), everything works fine.
The Invocation is done proper, the process is visible in the activity monitor and
the NSLevelIndicator (on the guy of the innovating app) is proceeding well according
the (float) progress variable.
BUT: if i "archive" the application and execute it outside of Xcode, the stdout of my
Command Line Tool never seem to reach the application. I tried writing a debug file
in
-(void) dataReady:(NSNotification*)n
No chance, it is never called! I tested the issue on several Macs, same problem...
Did I make an obvious mistake or is there some preferences to configure (Sandboxing is off),
maybe known issues that I overlooked?
Thank you for help
Greetings
Mat

AuthorizationExecuteWithPrivileges is Deprecated. Have to use something else? [duplicate]

Since updating to OSX 10.7 Lion, Xcode tells me that AuthorizationExecuteWithPrivileges is deprecated.
Can anyone suggest a way my application can write to a directory it doesn't have permission for?
I know it sounds crazy, but this actually works:
NSDictionary *error = [NSDictionary new];
NSString *script = #"do shell script \"whoami > /tmp/me\" with administrator privileges";
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
if ([appleScript executeAndReturnError:&error]) {
NSLog(#"success!");
} else {
NSLog(#"failure!");
}
I'm executing an Applescript from Objective C. The only disadvantage is that you cannot gain permanent root privileges with this. It will ask for the password each time you run this.
In fact, AuthorizationExecuteWithPrivileges() has been deprecated for a very long time, it's only recently that the header file has caught up with this fact.
You can create a privileged helper tool as part of your application. You can use ServiceManagement.framework's SMJobBless() function to have the helper deployed into the system launchd context: then when you need to perform privileged tasks, you just message the privileged helper to do that work.
There's a little bit of hidden complexity, in that the app and the helper must each declare the signing identity of the other before SMJobBless() believes they're supposed to be used together, and you need to get the linker to write the helper tool's Info.plist file into the binary. That's all covered by Apple's Documentation and Apple have provided a sample project, too.
I wrote an example application that uses SMJobBless() to deploy its privileged helper.
Based on a great find by user950473 I've implemented his/her discovery as a method; thought I'd share the code in case it's helpful.
- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath
withArguments:(NSArray *)arguments
output:(NSString **)output
errorDescription:(NSString **)errorDescription {
NSString * allArgs = [arguments componentsJoinedByString:#" "];
NSString * fullScript = [NSString stringWithFormat:#"'%#' %#", scriptPath, allArgs];
NSDictionary *errorInfo = [NSDictionary new];
NSString *script = [NSString stringWithFormat:#"do shell script \"%#\" with administrator privileges", fullScript];
NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
NSAppleEventDescriptor * eventResult = [appleScript executeAndReturnError:&errorInfo];
// Check errorInfo
if (! eventResult)
{
// Describe common errors
*errorDescription = nil;
if ([errorInfo valueForKey:NSAppleScriptErrorNumber])
{
NSNumber * errorNumber = (NSNumber *)[errorInfo valueForKey:NSAppleScriptErrorNumber];
if ([errorNumber intValue] == -128)
*errorDescription = #"The administrator password is required to do this.";
}
// Set error message from provided message
if (*errorDescription == nil)
{
if ([errorInfo valueForKey:NSAppleScriptErrorMessage])
*errorDescription = (NSString *)[errorInfo valueForKey:NSAppleScriptErrorMessage];
}
return NO;
}
else
{
// Set output to the AppleScript's output
*output = [eventResult stringValue];
return YES;
}
}
Usage example:
NSString * output = nil;
NSString * processErrorDescription = nil;
BOOL success = [self runProcessAsAdministrator:#"/usr/bin/id"
withArguments:[NSArray arrayWithObjects:#"-un", nil]
output:&output
errorDescription:&processErrorDescription];
if (!success) // Process failed to run
{
// ...look at errorDescription
}
else
{
// ...process output
}
It's very slightly hacky, but IMHO is a satisfactory solution.
AuthorizationExecuteWithPrivileges is indeed deprecated.
But fortunately, there is a new recommended way to proceed.
As of 10.6 there is the new API and it is recommended to install a helper tool that will perform the privileged operation. Apple provide a code sample that clearly demonstrate how to manage it.
Make sure you check out their readme.txt since contrarily to other code sample there is more to do than just downloading the project and running it.
From The SMJobBless example introduction
SMJobBless demonstrates how to securely install a helper tool that performs a privileged operation and how to associate the tool
with an application that invokes it.
As of Snow Leopard, this is the preferred method of managing privilege
escalation on Mac OS X and should be used instead of earlier
approaches such as BetterAuthorizationSample or directly calling
AuthorizationExecuteWithPrivileges.
SMJobBless uses ServiceManagement.framework that was introduced in Mac
OS X v10.6 Snow Leopard.
Source: Apple SMJobBless code sample