vizzly 0.2.1 → 0.3.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -2
  3. data/lib/vizzly.rb +122 -18
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d871ec31907ae78a9790e8763340300d5b3b0860048ad3bde1d540e8de746e2
4
- data.tar.gz: e03b0e7d53d526c82d813a3a9435cb3bb77a7af9450c2de5ddc03f09f7c05815
3
+ metadata.gz: 00b928505560c41d2c1402ccb75ce47a3814cfaedf3d15094b5685d21a5ae2fb
4
+ data.tar.gz: dafb6128f52bc6e76b9cba415ef1e8823615758048d58613fb1688e337ab8c15
5
5
  SHA512:
6
- metadata.gz: b482c246a5e588d07548a4ffdcf8a9bf61f158510ad49238e242a8fc5a26f0eb24ff02603a3623c9414ca069a6e553248d0d1aae0bd4da2dcdb7711d97ca18c1
7
- data.tar.gz: 1109017a272528539721084733f5f5ad0b6e1d50cbd9b077c0cecec588544cdf684dffea7f408dfc0cfbdf1a1dbcab28d898711e00f454a748b0c09e4b62b484
6
+ metadata.gz: 7f9afac44fa26d16dd705e82dbe6f38cc161004ebe97580f6c47d1d68f2f808a57f644008e718339f1695b9cc421206ca30ec58229ef7d8fb68d1b1bb1658d9f
7
+ data.tar.gz: d9af13050f7617fd2e0c94abc364a8b90ca890d92d3df7542fe18797dc4aea014a21dcb1d1e62f96e7ab8ac699441f5a8c8a84cfb5015f72c182749dd17800b0
data/README.md CHANGED
@@ -43,20 +43,40 @@ Vizzly.screenshot('checkout-page', image_data,
43
43
  browser: 'chrome',
44
44
  viewport: { width: 1920, height: 1080 }
45
45
  },
46
- threshold: 5
46
+ threshold: 5,
47
+ min_cluster_size: 3,
48
+ full_page: true,
49
+ build_id: 'build_123',
50
+ request_timeout: 60_000
47
51
  )
48
52
  ```
49
53
 
54
+ Ruby option names use snake_case. The client also accepts camelCase aliases for
55
+ parity with the JavaScript API: `minClusterSize`, `fullPage`, `buildId`, and
56
+ `requestTimeout`. `request_timeout` is measured in milliseconds, so
57
+ `60_000` means one minute.
58
+
50
59
  ### Using a Client Instance
51
60
 
52
61
  ```ruby
53
62
  client = Vizzly::Client.new
54
63
  client.screenshot('login-form', image_data)
55
64
 
65
+ # Override local TDD visual diff behavior for this client
66
+ strict_client = Vizzly::Client.new(fail_on_diff: true)
67
+
68
+ # Attach screenshots to a known build and tune request timeout in milliseconds
69
+ client.screenshot(
70
+ 'checkout',
71
+ image_data,
72
+ build_id: 'build_123',
73
+ request_timeout: 60_000
74
+ )
75
+
56
76
  # Check if client is ready
57
77
  puts "Ready: #{client.ready?}"
58
78
 
59
- # Get client info
79
+ # Get client info, including the effective fail_on_diff setting
60
80
  puts client.info
61
81
  ```
62
82
 
@@ -100,6 +120,8 @@ You can also configure via environment variables:
100
120
 
101
121
  - `VIZZLY_SERVER_URL` - Server URL (e.g., `http://localhost:47392`)
102
122
  - `VIZZLY_BUILD_ID` - Build identifier for grouping screenshots
123
+ - `VIZZLY_FAIL_ON_DIFF` - Set to `true` or `1` to raise when a local TDD
124
+ comparison returns a visual diff
103
125
 
104
126
  ## Development
105
127
 
data/lib/vizzly.rb CHANGED
@@ -11,12 +11,15 @@ module Vizzly
11
11
  # Default port for local TDD server
12
12
  DEFAULT_TDD_PORT = 47392
13
13
 
14
+ # rubocop:disable Metrics/ClassLength
14
15
  class Client
15
16
  attr_reader :server_url, :disabled
16
17
 
17
- def initialize(server_url: nil)
18
+ def initialize(server_url: nil, fail_on_diff: nil)
19
+ @server_info = nil
20
+ @configured_fail_on_diff = fail_on_diff
18
21
  @server_url = server_url || discover_server_url
19
- @disabled = false
22
+ @disabled = ENV['VIZZLY_ENABLED'] == 'false'
20
23
  @warned = false
21
24
  end
22
25
 
@@ -26,9 +29,13 @@ module Vizzly
26
29
  # @param image_data [String] PNG image data as binary string
27
30
  # @param options [Hash] Optional configuration
28
31
  # @option options [Hash] :properties Additional properties to attach
29
- # @option options [Integer] :threshold Pixel difference threshold (0-100)
30
- # @option options [Integer] :min_cluster_size Minimum cluster size to count as a real difference (default: 2)
32
+ # @option options [Numeric] :threshold Delta E comparison threshold. When
33
+ # omitted, the server's configured threshold is used.
34
+ # @option options [Integer] :min_cluster_size Ignore connected diff
35
+ # clusters smaller than this size. When omitted, the server config is used.
31
36
  # @option options [Boolean] :full_page Whether this is a full page screenshot
37
+ # @option options [String] :build_id Build ID for grouping screenshots
38
+ # @option options [Numeric] :request_timeout Request timeout in milliseconds
32
39
  #
33
40
  # @return [Hash, nil] Response data or nil if disabled/failed
34
41
  #
@@ -42,7 +49,7 @@ module Vizzly
42
49
  # properties: { browser: 'chrome', viewport: { width: 1920, height: 1080 } },
43
50
  # threshold: 5
44
51
  # )
45
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
52
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
46
53
  def screenshot(name, image_data, options = {})
47
54
  return nil if disabled?
48
55
 
@@ -53,27 +60,34 @@ module Vizzly
53
60
  end
54
61
 
55
62
  image_base64 = Base64.strict_encode64(image_data)
63
+ options = normalize_options(options)
64
+ normalized = normalize_screenshot_options(options)
56
65
 
57
- # Build properties hash - comparison options merged with user properties
58
- # Server extracts threshold/minClusterSize from properties, not top-level
59
- properties = (options[:properties] || {}).merge(
60
- threshold: options[:threshold],
61
- minClusterSize: options[:min_cluster_size],
62
- fullPage: options[:full_page]
63
- ).compact
66
+ normalized[:warnings].each { |warning| warn warning[:message] }
67
+
68
+ request_timeout = normalized[:request_timeout]
69
+ request_timeout_seconds = request_timeout ? request_timeout.to_f / 1000.0 : 30
70
+ build_id = normalized[:build_id] || ENV.fetch('VIZZLY_BUILD_ID', nil)
64
71
 
65
72
  payload = {
66
73
  name: name,
67
74
  image: image_base64,
68
75
  type: 'base64',
69
- buildId: ENV.fetch('VIZZLY_BUILD_ID', nil),
70
- properties: properties
76
+ buildId: build_id,
77
+ properties: normalized[:properties],
78
+ warnings: normalized[:warnings]
71
79
  }.compact
72
80
 
73
81
  uri = URI("#{@server_url}/screenshot")
74
82
 
75
83
  begin
76
- response = Net::HTTP.start(uri.host, uri.port, open_timeout: 10, read_timeout: 30) do |http|
84
+ response = Net::HTTP.start(
85
+ uri.host,
86
+ uri.port,
87
+ use_ssl: uri.scheme == 'https',
88
+ open_timeout: 10,
89
+ read_timeout: request_timeout_seconds
90
+ ) do |http|
77
91
  request = Net::HTTP::Post.new(uri)
78
92
  request['Content-Type'] = 'application/json'
79
93
  request.body = JSON.generate(payload)
@@ -92,6 +106,11 @@ module Vizzly
92
106
  comp = error_data['comparison']
93
107
  diff_percent = comp['diffPercentage']&.round(2) || 0.0
94
108
 
109
+ if fail_on_diff?
110
+ raise Error,
111
+ "Visual diff detected for \"#{comp['name'] || name}\" (#{comp['diffPercentage'] || 0}% difference)"
112
+ end
113
+
95
114
  # Extract port from server_url
96
115
  port = begin
97
116
  @server_url.match(/:(\d+)/)[1]
@@ -114,7 +133,13 @@ module Vizzly
114
133
  "Screenshot failed: #{response.code} #{response.message} - #{error_data['error'] || 'Unknown error'}"
115
134
  end
116
135
 
117
- JSON.parse(response.body)
136
+ body = JSON.parse(response.body)
137
+ if body['tddMode'] && %w[diff failed].include?(body['status']) && fail_on_diff?
138
+ raise Error,
139
+ "Visual diff detected for \"#{body['name'] || name}\" (#{body['diffPercentage'] || 0}% difference)"
140
+ end
141
+
142
+ body
118
143
  rescue Error => e
119
144
  # Re-raise Vizzly errors (like visual diffs)
120
145
  raise if e.message.include?('Visual diff')
@@ -152,7 +177,7 @@ module Vizzly
152
177
  nil
153
178
  end
154
179
  end
155
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
180
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
156
181
 
157
182
  # Wait for all queued screenshots to be processed
158
183
  # (Simple client doesn't need explicit flushing)
@@ -193,14 +218,82 @@ module Vizzly
193
218
  {
194
219
  enabled: !disabled?,
195
220
  server_url: @server_url,
221
+ serverUrl: @server_url,
196
222
  ready: ready?,
197
223
  build_id: ENV.fetch('VIZZLY_BUILD_ID', nil),
198
- disabled: disabled?
224
+ buildId: ENV.fetch('VIZZLY_BUILD_ID', nil),
225
+ disabled: disabled?,
226
+ fail_on_diff: fail_on_diff?,
227
+ failOnDiff: fail_on_diff?
199
228
  }
200
229
  end
201
230
 
202
231
  private
203
232
 
233
+ def normalize_options(options)
234
+ options.transform_keys { |key| key.is_a?(String) ? key.to_sym : key }
235
+ end
236
+
237
+ def option_value(options, *keys)
238
+ keys.each do |key|
239
+ return options[key] if options.key?(key)
240
+ end
241
+
242
+ nil
243
+ end
244
+
245
+ def normalize_screenshot_options(options)
246
+ threshold = options[:threshold]
247
+ min_cluster_size = option_value(options, :min_cluster_size, :minClusterSize)
248
+ full_page = options.key?(:full_page) ? options[:full_page] : options[:fullPage]
249
+ build_id = option_value(options, :build_id, :buildId)
250
+ request_timeout = option_value(options, :request_timeout, :requestTimeout)
251
+ properties = {}
252
+ warnings = []
253
+
254
+ (options[:properties] || {}).each do |key, value|
255
+ option = key.to_s
256
+ case option
257
+ when 'threshold'
258
+ threshold = value if threshold.nil?
259
+ when 'min_cluster_size', 'minClusterSize'
260
+ min_cluster_size = value if min_cluster_size.nil?
261
+ when 'full_page', 'fullPage'
262
+ full_page = value if full_page.nil?
263
+ when 'build_id', 'buildId'
264
+ build_id = value if build_id.nil?
265
+ when 'request_timeout', 'requestTimeout'
266
+ request_timeout = value if request_timeout.nil?
267
+ else
268
+ properties[key] = value
269
+ next
270
+ end
271
+
272
+ warnings << reserved_property_warning(option)
273
+ end
274
+
275
+ properties = properties.merge(
276
+ threshold: threshold,
277
+ minClusterSize: min_cluster_size,
278
+ fullPage: full_page
279
+ ).compact
280
+
281
+ {
282
+ build_id: build_id,
283
+ request_timeout: request_timeout,
284
+ properties: properties,
285
+ warnings: warnings
286
+ }
287
+ end
288
+
289
+ def reserved_property_warning(option)
290
+ {
291
+ code: 'reserved-property-option',
292
+ option: option,
293
+ message: "Move \"#{option}\" out of properties; properties is only for user metadata."
294
+ }
295
+ end
296
+
204
297
  def warn_once(message)
205
298
  return if @warned
206
299
 
@@ -217,6 +310,15 @@ module Vizzly
217
310
  auto_discover_tdd_server
218
311
  end
219
312
 
313
+ def fail_on_diff?
314
+ return @configured_fail_on_diff unless @configured_fail_on_diff.nil?
315
+
316
+ env_value = ENV.fetch('VIZZLY_FAIL_ON_DIFF', '').downcase
317
+ return true if %w[true 1].include?(env_value)
318
+
319
+ @server_info && @server_info['failOnDiff'] == true
320
+ end
321
+
220
322
  # Auto-discover local TDD server by checking for server.json
221
323
  def auto_discover_tdd_server
222
324
  dir = Dir.pwd
@@ -228,6 +330,7 @@ module Vizzly
228
330
  if File.exist?(server_json_path)
229
331
  begin
230
332
  server_info = JSON.parse(File.read(server_json_path))
333
+ @server_info = server_info
231
334
  port = server_info['port'] || DEFAULT_TDD_PORT
232
335
  return "http://localhost:#{port}"
233
336
  rescue JSON::ParserError, Errno::ENOENT
@@ -241,6 +344,7 @@ module Vizzly
241
344
  nil
242
345
  end
243
346
  end
347
+ # rubocop:enable Metrics/ClassLength
244
348
 
245
349
  class << self
246
350
  # Get or create the shared client instance
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vizzly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vizzly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-04 00:00:00.000000000 Z
11
+ date: 2026-06-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A lightweight client SDK for capturing screenshots and sending them to
14
14
  Vizzly for visual regression testing
@@ -44,7 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  requirements: []
47
- rubygems_version: 3.3.27
47
+ rubygems_version: 3.5.22
48
48
  signing_key:
49
49
  specification_version: 4
50
50
  summary: Vizzly visual regression testing client for Ruby