yordi_tests 0.1.7
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
- data/.gitignore +13 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/yordi +3 -0
- data/lib/yordi_tests/cli.rb +78 -0
- data/lib/yordi_tests/client.rb +59 -0
- data/lib/yordi_tests/data_manager.rb +190 -0
- data/lib/yordi_tests/generators/report.html.erb +100 -0
- data/lib/yordi_tests/generators/report.rb +19 -0
- data/lib/yordi_tests/image_compare.rb +92 -0
- data/lib/yordi_tests/local_store.rb +75 -0
- data/lib/yordi_tests/version.rb +3 -0
- data/lib/yordi_tests.rb +41 -0
- data/yordi_tests.gemspec +41 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 783043661bdec27c6adf4c9f4d3b1d545d675ef4
|
4
|
+
data.tar.gz: 64fc1f61a1a524e3e61aaedc4931c6dce74523b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7784cb732fcd3cb3794b3058d39efa00cace7d44dca2778f3a2c09549831997dffa5ce058063f553af65d476f34de4fe7334bb4bf0c2394a61efe5fbead0119f
|
7
|
+
data.tar.gz: 4a2b864d577a4d56f762ca2d57d62226c5e83fa08cee69aeb68f2dc35b0078c7f511621d5961670ab75bf4a8d30f62ed35f385cba8ccf714325079e5af65d002
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Brian OQR
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# YordiTests CLI
|
2
|
+
|
3
|
+
The yordi_tests gem is a companion for [YordiTests](https://yorditests.com).
|
4
|
+
This project is rapidly changing and in alpha phase, use at your own risk.
|
5
|
+
|
6
|
+
Basically YordiTests compares screenshots with benchmarks and can apply masks to hide dynamic content.
|
7
|
+
|
8
|
+
It only works with PNG files and requires the files to have the extensions .png for now and does not recurse directories.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
<!-- Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'yordi_tests'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
Or -->
|
22
|
+
Install it yourself as:
|
23
|
+
|
24
|
+
$ gem install yordi_tests
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
It's a CLI with Thor so calling help on the command line will give you more details
|
28
|
+
``` ruby
|
29
|
+
yordi help
|
30
|
+
```
|
31
|
+
To start a .yordi_tests directory must be initialized, either of these commands will create the directory relative
|
32
|
+
to the where you execute it. Using an apikey will automatically sync your local test directory with the benchmarks online.
|
33
|
+
``` ruby
|
34
|
+
yordi help init # for all the options
|
35
|
+
yordi init
|
36
|
+
yordi init --api_key apikey_from_yorditests.com
|
37
|
+
```
|
38
|
+
To test your screenshots
|
39
|
+
``` ruby
|
40
|
+
yordi help test # for more options
|
41
|
+
yordi test /path/to/screenshots
|
42
|
+
yordi test /path/to/screenshots --sync-all # push the new screenshots to yorditests.com
|
43
|
+
yordi test /path/to/screenshots --clean-dir # after the tests it deletes the screenshots tested
|
44
|
+
```
|
45
|
+
Show the report (only works on OSX, the report is located at .yordi_tests/report.html)
|
46
|
+
``` ruby
|
47
|
+
yordi open_report # opens the last generated html report
|
48
|
+
```
|
49
|
+
Refetch benchmarks and masks from online
|
50
|
+
``` ruby
|
51
|
+
yordi help fetch # for more options
|
52
|
+
yordi fetch -bm # fetches all benchmark details and masks from online for testing
|
53
|
+
```
|
54
|
+
Push benchmarks and masks to online store (not yet implemented)
|
55
|
+
``` ruby
|
56
|
+
yordi help push # for more options
|
57
|
+
yordi push -bm # pushes all benchmark details and masks to online store
|
58
|
+
```
|
59
|
+
### Standalone Scenario
|
60
|
+
So you have run an appium or selenium test that made a lot of screenshots.
|
61
|
+
Wherever you want to run your yordi test do the following.
|
62
|
+
``` ruby
|
63
|
+
yordi init
|
64
|
+
yordi test path/to/screenshot
|
65
|
+
yordi open_report
|
66
|
+
```
|
67
|
+
At which point you will see a report and all of your screens will have passed the test as there was no benchmark.
|
68
|
+
So run your appium or selenium tests again and create the screenshots a second time.
|
69
|
+
``` ruby
|
70
|
+
yordi test path/to/screenshot
|
71
|
+
yordi open_report
|
72
|
+
```
|
73
|
+
Now there is a good chance that some of your screenshots haved failed the test due to status bars with time or other
|
74
|
+
dynamic issues. If the screen changes are different but valid you need to mask this dynamic area to ensure 100% on the test.
|
75
|
+
This is easiest done with YordiTests.com editor, however the masks are simple JSON areas.
|
76
|
+
|
77
|
+
So if you don't want to use YordiTests.com you can manually open the config.json at .yordi_tests/config.json
|
78
|
+
and add some rectangles for masks as a value for masked_area: in the json,
|
79
|
+
you can use any image editing program to get the x, y, width and height you want
|
80
|
+
|
81
|
+
Here is an example of a masked_area value, masked_area is an array.
|
82
|
+
``` javascript
|
83
|
+
masked_area: [
|
84
|
+
{
|
85
|
+
"x": 0,
|
86
|
+
"y": 0,
|
87
|
+
"width": 800,
|
88
|
+
"height": 48
|
89
|
+
},
|
90
|
+
{
|
91
|
+
"x": 419,
|
92
|
+
"y": 385,
|
93
|
+
"width": 619,
|
94
|
+
"height": 372
|
95
|
+
},
|
96
|
+
|
97
|
+
|
98
|
+
]
|
99
|
+
```
|
100
|
+
In the config.json the property masked_area: is available in the root and in individual screens in the test_benchmarks array.
|
101
|
+
Placing a mask in root will have the mask be applied to all screens during the comparision. Placing the mask on a screen will apply the mask only to that screen.
|
102
|
+
So the screens get compared after both the benchmark and resulting screenshot have all global and local masks applied.
|
103
|
+
|
104
|
+
At some point you will want to update your benchmarks, for example if the design has changed. If you are using YordiTests.com
|
105
|
+
then if you sync your tests with the online store you can make the screenshot the benchmark online
|
106
|
+
and then re-fetch the benchmarks from the comand with `yordi fetch -b`.
|
107
|
+
|
108
|
+
However if you are running in standalone mode to update a benchmark at the moment you must manually copy the
|
109
|
+
screenshot into the benchmark folder in the subdirectories of .yordi_tests. Eventually a command will be added to make this easier.
|
110
|
+
|
111
|
+
|
112
|
+
## Development
|
113
|
+
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
115
|
+
|
116
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/PurelyCode/gem-yorditests.
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "yordi_tests"
|
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
data/exe/yordi
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yordi_tests'
|
3
|
+
require 'yordi_tests/data_manager'
|
4
|
+
module YordiTests
|
5
|
+
class CLI < Thor
|
6
|
+
package_name 'YordiTests'
|
7
|
+
|
8
|
+
desc 'init --api_key', 'Initializes a Yordi test directory at .yordi_tests relative to where you execute the command'
|
9
|
+
method_option :api_key, type: :string, aliases: '-k', desc: 'APIKEY from YordiTest.com if you want to sync results online'
|
10
|
+
|
11
|
+
def init
|
12
|
+
puts 'init'
|
13
|
+
if Dir.exist?(YORDI_DIR)
|
14
|
+
puts 'Already initialized'
|
15
|
+
exit(1)
|
16
|
+
end
|
17
|
+
puts "Creating #{YORDI_DIR} directory"
|
18
|
+
DataManager.create_store
|
19
|
+
|
20
|
+
store = DataManager.default_store(options.api_key)
|
21
|
+
puts "Saving store #{CONFIG_FILE}"
|
22
|
+
DataManager.save_store store
|
23
|
+
|
24
|
+
return unless options.api_key
|
25
|
+
puts 'Fetching remote store.'
|
26
|
+
DataManager.fetch(true, true, nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
desc 'test PATH_TO_SCREENSHOTS', 'Run a Yordi test on all images in folder'
|
31
|
+
method_option :name, type: :string, default: 'Generic Test', aliases: '-n', desc: 'Name for this test in report.'
|
32
|
+
method_option :clean_dir, type: :boolean, default: false, aliases: '-D', desc: 'This will DELETE all screenshots in PATH_TO_SCREENSHOTS after test completes.'
|
33
|
+
method_option :sync_all, type: :boolean, default: false, aliases: '-a', desc: 'Push all screens to YordiTests.com for evaluation.'
|
34
|
+
method_option :sync_failures, type: :boolean, default: false, aliases: '-f', desc: 'Push only failed screens to YordiTests.com for evaluation.'
|
35
|
+
method_option :filenames, type: :array, aliases: '-i', desc: 'Only test these specific files'
|
36
|
+
method_option :screens, type: :array, aliases: '-s', desc: 'Give the files a name other than there filename in the report (best used with -i option)'
|
37
|
+
|
38
|
+
def test(path_to_screens)
|
39
|
+
puts "Testing #{path_to_screens} name: #{options.name}, clean_dir: #{options.clean_dir}, sync_all: #{options.sync_all}, sync_failures: #{options.sync_failures}, filenames: #{options.filenames}, screens: #{options.screens}"
|
40
|
+
|
41
|
+
DataManager.run_test(path_to_screens, options.name, options.clean_dir, options.sync_all, options.sync_failures, options.filenames, options.screens)
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'make_report', 'Regenerate a Yordi report based on json report'
|
45
|
+
|
46
|
+
def make_report
|
47
|
+
DataManager.generate_report
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'fetch', 'Fetch remote data from YordiTests.com'
|
51
|
+
method_option :benchmarks, type: :boolean, default: false, aliases: '-b', desc: 'Fetch benchmarks'
|
52
|
+
method_option :masks, type: :boolean, default: false, aliases: '-m', desc: 'Fetch masks'
|
53
|
+
method_option :screens, type: :array, aliases: '-s', desc: 'Only fetch items associated with this list of screen names'
|
54
|
+
|
55
|
+
def fetch
|
56
|
+
puts "Fetching with options benchmarks: #{options.benchmarks}, masks: #{options.masks}, screens: #{options.screens}"
|
57
|
+
DataManager.fetch(options.benchmarks, options.masks, options.screens)
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'push', 'Push local data to YordiTests.com'
|
61
|
+
method_option :benchmarks, type: :boolean, default: false, aliases: '-b', desc: 'Push benchmarks'
|
62
|
+
method_option :masks, type: :boolean, default: false, aliases: '-m', desc: 'Push masks'
|
63
|
+
method_option :screens, type: :array, aliases: '-s', desc: 'Only push items associated with this list of screen names'
|
64
|
+
|
65
|
+
def push(path_to_screens)
|
66
|
+
puts "test #{path_to_screens}"
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
desc 'open_report', 'Open the last generated report'
|
71
|
+
|
72
|
+
def open_report
|
73
|
+
system "open #{REPORT_HTML}"
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'yordi_tests'
|
3
|
+
module YordiTests
|
4
|
+
|
5
|
+
module Client
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def apikey
|
9
|
+
@api_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def apikey=(v)
|
13
|
+
@api_key = v
|
14
|
+
end
|
15
|
+
|
16
|
+
def auth_header
|
17
|
+
"Bearer #{@api_key}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String] test_name
|
21
|
+
def start(test_name)
|
22
|
+
response = RestClient::Request.execute(method: :get, url: HOST + START_PATH,
|
23
|
+
payload: {name: test_name}, headers: {authorization: auth_header})
|
24
|
+
@test_key = JSON(response)['testkey']
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [String] screenshot_path
|
29
|
+
# @param [String] screen_name
|
30
|
+
def upload(screenshot_path, screen_name)
|
31
|
+
RestClient.post(HOST + UPLOAD_PATH,
|
32
|
+
{screenshot: File.new(screenshot_path),
|
33
|
+
screenname: screen_name, testkey: @test_key},
|
34
|
+
authorization: auth_header)
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
RestClient::Request.execute(method: :get, url: HOST + STOP_PATH,
|
40
|
+
payload: {testkey: @test_key}, headers: {authorization: auth_header})
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_application
|
46
|
+
response = RestClient::Request.execute(method: :get, url: HOST + FETCH_APPLICATION,
|
47
|
+
payload: {}, headers: {authorization: auth_header})
|
48
|
+
JSON(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch_benchmark(screen_name)
|
52
|
+
RestClient::Request.execute(method: :get, url: HOST + FETCH_BENCHMARK,
|
53
|
+
payload: {screenname: screen_name}, headers: {authorization: auth_header})
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'yordi_tests'
|
2
|
+
require 'yordi_tests/local_store'
|
3
|
+
require 'yordi_tests/image_compare'
|
4
|
+
require 'yordi_tests/generators/report'
|
5
|
+
module YordiTests
|
6
|
+
module DataManager
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def default_store(apikey)
|
10
|
+
{title: 'YordiTests', apikey: apikey, test_benchmarks: []}
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_store
|
14
|
+
Dir.mkdir(YORDI_DIR) unless Dir.exist? YORDI_DIR
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_store
|
18
|
+
read_json(CONFIG_FILE)
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_report
|
22
|
+
read_json(REPORT_FILE)
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_json(path)
|
26
|
+
if File.exist? path
|
27
|
+
file = File.read path
|
28
|
+
JSON(file)
|
29
|
+
else
|
30
|
+
puts "No file at: #{path} did you call initialize this directory"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def save_store(store_hash)
|
36
|
+
File.open(CONFIG_FILE, 'w') {|file| file.write(store_hash.to_json)}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Test entry from the CLI
|
40
|
+
def run_test(path_to_screens, name, clean_dir, sync_all, sync_failures, filenames, screens)
|
41
|
+
local_store = LocalStore.new(read_store)
|
42
|
+
if filenames
|
43
|
+
files = []
|
44
|
+
filenames.each do |filename|
|
45
|
+
files << path_to_screens + '/' + filename
|
46
|
+
end
|
47
|
+
else
|
48
|
+
files = Dir.glob(path_to_screens + '/*.png').sort
|
49
|
+
end
|
50
|
+
|
51
|
+
Dir.mkdir(BENCHMARKS_PATH) unless Dir.exist? BENCHMARKS_PATH
|
52
|
+
Dir.mkdir(SCREENS_PATH) unless Dir.exist? SCREENS_PATH
|
53
|
+
responses = []
|
54
|
+
files.each_with_index do |item, index|
|
55
|
+
puts "Testing #{item}"
|
56
|
+
screenname = (!screens.nil? && screens.size > index) ? screens[index] : File.basename(item, '.*')
|
57
|
+
benchmark = local_store.benchmark_by_screenname(screenname)
|
58
|
+
local_name = benchmark.nil? ? sanitize(screenname) + File.extname(item) : benchmark[LOCAL_FILENAME]
|
59
|
+
benchmark_path = BENCHMARKS_PATH + '/' + local_name
|
60
|
+
screenshot_path = SCREENS_PATH + '/' + local_name
|
61
|
+
FileUtils.copy item, screenshot_path
|
62
|
+
if benchmark.nil?
|
63
|
+
FileUtils.copy item, benchmark_path
|
64
|
+
benchmark = {SCREENNAME => screenname,
|
65
|
+
MASKED_AREA => nil,
|
66
|
+
FILENAME => local_name,
|
67
|
+
LOCAL_FILENAME => local_name}
|
68
|
+
local_store.update_benchmark(benchmark)
|
69
|
+
save_store local_store.data
|
70
|
+
end
|
71
|
+
global_mask = local_store.get(MASKED_AREA)
|
72
|
+
screen_mask = benchmark.nil? ? nil : benchmark[MASKED_AREA]
|
73
|
+
response = ImageCompare.perform(benchmark_path, screenshot_path, global_mask, screen_mask)
|
74
|
+
response[SCREENNAME] = screenname
|
75
|
+
response[LOCAL_FILENAME] = local_name
|
76
|
+
responses << response
|
77
|
+
File.delete item if clean_dir
|
78
|
+
end
|
79
|
+
report_hash = {name: name, tests: responses}
|
80
|
+
File.open(REPORT_FILE, 'w') {|file| file.write(report_hash.to_json)}
|
81
|
+
|
82
|
+
# sync with yorditests.com if desired
|
83
|
+
sync_with_yordi local_store, sync_all, sync_failures if sync_all || sync_failures
|
84
|
+
generate_report
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate_report
|
88
|
+
# generate report
|
89
|
+
YordiTests::Generators::Report.start([REPORT_HTML, read_report])
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def sync_with_yordi(store, sync_all, sync_failures)
|
94
|
+
# sync with remote
|
95
|
+
puts 'Syncing with YordiTests'
|
96
|
+
report = read_report
|
97
|
+
client = YordiTests.client
|
98
|
+
client.apikey = store.apikey if store.apikey
|
99
|
+
if !report.nil? && !report['tests'].empty?
|
100
|
+
reports = report['tests']
|
101
|
+
failures = 0
|
102
|
+
if sync_failures
|
103
|
+
reports.each do |item|
|
104
|
+
failures += 1 unless item['passed']
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if sync_all || failures > 0
|
109
|
+
client.start report['name']
|
110
|
+
reports.each do |item|
|
111
|
+
next unless sync_all || !item['passed']
|
112
|
+
puts item[SCREENNAME]
|
113
|
+
benchmark = store.benchmark_by_screenname(item[SCREENNAME])
|
114
|
+
filename = SCREENS_PATH + '/' + benchmark[LOCAL_FILENAME]
|
115
|
+
client.upload filename, item[SCREENNAME]
|
116
|
+
end
|
117
|
+
client.stop
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Fetch entry from the CLI
|
123
|
+
def fetch(get_benchmarks, get_masks, screens)
|
124
|
+
local_store = LocalStore.new(read_store)
|
125
|
+
## no api key
|
126
|
+
return unless local_store.apikey
|
127
|
+
|
128
|
+
client = YordiTests.client
|
129
|
+
client.apikey = local_store.apikey
|
130
|
+
remote_data = client.fetch_application
|
131
|
+
|
132
|
+
## no remote store
|
133
|
+
return unless remote_data
|
134
|
+
|
135
|
+
remote_store = LocalStore.new(remote_data)
|
136
|
+
|
137
|
+
update_store_base(local_store, remote_store)
|
138
|
+
replace_benchmarks(client, local_store, remote_store, screens) if get_benchmarks
|
139
|
+
replace_masks(local_store, remote_store, screens) if get_masks
|
140
|
+
|
141
|
+
save_store local_store.data
|
142
|
+
end
|
143
|
+
|
144
|
+
def update_store_base(local_store, remote_store)
|
145
|
+
local_store.put(TITLE, remote_store.get(TITLE))
|
146
|
+
local_store.put(APIKEY, remote_store.apikey)
|
147
|
+
end
|
148
|
+
|
149
|
+
def replace_benchmarks(client, local_store, remote_store, screens)
|
150
|
+
# no api key
|
151
|
+
screens = remote_store.all_screens unless screens
|
152
|
+
|
153
|
+
Dir.mkdir(BENCHMARKS_PATH) unless Dir.exist? BENCHMARKS_PATH
|
154
|
+
|
155
|
+
screens.each do |screen|
|
156
|
+
benchmark = remote_store.benchmark_by_screenname(screen)
|
157
|
+
next unless benchmark
|
158
|
+
|
159
|
+
benchmark[LOCAL_FILENAME] = sanitize(screen) + File.extname(benchmark[FILENAME])
|
160
|
+
|
161
|
+
# download benchmark
|
162
|
+
puts "Downloading #{screen}"
|
163
|
+
benchmark_image = client.fetch_benchmark(screen)
|
164
|
+
|
165
|
+
# update store to reflex file name of benchmark
|
166
|
+
file_path = "#{BENCHMARKS_PATH}/#{benchmark[LOCAL_FILENAME]}"
|
167
|
+
File.open(file_path, 'w') {|file| file.write(benchmark_image)}
|
168
|
+
local_store.update_benchmark(benchmark)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def replace_masks(local_store, remote_store, screens)
|
173
|
+
# global mask
|
174
|
+
local_store.put(MASKED_AREA, remote_store.get(MASKED_AREA))
|
175
|
+
screens = remote_store.all_screens unless screens
|
176
|
+
screens.each do |screen|
|
177
|
+
benchmark = remote_store.benchmark_by_screenname(screen)
|
178
|
+
# download benchmark
|
179
|
+
puts "Update mask for #{screen}"
|
180
|
+
local_store.update_mask(benchmark) if benchmark
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def sanitize(filename)
|
186
|
+
# Remove any character that aren't 0-9, A-Z, or a-z
|
187
|
+
filename.gsub(/[^0-9A-Z]/i, '_')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title><%= report_data['name'] %></title>
|
4
|
+
<style>
|
5
|
+
body {
|
6
|
+
font-family: "Roboto", Helvetica, sans-serif;
|
7
|
+
background-color: #8F5699;
|
8
|
+
}
|
9
|
+
|
10
|
+
.passed {
|
11
|
+
display: none;
|
12
|
+
}
|
13
|
+
|
14
|
+
table {
|
15
|
+
background: #fff;
|
16
|
+
border-radius: 4px;
|
17
|
+
padding: 10px;
|
18
|
+
margin: 2%;
|
19
|
+
}
|
20
|
+
|
21
|
+
h1 {
|
22
|
+
color:white;
|
23
|
+
margin-left: 2%;
|
24
|
+
}
|
25
|
+
|
26
|
+
h2 {
|
27
|
+
margin-top: 20px;
|
28
|
+
}
|
29
|
+
|
30
|
+
.toggle {
|
31
|
+
margin-right: 2%;
|
32
|
+
display: inline-block;
|
33
|
+
color: white;
|
34
|
+
float: right;
|
35
|
+
clear: both;
|
36
|
+
}
|
37
|
+
|
38
|
+
</style>
|
39
|
+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
40
|
+
<script>
|
41
|
+
$(document).ready(function () {
|
42
|
+
$('.toggle').on('click', function () {
|
43
|
+
if ($('.passed').css('display') == 'none') {
|
44
|
+
$('.passed').css('display', 'table-row');
|
45
|
+
$('.toggle').text('Hide successful tests');
|
46
|
+
} else {
|
47
|
+
|
48
|
+
$('.passed').css('display', 'none');
|
49
|
+
$('.toggle').text('Show successful tests');
|
50
|
+
}
|
51
|
+
})
|
52
|
+
});
|
53
|
+
</script>
|
54
|
+
</head>
|
55
|
+
<body>
|
56
|
+
<a class="toggle" href="#">
|
57
|
+
Show successful tests
|
58
|
+
</a>
|
59
|
+
<h1><%= report_data['name'] %></h1>
|
60
|
+
|
61
|
+
<table style="width:96%">
|
62
|
+
|
63
|
+
<% report_data['tests'].each do |item|
|
64
|
+
class_name = item['passed'] ? 'passed' : 'failed'
|
65
|
+
%>
|
66
|
+
<tr class="<%= class_name %>">
|
67
|
+
<td colspan="3">
|
68
|
+
<h2> <%= item['screenname'] %></h2>
|
69
|
+
<p>
|
70
|
+
<b style="font-size:24px;"><%= item['passed'] ? 'Passed' : 'Failed' %></b><br/>
|
71
|
+
<%= item['passed'] ? item['message'] : '' %><br/>
|
72
|
+
</p>
|
73
|
+
|
74
|
+
</td>
|
75
|
+
</tr>
|
76
|
+
<tr class="<%= class_name %>">
|
77
|
+
<td>
|
78
|
+
<b>Benchmark</b><br/>
|
79
|
+
<img style="width:100%" src="benchmarks/<%= item['local_filename'] %>"/>
|
80
|
+
|
81
|
+
</td>
|
82
|
+
|
83
|
+
<td>
|
84
|
+
<% if item['has_diff'] %>
|
85
|
+
<b>Diff</b><br/>
|
86
|
+
<img style="width:100%" src="screenshots/<%= item['local_filename'] %>.diff.png"/>
|
87
|
+
<% end %>
|
88
|
+
</td>
|
89
|
+
|
90
|
+
<td>
|
91
|
+
<b>Result</b><br/>
|
92
|
+
<img style="width:100%" src="benchmarks/<%= item['local_filename'] %>"/>
|
93
|
+
|
94
|
+
</td>
|
95
|
+
</tr>
|
96
|
+
<% end %>
|
97
|
+
|
98
|
+
</table>
|
99
|
+
</body>
|
100
|
+
</html>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
module YordiTests
|
3
|
+
module Generators
|
4
|
+
class Report < Thor::Group
|
5
|
+
include Thor::Actions
|
6
|
+
argument :report_path, type: :string
|
7
|
+
argument :report_data, type: :hash, default: {name: 'Unknown'}
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
File.dirname(__FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_report
|
14
|
+
template("report.html.erb", report_path, force: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'yordi_tests'
|
2
|
+
require 'mini_magick'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'yordi_tests/local_store'
|
5
|
+
module YordiTests
|
6
|
+
module ImageCompare
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def addMask(image_path, global_mask, screen_mask)
|
10
|
+
output_path = "#{image_path}#{SecureRandom.hex}.png"
|
11
|
+
convert = MiniMagick::Tool::Convert.new(whiny: false)
|
12
|
+
convert << image_path
|
13
|
+
convert.fill('white')
|
14
|
+
convert.stroke('none')
|
15
|
+
unless screen_mask.nil?
|
16
|
+
screen_mask.each do |item|
|
17
|
+
convert.draw("rectangle #{item['x']}, #{item['y']} #{item['x'] + item['width']},#{item['y'] + item['height']}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
unless global_mask.nil?
|
21
|
+
global_mask.each do |item|
|
22
|
+
convert.draw("rectangle #{item['x']}, #{item['y']} #{item['x'] + item['width']},#{item['y'] + item['height']}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
convert << output_path
|
26
|
+
|
27
|
+
convert.call do |stdout, stderr, status|
|
28
|
+
unless stderr.nil?
|
29
|
+
# error_count = stderr.to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
output_path
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform( benchmark_path, screenshot_path, global_mask, screen_mask)
|
36
|
+
response = { passed: false, message: 'None', has_diff: false, diff: nil}
|
37
|
+
|
38
|
+
#compare images
|
39
|
+
# begin
|
40
|
+
screenshot = MiniMagick::Image.open(screenshot_path)
|
41
|
+
benchmark_screenshot = MiniMagick::Image.open(benchmark_path)
|
42
|
+
if screenshot.nil?
|
43
|
+
response[:message] = 'Screen shot unreadable'
|
44
|
+
elsif benchmark_screenshot.nil?
|
45
|
+
response[:message] = 'Benchmark image unreadable'
|
46
|
+
elsif screenshot.width != benchmark_screenshot.width || screenshot.height != benchmark_screenshot.height
|
47
|
+
response[:message] = 'Screenshot has wrong dimensions'
|
48
|
+
|
49
|
+
else
|
50
|
+
if screenshot.signature == benchmark_screenshot.signature
|
51
|
+
response[:passed] = true
|
52
|
+
response[:message] = 'Screenshot is perfect'
|
53
|
+
else
|
54
|
+
compare_mask_one = addMask(benchmark_path, global_mask, screen_mask)
|
55
|
+
compare_mask_two = addMask(screenshot_path, global_mask, screen_mask)
|
56
|
+
|
57
|
+
image_diff = "#{screenshot_path}.diff.png"
|
58
|
+
compare = MiniMagick::Tool::Compare.new(whiny: false)
|
59
|
+
compare.metric('AE')
|
60
|
+
compare.fuzz('5%')
|
61
|
+
|
62
|
+
compare << compare_mask_one
|
63
|
+
compare << compare_mask_two
|
64
|
+
# compare.compose('Src')
|
65
|
+
compare << image_diff
|
66
|
+
|
67
|
+
error_count = 0
|
68
|
+
compare.call do |stdout, stderr, status|
|
69
|
+
unless stderr.nil?
|
70
|
+
error_count = stderr.to_i
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if error_count.zero?
|
75
|
+
response[:passed] = true
|
76
|
+
response[:message] = 'Screenshot seems to be the same.'
|
77
|
+
else
|
78
|
+
response[:message] = 'Hmm, has something changed, WTF!'
|
79
|
+
end
|
80
|
+
|
81
|
+
response[:diff] = image_diff
|
82
|
+
response[:has_diff] = true
|
83
|
+
File.delete(compare_mask_one) unless compare_mask_one.nil?
|
84
|
+
File.delete(compare_mask_two) unless compare_mask_two.nil?
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
response
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'yordi_tests'
|
2
|
+
module YordiTests
|
3
|
+
BENCHMARKS = 'test_benchmarks'.freeze
|
4
|
+
SCREENNAME = 'screenname'.freeze
|
5
|
+
FILENAME = 'filename'.freeze
|
6
|
+
MASKED_AREA = 'masked_area'.freeze
|
7
|
+
APIKEY = 'apikey'.freeze
|
8
|
+
LOCAL_FILENAME = 'local_filename'.freeze
|
9
|
+
TITLE = 'title'.freeze
|
10
|
+
|
11
|
+
class LocalStore
|
12
|
+
|
13
|
+
def initialize(json_store)
|
14
|
+
@store = json_store
|
15
|
+
end
|
16
|
+
|
17
|
+
def apikey
|
18
|
+
get(APIKEY)
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
@store
|
23
|
+
end
|
24
|
+
|
25
|
+
def put(key, data)
|
26
|
+
@store[key] = data
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
@store[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def all_screens
|
34
|
+
screens = []
|
35
|
+
@store[BENCHMARKS].each do |benchmark|
|
36
|
+
screens << benchmark[SCREENNAME]
|
37
|
+
end
|
38
|
+
screens
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_benchmark(benchmark)
|
42
|
+
benchmark_pos = benchmark_pos_by_screenname(benchmark[SCREENNAME])
|
43
|
+
if benchmark_pos < 0
|
44
|
+
@store[BENCHMARKS] << benchmark.dup
|
45
|
+
else
|
46
|
+
@store[BENCHMARKS][benchmark_pos][SCREENNAME] = benchmark[SCREENNAME]
|
47
|
+
@store[BENCHMARKS][benchmark_pos][FILENAME] = benchmark[FILENAME]
|
48
|
+
@store[BENCHMARKS][benchmark_pos][LOCAL_FILENAME] = benchmark[LOCAL_FILENAME]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_mask(benchmark)
|
53
|
+
benchmark_pos = benchmark_pos_by_screenname(benchmark[SCREENNAME])
|
54
|
+
if benchmark_pos < 0
|
55
|
+
@store[BENCHMARKS] << benchmark.dup
|
56
|
+
else
|
57
|
+
@store[BENCHMARKS][benchmark_pos][MASKED_AREA] = benchmark[MASKED_AREA]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def benchmark_by_screenname(screenname)
|
62
|
+
@store[BENCHMARKS].each do |benchmark|
|
63
|
+
return benchmark.dup if screenname.eql? benchmark[SCREENNAME]
|
64
|
+
end
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def benchmark_pos_by_screenname(screenname)
|
69
|
+
@store[BENCHMARKS].each_with_index do |benchmark, index|
|
70
|
+
return index if screenname.eql? benchmark[SCREENNAME]
|
71
|
+
end
|
72
|
+
-1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/yordi_tests.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yordi_tests/version'
|
2
|
+
require 'yordi_tests/client'
|
3
|
+
module YordiTests
|
4
|
+
|
5
|
+
HOST = ENV['YORDI_HOST'].freeze || 'https://yorditests.com'.freeze
|
6
|
+
START_PATH = '/api/v1/start'.freeze
|
7
|
+
UPLOAD_PATH = '/api/v1/upload'.freeze
|
8
|
+
STOP_PATH = '/api/v1/stop'.freeze
|
9
|
+
FETCH_APPLICATION = '/api/v1/fetch_application'.freeze
|
10
|
+
PUSH_BENCHMARK = '/api/v1/push_benchmarks'.freeze
|
11
|
+
PUSH_MASK = '/api/v1/push_masks'.freeze
|
12
|
+
FETCH_BENCHMARK = '/api/v1/benchmark'.freeze
|
13
|
+
|
14
|
+
|
15
|
+
YORDI_DIR = ENV['YORDI_DIR'].freeze || '.yordi_tests'.freeze
|
16
|
+
BENCHMARKS_PATH = YORDI_DIR + '/benchmarks'.freeze
|
17
|
+
SCREENS_PATH = YORDI_DIR + '/screenshots'.freeze
|
18
|
+
CONFIG_FILE = YORDI_DIR + '/config.json'.freeze
|
19
|
+
REPORT_FILE = YORDI_DIR + '/report.json'.freeze
|
20
|
+
REPORT_HTML = YORDI_DIR + '/report.html'.freeze
|
21
|
+
|
22
|
+
module_function
|
23
|
+
|
24
|
+
def client=(v)
|
25
|
+
@client = v
|
26
|
+
end
|
27
|
+
|
28
|
+
def client
|
29
|
+
@client
|
30
|
+
end
|
31
|
+
|
32
|
+
# set default value
|
33
|
+
@client = Client
|
34
|
+
|
35
|
+
def new_client(apikey)
|
36
|
+
client = new
|
37
|
+
client.extend(Client)
|
38
|
+
client.apikey = apikey
|
39
|
+
client
|
40
|
+
end
|
41
|
+
end
|
data/yordi_tests.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "yordi_tests"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "yordi_tests"
|
8
|
+
spec.version = YordiTests::VERSION
|
9
|
+
spec.authors = ["Brian OQR"]
|
10
|
+
spec.email = ["oqrbrian@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "This is a CLI to run standalone or integrate with online YordiTests.com"
|
13
|
+
spec.description = "This is an alpha version and under continual development, not yet stable"
|
14
|
+
spec.homepage = "https://www.yorditests.com"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = '~> 2.0'
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_dependency 'thor', '~>0.20'
|
34
|
+
spec.add_dependency 'rest-client', '~>2.0'
|
35
|
+
spec.add_dependency 'mini_magick', '~>4.8'
|
36
|
+
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
39
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
40
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yordi_tests
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian OQR
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.20'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.20'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mini_magick
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.15'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.15'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
description: This is an alpha version and under continual development, not yet stable
|
98
|
+
email:
|
99
|
+
- oqrbrian@gmail.com
|
100
|
+
executables:
|
101
|
+
- yordi
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".travis.yml"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- exe/yordi
|
114
|
+
- lib/yordi_tests.rb
|
115
|
+
- lib/yordi_tests/cli.rb
|
116
|
+
- lib/yordi_tests/client.rb
|
117
|
+
- lib/yordi_tests/data_manager.rb
|
118
|
+
- lib/yordi_tests/generators/report.html.erb
|
119
|
+
- lib/yordi_tests/generators/report.rb
|
120
|
+
- lib/yordi_tests/image_compare.rb
|
121
|
+
- lib/yordi_tests/local_store.rb
|
122
|
+
- lib/yordi_tests/version.rb
|
123
|
+
- yordi_tests.gemspec
|
124
|
+
homepage: https://www.yorditests.com
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata:
|
128
|
+
allowed_push_host: https://rubygems.org
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2.0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.6.13
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: This is a CLI to run standalone or integrate with online YordiTests.com
|
149
|
+
test_files: []
|