unobtainium 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7dfaed7fd3b12c6c2506ec09194c34f9939ef16
4
- data.tar.gz: 535afb416ce0449d2d4a96bfc5a57cbcd8a93224
3
+ metadata.gz: 194953d1b77be115316598397b69e99037647085
4
+ data.tar.gz: 9194ccf829bcf5c6e98027251716b8598ea1979b
5
5
  SHA512:
6
- metadata.gz: dec4e9b74f39c887ce315209cd340363eca9b02bc47e9637337b7195b1dabe467ee3a69e66f41fde71400afcfddfa1a453a83a1a9a757fb818f5a81ade292091
7
- data.tar.gz: 8599bcf5b567faf0294cd6c1fbb820b06d64a61c413f4979427a3527ed39b97867d9cb4f891b63b55e7a2ea8cfa4aaa030b9803862a0961051b21a613bbe776e
6
+ metadata.gz: 2e43f762d2c90323173cdfeedc2d57e764114558282066bd28a9208ed1fb175deb71917b23cbc135e8f476770e5c396414a572dcb2814ecd5e93449999cb1a8a
7
+ data.tar.gz: 58fd4916229bf0528304c9cb18541f5b6f2431e958da9f7154b70f1a553ffc04e26be791a02442c0e858ce45e1d42e98bb8c8f9f48e6f9a7fa6866843432baac
data/Gemfile CHANGED
@@ -2,6 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in unobtainium.gemspec
4
4
  gemspec
5
-
6
- gem 'selenium-webdriver'
7
- gem 'appium_lib'
@@ -6,21 +6,7 @@ PATH
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- appium_lib (8.0.2)
10
- awesome_print (~> 1.6)
11
- json (~> 1.8)
12
- nokogiri (~> 1.6.6)
13
- selenium-webdriver (~> 2.49)
14
- tomlrb (~> 1.1)
15
9
  ast (2.2.0)
16
- awesome_print (1.6.1)
17
- childprocess (0.5.9)
18
- ffi (~> 1.0, >= 1.0.11)
19
- ffi (1.9.10)
20
- json (1.8.3)
21
- mini_portile2 (2.0.0)
22
- nokogiri (1.6.7.2)
23
- mini_portile2 (~> 2.0.0.rc2)
24
10
  parser (2.3.0.7)
25
11
  ast (~> 2.2)
26
12
  powerpack (0.1.1)
@@ -32,23 +18,14 @@ GEM
32
18
  ruby-progressbar (~> 1.7)
33
19
  unicode-display_width (~> 1.0, >= 1.0.1)
34
20
  ruby-progressbar (1.7.5)
35
- rubyzip (1.2.0)
36
- selenium-webdriver (2.53.0)
37
- childprocess (~> 0.5)
38
- rubyzip (~> 1.0)
39
- websocket (~> 1.0)
40
- tomlrb (1.2.0)
41
21
  unicode-display_width (1.0.3)
42
- websocket (1.2.2)
43
22
 
44
23
  PLATFORMS
45
24
  ruby
46
25
 
47
26
  DEPENDENCIES
48
- appium_lib
49
27
  bundler (~> 1.11)
50
28
  rubocop (~> 0.39)
51
- selenium-webdriver
52
29
  unobtainium!
53
30
 
54
31
  BUNDLED WITH
data/README.md CHANGED
@@ -12,6 +12,8 @@ so that test code can more easily cover:
12
12
  Some additional useful functionality for the maintenance of test suites is
13
13
  also added.
14
14
 
15
+ [![Gem Version](https://badge.fury.io/rb/unobtainium.svg)](https://badge.fury.io/rb/unobtainium)
16
+
15
17
  # Credits
16
18
  This gem is inspired by [LapisLazuli](https://github.com/spriteCloud/lapis-lazuli),
17
19
  but vastly less complex, and aims to stay so.
@@ -0,0 +1,170 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium
4
+ # https://github.com/jfinkhaeuser/unobtainium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ require 'pathname'
11
+
12
+ require 'unobtainium/pathed_hash'
13
+
14
+ module Unobtainium
15
+ ##
16
+ # The Config class extends PathedHash by two main pieces of functionality:
17
+ # a) it loads configuration files and turns them into pathed hashes, and
18
+ # b) it treats environment variables as overriding anything contained in
19
+ # the configuration file.
20
+ #
21
+ # For configuration file loading, a named configuration file will be laoaded
22
+ # if present. A file with the same name but '-local' appended before the
23
+ # extension will be loaded as well, overriding any values in the original
24
+ # configuration file.
25
+ #
26
+ # For environment variable support, any environment variable named like a
27
+ # path into the configuration hash, but with separators transformed to
28
+ # underscore and all letters capitalized will override values from the
29
+ # configuration files under that path, i.e. FOO_BAR will override 'foo.bar'.
30
+ #
31
+ # Environment variables can contain JSON *only*; if the value can be parsed
32
+ # as JSON, it becomes a Hash in the configuration tree. If it cannot be parsed
33
+ # as JSON, it remains a string.
34
+ #
35
+ # Note: if your configuration file's top-level structure is an array, it will
36
+ # be returned as a hash with a 'config' key that maps to your file's contents.
37
+ #
38
+ class Config < PathedHash
39
+ # Very simple YAML parser
40
+ class YAMLParser
41
+ require 'yaml'
42
+
43
+ def self.parse(string)
44
+ YAML.load(string)
45
+ end
46
+ end
47
+
48
+ # Very simple JSON parser
49
+ class JSONParser
50
+ require 'json'
51
+
52
+ def self.parse(string)
53
+ JSON.parse(string)
54
+ end
55
+ end
56
+
57
+ PathedHash::READ_METHODS.each do |method|
58
+ # Wrap all read functions into something that checks for environment
59
+ # variables first.
60
+ define_method(method) do |*args, &block|
61
+ # We'll make it rather simple: since the first argument is a key, we
62
+ # will just transform it to the matching environment variable name,
63
+ # and see if that environment variable is set.
64
+ env_name = args[0].upcase.gsub(split_pattern, '_')
65
+ contents = ENV[env_name]
66
+
67
+ # No environment variable set? Fine, just do the usual thing.
68
+ if contents.nil?
69
+ return super(*args, &block)
70
+ end
71
+
72
+ # With an environment variable, we will try to parse it as JSON first.
73
+ begin
74
+ return JSONParser.parse(contents)
75
+ rescue JSON::ParserError
76
+ return contents
77
+ end
78
+ end
79
+ end
80
+
81
+ class << self
82
+ # Mapping of file name extensions to parser types.
83
+ FILE_TO_PARSER = {
84
+ '.yml' => YAMLParser,
85
+ '.yaml' => YAMLParser,
86
+ '.json' => JSONParser,
87
+ }.freeze
88
+
89
+ # If the config file contains an Array, this is what they key of the
90
+ # returned Hash will be.
91
+ ARRAY_KEY = 'config'.freeze
92
+
93
+ ##
94
+ # Loads a configuration file with the given file name. The format is
95
+ # detected based on one of the extensions in FILE_TO_PARSER.
96
+ def load_config(path)
97
+ # Load base and local configuration files
98
+ base, config = load_base_config(path)
99
+ _, local_config = load_local_config(base)
100
+
101
+ # We can't sensibly merge arrays and hashes, so bail if the two classes
102
+ # don't match.
103
+ if config.class != local_config.class
104
+ raise ArgumentError, "Config file and local override file do not have "\
105
+ "the same top-level structure (hash or array), and therefore "\
106
+ "cannot be merged!"
107
+ end
108
+
109
+ # Merge
110
+ if config.is_a? Array
111
+ config = { ARRAY_KEY => config.push(*local_config) }
112
+ elsif config.is_a? Hash
113
+ # rubocop:disable Style/CaseEquality
114
+ merger = proc do |_, v1, v2|
115
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
116
+ end
117
+ # rubocop:enable Style/CaseEquality
118
+ config.merge!(local_config, &merger)
119
+ else
120
+ raise "Unexpected top-level structure in configuration file: "\
121
+ "#{config.class}"
122
+ end
123
+
124
+ return Config.new(config)
125
+ end
126
+
127
+ private
128
+
129
+ def load_base_config(path)
130
+ # Make sure the format is recognized early on.
131
+ base = Pathname.new(path)
132
+ formats = FILE_TO_PARSER.keys
133
+ if not formats.include?(base.extname)
134
+ raise ArgumentError, "Files with extension '#{base.extname}' are not"\
135
+ " recognized; please use one of #{formats}!"
136
+ end
137
+
138
+ # Don't check the path whether it exists - loading a nonexistent
139
+ # file will throw a nice error for the user to catch.
140
+ file = base.open
141
+ contents = file.read
142
+
143
+ # Parse the contents.
144
+ config = FILE_TO_PARSER[base.extname].parse(contents)
145
+
146
+ return base, config
147
+ end
148
+
149
+ def load_local_config(base)
150
+ # Now construct a file name for a local override.
151
+ local = Pathname.new(base.dirname)
152
+ local = local.join(base.basename(base.extname).to_s + "-local" +
153
+ base.extname)
154
+ puts local
155
+ if not local.exist?
156
+ return Config.new(config)
157
+ end
158
+
159
+ # We know the local override file exists, but we do want to let any errors
160
+ # go through that come with reading or parsing it.
161
+ file = local.open
162
+ contents = file.read
163
+
164
+ local_config = FILE_TO_PARSER[base.extname].parse(contents)
165
+
166
+ return local, local_config
167
+ end
168
+ end # class << self
169
+ end # class Config
170
+ end # module Unobtainium
@@ -23,23 +23,77 @@ module Unobtainium
23
23
  ##
24
24
  # Create a driver instance with the given arguments
25
25
  def create(*args)
26
- Driver.new(*args)
26
+ new(*args)
27
27
  end
28
+
29
+ ##
30
+ # Add a new driver implementation. The first parameter is the class
31
+ # itself, the second should be a file path pointing to the file where
32
+ # the class is defined. You would typically pass __FILE__ for the second
33
+ # parameter.
34
+ #
35
+ # Using file names lets us figure out whether the class is a duplicate,
36
+ # or merely a second registration of the same class.
37
+ def register_implementation(klass, path)
38
+ # We need to deal with absolute paths only
39
+ fpath = File.absolute_path(path)
40
+
41
+ # Figure out if the class implements all the methods we need; we're not
42
+ # checking for anything else.
43
+ klass_methods = klass.methods - klass.instance_methods - Object.methods
44
+
45
+ if DRIVER_METHODS - klass_methods != []
46
+ raise LoadError, "Driver #{klass.name} is not implementing all of "\
47
+ "the class methods #{DRIVER_METHODS}, aborting!"
48
+ end
49
+
50
+ # The second question is whether the same class is already known, or
51
+ # whether a class with the same name but under a different location is
52
+ # known.
53
+ if @@drivers.include?(klass) and @@drivers[klass] != fpath
54
+ raise LoadError, "Driver #{klass.name} is duplicated in file "\
55
+ "'#{fpath}'; previous definition is here: "\
56
+ "'#{@@drivers[klass]}'"
57
+ end
58
+
59
+ # If all of that was ok, we can register the implementation.
60
+ @@drivers[klass] = fpath
61
+ end
62
+
63
+ private :new
28
64
  end # class << self
29
65
 
30
66
  ############################################################################
31
67
  # Public methods
32
68
  attr_reader :label, :options, :impl
33
69
 
70
+ ##
71
+ # Map any missing method to the driver implementation
72
+ def respond_to?(meth)
73
+ if not @impl.nil? and @impl.respond_to?(meth)
74
+ return true
75
+ end
76
+ return super
77
+ end
78
+
79
+ def method_missing(meth, *args, &block)
80
+ if not @impl.nil? and @impl.respond_to?(meth)
81
+ return @impl.send(meth.to_s, *args, &block)
82
+ end
83
+ return super
84
+ end
85
+
86
+ private
87
+
34
88
  ##
35
89
  # Initializer
36
90
  def initialize(*args)
37
- # Sanitize options
38
- @label, @options = sanitize_options(*args)
39
-
40
91
  # Load drivers
41
92
  load_drivers
42
93
 
94
+ # Sanitize options
95
+ @label, @options = sanitize_options(*args)
96
+
43
97
  # Determine the driver class, if any
44
98
  driver_klass = get_driver(@label)
45
99
  if not driver_klass
@@ -54,8 +108,6 @@ module Unobtainium
54
108
  @impl = driver_klass.create(@label, @options)
55
109
  end
56
110
 
57
- private
58
-
59
111
  # Class variables have their place, rubocop... still, err on the strict
60
112
  # side and just skip this check here.
61
113
  # rubocop:disable Style/ClassVars
@@ -70,18 +122,31 @@ module Unobtainium
70
122
  ].freeze
71
123
 
72
124
  ##
73
- # FIXME
125
+ # Ensures arguments are according to expectations.
74
126
  def sanitize_options(*args)
75
- load_drivers
76
- require 'pp'
77
- pp args
127
+ if args.empty?
128
+ raise ArgumentError, "Need at least one argument specifying the driver!"
129
+ end
130
+
131
+ label = args[0].to_sym
132
+
133
+ options = nil
134
+ if args.length > 1
135
+ if not args[1].is_a? Hash
136
+ raise ArgumentError, "The second argument is expected to be an options "\
137
+ "hash!"
138
+ end
139
+ options = args[1]
140
+ end
141
+
142
+ return label, options
78
143
  end
79
144
 
80
145
  ##
81
- # Load drivers.
146
+ # Load drivers; this loads all driver implementations included in this gem.
147
+ # You can register external implementations with the :register_implementation
148
+ # method.
82
149
  def load_drivers
83
- # TODO: add load path for external drivers, or let them be specified via
84
- # the driver environment/config variables.
85
150
  pattern = File.join(File.dirname(__FILE__), 'drivers', '*.rb')
86
151
  Dir.glob(pattern).each do |fpath|
87
152
  # Determine class name from file name
@@ -92,20 +157,7 @@ module Unobtainium
92
157
  require fpath
93
158
  klassname = 'Unobtainium::Drivers::' + fname
94
159
  klass = Object.const_get(klassname)
95
- klass_methods = klass.methods - klass.instance_methods - Object.methods
96
-
97
- if DRIVER_METHODS - klass_methods != []
98
- raise LoadError, "Driver #{klassname} is not implementing all of "\
99
- "#{DRIVER_METHODS}, aborting!"
100
- end
101
-
102
- if @@drivers.include?(klass) and @@drivers[klass] != fpath
103
- raise LoadError, "Driver #{klassname} is duplicated in file "\
104
- "'#{fpath}'; previous definition is here: "\
105
- "'#{@@drivers[klass]}'"
106
- end
107
- @@drivers[klass] = fpath
108
-
160
+ Driver.register_implementation(klass, fpath)
109
161
  rescue LoadError => err
110
162
  raise LoadError, "#{err.message}: unknown problem loading driver, "\
111
163
  "aborting!"
@@ -0,0 +1,121 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium
4
+ # https://github.com/jfinkhaeuser/unobtainium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ module Unobtainium
11
+
12
+ ##
13
+ # The PathedHash class wraps Hash by offering pathed access on top of
14
+ # regular access, i.e. instead of h["first"]["second"] you can write
15
+ # h["first.second"]
16
+ class PathedHash
17
+ ##
18
+ # Initializer
19
+ def initialize(init = {})
20
+ @data = init
21
+ @separator = '.'
22
+ end
23
+
24
+ # The separator is the character or pattern splitting paths
25
+ attr_accessor :separator
26
+
27
+ READ_METHODS = [
28
+ :[], :default, :delete, :fetch, :has_key?, :include?, :key?, :member?,
29
+ ].freeze
30
+ WRITE_METHODS = [
31
+ :[]=, :store,
32
+ ].freeze
33
+
34
+ ##
35
+ # Returns the pattern to split paths at
36
+ def split_pattern
37
+ /(?<!\\)#{Regexp.escape(@separator)}/
38
+ end
39
+
40
+ (READ_METHODS + WRITE_METHODS).each do |method|
41
+ # Wrap all accessor functions to deal with paths
42
+ define_method(method) do |*args, &block|
43
+ # With any of the dispatch methods, we know that the first argument has
44
+ # to be a key. We'll try to split it by the path separator.
45
+ components = args[0].to_s.split(split_pattern)
46
+
47
+ # This PathedHash is already the leaf-most Hash
48
+ if components.length == 1
49
+ return @data.send(method, *args, &block)
50
+ end
51
+
52
+ # Deal with other paths. The frustrating part here is that for nested
53
+ # hashes, only this outermost one is guaranteed to know anything about
54
+ # path splitting, so we'll have to recurse down to the leaf here.
55
+ #
56
+ # For write methods, we need to create intermediary hashes.
57
+ leaf = recursive_fetch(components, @data,
58
+ create: WRITE_METHODS.include?(method))
59
+
60
+ # If the leaf is nil, we can't send it any method without raising
61
+ # an error. We'll instead send the method to an empty hash, to mimic
62
+ # the correct behaviour.
63
+ if leaf.nil?
64
+ return {}.send(method, *args, &block)
65
+ end
66
+
67
+ # If we have a leaf, we want to send the requested method to that
68
+ # leaf.
69
+ copy = args.dup
70
+ copy[0] = components.last
71
+ return leaf.send(method, *copy, &block)
72
+ end
73
+ end
74
+
75
+ def to_s
76
+ @data.to_s
77
+ end
78
+
79
+ ##
80
+ # Map any missing method to the driver implementation
81
+ def respond_to?(meth)
82
+ if not @data.nil? and @data.respond_to?(meth)
83
+ return true
84
+ end
85
+ return super
86
+ end
87
+
88
+ def method_missing(meth, *args, &block)
89
+ if not @data.nil? and @data.respond_to?(meth)
90
+ return @data.send(meth.to_s, *args, &block)
91
+ end
92
+ return super
93
+ end
94
+
95
+ private
96
+
97
+ ##
98
+ # Given the path components, recursively fetch any but the last key.
99
+ def recursive_fetch(path, data, options = {})
100
+ # For the leaf element, we do nothing because that's where we want to
101
+ # dispatch to.
102
+ if path.length == 1
103
+ return data
104
+ end
105
+
106
+ # Split path into head and tail; for the next iteration, we'll look use only
107
+ # head, and pass tail on recursively.
108
+ head = path[0]
109
+ tail = path.slice(1, path.length)
110
+
111
+ # If we're a write function, then we need to create intermediary objects,
112
+ # i.e. what's at head if nothing is there.
113
+ if options[:create] and data.fetch(head, nil).nil?
114
+ data[head] = {}
115
+ end
116
+
117
+ # Ok, recurse.
118
+ return recursive_fetch(tail, data[head], options)
119
+ end
120
+ end # class PathedHash
121
+ end # module Unobtainium
@@ -0,0 +1,126 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium
4
+ # https://github.com/jfinkhaeuser/unobtainium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium contributors.
7
+ # All rights reserved.
8
+ #
9
+ require 'singleton'
10
+
11
+ module Unobtainium
12
+
13
+ ##
14
+ # The Runtime class is a singleton scoped to destroy itself when script
15
+ # execution stops. It's also an object map, which will destroy all object
16
+ # it contains when it destroys itself.
17
+ #
18
+ # Therefore, it can be used as a way to register object instances for
19
+ # destruction at script end.
20
+ class Runtime
21
+ include Singleton
22
+
23
+ ##
24
+ # Initializer
25
+ def initialize
26
+ @objects = {}
27
+
28
+ # Create our own finalizer
29
+ ObjectSpace.define_finalizer(self) do
30
+ @objects.keys.each do |key|
31
+ unset(key)
32
+ end
33
+ end
34
+ end
35
+
36
+ ##
37
+ # Number of objects stored in the object map
38
+ def length
39
+ return @objects.length
40
+ end
41
+
42
+ ##
43
+ # Does an object with the given name exist?
44
+ def has?(name)
45
+ return @objects.key?(name)
46
+ end
47
+
48
+ ##
49
+ # Store the given object under the given name. This overwrites any objects
50
+ # already stored under that name, which are destroyed before the new object
51
+ # is stored.
52
+ #
53
+ # If a destructor is passed, it is used to destroy the *new* object only.
54
+ # If no destructor is passed and the object responds to a :destroy method, that
55
+ # method is called.
56
+ def set(name, object, destructor = nil)
57
+ unset(name)
58
+
59
+ @objects[name] = [object, destructor]
60
+
61
+ return object
62
+ end
63
+
64
+ ##
65
+ # Store the object returned by the block, if any. If no object is returned
66
+ # or no block is given, this function does nothing.
67
+ #
68
+ # Otherwise it works much like :set above.
69
+ def set_with(name, destructor = nil, &block)
70
+ object = nil
71
+ if not block.nil?
72
+ object = yield
73
+ end
74
+
75
+ if object.nil?
76
+ return
77
+ end
78
+
79
+ return set(name, object, destructor)
80
+ end
81
+
82
+ ##
83
+ # Unsets (and destroys) any object found under the given name.
84
+ def unset(name)
85
+ if not @objects.key?(name)
86
+ return
87
+ end
88
+
89
+ obj, dtor = @objects[name]
90
+ @objects.delete(name)
91
+ destroy(obj, dtor)
92
+ end
93
+
94
+ ##
95
+ # Returns the object with the given name, or the default value if no such
96
+ # object exists.
97
+ def fetch(name, default = nil)
98
+ return @objects.fetch(name, default)
99
+ end
100
+
101
+ ##
102
+ # Similar to :fetch, but always returns nil for an object that could not
103
+ # be found.
104
+ def [](name)
105
+ return @objects[name]
106
+ end
107
+
108
+ private
109
+
110
+ ##
111
+ # Destroy the given object with the destructor provided.
112
+ def destroy(object, destructor)
113
+ if not destructor.nil?
114
+ destructor.call
115
+ return
116
+ end
117
+
118
+ if not object.respond_to?(:destroy)
119
+ return
120
+ end
121
+
122
+ object.send(:destroy)
123
+ end
124
+ end # class Runtime
125
+
126
+ end # module Unobtainium
@@ -7,5 +7,5 @@
7
7
  # All rights reserved.
8
8
  #
9
9
  module Unobtainium
10
- VERSION = "0.0.1".freeze
10
+ VERSION = "0.0.2".freeze
11
11
  end
@@ -20,11 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.email = ["jens@finkhaeuser.de"]
21
21
  spec.description = %q(
22
22
  Unobtainium wraps Selenium and Appium in a simple driver abstraction so that
23
- test code can more easily cover:
24
-
25
- - Desktop browsers
26
- - Mobile browsers
27
- - Mobile apps
23
+ test code can more easily cover desktop browsers, mobile browsers and mobile
24
+ apps.
28
25
 
29
26
  Some additional useful functionality for the maintenance of test suites is
30
27
  also added.
@@ -40,6 +37,10 @@ Gem::Specification.new do |spec|
40
37
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
41
38
  spec.require_paths = ["lib"]
42
39
 
40
+ spec.required_ruby_version = '>= 1.9'
41
+
42
+ spec.requirements = "Either or all of 'selenium-webdriver', 'appium_lib'"
43
+
43
44
  spec.add_development_dependency "bundler", "~> 1.11"
44
45
  spec.add_development_dependency "rubocop", "~> 0.39"
45
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unobtainium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jens Finkhaeuser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-07 00:00:00.000000000 Z
11
+ date: 2016-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -39,9 +39,9 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.39'
41
41
  description: "\n Unobtainium wraps Selenium and Appium in a simple driver abstraction
42
- so that\n test code can more easily cover:\n\n - Desktop browsers\n -
43
- Mobile browsers\n - Mobile apps\n\n Some additional useful functionality
44
- for the maintenance of test suites is\n also added.\n "
42
+ so that\n test code can more easily cover desktop browsers, mobile browsers and
43
+ mobile\n apps.\n\n Some additional useful functionality for the maintenance
44
+ of test suites is\n also added.\n "
45
45
  email:
46
46
  - jens@finkhaeuser.de
47
47
  executables: []
@@ -55,9 +55,12 @@ files:
55
55
  - LICENSE
56
56
  - README.md
57
57
  - lib/unobtainium.rb
58
+ - lib/unobtainium/config.rb
58
59
  - lib/unobtainium/driver.rb
59
60
  - lib/unobtainium/drivers/appium.rb
60
61
  - lib/unobtainium/drivers/selenium.rb
62
+ - lib/unobtainium/pathed_hash.rb
63
+ - lib/unobtainium/runtime.rb
61
64
  - lib/unobtainium/version.rb
62
65
  - unobtainium.gemspec
63
66
  homepage: https://github.com/jfinkhaeuser/unobtainium
@@ -72,13 +75,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
75
  requirements:
73
76
  - - ">="
74
77
  - !ruby/object:Gem::Version
75
- version: '0'
78
+ version: '1.9'
76
79
  required_rubygems_version: !ruby/object:Gem::Requirement
77
80
  requirements:
78
81
  - - ">="
79
82
  - !ruby/object:Gem::Version
80
83
  version: '0'
81
- requirements: []
84
+ requirements:
85
+ - Either or all of 'selenium-webdriver', 'appium_lib'
82
86
  rubyforge_project:
83
87
  rubygems_version: 2.4.5.1
84
88
  signing_key: