Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,19 @@ test('homepage looks correct', async ({ page }) => {
fullPage: true,
requestTimeout: 5000,
properties: {
browser: 'chrome',
viewport: { width: 1920, height: 1080 },
theme: 'dark',
locale: 'en-US',
},
});
});
```

`properties` is your metadata bag for baseline grouping, filtering, and
debugging. SDK options such as `threshold`, `minClusterSize`, `fullPage`,
`requestTimeout`, and `buildId` stay at the top level. Reserved SDK and capture
fields inside `properties` are ignored and produce a warning; they are never
interpreted as options or stored as user metadata.
`properties` is your key/value metadata bag for baseline grouping, filtering,
and debugging. Vizzly passes every property through as user metadata; property
names are never interpreted as options. The supported top-level options are
`threshold`, `minClusterSize`, `fullPage`, `requestTimeout`, and `buildId`.
Vizzly reads width and height from the captured image, so viewport dimensions do
not need to be included in `properties`.

The client SDK is lightweight. It posts screenshots to the local Vizzly server
or the cloud build wrapper. It works with any test runner.
Expand Down
5 changes: 2 additions & 3 deletions clients/ember/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,8 @@ await vizzlyScreenshot('screenshot-name', {
setting injected from `VIZZLY_FAIL_ON_DIFF` or `.vizzly/server.json`, then
non-failing mode.

Each screenshot also includes Vizzly metadata for grouping and comparison:
`browser`, `viewport_width`, `viewport_height`, `url`, and any custom
`properties` you provide.
Each screenshot also includes `browser`, `url`, and any custom `properties` you
provide. Vizzly reads dimensions from the captured image.

The function automatically:
- Waits for Ember's `settled()` before capturing
Expand Down
19 changes: 1 addition & 18 deletions clients/ember/src/test-support/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,30 +269,13 @@ export async function vizzlyScreenshot(name, options = {}) {
// eslint-disable-next-line no-unused-expressions
document.body.offsetHeight;

let customViewport = properties.viewport;
let customViewportWidth = properties.viewport_width;
let customViewportHeight = properties.viewport_height;
let screenshotProperties = {
...properties,
framework: 'ember',
browser: detectBrowser(),
viewport_width: width,
viewport_height: height,
url: window.location.href,
...properties,
};

if (customViewport !== undefined) {
screenshotProperties.viewport = customViewport;
}

if (customViewportWidth !== undefined) {
screenshotProperties.viewport_width = customViewportWidth;
}

if (customViewportHeight !== undefined) {
screenshotProperties.viewport_height = customViewportHeight;
}

// Build request payload
let payload = {
buildId: buildId || window.__VIZZLY_BUILD_ID__ || null,
Expand Down
10 changes: 4 additions & 6 deletions clients/ember/tests/unit/test-support.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,14 @@ describe('test-support', () => {
assert.deepStrictEqual(capturedBody.properties, {
framework: 'ember',
browser: 'chromium',
viewport_width: 1440,
viewport_height: 900,
url: 'http://localhost:4200/dashboard',
theme: 'dark',
});
assert.strictEqual(capturedBody.threshold, 5);
assert.strictEqual(capturedBody.minClusterSize, 10);
});

it('keeps reserved metadata stable while allowing custom viewport metadata', async () => {
it('preserves viewport keys when the user explicitly supplies them', async () => {
let capturedBody = null;

installBrowserGlobals(async (_url, request) => {
Expand All @@ -136,9 +134,9 @@ describe('test-support', () => {
});

assert.deepStrictEqual(capturedBody.properties, {
framework: 'ember',
browser: 'chromium',
url: 'http://localhost:4200/dashboard',
framework: 'custom-framework',
browser: 'webkit',
url: 'http://evil.example',
theme: 'dark',
viewport: { width: 375, height: 667 },
viewport_width: 375,
Expand Down
6 changes: 4 additions & 2 deletions clients/ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Vizzly.screenshot('homepage', image_data)
Vizzly.screenshot('checkout-page', image_data,
properties: {
browser: 'chrome',
viewport: { width: 1920, height: 1080 }
theme: 'dark',
locale: 'en-US'
},
threshold: 5,
min_cluster_size: 3,
Expand Down Expand Up @@ -104,7 +105,8 @@ RSpec.describe 'Homepage', type: :feature do
Vizzly.screenshot('checkout-form', image_data,
properties: {
browser: 'chrome',
viewport: { width: 1920, height: 1080 }
theme: 'dark',
locale: 'en-US'
}
)
end
Expand Down
3 changes: 2 additions & 1 deletion clients/ruby/example/test_screenshot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def test_captures_vizzly_homepage
result = Vizzly.screenshot('vizzly-homepage', image_data,
properties: {
browser: 'chrome',
viewport: { width: 1920, height: 1080 }
theme: 'light',
locale: 'en-US'
})

puts "\n✓ Screenshot captured!"
Expand Down
50 changes: 3 additions & 47 deletions clients/ruby/lib/vizzly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,6 @@ class Error < StandardError; end

# Default port for local TDD server
DEFAULT_TDD_PORT = 47392
RESERVED_PROPERTY_OPTIONS = %w[
threshold
min_cluster_size
minClusterSize
full_page
fullPage
capture_mode
captureMode
device_scale_factor
deviceScaleFactor
pixelRatio
dpr
selector
component
element_selector
elementSelector
build_id
buildId
request_timeout
requestTimeout
].freeze

class Client # rubocop:disable Metrics/ClassLength
attr_reader :server_url, :disabled

Expand Down Expand Up @@ -66,7 +44,7 @@ def initialize(server_url: nil, fail_on_diff: nil)
#
# @example With options
# client.screenshot('checkout', image_data,
# properties: { browser: 'chrome', viewport: { width: 1920, height: 1080 } },
# properties: { theme: 'dark', locale: 'en-US' },
# threshold: 5
# )
def screenshot(name, image_data, options = {}) # rubocop:disable Metrics
Expand All @@ -82,8 +60,6 @@ def screenshot(name, image_data, options = {}) # rubocop:disable Metrics
options = normalize_options(options)
normalized = normalize_screenshot_options(options)

normalized[:warnings].each { |warning| warn warning[:message] }

request_timeout = normalized[:request_timeout]
request_timeout_seconds = request_timeout ? request_timeout.to_f / 1000.0 : 30
build_id = normalized[:build_id] || ENV.fetch('VIZZLY_BUILD_ID', nil)
Expand All @@ -96,8 +72,7 @@ def screenshot(name, image_data, options = {}) # rubocop:disable Metrics
properties: normalized[:properties],
threshold: normalized[:threshold],
minClusterSize: normalized[:minClusterSize],
fullPage: normalized[:fullPage],
warnings: normalized[:warnings]
fullPage: normalized[:fullPage]
}.compact

uri = URI("#{@server_url}/screenshot")
Expand Down Expand Up @@ -270,33 +245,14 @@ def normalize_screenshot_options(options)
build_id = option_value(options, :build_id, :buildId)
request_timeout = option_value(options, :request_timeout, :requestTimeout)
properties = options[:properties] || {}
warnings = []

properties = properties.each_with_object({}) do |(key, value), normalized_properties|
option = key.to_s
if RESERVED_PROPERTY_OPTIONS.include?(option)
warnings << reserved_property_warning(option)
else
normalized_properties[key] = value
end
end

{
build_id: build_id,
request_timeout: request_timeout,
properties: properties,
threshold: threshold,
minClusterSize: min_cluster_size,
fullPage: full_page,
warnings: warnings
}
end

def reserved_property_warning(option)
{
code: 'reserved-property-option',
option: option,
message: "Move \"#{option}\" out of properties; properties is only for user metadata."
fullPage: full_page
}
end

Expand Down
17 changes: 7 additions & 10 deletions clients/ruby/test/vizzly_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def test_screenshot_serializes_fractional_threshold_separately_from_properties
captured_body = JSON.parse(request.body)
response
end

Net::HTTP.define_singleton_method(:start) do |_host, _port, **_options, &block|
block.call(fake_http)
end
Expand Down Expand Up @@ -330,7 +331,7 @@ def test_screenshot_accepts_string_keys_and_preserves_zero_values
Net::HTTP.define_singleton_method(:start, original_start)
end

def test_screenshot_does_not_promote_reserved_options_from_properties
def test_screenshot_preserves_option_shaped_user_properties_without_promoting_them
captured_body = nil
original_start = Net::HTTP.method(:start)
response = Net::HTTPOK.new('1.1', '200', 'OK')
Expand All @@ -345,7 +346,7 @@ def test_screenshot_does_not_promote_reserved_options_from_properties
end

client = Vizzly::Client.new(server_url: 'http://localhost:47392')
result = capture_io do
capture_io do
client.screenshot(
'reserved-properties',
'fake_image_data',
Expand All @@ -358,16 +359,12 @@ def test_screenshot_does_not_promote_reserved_options_from_properties
)
end

assert_match(/Move "threshold" out of properties/, result[1])
refute_equal 'build-from-properties', captured_body['buildId']
assert_equal 'dark', captured_body['properties']['theme']
refute_includes captured_body['properties'], 'threshold'
refute_includes captured_body['properties'], 'minClusterSize'
refute_includes captured_body['properties'], 'buildId'
assert_equal(
%w[threshold minClusterSize buildId],
captured_body['warnings'].map { |warning| warning['option'] }
)
assert_equal 1.5, captured_body['properties']['threshold']
assert_equal 3, captured_body['properties']['minClusterSize']
assert_equal 'build-from-properties', captured_body['properties']['buildId']
refute_includes captured_body, 'warnings'
ensure
Net::HTTP.define_singleton_method(:start, original_start)
end
Expand Down
20 changes: 9 additions & 11 deletions clients/static-site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,23 +252,21 @@ Patterns support glob-like syntax:

## Screenshot Naming

Screenshots are named based on the page path. The plugin records browser,
viewport, viewport dimensions, page URL, and capture mode metadata
automatically. You can add custom screenshot `properties` from config when you
need extra signature dimensions such as theme, locale, or auth state:
Screenshots are named based on the page path. The plugin records the browser and
page URL automatically. Vizzly reads the image dimensions from the captured
image. You can add custom screenshot `properties` from config when you need
extra signature values such as theme, locale, or auth state:

**Name format:** `path-to-page` (slashes replaced with hyphens)

**Properties:** Browser, viewport, URL, capture-mode metadata, and any custom
properties (`browser`, `viewport`, `viewport_width`, `viewport_height`, `url`,
`fullPage`, plus user-defined fields)
**Properties:** Browser, URL, and any custom user-defined fields

Examples:
- Name: `index`, Properties: `{ browser: 'chromium', viewport: 'mobile', viewport_width: 375, viewport_height: 667, url: 'http://localhost:3000/' }`
- Name: `blog-post-1`, Properties: `{ browser: 'chromium', viewport: 'desktop', viewport_width: 1920, viewport_height: 1080, url: 'http://localhost:3000/blog/post-1' }`
- Name: `docs-getting-started`, Properties: `{ browser: 'webkit', viewport: 'tablet', viewport_width: 768, viewport_height: 1024, url: 'http://localhost:3000/docs/getting-started' }`
- Name: `index`, Properties: `{ browser: 'chromium', url: 'http://localhost:3000/' }`
- Name: `blog-post-1`, Properties: `{ browser: 'chromium', url: 'http://localhost:3000/blog/post-1' }`
- Name: `docs-getting-started`, Properties: `{ browser: 'webkit', url: 'http://localhost:3000/docs/getting-started' }`

This approach allows Vizzly to group screenshots by viewport while keeping names clean and compatible with file system restrictions.
Static-site screenshot names stay clean and compatible with file system restrictions.

## Visual Development Workflow

Expand Down
14 changes: 5 additions & 9 deletions clients/static-site/src/screenshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,13 @@ export function generateScreenshotName(page) {
}

/**
* Generate screenshot properties from viewport
* Properties are used by Vizzly for grouping and identification
* @param {Object} viewport - Viewport object with name, width, height
* Generate screenshot properties for the captured page.
* Image dimensions are read from the captured image by Vizzly.
* @param {Object} _viewport - Capture viewport, intentionally not serialized
* @returns {Object} Screenshot properties
*/
export function generateScreenshotProperties(viewport, options = {}) {
let properties = {
viewport: viewport.name,
viewport_width: viewport.width,
viewport_height: viewport.height,
};
export function generateScreenshotProperties(_viewport, options = {}) {
let properties = {};

if (options.browser) {
properties.browser = options.browser;
Expand Down
Loading