yordi_tests 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+ /test/fixtures/
12
+ /test/fixtures/benchmarks/
13
+ /test/fixtures/screenshots/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in yordi_tests.gemspec
6
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/yordi ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yordi_tests/cli'
3
+ YordiTests::CLI.start
@@ -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
@@ -0,0 +1,3 @@
1
+ module YordiTests
2
+ VERSION = "0.1.7"
3
+ end
@@ -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
@@ -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: []