unobtainium 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.rubocop.yml +6 -0
- data/Gemfile.lock +44 -0
- data/README.md +17 -14
- data/Rakefile +20 -4
- data/config/config.yml +14 -0
- data/docs/CONFIGURATION.md +250 -0
- data/{cuke/features → features}/step_definitions/steps.rb +0 -0
- data/{cuke/features → features}/support/env.rb +0 -0
- data/{cuke/features → features}/world.feature +0 -0
- data/lib/unobtainium/config.rb +31 -10
- data/lib/unobtainium/driver.rb +55 -36
- data/lib/unobtainium/drivers/appium.rb +16 -13
- data/lib/unobtainium/drivers/phantom.rb +138 -0
- data/lib/unobtainium/drivers/selenium.rb +6 -5
- data/lib/unobtainium/pathed_hash.rb +29 -7
- data/lib/unobtainium/recursive_merge.rb +15 -0
- data/lib/unobtainium/runtime.rb +33 -8
- data/lib/unobtainium/support/port_scanner.rb +160 -0
- data/lib/unobtainium/support/runner.rb +180 -0
- data/lib/unobtainium/{drivers/support → support}/util.rb +10 -3
- data/lib/unobtainium/version.rb +2 -1
- data/lib/unobtainium/world.rb +6 -3
- data/spec/port_scanner_spec.rb +131 -0
- data/spec/runner_spec.rb +66 -0
- data/spec/utility_spec.rb +31 -0
- data/unobtainium.gemspec +9 -1
- metadata +105 -12
- data/cuke/.gitignore +0 -2
- data/cuke/Gemfile +0 -13
- data/cuke/Gemfile.lock +0 -59
- data/cuke/README.md +0 -2
- data/cuke/config/config.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9921b8f7735c0d00216208fe5ad193525671fd8f
|
4
|
+
data.tar.gz: 1557e2461fc29570eb97f975d83a4b792b838862
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd7643c1a36ea61aa917629a922cd3535b408f70a4206e18d77cebcd11327e0cb13931589eef0b7367c9c243928fd9c23ea82196397fce2bc756db9edbed1f85
|
7
|
+
data.tar.gz: f435d50ed4e50e697dbe1b7b1a3c0ddf5ccc50c2148d4d02cb5fea2ac30e7d738e59c47371655a4ccb449bb56c24a66760abebd7838fc22dd0cdecd973c00af6
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,18 +2,48 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
unobtainium (0.2.1)
|
5
|
+
sys-proctable (~> 1.0)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
10
|
+
appium_lib (8.0.2)
|
11
|
+
awesome_print (~> 1.6)
|
12
|
+
json (~> 1.8)
|
13
|
+
nokogiri (~> 1.6.6)
|
14
|
+
selenium-webdriver (~> 2.49)
|
15
|
+
tomlrb (~> 1.1)
|
9
16
|
ast (2.2.0)
|
17
|
+
awesome_print (1.6.1)
|
18
|
+
builder (3.2.2)
|
19
|
+
childprocess (0.5.9)
|
20
|
+
ffi (~> 1.0, >= 1.0.11)
|
10
21
|
codeclimate-test-reporter (0.5.0)
|
11
22
|
simplecov (>= 0.7.1, < 1.0.0)
|
23
|
+
cucumber (2.3.3)
|
24
|
+
builder (>= 2.1.2)
|
25
|
+
cucumber-core (~> 1.4.0)
|
26
|
+
cucumber-wire (~> 0.0.1)
|
27
|
+
diff-lcs (>= 1.1.3)
|
28
|
+
gherkin (~> 3.2.0)
|
29
|
+
multi_json (>= 1.7.5, < 2.0)
|
30
|
+
multi_test (>= 0.1.2)
|
31
|
+
cucumber-core (1.4.0)
|
32
|
+
gherkin (~> 3.2.0)
|
33
|
+
cucumber-wire (0.0.1)
|
12
34
|
diff-lcs (1.2.5)
|
13
35
|
docile (1.1.5)
|
36
|
+
ffi (1.9.10)
|
37
|
+
gherkin (3.2.0)
|
14
38
|
json (1.8.3)
|
39
|
+
mini_portile2 (2.0.0)
|
40
|
+
multi_json (1.11.2)
|
41
|
+
multi_test (0.1.2)
|
42
|
+
nokogiri (1.6.7.2)
|
43
|
+
mini_portile2 (~> 2.0.0.rc2)
|
15
44
|
parser (2.3.0.7)
|
16
45
|
ast (~> 2.2)
|
46
|
+
phantomjs (2.1.1.0)
|
17
47
|
powerpack (0.1.1)
|
18
48
|
rainbow (2.1.0)
|
19
49
|
rake (11.1.2)
|
@@ -37,24 +67,38 @@ GEM
|
|
37
67
|
ruby-progressbar (~> 1.7)
|
38
68
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
39
69
|
ruby-progressbar (1.7.5)
|
70
|
+
rubyzip (1.2.0)
|
71
|
+
selenium-webdriver (2.53.0)
|
72
|
+
childprocess (~> 0.5)
|
73
|
+
rubyzip (~> 1.0)
|
74
|
+
websocket (~> 1.0)
|
40
75
|
simplecov (0.11.2)
|
41
76
|
docile (~> 1.1.0)
|
42
77
|
json (~> 1.8)
|
43
78
|
simplecov-html (~> 0.10.0)
|
44
79
|
simplecov-html (0.10.0)
|
80
|
+
sys-proctable (1.0.0)
|
81
|
+
tomlrb (1.2.1)
|
45
82
|
unicode-display_width (1.0.3)
|
83
|
+
websocket (1.2.3)
|
84
|
+
yard (0.8.7.6)
|
46
85
|
|
47
86
|
PLATFORMS
|
48
87
|
ruby
|
49
88
|
|
50
89
|
DEPENDENCIES
|
90
|
+
appium_lib
|
51
91
|
bundler (~> 1.11)
|
52
92
|
codeclimate-test-reporter
|
93
|
+
cucumber
|
94
|
+
phantomjs
|
53
95
|
rake (~> 11.1)
|
54
96
|
rspec (~> 3.4)
|
55
97
|
rubocop (~> 0.39)
|
98
|
+
selenium-webdriver
|
56
99
|
simplecov (~> 0.11)
|
57
100
|
unobtainium!
|
101
|
+
yard (~> 0.8)
|
58
102
|
|
59
103
|
BUNDLED WITH
|
60
104
|
1.11.2
|
data/README.md
CHANGED
@@ -9,6 +9,8 @@ so that test code can more easily cover:
|
|
9
9
|
- Mobile browsers
|
10
10
|
- Mobile apps
|
11
11
|
|
12
|
+
The gem also wraps [PhantomJS](http://phantomjs.org/) for headless testing.
|
13
|
+
|
12
14
|
Some additional useful functionality for the maintenance of test suites is
|
13
15
|
also added.
|
14
16
|
|
@@ -26,26 +28,27 @@ Unobtainium's functionality is in standalone classes, but it's all combined in
|
|
26
28
|
the `Unobtainium::World` module.
|
27
29
|
|
28
30
|
- The `PathedHash` class extends `Hash` by allowing paths to nested values, e.g.:
|
29
|
-
```ruby
|
30
|
-
h = PathedHash.new { "foo" => { "bar" => 42 }}
|
31
|
-
h["foo.bar"] == 42 # true
|
32
|
-
```
|
33
|
-
- The `Config` class is a `PathedHash`, but also reads JSON or YAML files to
|
34
|
-
initialize itself with values, and allows config paths to be overridden by
|
35
|
-
environment variables: `FOO_BAR` overrides the "foo.bar" path.
|
36
31
|
|
37
|
-
|
32
|
+
```ruby
|
33
|
+
h = PathedHash.new { "foo" => { "bar" => 42 }}
|
34
|
+
h["foo.bar"] == 42 # true
|
35
|
+
```
|
36
|
+
|
37
|
+
- The `Config` class is a `PathedHash`, but also reads JSON or YAML files to
|
38
|
+
initialize itself with values. See the documentation on [configuration features](docs/CONFIGURATION.md)
|
39
|
+
for details
|
38
40
|
- The `Runtime` class is a singleton and a `Hash`-like container (but simpler),
|
39
41
|
that destroys all of its contents at the end of a script, calling custom
|
40
42
|
destructors if required. That allows for clean teardown and avoids everything
|
41
|
-
|
43
|
+
having to implement the Singleton pattern itself.
|
42
44
|
- The `Driver` class, of course, wraps either of Appium or Selenium drivers:
|
43
|
-
```ruby
|
44
|
-
drv = Driver.create(:firefox) # uses Selenium
|
45
|
-
drv = Driver.create(:android) # uses Appium
|
46
45
|
|
47
|
-
|
48
|
-
|
46
|
+
```ruby
|
47
|
+
drv = Driver.create(:firefox) # uses Selenium
|
48
|
+
drv = Driver.create(:android) # uses Appium
|
49
|
+
|
50
|
+
drv.navigate.to "..." # delegates to Selenium or Appium
|
51
|
+
```
|
49
52
|
|
50
53
|
## World
|
51
54
|
|
data/Rakefile
CHANGED
@@ -1,16 +1,32 @@
|
|
1
1
|
# Rubocop
|
2
2
|
require 'rubocop/rake_task'
|
3
|
-
RuboCop::RakeTask.new
|
3
|
+
RuboCop::RakeTask.new(:rubocop)
|
4
4
|
|
5
5
|
# Rspec
|
6
6
|
require 'rspec/core/rake_task'
|
7
|
-
RSpec::Core::RakeTask.new(:
|
7
|
+
RSpec::Core::RakeTask.new(:rspec)
|
8
|
+
|
9
|
+
# Cucumber
|
10
|
+
require 'cucumber'
|
11
|
+
require 'cucumber/rake/task'
|
12
|
+
Cucumber::Rake::Task.new(:cuke) do |t|
|
13
|
+
t.cucumber_opts = "--fail-fast --format=pretty --expand "\
|
14
|
+
"--order=random --backtrace"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Documentation
|
18
|
+
require 'yard'
|
19
|
+
YARD::Rake::YardocTask.new do |t|
|
20
|
+
t.files = ['lib/**/*.rb']
|
21
|
+
t.options = ['-m', 'markdown']
|
22
|
+
t.stats_options = ['--list-undoc']
|
23
|
+
end
|
8
24
|
|
9
25
|
# Combined test task
|
10
|
-
desc "Test the
|
26
|
+
desc "Test all the things!"
|
11
27
|
task :test do
|
12
28
|
Rake::Task[:rubocop].invoke
|
13
|
-
Rake::Task[:
|
29
|
+
Rake::Task[:rspec].invoke
|
14
30
|
end
|
15
31
|
|
16
32
|
# Default is the test task
|
data/config/config.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
drivers:
|
3
|
+
firefox:
|
4
|
+
android:
|
5
|
+
browser: chrome
|
6
|
+
android_remote:
|
7
|
+
extends: remote
|
8
|
+
desired_capabilities:
|
9
|
+
platform: "ANDROID"
|
10
|
+
browserName: "galaxy_nexus"
|
11
|
+
remote:
|
12
|
+
url: "http://hub.testingbot.com:4444/wd/hub"
|
13
|
+
driver: firefox
|
14
|
+
at_end: quit
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Configuration
|
2
|
+
|
3
|
+
The `Config` class, and it's major user, the `World.config` method make using
|
4
|
+
complex configuration files in your project easier.
|
5
|
+
|
6
|
+
## Basic Configuration Features
|
7
|
+
|
8
|
+
### Pathed Access
|
9
|
+
|
10
|
+
`Config` is a `Hash` with pathed access. Take a nested structure such as this
|
11
|
+
as an example:
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
---
|
15
|
+
foo:
|
16
|
+
bar: baz
|
17
|
+
```
|
18
|
+
|
19
|
+
Instead of accessing `config['foo']['bar']`, you can instead use a path such
|
20
|
+
as `config['foo.bar']`. The major benefit is that if *any* of the path components
|
21
|
+
does not exist, nil is returned (and the behaviour is equivalent to other access
|
22
|
+
methods such as `:fetch`, etc.)
|
23
|
+
|
24
|
+
Similarly you can use this type of access for writing: `config['baz.quux'] = 42`
|
25
|
+
will create both the `baz` hash, and it's child the `quux` key.
|
26
|
+
|
27
|
+
### Configuration Loading
|
28
|
+
|
29
|
+
If you're using `World.config`, configuration is automatically loaded at the
|
30
|
+
time it's first invoked from the location specified by `World.config_file`, which
|
31
|
+
defaults to `config/config.yml`.
|
32
|
+
|
33
|
+
This is equivalent to invoking `Config.load_config` with that location manually.
|
34
|
+
|
35
|
+
### Local Configuration Overrides
|
36
|
+
|
37
|
+
For the example file of `config/config.yml`, if a file with the same path and
|
38
|
+
name, and the name postfix `-local` exists (i.e. `config/config-local.yml`), that
|
39
|
+
file will also be loaded. It's keys will be recursively added to the keys from the
|
40
|
+
main configuration file, overwriting only leaves, not entire hashes.
|
41
|
+
|
42
|
+
Example:
|
43
|
+
|
44
|
+
```yaml
|
45
|
+
# config/config.yml
|
46
|
+
---
|
47
|
+
foo:
|
48
|
+
bar: 42
|
49
|
+
baz: quux
|
50
|
+
|
51
|
+
# config/config-local.yml
|
52
|
+
---
|
53
|
+
something: else
|
54
|
+
foo:
|
55
|
+
baz: override
|
56
|
+
|
57
|
+
# result
|
58
|
+
---
|
59
|
+
something: else
|
60
|
+
foo:
|
61
|
+
bar: 42
|
62
|
+
baz: override
|
63
|
+
```
|
64
|
+
|
65
|
+
### Configuration Extension
|
66
|
+
|
67
|
+
An additional feature of the `Config` class is that you can extend individual
|
68
|
+
hashes with values from other hashes.
|
69
|
+
|
70
|
+
```yaml
|
71
|
+
---
|
72
|
+
root:
|
73
|
+
foo: bar
|
74
|
+
derived:
|
75
|
+
baz: quux
|
76
|
+
extends: root
|
77
|
+
```
|
78
|
+
|
79
|
+
This results in:
|
80
|
+
|
81
|
+
```yaml
|
82
|
+
---
|
83
|
+
root:
|
84
|
+
foo: bar
|
85
|
+
derived:
|
86
|
+
baz: quux
|
87
|
+
foo: bar
|
88
|
+
base: root
|
89
|
+
```
|
90
|
+
|
91
|
+
**Notes:**
|
92
|
+
|
93
|
+
- This feature means that `extends` and `base` are reserved configuration keys!
|
94
|
+
- Multiple levels of extension are supported. The `base` keyword will *always
|
95
|
+
name the root-most element*, not the immediate ancestor.
|
96
|
+
- Extending from multiple bases is not supported.
|
97
|
+
- Extending from nonexistent bases is supported; all that happens is that the
|
98
|
+
`base` key is set.
|
99
|
+
|
100
|
+
### Environment Variable Override
|
101
|
+
|
102
|
+
Given a configuration path, any environment variable with the same name (change
|
103
|
+
path to upper case letters and replace `.` with `_`, e.g. `foo.bar` becomes
|
104
|
+
`FOO_BAR`) overrides the values in the configuration file.
|
105
|
+
|
106
|
+
If the environment variable is parseable as JSON, then that parsed JSON will
|
107
|
+
**replace** the original configuration path (i.e. it will not be merged).
|
108
|
+
|
109
|
+
## Configuration Values Interpreted by World
|
110
|
+
|
111
|
+
- The `driver` path specifies which driver to use; the value shold be a label
|
112
|
+
recognized by the `Driver.create` method.
|
113
|
+
- The `drivers` (plural) path contains options for the different drivers. Each
|
114
|
+
driver configuration is nested under a key matching a `Driver.create` label,
|
115
|
+
e.g.
|
116
|
+
```yaml
|
117
|
+
---
|
118
|
+
drivers:
|
119
|
+
firefox:
|
120
|
+
some: option
|
121
|
+
```
|
122
|
+
- The `at_end` path specifies what should be done with the driver when the
|
123
|
+
script ends. Possible values are `close` and `quit`, and the default is `quit`.
|
124
|
+
|
125
|
+
## Driver Configurations
|
126
|
+
|
127
|
+
You can create quite complex driver configurations with the above features, for
|
128
|
+
very convenient test suite development.
|
129
|
+
|
130
|
+
### Select Driver
|
131
|
+
|
132
|
+
Typically, you will configure all drivers in use by your test suite in the
|
133
|
+
`drivers` section of the configuration file. Then, use the `DRIVER` environment
|
134
|
+
variable to override/set which driver to use:
|
135
|
+
|
136
|
+
```bash
|
137
|
+
$ DRIVER=chrome bundle exec my_tests
|
138
|
+
```
|
139
|
+
|
140
|
+
### Mobile
|
141
|
+
|
142
|
+
When running mobile test suites, [Appium](https://github.com/appium/appium)
|
143
|
+
requires that you identify the app and/or device you want to run tests against.
|
144
|
+
|
145
|
+
That typically means specifying parts of a configuration that is applicable to
|
146
|
+
any user of the test code, and specifying parts that are applicable only to an
|
147
|
+
individual.
|
148
|
+
|
149
|
+
Use the local configuration override to achieve this split:
|
150
|
+
|
151
|
+
```yaml
|
152
|
+
# config/config.yml
|
153
|
+
---
|
154
|
+
drivers:
|
155
|
+
android:
|
156
|
+
caps:
|
157
|
+
platformName: android
|
158
|
+
...
|
159
|
+
|
160
|
+
# config/config-local.yml
|
161
|
+
---
|
162
|
+
drivers:
|
163
|
+
android:
|
164
|
+
caps:
|
165
|
+
deviceName: deadbeef
|
166
|
+
```
|
167
|
+
|
168
|
+
### Browser/Device Farms
|
169
|
+
|
170
|
+
The tests can be run on device/browser farms. Typically you only need to
|
171
|
+
configure drivers, much like for mobile testing. The following example
|
172
|
+
is for [TestingBot](https://testingbot.com). Note that each farm expects
|
173
|
+
different configuration keys for selecting browsers and for authentication.
|
174
|
+
|
175
|
+
```yaml
|
176
|
+
# config/config.yml
|
177
|
+
drivers:
|
178
|
+
remote:
|
179
|
+
url: "http://hub.testingbot.com:4444/wd/hub"
|
180
|
+
desired_capabilities:
|
181
|
+
platform: "WIN8"
|
182
|
+
browserName: "chrome"
|
183
|
+
version: "35"
|
184
|
+
```
|
185
|
+
|
186
|
+
It's good practice to keep authentication data out of the github repository,
|
187
|
+
so the TestingBot API key and secret should live only in `config/config-local.yml`.
|
188
|
+
|
189
|
+
```yaml
|
190
|
+
# config/config-local.yml
|
191
|
+
drivers:
|
192
|
+
remote:
|
193
|
+
desired_capabilities:
|
194
|
+
api_key: "1ceb00da"
|
195
|
+
api_secret: "cefaedfe"
|
196
|
+
```
|
197
|
+
|
198
|
+
Then run:
|
199
|
+
|
200
|
+
```bash
|
201
|
+
$ DRIVER=remote bundle exec cucumber
|
202
|
+
```
|
203
|
+
|
204
|
+
### Complex Configurations
|
205
|
+
|
206
|
+
With device/browser farms in particular, you typically do not want to use a
|
207
|
+
single `remote` configuration, but rather one for each remote browser or
|
208
|
+
device you want to use in testing. This is possible with the configuration
|
209
|
+
extension mechanism:
|
210
|
+
|
211
|
+
```yaml
|
212
|
+
# config/config.yml
|
213
|
+
---
|
214
|
+
drivers:
|
215
|
+
remote:
|
216
|
+
url: "http://hub.testingbot.com:4444/wd/hub"
|
217
|
+
remote_win8_chrome:
|
218
|
+
extends: remote
|
219
|
+
desired_capabilities:
|
220
|
+
platform: "WIN8"
|
221
|
+
browserName: "chrome"
|
222
|
+
version: "35"
|
223
|
+
|
224
|
+
# config/config-local.yml
|
225
|
+
---
|
226
|
+
drivers:
|
227
|
+
remote:
|
228
|
+
desired_capabilities:
|
229
|
+
api_key: "1ceb00da"
|
230
|
+
api_secret: "cefaedfe"
|
231
|
+
```
|
232
|
+
|
233
|
+
As noted in the section on the extension mechanism, the `extends` keyword
|
234
|
+
gets replaced with a `base` keyword that contains the *root-most* element.
|
235
|
+
In this case, `drivers.remote_win8_chrome.base` becomes `remote`, but even if
|
236
|
+
you had a `drivers.remote_win8_chrome38` section that overrides the desired
|
237
|
+
chrome version, `drivers.remote_win8_chrome38.base` would still become
|
238
|
+
`remote`.
|
239
|
+
|
240
|
+
The `World.config` method makes use of this, and replaces the driver label
|
241
|
+
you specified with the `driver` path or `DRIVER` environment variable with
|
242
|
+
this `base` value. The effect is that you can give your driver configuration
|
243
|
+
sections any name, as long as they're eventually extended by a section with
|
244
|
+
a key that `Driver.create` recognizes.
|
245
|
+
|
246
|
+
Thus, the following starts Chrome 35 on Windows 8 on TestingBot:
|
247
|
+
|
248
|
+
```bash
|
249
|
+
$ DRIVER=remote_win8_chrome bundle exec my_test
|
250
|
+
```
|