Author: imyzf

This paper will introduce the principle of automatic control of iOS simulator and provide help for the development of front-end debugging scheme based on iOS simulator.

We had a big pain point when developing front-end pages in iOS apps. The pages couldn’t be debugged using tools like Safari Inspector. When we encounter problems, we can only try to add vConsole, or inject Weinre, or blind change, if not, ask the client to manually package and debug, in short, it is very difficult to solve the problem.

After consulting cross-platform frameworks such as RN and Weex, we found that using emulator debugging was a good way to solve this problem. When we put the front end page to run in the emulator App, Apple did not restrict it and allowed us to use Safari Inspector for debugging.

Safari Inspector is a debugging tool similar to Chrome Devtools. It comes with Safari and supports the following functions:

  • Checking page elements
  • Viewing network Requests
  • Breakpoint debugging
  • Storage management (Local Storage, Cookies, etc.)

These features are unmatched by tools such as vConsole and Weinre to help us quickly locate problems.

Based on these principles, we have developed a tool internally, and some of the features can be previewed here. However, due to the deep coupling between the tool and internal business, there is no open source plan at present.

The premise condition

Before introducing this scheme, we need to know the premise of the scheme:

  • Computers with macOS and Xcode: Emulators and Xcode can only run on macOS due to Apple’s restrictions. Xcode can be installed directly in the App Store. It is very simple and requires no other operations.
  • App package built for emulator: Since the emulator is based on x86 architecture, the client developer is required to provide the package built for the emulator, which will be different from the package installed on the phone.
  • App that supports URL Scheme evoking: The App hosting the front-end page must support protocol evoking and opening the page to realize automation with the tool; otherwise, the page can only be opened manually by clicking the relevant link in the App.

The overall process

The overall process of our simulator debugging scheme is as shown in the figure above:

  1. Obtain the device list for the user to select
  2. Check the status of the emulator and start it if it is not started
  3. Check whether the corresponding App is installed. If not, download the installation package to install it
  4. Start the App and open the page to be debugged
  5. Use the appropriate tool for debugging based on the page type (such as Safari Inspector)

Core tools

We implemented the solution based on the following tools:

  • xcrunXcode provides a command-line toolxcrunControl of development-related functions is a collection of tools.
  • simctl:xcrunA subcommand is providedsimctlIt is used to control the simulator, and provides the simulator startup, shutdown, application installation, URL opening and other functions. It can be run directlyxcrun simctlCheck the help documentation.
  • node-simctl: Courtesy of AppiumsimctlJS package of tools. Because front-end schemes are typically developed based on Node.js, you can use the Node-simctl package to more easily control the emulator. But due to thenode-simctlIt only provides partial encapsulation of functionality, which we still need to call manuallyxcrunCommand to achieve more functions.

Simulator control

In this scheme, the most important part is the control of the simulator.

preparation

After installing Xcode through the App Store, users need to agree with Apple’s license for the first time, and then automatically install some components before it can be used normally. For ease of use, we want to automate this process, rather than tell the user to do something after installing Xcode.

If the user runs the xcrun simctl command for the first time, the error message will remind the user to manually run xcodebuild-license to accept the license. Therefore, we can search the xcodebuild-license string in the error message. If it is found, Automatically run the Xcodebuild-license Accept command to help users automatically accept licenses. Note here that you need root permission to run the command, and you can run the command using packages such as sudo-prompt.

Obtaining the Device List

We can directly use the node-simctl getDevices() function to get the list of all devices installed locally. It is more convenient than calling the command line. We can directly get an object without parsing it by ourselves.

{
    '13.4': [{sdk: '13.4'.dataPath: '/Users/xx/Library/Developer/CoreSimulator/Devices/xxx/data'.logPath: '/Users/xx/Library/Logs/xxx'.udid: 'C1AA9736-XXX-YYY-ZZZ-2A4A674B6B21'.isAvailable: true.deviceTypeIdentifier: 'com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max'.state: 'Shutdown'.name: 'iPhone 11 Pro Max'.platform: 'iOS'}]]Copy the code

This includes not only iPhone, but also Devices like Apple Watch and Apple TV. We can iterate over the returned results and filter them through the name field, because generally we only need to debug in iPhone.

Boot device

We can use xcrun simctl bootStatus ${deviceId} to get the status of the device (deviceId is the UDID from the device list above), but if the device is not started, it will wait. Does not exit, so we can use this feature to determine whether the device is started based on whether the command times out (for example, 1000ms no result is returned).

You can then start the corresponding device directly with the xcRUN Instruments -w ${deviceId} command.

Code examples:

let status = ' ';
try {
    status = execSync(
        `xcrun simctl bootstatus ${deviceId}`,
        { timeout: 1000}); }catch (error) {
    // If the emulator is not started, it will wait and then timeout kill, raising an ETIMEDOUT exception
    if(error.code ! = ='ETIMEDOUT') {
        throw error
    }
}
// Check whether it is started
if (status.indexOf('Device already booted') < 0) {
    console.log('Starting the emulator... ')
    execSync(`xcrun instruments -w ${deviceId}`)}Copy the code

Install the App

The installation package of the emulator is a folder with an end name of.app, similar to macOS apps, rather than the.IPA package used for real iPhone installations. Therefore, use tools such as ZIP to package the installation package and upload it to the server. Before installation, download the package to the local PC and decompress it. Use the installApp() method of Node-simctl to install the package.

App check and start

Whether the user has installed the App is actually determined by analyzing the error messages that evoke the App. If the App is not installed, an error will be reported when it is invoked. The error message contains domain=NSOSStatusErrorDomain string, indicating that the App is not installed. At this time, we can call the above installation process.

The most important step in the whole process is how to open our page in the App, which is actually very simple, just need the App itself to support something like CloudMusic ://open? Url = XXX url Scheme. We call Scheme directly through the openUrl() method of Node-simctl, and the simulator will help us start the associated App, and then the App will help us open the page we need to debug according to the Scheme parameters received.

Code examples:

try {
    await simctl.openUrl(deviceId, url)
} catch (error) {
    // If no App is installed, open the protocol and report NSOSStatusErrorDomain
    if (error.message.indexOf('domain=NSOSStatusErrorDomain') > =0) {
        await simctl.installApp(deviceId, appPath)
        await simctl.openUrl(deviceId, url)
    } else {
        throw error
    }
}
Copy the code

Start the debugger

After opening the debug page in the simulator, we can debug RN pages using tools such as the React Native Debugger. For the H5 page, we can open Inspector Debugging from the Safari menu (if there is no “Develop” menu, go to Safari Preferences – Advanced – select the “Develop” menu in the middle line of the menu bar).

Of course, this step can also be automated, requiring Apple Script to search for keywords in the Safari menu and simulate clicking, which is a bit complicated and may not work as the system is upgraded, please refer to some online discussions.

Plan extension

So far, we have seen how to control the simulator to achieve the most basic functions, but we can extend the implementation of the scheme to improve ease of use.

Access CI Service

The client regularly releases new versions with new features, so we also need to keep the debug package to a newer version. Typically, client teams build their own CI services (such as Jenkins) to package, so we can plug in and automatically download and install the latest packages. We can even pull the package list on the CI server to install the historical version, regression debugging some functions.

Note that client teams typically package only for ARM architectures, so x86 build targets need to be added to CI for the build to run successfully on the emulator.

More App support

With the expansion of the company’s business scope, we may need to debug pages in multiple apps. The adaptation of multiple apps can be realized by specifying the following two points:

  1. URL Scheme: You can open pages in different apps by specifying different schemes
  2. Bundle ID: Similarcom.netease.cloudmusicSuch a string is the unique identifier of the App. You can use this ID to start, stop, and uninstall the App

conclusion

So far, we have introduced the basic principle of building a front-end debugging solution based on iOS emulator. Based on the above content, we can combine Commander and Inquirer to develop a set of CLI tools, and can also combine Electron to develop a set of GUI tools to improve the development efficiency. If you have any more ideas or experiences, feel free to share them in the comments section

This article is published from netease Cloud music front end team, the article is prohibited to be reproduced in any form without authorization. We’re always looking for people, so if you’re ready to change jobs and you like cloud music, join us!