Automating Provisioning Profile Refreshes in Xcode

Refreshing provisioning profiles is a pain. Okay, everything about provisioning profiles is a pain. This pain is amplified once you add a continuous integration environment into the mix. When you add to that a white-label platform like ours with a ton of apps building in it, things get very frustrating. For every app we add to the system, not only do we need to provision everything properly in the developer portal, but we also need to have someone remote into the Mac build agent, open Xcode, and refresh the profiles so we can package the new app. This sucks.

Side Note: if you know about a secret command line utility that can do this, please let me know. My efforts to locate one have been unsuccessful thus far.

So, without the ability to hit a command line utility to trigger this refresh I set out to try the next best thing: automate the UI interaction. I'd never actually tried automating any UI in OS X, so I figured it would make for a fun experiment. I wouldn't say the whole process could be classified as "fun" in the end, but at least I get a blog post and some automation out of it.

In Yosemite, Apple introduced the ability to use JavaScript instead of AppleScript, so I gave that a shot. There are a couple ways you can try it out. First, there's the Script Editor app that ships in OS X that you can use to write and run your scripts, as well as export them either as script bundles or even .app files. You can also use the command line REPL by running:

osascript -il JavaScript  

Another essential utility to be aware of is the Accessibility Inspector. It allows you to hover over the UI of any running app and see the view hierarchy, as well as many properties of the elements there:

Accessibility Inspector

Armed with these tools, I was able to hack together the following script. They key word there is hack. This is pretty ugly, and I'd love to know cleaner ways to get at some of these elements, but for the time being at least it works!

Application("Xcode").activate();

delay(2);

var system = Application("System Events"),  
    xcode = system.processes["Xcode"];

// open preferences
system.keystroke(",", { "using": "command down" });

// click on Accounts tab
xcode.windows[0].toolbars[0].uiElements[1].click();

var accountRows = xcode.windows[0].scrollAreas[0].tables[0].rows,  
    currentRow,
    rowName;

for (var accountIndex = 1; accountIndex < accountRows.length; accountIndex++) {  
    currentRow = accountRows[accountIndex];
    rowName = currentRow.uiElements()[0].name();

    if (rowName === "Repositories") {
        break;
    }

    currentRow.select();
    delay(1);

    // click "View Details"
    xcode.windows[0].groups[0].buttons[0].click();
    delay(1);

    // click "Refresh"
    xcode.windows[0].sheets[0].buttons[1].click();
    delay(5);

    // click "Done"
    xcode.windows[0].sheets[0].buttons[0].click();
    delay(1);
}

A bit ugly, and undoubtedly brittle, but not much code in the end. I put some artificial delays in there to allow the UI time to catch up, and in future versions I'd love to figure out how to check the status of the progress indicator rather than just pausing for 5-10 seconds and then assuming it completed.

Instead of exporting it as a .app file and running it that way, I decided to just execute the script file directly using the same utility mentioned earlier for the REPL:

osascript -l JavaScript RefreshXcodeProfiles.scpt  

Since this can be invoked from the command line, it was trivial to incorporate it into our FAKE scripts:

Refreshing in action

Automate all the things!

comments powered by Disqus
Navigation