Class: ParallelAppium::ParallelAppium

Inherits:
Object
  • Object
show all
Defined in:
lib/parallel_appium.rb

Overview

Set up environment, Selenium and Appium

Instance Method Summary collapse

Constructor Details

#initializeParallelAppium

Returns a new instance of ParallelAppium.



16
17
18
# File 'lib/parallel_appium.rb', line 16

def initialize
  @server = Server.new
end

Instance Method Details

#check_platform(platform) ⇒ Object

Validate platform is valid



120
121
122
123
124
125
126
127
128
129
# File 'lib/parallel_appium.rb', line 120

def check_platform(platform)
  options = %w[ios android all]
  if platform.nil?
    puts 'No platform detected... Options: ios,android,all'
    exit
  elsif !options.include? platform.downcase
    puts "Invalid platform #{platform}"
    exit
  end
end

#execute_specs(platform, threads, spec_path, parallel = false) ⇒ Object

Decide whether to execute specs in parallel or not

Parameters:

  • platform (String)
  • threads (int)
  • spec_path (String)
  • parallel (boolean) (defaults to: false)


94
95
96
97
98
99
100
101
102
103
# File 'lib/parallel_appium.rb', line 94

def execute_specs(platform, threads, spec_path, parallel = false)
  command = if parallel
              "platform=#{platform} parallel_rspec -n #{threads} #{spec_path}"
            else
              "platform=#{platform} rspec #{spec_path} --tag #{platform}"
            end

  puts "Executing #{command}"
  exec command
end

#initialize_appium(**args) ⇒ Object

Load appium text file if available and attempt to start the driver platform is either android or ios, otherwise read from ENV caps is mapping of appium capabilities



47
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
# File 'lib/parallel_appium.rb', line 47

def initialize_appium(**args)
  platform = args[:platform]
  caps = args[:caps]

  platform = ENV['platform'] if platform.nil?

  if platform.nil?
    puts 'Platform not found in environment variable'
    exit
  end

  caps = Appium.load_appium_txt file: File.new("#{Dir.pwd}/appium-#{platform}.txt") if caps.nil?

  if caps.nil?
    puts 'No capabilities specified'
    exit
  end
  puts 'Preparing to load capabilities'
  capabilities = load_capabilities(caps)
  puts 'Loaded capabilities'
  @driver = Appium::Driver.new(capabilities, true)
  puts 'Created driver'
  Appium.promote_appium_methods Object
  Appium.promote_appium_methods RSpec::Core::ExampleGroup
  @driver
end

#kill_process(process) ⇒ Object

Kill process by pattern name



21
22
23
# File 'lib/parallel_appium.rb', line 21

def kill_process(process)
  `ps -ef | grep #{process} | awk '{print $2}' | xargs kill -9 >> /dev/null 2>&1`
end

#load_capabilities(caps) ⇒ Object

Load capabilities based on current device data



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/parallel_appium.rb', line 26

def load_capabilities(caps)
  device = @server.device_data
  unless device.nil?
    caps[:caps][:udid] = device.fetch('udid', nil)
    caps[:caps][:platformVersion] = device.fetch('os', caps[:caps][:platformVersion])
    caps[:caps][:deviceName] = device.fetch('name', caps[:caps][:deviceName])
    caps[:caps][:wdaLocalPort] = device.fetch('wdaPort', nil)
  end

  caps[:caps][:sessionOverride] = true
  caps[:caps][:useNewWDA] = true
  # TODO: Optionally set these capabilities below
  caps[:caps][:noReset] = true
  caps[:caps][:fullReset] = false
  caps[:appium_lib][:server_url] = ENV['SERVER_URL']
  caps
end

#setup(platform, file_path, threads, parallel) ⇒ Object

Define Spec path, validate platform and execute specs



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/parallel_appium.rb', line 106

def setup(platform, file_path, threads, parallel)
  spec_path = 'spec/'
  spec_path = file_path.to_s unless file_path.nil?
  puts "SPEC PATH:#{spec_path}"

  unless %w[ios android].include? platform
    puts "Invalid platform #{platform}"
    exit
  end

  execute_specs platform, threads, spec_path, parallel
end

#setup_signal_handler(ios_pid = nil, android_pid = nil) ⇒ Object

Define a signal handler for SIGINT



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/parallel_appium.rb', line 75

def setup_signal_handler(ios_pid = nil, android_pid = nil)
  Signal.trap('INT') do
    Process.kill('INT', ios_pid) unless ios_pid.nil?
    Process.kill('INT', android_pid) unless android_pid.nil?

    # Kill any existing Appium and Selenium processes
    kill_process 'appium'
    kill_process 'selenium'

    # Terminate ourself
    exit 1
  end
end

#start(**args) ⇒ Object

Fire necessary appium server instances and Selenium grid server if needed.



132
133
134
135
136
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
183
184
185
186
187
188
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
# File 'lib/parallel_appium.rb', line 132

def start(**args)

  platform = args[:platform]
  file_path = args[:file_path]

  # Validate environment variable
  if ENV['platform'].nil?
    if platform.nil?
      puts 'No platform detected in environment and none passed to start...'
      exit
    end
    ENV['platform'] = platform
  end

  sleep 3

  # Appium ports
  ios_port = 4725
  android_port = 4727
  default_port = 4725

  platform = ENV['platform']

  # Platform is required
  check_platform platform

  platform = platform.downcase
  ENV['BASE_DIR'] = Dir.pwd

  # Check if multithreaded for distributing tests across devices
  threads = ENV['THREADS'].nil? ? 1 : ENV['THREADS'].to_i
  parallel = threads != 1

  if platform != 'all'
    pid = fork do
      if !parallel
        ENV['SERVER_URL'] = "http://0.0.0.0:#{default_port}/wd/hub"
        @server.start_single_appium platform, default_port
      else
        ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
        @server.launch_hub_and_nodes platform
      end
      setup(platform, file_path, threads, parallel)
    end

    puts "PID: #{pid}"
    setup_signal_handler(pid)
    Process.waitpid(pid)
  else # Spin off 2 sub-processes, one for Android connections and another for iOS,
    # each with redefining environment variables for the server url, number of threads and platform
    ios_pid = fork do
      ENV['THREADS'] = threads.to_s
      if parallel
        ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
        puts 'Start iOS'
        @server.launch_hub_and_nodes 'ios'
      else
        ENV['SERVER_URL'] = "http://0.0.0.0:#{ios_port}/wd/hub"
        puts 'Start iOS'
        @server.start_single_appium 'ios', ios_port
      end
      setup('ios', file_path, threads, parallel)
    end

    android_pid = fork do
      ENV['THREADS'] = threads.to_s
      if parallel
        ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
        puts 'Start Android'
        @server.launch_hub_and_nodes 'android'
      else
        ENV['SERVER_URL'] = "http://0.0.0.0:#{android_port}/wd/hub"
        puts 'Start Android'
        @server.start_single_appium 'android', android_port
      end
      setup('android', file_path, threads, parallel)
    end

    puts "iOS PID: #{ios_pid}\nAndroid PID: #{android_pid}"
    setup_signal_handler(ios_pid, android_pid)
    [ios_pid, android_pid].each { |process_pid| Process.waitpid(process_pid) }
  end

  # Kill any existing Appium and Selenium processes
  kill_process 'appium'
  kill_process 'selenium'
end