vizzly 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +118 -0
- data/lib/vizzly.rb +257 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 83c5bb29a091449d684562effec3161f0ddab82fe2be22b3d357cb66723d44b9
|
4
|
+
data.tar.gz: 1a97c02f6b0d670a6841d9536ee7fe434340b5ca210bf315f764a03e350e360c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64a47f4a33270a9a82c4c1edbe267e4ef2eedd7f0f096859f28a306aa53d90401ffb3440cc5f7191a2138e2a6ac732dc1214b4d029c911e3d7d705f30a692056
|
7
|
+
data.tar.gz: f16bd4924bcfc9a2ddadbe0d639f01267853a1d2107dab97eb65e2d7ff1f01d63a21bd8db82a76cb4d1c281bacde9bc3fae38d194733dfb26f1f465a98ff68b3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Stubborn Mule Software
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# Vizzly Ruby Client
|
2
|
+
|
3
|
+
A lightweight Ruby client SDK for capturing screenshots and sending them to Vizzly for visual
|
4
|
+
regression testing.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'vizzly'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
bundle install
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
gem install vizzly
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Basic Usage
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require 'vizzly'
|
32
|
+
|
33
|
+
# Take a screenshot
|
34
|
+
image_data = File.binread('screenshot.png')
|
35
|
+
Vizzly.screenshot('homepage', image_data)
|
36
|
+
```
|
37
|
+
|
38
|
+
### With Options
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Vizzly.screenshot('checkout-page', image_data,
|
42
|
+
properties: {
|
43
|
+
browser: 'chrome',
|
44
|
+
viewport: { width: 1920, height: 1080 }
|
45
|
+
},
|
46
|
+
threshold: 5
|
47
|
+
)
|
48
|
+
```
|
49
|
+
|
50
|
+
### Using a Client Instance
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
client = Vizzly::Client.new
|
54
|
+
client.screenshot('login-form', image_data)
|
55
|
+
|
56
|
+
# Check if client is ready
|
57
|
+
puts "Ready: #{client.ready?}"
|
58
|
+
|
59
|
+
# Get client info
|
60
|
+
puts client.info
|
61
|
+
```
|
62
|
+
|
63
|
+
### Integration with Test Frameworks
|
64
|
+
|
65
|
+
#### RSpec + Capybara
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
RSpec.describe 'Homepage', type: :feature do
|
69
|
+
it 'displays the homepage correctly' do
|
70
|
+
visit '/'
|
71
|
+
expect(page).to have_content('Welcome')
|
72
|
+
|
73
|
+
# Take a screenshot for visual regression testing
|
74
|
+
image_data = page.driver.browser.screenshot_as(:png)
|
75
|
+
Vizzly.screenshot('homepage', image_data)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'displays the checkout page' do
|
79
|
+
visit '/checkout'
|
80
|
+
fill_in 'Email', with: 'test@example.com'
|
81
|
+
|
82
|
+
# Take a screenshot with properties
|
83
|
+
image_data = page.driver.browser.screenshot_as(:png)
|
84
|
+
Vizzly.screenshot('checkout-form', image_data,
|
85
|
+
properties: {
|
86
|
+
browser: 'chrome',
|
87
|
+
viewport: { width: 1920, height: 1080 }
|
88
|
+
}
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
## Configuration
|
95
|
+
|
96
|
+
The client automatically discovers a running Vizzly TDD server by looking for `.vizzly/server.json`
|
97
|
+
in the current and parent directories.
|
98
|
+
|
99
|
+
You can also configure via environment variables:
|
100
|
+
|
101
|
+
- `VIZZLY_SERVER_URL` - Server URL (e.g., `http://localhost:47392`)
|
102
|
+
- `VIZZLY_BUILD_ID` - Build identifier for grouping screenshots
|
103
|
+
|
104
|
+
## Development
|
105
|
+
|
106
|
+
After checking out the repo, run tests:
|
107
|
+
|
108
|
+
```bash
|
109
|
+
ruby test/vizzly_test.rb
|
110
|
+
```
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/vizzly-testing/cli.
|
115
|
+
|
116
|
+
## License
|
117
|
+
|
118
|
+
The gem is available as open source under the terms of the MIT License.
|
data/lib/vizzly.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
# Vizzly visual regression testing client
|
8
|
+
module Vizzly
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
# Default port for local TDD server
|
12
|
+
DEFAULT_TDD_PORT = 47392
|
13
|
+
|
14
|
+
class Client
|
15
|
+
attr_reader :server_url, :disabled
|
16
|
+
|
17
|
+
def initialize(server_url: nil)
|
18
|
+
@server_url = server_url || discover_server_url
|
19
|
+
@disabled = false
|
20
|
+
@warned = false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Take a screenshot for visual regression testing
|
24
|
+
#
|
25
|
+
# @param name [String] Unique name for the screenshot
|
26
|
+
# @param image_data [String] PNG image data as binary string
|
27
|
+
# @param options [Hash] Optional configuration
|
28
|
+
# @option options [Hash] :properties Additional properties to attach
|
29
|
+
# @option options [Integer] :threshold Pixel difference threshold (0-100)
|
30
|
+
# @option options [Boolean] :full_page Whether this is a full page screenshot
|
31
|
+
#
|
32
|
+
# @return [Hash, nil] Response data or nil if disabled/failed
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# client = Vizzly::Client.new
|
36
|
+
# image_data = File.binread('screenshot.png')
|
37
|
+
# client.screenshot('homepage', image_data)
|
38
|
+
#
|
39
|
+
# @example With options
|
40
|
+
# client.screenshot('checkout', image_data,
|
41
|
+
# properties: { browser: 'chrome', viewport: { width: 1920, height: 1080 } },
|
42
|
+
# threshold: 5
|
43
|
+
# )
|
44
|
+
def screenshot(name, image_data, options = {})
|
45
|
+
return nil if disabled?
|
46
|
+
|
47
|
+
unless @server_url
|
48
|
+
warn_once('Vizzly client not initialized. Screenshots will be skipped.')
|
49
|
+
disable!
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
image_base64 = Base64.strict_encode64(image_data)
|
54
|
+
|
55
|
+
payload = {
|
56
|
+
name: name,
|
57
|
+
image: image_base64,
|
58
|
+
buildId: ENV.fetch('VIZZLY_BUILD_ID', nil),
|
59
|
+
threshold: options[:threshold] || 0,
|
60
|
+
fullPage: options[:full_page] || false,
|
61
|
+
properties: options[:properties] || {}
|
62
|
+
}.compact
|
63
|
+
|
64
|
+
uri = URI("#{@server_url}/screenshot")
|
65
|
+
|
66
|
+
begin
|
67
|
+
response = Net::HTTP.start(uri.host, uri.port, read_timeout: 30) do |http|
|
68
|
+
request = Net::HTTP::Post.new(uri)
|
69
|
+
request['Content-Type'] = 'application/json'
|
70
|
+
request.body = JSON.generate(payload)
|
71
|
+
http.request(request)
|
72
|
+
end
|
73
|
+
|
74
|
+
unless response.is_a?(Net::HTTPSuccess)
|
75
|
+
error_data = begin
|
76
|
+
JSON.parse(response.body)
|
77
|
+
rescue JSON::ParserError, StandardError
|
78
|
+
{}
|
79
|
+
end
|
80
|
+
|
81
|
+
# In TDD mode with visual differences, log but don't raise
|
82
|
+
if response.code == '422' && error_data['tddMode'] && error_data['comparison']
|
83
|
+
comp = error_data['comparison']
|
84
|
+
diff_percent = comp['diffPercentage']&.round(2) || 0.0
|
85
|
+
|
86
|
+
# Extract port from server_url
|
87
|
+
port = begin
|
88
|
+
@server_url.match(/:(\d+)/)[1]
|
89
|
+
rescue StandardError
|
90
|
+
DEFAULT_TDD_PORT.to_s
|
91
|
+
end
|
92
|
+
dashboard_url = "http://localhost:#{port}/dashboard"
|
93
|
+
|
94
|
+
warn "⚠️ Visual diff: #{comp['name']} (#{diff_percent}%) → #{dashboard_url}"
|
95
|
+
|
96
|
+
return {
|
97
|
+
success: true,
|
98
|
+
status: 'failed',
|
99
|
+
name: comp['name'],
|
100
|
+
diffPercentage: comp['diffPercentage']
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
raise Error,
|
105
|
+
"Screenshot failed: #{response.code} #{response.message} - #{error_data['error'] || 'Unknown error'}"
|
106
|
+
end
|
107
|
+
|
108
|
+
JSON.parse(response.body)
|
109
|
+
rescue Error => e
|
110
|
+
# Re-raise Vizzly errors (like visual diffs)
|
111
|
+
raise if e.message.include?('Visual diff')
|
112
|
+
|
113
|
+
warn "Vizzly screenshot failed for #{name}: #{e.message}"
|
114
|
+
|
115
|
+
if e.message.include?('Connection refused') || e.is_a?(Errno::ECONNREFUSED)
|
116
|
+
warn "Server URL: #{@server_url}/screenshot"
|
117
|
+
warn 'This usually means the Vizzly server is not running or not accessible'
|
118
|
+
warn 'Check that the server is started and the port is correct'
|
119
|
+
elsif e.message.include?('404') || e.message.include?('Not Found')
|
120
|
+
warn "Server URL: #{@server_url}/screenshot"
|
121
|
+
warn 'The screenshot endpoint was not found - check server configuration'
|
122
|
+
end
|
123
|
+
|
124
|
+
# Disable the SDK after first failure to prevent spam
|
125
|
+
disable!('failure')
|
126
|
+
|
127
|
+
nil
|
128
|
+
rescue StandardError => e
|
129
|
+
warn "Vizzly screenshot failed for #{name}: #{e.message}"
|
130
|
+
disable!('failure')
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Wait for all queued screenshots to be processed
|
136
|
+
# (Simple client doesn't need explicit flushing)
|
137
|
+
#
|
138
|
+
# @return [true]
|
139
|
+
def flush
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
# Check if the client is ready to capture screenshots
|
144
|
+
#
|
145
|
+
# @return [Boolean]
|
146
|
+
def ready?
|
147
|
+
!disabled? && !@server_url.nil?
|
148
|
+
end
|
149
|
+
|
150
|
+
# Disable screenshot capture
|
151
|
+
#
|
152
|
+
# @param reason [String] Optional reason for disabling
|
153
|
+
def disable!(reason = 'disabled')
|
154
|
+
@disabled = true
|
155
|
+
return if reason == 'disabled'
|
156
|
+
|
157
|
+
warn "Vizzly SDK disabled due to #{reason}. Screenshots will be skipped for the remainder of this session."
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check if screenshot capture is disabled
|
161
|
+
#
|
162
|
+
# @return [Boolean]
|
163
|
+
def disabled?
|
164
|
+
@disabled
|
165
|
+
end
|
166
|
+
|
167
|
+
# Get client information
|
168
|
+
#
|
169
|
+
# @return [Hash] Client state information
|
170
|
+
def info
|
171
|
+
{
|
172
|
+
enabled: !disabled?,
|
173
|
+
server_url: @server_url,
|
174
|
+
ready: ready?,
|
175
|
+
build_id: ENV.fetch('VIZZLY_BUILD_ID', nil),
|
176
|
+
disabled: disabled?
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def warn_once(message)
|
183
|
+
return if @warned
|
184
|
+
|
185
|
+
warn message
|
186
|
+
@warned = true
|
187
|
+
end
|
188
|
+
|
189
|
+
# Discover Vizzly server URL from environment or auto-discovery
|
190
|
+
def discover_server_url
|
191
|
+
# First check environment variable
|
192
|
+
return ENV['VIZZLY_SERVER_URL'] if ENV['VIZZLY_SERVER_URL']
|
193
|
+
|
194
|
+
# Then try auto-discovery
|
195
|
+
auto_discover_tdd_server
|
196
|
+
end
|
197
|
+
|
198
|
+
# Auto-discover local TDD server by checking for server.json
|
199
|
+
def auto_discover_tdd_server
|
200
|
+
dir = Dir.pwd
|
201
|
+
root = File.expand_path('/')
|
202
|
+
|
203
|
+
until dir == root
|
204
|
+
server_json_path = File.join(dir, '.vizzly', 'server.json')
|
205
|
+
|
206
|
+
if File.exist?(server_json_path)
|
207
|
+
begin
|
208
|
+
server_info = JSON.parse(File.read(server_json_path))
|
209
|
+
port = server_info['port'] || DEFAULT_TDD_PORT
|
210
|
+
return "http://localhost:#{port}"
|
211
|
+
rescue JSON::ParserError, Errno::ENOENT
|
212
|
+
# Invalid JSON or file disappeared, continue searching
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
dir = File.dirname(dir)
|
217
|
+
end
|
218
|
+
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class << self
|
224
|
+
# Get or create the shared client instance
|
225
|
+
#
|
226
|
+
# @return [Client]
|
227
|
+
def client
|
228
|
+
@client ||= Client.new
|
229
|
+
end
|
230
|
+
|
231
|
+
# Take a screenshot using the shared client
|
232
|
+
#
|
233
|
+
# @see Client#screenshot
|
234
|
+
def screenshot(name, image_data, options = {})
|
235
|
+
client.screenshot(name, image_data, options)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Flush the shared client
|
239
|
+
#
|
240
|
+
# @see Client#flush
|
241
|
+
def flush
|
242
|
+
client.flush
|
243
|
+
end
|
244
|
+
|
245
|
+
# Check if the shared client is ready
|
246
|
+
#
|
247
|
+
# @see Client#ready?
|
248
|
+
def ready?
|
249
|
+
client.ready?
|
250
|
+
end
|
251
|
+
|
252
|
+
# Reset the shared client (useful for testing)
|
253
|
+
def reset!
|
254
|
+
@client = nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vizzly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vizzly
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A lightweight client SDK for capturing screenshots and sending them to
|
14
|
+
Vizzly for visual regression testing
|
15
|
+
email:
|
16
|
+
- support@vizzly.dev
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- lib/vizzly.rb
|
24
|
+
homepage: https://github.com/vizzly-testing/cli
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata:
|
28
|
+
homepage_uri: https://github.com/vizzly-testing/cli
|
29
|
+
source_code_uri: https://github.com/vizzly-testing/cli
|
30
|
+
changelog_uri: https://github.com/vizzly-testing/cli/blob/main/clients/ruby/CHANGELOG.md
|
31
|
+
rubygems_mfa_required: 'true'
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubygems_version: 3.3.27
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Vizzly visual regression testing client for Ruby
|
51
|
+
test_files: []
|