unobtainium 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: