Module: Calabash::Cucumber::SimulatorAccessibility

Includes:
PlistBuddy, XcodeTools
Included in:
Launcher
Defined in:
lib/calabash-cucumber/utils/simulator_accessibility.rb

Overview

methods for checking and setting simulator accessibility

Instance Method Summary collapse

Methods included from PlistBuddy

#build_plist_cmd, #execute_plist_cmd, #plist_buddy, #plist_key_exists?, #plist_read, #plist_set

Methods included from XcodeTools

#installed_simulators, #instruments, #instruments_supports_hyphen_s?, #xcode_bin_dir, #xcode_developer_dir

Instance Method Details

#accessibility_properties_hashHash

a hash table of the accessibility properties that control whether or not accessibility is enabled and whether the AXInspector is visible.

Returns:

  • (Hash)

    table of accessibility properties found in the Library/Preferences/com.apple.Accessibility.plist



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 189

def accessibility_properties_hash
  {
        # this is required
        :access_enabled => {:key => 'AccessibilityEnabled',
                            :value => 'true',
                            :type => 'bool'},
        # i _think_ this is legacy
        :app_access_enabled => {:key => 'ApplicationAccessibilityEnabled',
                                :value => 'true',
                                :type => 'bool'},

        # i don't know what this does
        :automation_enabled => {:key => 'AutomationEnabled',
                                :value => 'true',
                                :type => 'bool'},

        # determines if the Accessibility Inspector is showing
        :inspector_showing => {:key => 'AXInspectorEnabled',
                               :value => 'false',
                               :type => 'bool'},

        # controls if the Accessibility Inspector is expanded or not expanded
        :inspector_full_size => {:key => 'AXInspector.enabled',
                                 :value => 'false',
                                 :type => 'bool'},

        # controls the frame of the Accessibility Inspector
        # this is a 'string' => {{0, 0}, {276, 166}}
        :inspector_frame => {:key => 'AXInspector.frame',
                             :value => '{{270, -13}, {276, 166}}',
                             :type => 'string'}
  }
end

#enable_accessibility_in_sdk_dir(sim_app_support_sdk_dir, opts = {}) ⇒ Boolean

enables accessibility on the simulator indicated by sim_app_support_sdk_dir.

WARNING: this will quit the simulator

path = '/6.1'
enable_accessibility_in_sdk_dir(path)

this method also hides the AXInspector.

if the Library/Preferences/com.apple.Accessibility.plist does not exist this method will create a Library/Preferences/com.apple.Accessibility.plist that (oddly) the Simulator will not overwrite.

Parameters:

  • sim_app_support_sdk_dir (String)

    the directory where the Library/Preferences/com.apple.Accessibility.plist can be found.

  • opts (Hash) (defaults to: {})

    controls the behavior of the method

Options Hash (opts):

  • :verbose (Boolean)

    controls logging output

Returns:

  • (Boolean)

    iff the plist exists and the plist was successfully updated.

See Also:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 137

def enable_accessibility_in_sdk_dir(sim_app_support_sdk_dir, opts={})
  default_opts = {:verbose => false}
  merged = default_opts.merge(opts)

  quit_simulator

  verbose = merged[:verbose]
  sdk = File.basename(sim_app_support_sdk_dir)
  msgs = ["cannot enable accessibility for #{sdk} SDK"]

  plist_path = File.expand_path("#{sim_app_support_sdk_dir}/Library/Preferences/com.apple.Accessibility.plist")

  hash = accessibility_properties_hash()

  if File.exist?(plist_path)
    res = hash.map do |hash_key, settings|
      success = plist_set(settings[:key], settings[:type], settings[:value], plist_path)
      unless success
        if verbose
          if settings[:type] == 'bool'
            value = settings[:value] ? 'YES' : 'NO'
          else
            value = settings[:value]
          end
          msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
          calabash_warn(msgs.join("\n"))
        end
      end
      success
    end
    res.all? { |elm| elm }
  else
    FileUtils.mkdir_p("#{sim_app_support_sdk_dir}/Library/Preferences")
    plist = CFPropertyList::List.new
    data = {}
    # CFPropertyList gem is super wonky
    # it matches Boolean to a string type with 'true/false' values
    # - stick with PlistBuddy
    # hash.each do |_, settings|
    #   data[settings[:key]] = settings[:value]
    # end
    plist.value = CFPropertyList.guess(data)
    plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
    enable_accessibility_in_sdk_dir(sim_app_support_sdk_dir)
  end
end

#enable_accessibility_on_simulators(opts = {}) ⇒ Boolean

enables accessibility on any existing iOS Simulator by adjusting the simulator’s Library/Preferences/com.apple.Accessibility.plist contents.

a simulator ‘exists’ if has an Application Support directory. for example, the 6.1, 7.0.3-64, and 7.1 simulators exist if the following directories are present:

~/Library/Application Support/iPhone Simulator/Library/6.1
~/Library/Application Support/iPhone Simulator/Library/7.0.3-64
~/Library/Application Support/iPhone Simulator/Library/7.1

a simulator is ‘possible’ if the SDK is available in the Xcode version.

this method merges (uniquely) the possible and existing SDKs.

this method also hides the AXInspector.

Parameters:

  • opts (Hash) (defaults to: {})

    controls the behavior of the method

Options Hash (opts):

  • :verbose (Boolean)

    controls logging output

Returns:

  • (Boolean)

    true iff enabling accessibility worked on all sdk directories



102
103
104
105
106
107
108
109
110
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 102

def enable_accessibility_on_simulators(opts={})
  possible = possible_simulator_support_sdk_dirs
  existing = existing_simulator_support_sdk_dirs
  dirs = (possible + existing).uniq
  results = dirs.map do |dir|
    enable_accessibility_in_sdk_dir(dir, opts)
  end
  results.all? { |elm| elm }
end

#existing_simulator_support_sdk_dirsObject

returns a list of absolute paths the existing simulator directories.

a simulator ‘exists’ if has an Application Support directory. for example, the 6.1, 7.0.3-64, and 7.1 simulators exist if the following directories are present:

~/Library/Application Support/iPhone Simulator/Library/6.1
~/Library/Application Support/iPhone Simulator/Library/7.0.3-64
~/Library/Application Support/iPhone Simulator/Library/7.1

@return a list of absolute paths to simulator directories



247
248
249
250
251
252
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 247

def existing_simulator_support_sdk_dirs
  sim_app_support_path = simulator_app_support_dir()
  Dir.glob("#{sim_app_support_path}/*").select { |path|
    path =~ /(\d)\.(\d)\.?(\d)?(-64)?/
  }
end

#launch_simulatorObject

launches the iOS Simulator indicated by xcode-select or DEVELOPER_DIR



33
34
35
36
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 33

def launch_simulator
  dev_dir = xcode_developer_dir
  system "open -a \"#{dev_dir}/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app\""
end

#plist_path_with_sdk_dir(sdk_dir) ⇒ String

the absolute path to the SDK’s com.apple.Accessibility.plist file

Parameters:

  • sdk_dir (String)

    base path the SDK directory

Returns:

  • (String)

    an absolute path



232
233
234
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 232

def plist_path_with_sdk_dir(sdk_dir)
  File.expand_path("#{sdk_dir}/Library/Preferences/com.apple.Accessibility.plist")
end

#possible_simulator_sdksArray<String>

returns a list of possible SDKs per Xcode version

it is not enough to ask for the available sdks because of the new 64-bit variants that started to appear Xcode 5 and the potential for patch level versions.

unfortunately, this method will need be maintained per Xcode version.

Returns:

  • (Array<String>)

    ex. [‘6.1’, ‘7.1’, ‘7.0.3’, ‘7.0.3-64’]



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 263

def possible_simulator_sdks
  sdk_detector = SimLauncher::SdkDetector.new
  available = sdk_detector.available_sdk_versions
  instruments_version = instruments(:version)

  # in Xcode 5.1* SDK 7.0 ==> 7.0.3
  if (instruments_version == '5.1' or instruments_version == '5.1.1') and available.include?('7.0')
    available << '7.0.3'
    available << '7.0.3-64'
  end

  if instruments_version == '5.1.1' and available.include?('7.1')
    available << '7.1'
    available << '7.1-64'
  end

  gem_compat_xcode_versions = ['5.1', '5.1.1']
  unless gem_compat_xcode_versions.include?(instruments_version)
    msg = ["expected Xcode instruments version to be '5.1' or '5.1.1'",
           "but found version '#{instruments_version}'",
           "Gem needs a manual update for Xcode #{instruments_version}!"].join("\n")
    calabash_warn(msg)
  end

  # in Xcode 5.1* SDK 7.0 ==> 7.0.3 so we should not include '7.0'
  # if the user's support directory already contains a 7.0 and 7.0-64 dir
  # we will detect it by reading from disk.
  (available - ['7.0']).uniq.sort
end

#possible_simulator_support_sdk_dirsArray<String>

return absolute paths to possible simulator support sdk dirs

these directories may or may not exist

Returns:

  • (Array<String>)

    an array of absolute paths



297
298
299
300
301
302
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 297

def possible_simulator_support_sdk_dirs
  base_dir = simulator_app_support_dir
  possible_simulator_sdks.map { |sdk|
    "#{base_dir}/#{sdk}"
  }
end

#quit_simulatorObject

quits the iOS Simulator

ATM there can only be only simulator open at a time, so simply doing what the sim_launcher gem does:

def quit_simulator
  `echo 'application "iPhone Simulator" quit' | osascript`
end

works. I am not sure if we will ever be able to launch more than one simulator, but in case we can, this method will quit the simulator that is indicated by xcode-select or DEVELOPER_DIR.



27
28
29
30
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 27

def quit_simulator
  dev_dir = xcode_developer_dir
  system "/usr/bin/osascript -e 'tell application \"#{dev_dir}/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app\" to quit'"
end

#reset_simulator_content_and_settingsObject

resets the simulator content and settings. it is analogous to touching the menu item.

it works by deleting the following directories:

  • ~/Library/Application Support/iPhone Simulator/Library

  • ~/Library/Application Support/iPhone Simulator/Library/<sdk>

and relaunching the iOS Simulator which will recreate the Library directory and the latest SDK directory.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 48

def reset_simulator_content_and_settings
  quit_simulator
  sim_lib_path = File.join(simulator_app_support_dir(), 'Library')
  FileUtils.rm_rf(sim_lib_path)
  existing_simulator_support_sdk_dirs.each do |dir|
    FileUtils.rm_rf(dir)
  end

  launch_simulator

  # this is tricky because we need to wait for the simulator to recreate
  # the directories.  specifically, we need the Accessibility plist to be
  # exist so subsequent calabash launches will be able to enable
  # accessibility.
  #
  # the directories take ~3.0 - ~5.0 to create.
  counter = 0
  loop do
    break if counter == 80
    dirs = existing_simulator_support_sdk_dirs
    if dirs.count == 0
      sleep(0.2)
    else
      break if dirs.all? { |dir|
        plist = File.expand_path("#{dir}/Library/Preferences/com.apple.Accessibility.plist")
        File.exists?(plist)
      }
      sleep(0.2)
    end
    counter = counter + 1
  end
end

#simulator_app_support_dirString

the absolute path to the iPhone Simulator Application Support directory

Returns:

  • (String)

    absolute path



225
226
227
# File 'lib/calabash-cucumber/utils/simulator_accessibility.rb', line 225

def simulator_app_support_dir
  File.expand_path('~/Library/Application Support/iPhone Simulator')
end