parallel_appium 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +3 -0
- data/.gitignore +457 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +674 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/apps/.gitignore +3 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/certs/javondavis.pem +27 -0
- data/docs/.gitignore +0 -0
- data/docs/_config.yml +1 -0
- data/exe/parallel_appium +4 -0
- data/lib/parallel_appium/android.rb +35 -0
- data/lib/parallel_appium/ios.rb +30 -0
- data/lib/parallel_appium/selenium-server-standalone-3.12.0.jar +0 -0
- data/lib/parallel_appium/server.rb +164 -0
- data/lib/parallel_appium/version.rb +3 -0
- data/lib/parallel_appium.rb +218 -0
- data/parallel_appium.gemspec +52 -0
- data.tar.gz.sig +0 -0
- metadata +200 -0
- metadata.gz.sig +0 -0
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# parallel_appium
|
2
|
+
|
3
|
+
Distributed mobile testing in Appium
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'parallel_appium'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install parallel_appium
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Development
|
26
|
+
|
27
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
28
|
+
|
29
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/JavonDavis/parallel_appium. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
34
|
+
|
35
|
+
## License
|
36
|
+
|
37
|
+
The gem is available as open source under the terms of the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html).
|
38
|
+
|
39
|
+
## Code of Conduct
|
40
|
+
|
41
|
+
Everyone interacting in the ParallelAppium project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/JavonDavis/parallel_appium/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/apps/.gitignore
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'parallel_appium'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEhTCCAu2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBEMRYwFAYDVQQDDA1qYXZv
|
3
|
+
bmxkYXZpczE0MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
4
|
+
FgNjb20wHhcNMTgwNjA2MTcxNjM2WhcNMTkwNjA2MTcxNjM2WjBEMRYwFAYDVQQD
|
5
|
+
DA1qYXZvbmxkYXZpczE0MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJ
|
6
|
+
k/IsZAEZFgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDCwCJw
|
7
|
+
CoVPoQgQSDGGGRWyt40oMNZHFc85AiRjBU7UMFgYSPQIt0M+SCyJaptM0pJYGG65
|
8
|
+
M/hx4Hnqw93ELvPWtAAHgP/9u05K3uiWgsxFRHm5S9JxMS/nvNRatVYvy2hKQGTU
|
9
|
+
zJpczncAhu0Lj9tlCuZMdvnhh9PAInrNaXQMkkk4n/XV9dk3lhyqemMFyKJhS0dO
|
10
|
+
k3JTn3muVdSOdLMOGJtreHdV5TByA/rIYQGraIqHrlbAbnGOnNunMk53M9eI3zAq
|
11
|
+
gz8fydEkaHswv7Mbk3kWnN67UMaNUpoElnEEj0242BtXhXeXr9FnqcFLwUrQLXKK
|
12
|
+
kc2BjV1CbK7AHMKLfU2yBQJVqEkb+Xu8dAkpHP333adf7KEW8N0vf+639jgBPbxa
|
13
|
+
y25Sh4ZQAuue1NVl43Uj7Qkbb2V4epiNr1JhRrpvMSQah2lx9yNs/NtgdZWHtidd
|
14
|
+
+3chmKUroK4elci3NKa76G+iwbD/pJkWg0zFt4cg4+pgWdmjX3PLnd0iv2UCAwEA
|
15
|
+
AaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSjN7L+WZRc
|
16
|
+
/FBz01MZd7lsGI5j7jAiBgNVHREEGzAZgRdqYXZvbmxkYXZpczE0QGdtYWlsLmNv
|
17
|
+
bTAiBgNVHRIEGzAZgRdqYXZvbmxkYXZpczE0QGdtYWlsLmNvbTANBgkqhkiG9w0B
|
18
|
+
AQsFAAOCAYEAoxRokaBO+VS8xqmPtqEKofcXF2kJDemn9Jn9Begjj7mzqUVHOR9w
|
19
|
+
4HHaAY79m1xm+yiveWgOsHvb04GCjR32dJjAcbb7dsg3aXOd49UoQ0lOVtOigH2s
|
20
|
+
v5BqR5svvPKoDA8nP0V9pTOxFboJw5dtY+s95Py6mc6nTsK3XdCUS80PiY3xPWS+
|
21
|
+
pCzJWE1mNMhRndpb9E0BAiimdmo3suQo7EVYQ3tPsnHwaSdv52tt9kW+rpoHFRV5
|
22
|
+
gjyXjRGX/lW897Zs2AA7yFb7oVvbaAlGyH2MLVnB5aiTywqE0ZgD9CTgSnGYu0iY
|
23
|
+
28Uiprdx/AqGsAVNV8YAsPMEm/81xTXmJ0HRrpyvMHyAXuNPp84t4TsO2DQtAlrU
|
24
|
+
XuXlmerWWuxSKZOwV9fJfl+j5fO7bzUyeWK76xvvWGNtIF19xPnGdPpupaClWMHg
|
25
|
+
pjBkMT4ow4ZhVgBKPDT2jh74b8g2rIDSbkJIj0lf35WXqJqZzseabGPpTbtzJEap
|
26
|
+
HIrd7mPILLe3
|
27
|
+
-----END CERTIFICATE-----
|
data/docs/.gitignore
ADDED
File without changes
|
data/docs/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-cayman
|
data/exe/parallel_appium
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module ParallelAppium
|
2
|
+
# Connecting to Android devices
|
3
|
+
class Android
|
4
|
+
# Fire up the Android emulators
|
5
|
+
def start_emulators
|
6
|
+
emulators = `emulator -list-avds`.split("\n")
|
7
|
+
emulators = emulators[0, ENV['THREADS'].to_i]
|
8
|
+
Parallel.map(emulators, in_threads: emulators.size) do |emulator|
|
9
|
+
spawn("emulator -avd #{emulator} -scale 100dpi -no-boot-anim -no-audio -accel on &", out: '/dev/null')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get additional information for the Android device with unique identifier udid
|
14
|
+
def get_android_device_data(udid)
|
15
|
+
specs = { os: 'ro.build.version.release', manufacturer: 'ro.product.manufacturer', model: 'ro.product.model', sdk: 'ro.build.version.sdk' }
|
16
|
+
hash = {}
|
17
|
+
specs.each do |key, spec|
|
18
|
+
value = `adb -s #{udid} shell getprop "#{spec}"`.strip
|
19
|
+
hash.merge!(key => value.to_s)
|
20
|
+
end
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
# Devices after cleanup and supplemental data included
|
25
|
+
def devices
|
26
|
+
start_emulators
|
27
|
+
sleep 10
|
28
|
+
devices = `adb devices`.split("\n").select { |x| x.include? "\tdevice" }.map.each_with_index { |d, i| {platform: 'android', name: 'android', udid: d.split("\t")[0], wdaPort: 8100 + i, thread: i + 1} }
|
29
|
+
devices = devices.map { |x| x.merge(get_android_device_data(x[:udid])) }
|
30
|
+
|
31
|
+
ENV['DEVICES'] = JSON.generate(devices)
|
32
|
+
devices
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ParallelAppium
|
2
|
+
# Connecting to iOS devices
|
3
|
+
class IOS
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Get available simulators
|
7
|
+
@simulators = `instruments -s devices`.split("\n").reverse
|
8
|
+
end
|
9
|
+
|
10
|
+
# Filter simulator data
|
11
|
+
def simulator_information
|
12
|
+
re = /\([0-9]+\.[0-9]\) \[[0-9A-Z-]+\]/m
|
13
|
+
|
14
|
+
# Filter out simulator info for iPhone platform version and udid
|
15
|
+
@simulators.select { |simulator_data| simulator_data.include?('iPhone') && !simulator_data.include?('Apple Watch') }
|
16
|
+
.map { |simulator_data| simulator_data.scan(re)[0].tr('()[]', '').split }[0, ENV['THREADS'].to_i]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Devices after cleanup and supplemental data included
|
20
|
+
def devices
|
21
|
+
devices = []
|
22
|
+
simulator_information.each_with_index do |data, i|
|
23
|
+
devices.push(name: @simulators[i][0, @simulators[i].index('(') - 1], platform: 'ios', os: data[0], udid: data[1],
|
24
|
+
wdaPort: 8100 + i + ENV['THREADS'].to_i, thread: i + 1)
|
25
|
+
end
|
26
|
+
ENV['DEVICES'] = JSON.generate(devices)
|
27
|
+
devices
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
Binary file
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module ParallelAppium
|
2
|
+
# Setting up the selenium grid server
|
3
|
+
class Server
|
4
|
+
# Sets the current thread number environment variable(TEST_ENV_NUMBER)
|
5
|
+
def thread
|
6
|
+
(ENV['TEST_ENV_NUMBER'].nil? || ENV['TEST_ENV_NUMBER'].empty? ? 1 : ENV['TEST_ENV_NUMBER']).to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the device data from the DEVICES environment variable
|
10
|
+
def device_data
|
11
|
+
JSON.parse(ENV['DEVICES']).find { |t| t['thread'].eql? thread } unless ENV['DEVICES'].nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
# Save device specifications to output directory
|
15
|
+
def save_device_data(dev_array)
|
16
|
+
dev_array.each do |device|
|
17
|
+
device_hash = {}
|
18
|
+
device.each do |key, value|
|
19
|
+
device_hash[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delete and create output folder
|
23
|
+
`rm -rf output`
|
24
|
+
`mkdir output`
|
25
|
+
|
26
|
+
device.each do |k, v|
|
27
|
+
open("output/specs-#{device_hash[:udid]}.log", 'a') do |file|
|
28
|
+
file << "#{k}: #{v}\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set UDID and name environment variable
|
35
|
+
def set_udid_environment_variable
|
36
|
+
ENV['UDID'] = device_data['udid'] unless device_data.nil?
|
37
|
+
ENV['name'] = device_data['name'] unless device_data.nil? # Unique on ios but could be repeated on android
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get the device information for the respective platform
|
41
|
+
def get_devices(platform)
|
42
|
+
ENV['THREADS'] = '1' if ENV['THREADS'].nil?
|
43
|
+
if platform == 'android'
|
44
|
+
Android.new.devices
|
45
|
+
elsif platform == 'ios'
|
46
|
+
IOS.new.devices
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Start the appium server with the specified options
|
51
|
+
def appium_server_start(**options)
|
52
|
+
command = +'appium'
|
53
|
+
command << " --nodeconfig #{options[:config]}" if options.key?(:config)
|
54
|
+
command << " -p #{options[:port]}" if options.key?(:port)
|
55
|
+
command << " -bp #{options[:bp]}" if options.key?(:bp)
|
56
|
+
command << " --log #{Dir.pwd}/output/#{options[:log]}" if options.key?(:log)
|
57
|
+
command << " --tmp #{ENV['BASE_DIR']}/tmp/#{options[:tmp]}" if options.key?(:tmp)
|
58
|
+
Dir.chdir('.') do
|
59
|
+
puts(command)
|
60
|
+
pid = spawn(command, out: '/dev/null')
|
61
|
+
puts 'Waiting for Appium to start up...'
|
62
|
+
sleep 10
|
63
|
+
puts "Appium PID: #{pid}"
|
64
|
+
puts 'Appium server did not start' if pid.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Generate node config for sellenium grid
|
69
|
+
def generate_node_config(file_name, appium_port, device)
|
70
|
+
system 'mkdir node_configs >> /dev/null 2>&1'
|
71
|
+
f = File.new("#{Dir.pwd}/node_configs/#{file_name}", 'w')
|
72
|
+
f.write(JSON.generate(
|
73
|
+
capabilities: [{ browserName: device[:udid], maxInstances: 5, platform: device[:platform] }],
|
74
|
+
configuration: { cleanUpCycle: 2000,
|
75
|
+
timeout: 1_800_000,
|
76
|
+
registerCycle: 5000,
|
77
|
+
proxy: 'org.openqa.grid.selenium.proxy.DefaultRemoteProxy',
|
78
|
+
url: "http://127.0.0.1:#{appium_port}/wd/hub",
|
79
|
+
host: '127.0.0.1',
|
80
|
+
port: appium_port,
|
81
|
+
maxSession: 5,
|
82
|
+
register: true,
|
83
|
+
hubPort: 4444,
|
84
|
+
hubHost: 'localhost' }
|
85
|
+
))
|
86
|
+
f.close
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start the Selenium grid server as a hub
|
90
|
+
def start_hub
|
91
|
+
spawn("java -jar #{File.dirname(__FILE__)}/selenium-server-standalone-3.12.0.jar -role hub -newSessionWaitTimeout 250000 -log #{Dir.pwd}/output/hub.log &", out: '/dev/null')
|
92
|
+
sleep 3 # wait for hub to start...
|
93
|
+
spawn('open -a safari http://127.0.0.1:4444/grid/console')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Start an appium server or the platform on the specified port
|
97
|
+
def start_single_appium(platform, port)
|
98
|
+
puts 'Getting Device data'
|
99
|
+
devices = get_devices(platform)[0]
|
100
|
+
if devices.nil?
|
101
|
+
puts "No devices for #{platform}, Exiting..."
|
102
|
+
exit
|
103
|
+
else
|
104
|
+
udid = devices[:udid]
|
105
|
+
save_device_data [devices]
|
106
|
+
end
|
107
|
+
ENV['UDID'] = udid
|
108
|
+
appium_server_start udid: udid, log: "appium-#{udid}.log", port: port
|
109
|
+
end
|
110
|
+
|
111
|
+
# Check if a port on an ip address is available
|
112
|
+
def port_open?(ip, port)
|
113
|
+
begin
|
114
|
+
Timeout.timeout(1) do
|
115
|
+
begin
|
116
|
+
s = TCPSocket.new(ip, port)
|
117
|
+
s.close
|
118
|
+
return true
|
119
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
rescue Timeout::Error
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Launch the Selenium grid hub and required appium instances
|
130
|
+
def launch_hub_and_nodes(platform)
|
131
|
+
start_hub unless port_open?('localhost', 4444)
|
132
|
+
devices = get_devices(platform)
|
133
|
+
|
134
|
+
if devices.nil?
|
135
|
+
puts "No devices for #{platform}, Exiting...."
|
136
|
+
exit
|
137
|
+
else
|
138
|
+
save_device_data [devices]
|
139
|
+
end
|
140
|
+
|
141
|
+
threads = ENV['THREADS'].to_i
|
142
|
+
if devices.size < threads
|
143
|
+
puts "Not enough available devices, reducing to #{devices.size} threads"
|
144
|
+
ENV['THREADS'] = devices.size.to_s
|
145
|
+
else
|
146
|
+
puts "Using #{threads} of the available #{devices.size} devices"
|
147
|
+
devices = devices[0, threads]
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
Parallel.map_with_index(devices, in_processes: devices.size) do |device, index|
|
152
|
+
offset = platform == 'android' ? 0 : threads
|
153
|
+
port = 4000 + index + offset
|
154
|
+
bp = 2250 + index + offset
|
155
|
+
config_name = "#{device[:udid]}.json"
|
156
|
+
generate_node_config config_name, port, device
|
157
|
+
node_config = "#{Dir.pwd}/node_configs/#{config_name}"
|
158
|
+
puts port
|
159
|
+
appium_server_start config: node_config, port: port, bp: bp, udid: device[:udid],
|
160
|
+
log: "appium-#{device[:udid]}.log", tmp: device[:udid]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'parallel_appium/version'
|
2
|
+
require 'parallel_appium/server'
|
3
|
+
require 'parallel_appium/android'
|
4
|
+
require 'parallel_appium/ios'
|
5
|
+
require 'parallel_tests'
|
6
|
+
require 'parallel'
|
7
|
+
require 'appium_lib'
|
8
|
+
require 'socket'
|
9
|
+
require 'timeout'
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
module ParallelAppium
|
13
|
+
# Set up environment, Selenium and Appium
|
14
|
+
class ParallelAppium
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@server = Server.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# Kill process by pattern name
|
21
|
+
def kill_process(process)
|
22
|
+
`ps -ef | grep #{process} | awk '{print $2}' | xargs kill -9 >> /dev/null 2>&1`
|
23
|
+
end
|
24
|
+
|
25
|
+
# Load capabilities based on current device data
|
26
|
+
def load_capabilities(caps)
|
27
|
+
device = @server.device_data
|
28
|
+
unless device.nil?
|
29
|
+
caps[:caps][:udid] = device.fetch('udid', nil)
|
30
|
+
caps[:caps][:platformVersion] = device.fetch('os', caps[:caps][:platformVersion])
|
31
|
+
caps[:caps][:deviceName] = device.fetch('name', caps[:caps][:deviceName])
|
32
|
+
caps[:caps][:wdaLocalPort] = device.fetch('wdaPort', nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
caps[:caps][:sessionOverride] = true
|
36
|
+
caps[:caps][:useNewWDA] = true
|
37
|
+
# TODO: Optionally set these capabilities below
|
38
|
+
caps[:caps][:noReset] = true
|
39
|
+
caps[:caps][:fullReset] = false
|
40
|
+
caps[:appium_lib][:server_url] = ENV['SERVER_URL']
|
41
|
+
caps
|
42
|
+
end
|
43
|
+
|
44
|
+
# Load appium text file if available and attempt to start the driver
|
45
|
+
# platform is either android or ios, otherwise read from ENV
|
46
|
+
# caps is mapping of appium capabilities
|
47
|
+
def initialize_appium(**args)
|
48
|
+
platform = args[:platform]
|
49
|
+
caps = args[:caps]
|
50
|
+
|
51
|
+
platform = ENV['platform'] if platform.nil?
|
52
|
+
|
53
|
+
if platform.nil?
|
54
|
+
puts 'Platform not found in environment variable'
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
caps = Appium.load_appium_txt file: File.new("#{Dir.pwd}/appium-#{platform}.txt") if caps.nil?
|
59
|
+
|
60
|
+
if caps.nil?
|
61
|
+
puts 'No capabilities specified'
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
capabilities = load_capabilities(caps)
|
65
|
+
@driver = Appium::Driver.new(capabilities, true)
|
66
|
+
@driver.start_driver
|
67
|
+
Appium.promote_appium_methods Object
|
68
|
+
Appium.promote_appium_methods RSpec::Core::ExampleGroup
|
69
|
+
@driver
|
70
|
+
end
|
71
|
+
|
72
|
+
# Define a signal handler for SIGINT
|
73
|
+
def setup_signal_handler(ios_pid = nil, android_pid = nil)
|
74
|
+
Signal.trap('INT') do
|
75
|
+
Process.kill('INT', ios_pid) unless ios_pid.nil?
|
76
|
+
Process.kill('INT', android_pid) unless android_pid.nil?
|
77
|
+
|
78
|
+
# Kill any existing Appium and Selenium processes
|
79
|
+
kill_process 'appium'
|
80
|
+
kill_process 'selenium'
|
81
|
+
|
82
|
+
# Terminate ourself
|
83
|
+
exit 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Decide whether to execute specs in parallel or not
|
88
|
+
# @param [String] platform
|
89
|
+
# @param [int] threads
|
90
|
+
# @param [String] spec_path
|
91
|
+
# @param [boolean] parallel
|
92
|
+
def execute_specs(platform, threads, spec_path, parallel = false)
|
93
|
+
command = if parallel
|
94
|
+
"platform=#{platform} parallel_rspec -n #{threads} #{spec_path} > output/#{platform}.log"
|
95
|
+
else
|
96
|
+
"platform=#{platform} rspec #{spec_path} --tag #{platform} > output/#{platform}.log"
|
97
|
+
end
|
98
|
+
|
99
|
+
puts "Executing #{command}"
|
100
|
+
exec command
|
101
|
+
end
|
102
|
+
|
103
|
+
# Define Spec path, validate platform and execute specs
|
104
|
+
def setup(platform, file_path, threads, parallel)
|
105
|
+
spec_path = 'spec/'
|
106
|
+
spec_path = file_path.to_s unless file_path.nil?
|
107
|
+
puts "SPEC PATH:#{spec_path}"
|
108
|
+
|
109
|
+
unless %w[ios android].include? platform
|
110
|
+
puts "Invalid platform #{platform}"
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
|
114
|
+
execute_specs platform, threads, spec_path, parallel
|
115
|
+
end
|
116
|
+
|
117
|
+
# Validate platform is valid
|
118
|
+
def check_platform(platform)
|
119
|
+
options = %w[ios android all]
|
120
|
+
if platform.nil?
|
121
|
+
puts 'No platform detected... Options: ios,android,all'
|
122
|
+
exit
|
123
|
+
elsif !options.include? platform.downcase
|
124
|
+
puts "Invalid platform #{platform}"
|
125
|
+
exit
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Fire necessary appium server instances and Selenium grid server if needed.
|
130
|
+
def start(**args)
|
131
|
+
|
132
|
+
platform = args[:platform]
|
133
|
+
file_path = args[:file_path]
|
134
|
+
|
135
|
+
# Validate environment variable
|
136
|
+
if ENV['platform'].nil?
|
137
|
+
if platform.nil?
|
138
|
+
puts 'No platform detected in environment and none passed to start...'
|
139
|
+
exit
|
140
|
+
end
|
141
|
+
ENV['platform'] = platform
|
142
|
+
end
|
143
|
+
|
144
|
+
sleep 3
|
145
|
+
|
146
|
+
# Appium ports
|
147
|
+
ios_port = 4725
|
148
|
+
android_port = 4727
|
149
|
+
default_port = 4725
|
150
|
+
|
151
|
+
platform = ENV['platform']
|
152
|
+
|
153
|
+
# Platform is required
|
154
|
+
check_platform platform
|
155
|
+
|
156
|
+
platform = platform.downcase
|
157
|
+
ENV['BASE_DIR'] = Dir.pwd
|
158
|
+
|
159
|
+
# Check if multithreaded for distributing tests across devices
|
160
|
+
threads = ENV['THREADS'].nil? ? 1 : ENV['THREADS'].to_i
|
161
|
+
parallel = threads != 1
|
162
|
+
|
163
|
+
if platform != 'all'
|
164
|
+
pid = fork do
|
165
|
+
if !parallel
|
166
|
+
ENV['SERVER_URL'] = "http://0.0.0.0:#{default_port}/wd/hub"
|
167
|
+
@server.start_single_appium platform, default_port
|
168
|
+
else
|
169
|
+
ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
|
170
|
+
@server.launch_hub_and_nodes platform
|
171
|
+
end
|
172
|
+
setup(platform, file_path, threads, parallel)
|
173
|
+
end
|
174
|
+
|
175
|
+
puts "PID: #{pid}"
|
176
|
+
setup_signal_handler(pid)
|
177
|
+
Process.waitpid(pid)
|
178
|
+
else # Spin off 2 sub-processes, one for Android connections and another for iOS,
|
179
|
+
# each with redefining environment variables for the server url, number of threads and platform
|
180
|
+
ios_pid = fork do
|
181
|
+
ENV['THREADS'] = threads.to_s
|
182
|
+
if parallel
|
183
|
+
ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
|
184
|
+
puts 'Start iOS'
|
185
|
+
@server.launch_hub_and_nodes 'ios'
|
186
|
+
else
|
187
|
+
ENV['SERVER_URL'] = "http://0.0.0.0:#{ios_port}/wd/hub"
|
188
|
+
puts 'Start iOS'
|
189
|
+
@server.start_single_appium 'ios', ios_port
|
190
|
+
end
|
191
|
+
setup('ios', file_path, threads, parallel)
|
192
|
+
end
|
193
|
+
|
194
|
+
android_pid = fork do
|
195
|
+
ENV['THREADS'] = threads.to_s
|
196
|
+
if parallel
|
197
|
+
ENV['SERVER_URL'] = 'http://localhost:4444/wd/hub'
|
198
|
+
puts 'Start Android'
|
199
|
+
@server.launch_hub_and_nodes 'android'
|
200
|
+
else
|
201
|
+
ENV['SERVER_URL'] = "http://0.0.0.0:#{android_port}/wd/hub"
|
202
|
+
puts 'Start Android'
|
203
|
+
@server.start_single_appium 'android', android_port
|
204
|
+
end
|
205
|
+
setup('android', file_path, threads, parallel)
|
206
|
+
end
|
207
|
+
|
208
|
+
puts "iOS PID: #{ios_pid}\nAndroid PID: #{android_pid}"
|
209
|
+
setup_signal_handler(ios_pid, android_pid)
|
210
|
+
[ios_pid, android_pid].each { |process_pid| Process.waitpid(process_pid) }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Kill any existing Appium and Selenium processes
|
214
|
+
kill_process 'appium'
|
215
|
+
kill_process 'selenium'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path('lib', __dir__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'parallel_appium/version'
|
5
|
+
|
6
|
+
summary = 'Start multi-threaded appium servers'
|
7
|
+
description = 'Through the creation of multiple environments
|
8
|
+
this library allows for the distribution of multiple tests across 1 or more
|
9
|
+
devices or simulators. The library uses Selenium Grid and Appium to launch a
|
10
|
+
specified number of web driver and appium instances to spread the test load
|
11
|
+
across available devices'
|
12
|
+
|
13
|
+
Gem::Specification.new do |spec|
|
14
|
+
spec.name = 'parallel_appium'
|
15
|
+
spec.version = ParallelAppium::VERSION
|
16
|
+
spec.authors = ['Javon Davis']
|
17
|
+
spec.email = ['javonldavis14@gmail.com']
|
18
|
+
|
19
|
+
spec.summary = summary
|
20
|
+
spec.description = description
|
21
|
+
spec.homepage = 'https://github.com/JavonDavis/Parallel_Appium'
|
22
|
+
spec.license = 'GPL-3.0'
|
23
|
+
|
24
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
25
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
26
|
+
# if spec.respond_to?(:metadata)
|
27
|
+
# spec.metadata['allowed_push_host'] = "http://mygemserver.com"
|
28
|
+
# else
|
29
|
+
# raise 'RubyGems 2.4.1 or newer is required to protect against ' \
|
30
|
+
# 'public gem pushes.'
|
31
|
+
# end
|
32
|
+
|
33
|
+
# Specify which files should be added to the gem when it is released.
|
34
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
35
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
36
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
37
|
+
end
|
38
|
+
spec.bindir = 'exe'
|
39
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
40
|
+
spec.require_paths = ['lib']
|
41
|
+
|
42
|
+
spec.cert_chain = ['certs/javondavis.pem']
|
43
|
+
spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if $PROGRAM_NAME =~ /gem\z/
|
44
|
+
|
45
|
+
spec.add_development_dependency 'appium_lib', '~> 9.14'
|
46
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
47
|
+
spec.add_development_dependency 'json', '~> 1.8'
|
48
|
+
spec.add_development_dependency 'parallel', '~> 1.12'
|
49
|
+
spec.add_development_dependency 'parallel_tests', '~> 2.21'
|
50
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
51
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
52
|
+
end
|
data.tar.gz.sig
ADDED
Binary file
|