Version 15.4 (15F31d) Xcode NSAppleScript of Safari broken

All attempts to script Safari in Xcode using NSAppleScript returns the following message.

error: { NSAppleScriptErrorAppName = Safari; NSAppleScriptErrorBriefMessage = "Application isn\U2019t running."; NSAppleScriptErrorMessage = "Safari got an error: Application isn\U2019t running."; NSAppleScriptErrorNumber = "-600"; NSAppleScriptErrorRange = "NSRange: {32, 3}"; }

Latest script attempt: func getHTML() -> String {

let source = """
tell application "Safari"
  get URL of tab 1 of window 1
end tell

"""

//print(source)
var a = "hello"
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: source) {
    if let scriptResult = scriptObject.executeAndReturnError(&error).stringValue
    {
        a = scriptResult
        print(scriptResult)
    } else if (error != nil)  {
        print("error: ",error!)
    }
}

return a
}
Answered by DTS Engineer in 794950022

To be clear, you’re not running this in Xcode but using Xcode to build an app that runs this. Right?

If so, there are a number of potential issues. Lemme walk you through a process that works for me:

  1. First run the script in Script Editor to confirm that it works. If it doesn’t work here, you have an AppleScript problem.

  2. Then, in Xcode, create a new project from the macOS > App template.

  3. In Signing & Capabilities, remove App Sandbox. Scripting from a sandboxed app is tricky, so it’s best to start out with the sandbox disabled.

  4. Still in Signing & Capabilities, enable Hardened Runtime > Resource Access > Apple Events. AppleScript is based on Apple events, which the hardened runtime blocks by default.

  5. In the Info tab, add a NSAppleEventsUsageDescription with a privacy string.

    IMPORTANT The property list editor shows this as Privacy - AppleEvents Sending Usage Description.

  6. In ContentView.swift, create a button that calls a test() method.

  7. Add that test() method using the code at the end of this post.

  8. Build and Run the app.

  9. In the app, click the Test button.

  10. The system presents an alert:

    “Test759287“ wants access to control “Safari“. 
    Allowing control will provide access to documents and 
    data in “Safari“, and to perform actions within that app.
    
    [Don’t Allow] [Allow]
    

    Click Allow.

  11. Xcode prints the AppleScript result:

    <NSAppleEventDescriptor: 'utxt'("https://developer.apple.com/forums/thread/759287")>
    

    In this case my frontmost Safari window is, indeed, this DevForums thread (-:

I testing this with Xcode 15.4 on macOS 14.5.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

func test() {
    let source = """
        tell application "Safari"
            URL of tab 1 of window 1
        end tell
        """
    let script = NSAppleScript(source: source)!
    var errorDict: NSDictionary? = nil
    guard let result = script.executeAndReturnError(&errorDict) as NSAppleEventDescriptor? else {
        print(errorDict!)
        return
    }
    print(result)
}
Accepted Answer

To be clear, you’re not running this in Xcode but using Xcode to build an app that runs this. Right?

If so, there are a number of potential issues. Lemme walk you through a process that works for me:

  1. First run the script in Script Editor to confirm that it works. If it doesn’t work here, you have an AppleScript problem.

  2. Then, in Xcode, create a new project from the macOS > App template.

  3. In Signing & Capabilities, remove App Sandbox. Scripting from a sandboxed app is tricky, so it’s best to start out with the sandbox disabled.

  4. Still in Signing & Capabilities, enable Hardened Runtime > Resource Access > Apple Events. AppleScript is based on Apple events, which the hardened runtime blocks by default.

  5. In the Info tab, add a NSAppleEventsUsageDescription with a privacy string.

    IMPORTANT The property list editor shows this as Privacy - AppleEvents Sending Usage Description.

  6. In ContentView.swift, create a button that calls a test() method.

  7. Add that test() method using the code at the end of this post.

  8. Build and Run the app.

  9. In the app, click the Test button.

  10. The system presents an alert:

    “Test759287“ wants access to control “Safari“. 
    Allowing control will provide access to documents and 
    data in “Safari“, and to perform actions within that app.
    
    [Don’t Allow] [Allow]
    

    Click Allow.

  11. Xcode prints the AppleScript result:

    <NSAppleEventDescriptor: 'utxt'("https://developer.apple.com/forums/thread/759287")>
    

    In this case my frontmost Safari window is, indeed, this DevForums thread (-:

I testing this with Xcode 15.4 on macOS 14.5.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

func test() {
    let source = """
        tell application "Safari"
            URL of tab 1 of window 1
        end tell
        """
    let script = NSAppleScript(source: source)!
    var errorDict: NSDictionary? = nil
    guard let result = script.executeAndReturnError(&errorDict) as NSAppleEventDescriptor? else {
        print(errorDict!)
        return
    }
    print(result)
}

Thanks for your response. I don't know if this question got thru when I marked the response as acceptable, so I will just say TIA and ask again.

What is the cause, and how do I fix/remove the following warnings that are popping up when the app is run in developer mode in code? Error follows:

This method should not be called on the main thread as it may lead to UI unresponsiveness. This method should not be called on the main thread as it may lead to UI unresponsiveness.

(about 7/8 of those lines)

end of errors.

Your insight would be welcome.

Thanks again.

I don't know if this question got thru

It did not )-: Per Quinn’s Top Ten DevForums Tips, it’s better to reply in a reply rather in the comments.

Regarding your follow-up question, the warning is correct: Running an AppleScript on the main thread could block the main thread indefinitely. As to whether that’s a big concern, it kinda depends. For a short script like this, it might be reasonable to block the app for the duration. It’s not a great user experience, but it’s not a particularly terrible one either [1]. And it certainly makes the code a lot easier.

NSAppleScript was historically listed as a main-thread-only class, but that limitation was raised in macOS 10.6. However, the warning from that doc is still relevant: If you’re going to use NSAppleScript from a secondary thread, make sure you serialise that work.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] On the Mac. On iOS that’s a different matter, but AppleScript isn’t available on iOS.

Version 15.4 (15F31d) Xcode NSAppleScript of Safari broken
 
 
Q