Skip to content

Instantly share code, notes, and snippets.

@heckj
Created April 24, 2020 20:22
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save heckj/5b7bb332463a762639e179a37ea3a216 to your computer and use it in GitHub Desktop.
Save heckj/5b7bb332463a762639e179a37ea3a216 to your computer and use it in GitHub Desktop.

Notes on automating macOS using Javascript (JXA)

Original goal:

  • open a web page and automate the process of selecting "export to PDF..." from that page.

Resources that helped significantly:

Introspecting the application views and UI elements quickly became the hardest component.

I tried inspecting through a number of avenues, including the debugger context that could be opened from Automator, XCode's accessibility inspector, and various archived documentation sources. In the end, what seemed to provide the most context was interactively requesting "what's in the view" from a terminal prompt:

osascript -l JavaScript -i

And then interactively in javascript:

// working with safari's available extensions
var appSafari = Application('Safari');
appSafari.Document().make();
appSafari.windows[0].currentTab.url = "https://apple.com/"
appSafari.activate()

// working through system accessibility to interact with safari
var se = Application('System Events')
var browser = se.processes.byName('Safari')

function clickMenuItem(processName, menuName, menuItemNames, interval) {
    //function logTitles(menuItems) {
    //    for (var item, i = 0, j = menuItems.length; i < j; i++) {
    //        item = menuItems[i];
    //        console.log(item.title());
    //    }
    //}

    interval = interval || 0.5;

    let process = se.processes.byName(processName);

    let menu = process.menuBars[0].menuBarItems.byName(menuName);
    menu.click();
    delay(interval);

    let previousMenu = menu;
    //logTitles(previousMenu.menus[0].menuItems);

    for (let menuItemName of menuItemNames) {
        let menuItem = previousMenu.menus[0].menuItems.byName(menuItemName);
        menuItem.click();
        delay(interval);
        previousMenu = menuItem;
    }
    return previousMenu;
}

clickMenuItem('Safari', 'File', ['Export as PDF…']);
delay(0.5)

// inspection function  from <https://stackoverflow.com/questions/38290690/applescript-iterate-uielements-using-javascript>

function iterate(obj) {
    for (var i in obj) {
        if(obj[i] instanceof Array) {
            iterate(obj[i])
        } else if(obj[i] instanceof Object){
            console.log("Object: " + i + ': name = ' + obj[i].name() + ', value = ' + obj[i].value() + ', class = ' + obj[i].class() + ', description = ' + obj[i].description())
            iterate(obj[i].uiElements())
        }
    }
}

// inspecting - this takes a while (several seconds) and generates a
// LOT of output.
browser.windows[0].entireContents()
// a pre-compiled version of this sort of thing is also available at
// <https://github.com/sancarn/Element-Scripter>

// other introspection tools:
browser.windows[0].properties()
browser.windows[0].attributes()

// get the front window of Safari
var winFront = browser.windows[0]
var frontSheet = winFront.sheets[0]
var saveButton = frontSheet.buttons.byName("Save")
//debugger
saveButton.click()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment