unobtainium 0.2.1 → 0.3.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 +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
|
+
```
|