Skip to content

Comments

🐛 Flatten nested properties in screenshot upload payload#221

Closed
Robdel12 wants to merge 2 commits intomainfrom
fix/flatten-screenshot-properties
Closed

🐛 Flatten nested properties in screenshot upload payload#221
Robdel12 wants to merge 2 commits intomainfrom
fix/flatten-screenshot-properties

Conversation

@Robdel12
Copy link
Contributor

Summary

  • When SDK clients pass { properties: { url, viewport } } as options to vizzlyScreenshot(), the client sent properties: { properties: { url } } — double-nesting the data
  • The server reads properties.url (top-level) and got undefined, so screenshots.url was never populated for static site uploads
  • This broke preview linking in the full-screen review UI since canLinkToPreview() needs a valid URL from comparison.current_url
  • Fix: spread options.properties into the top level so url, viewport, etc. are directly accessible

Context

Server-side fallback already deployed (properties.properties?.url) but this fixes the source of the nesting bug.

Test plan

  • SDK client tests pass (32/32)
  • Re-run static-site CI build and verify screenshots.url is populated
  • Verify preview links appear in full-screen review UI

When callers pass { properties: { url, viewport } } as options,
the payload was sent as properties: { properties: { url } } — double
nesting. The server read properties.url (top-level) and got undefined.

Spread options.properties into the top level so url, viewport, etc.
are accessible directly as properties.url on the server side.
@vizzly-testing

This comment has been minimized.

@claude
Copy link

claude bot commented Feb 16, 2026

Code Review

I've reviewed PR #221 and the fix looks good overall. Here's my analysis:

Summary

The fix correctly addresses the double-nesting issue where { properties: { url, viewport } } was being sent as { properties: { properties: { url, viewport } } }.

Positive Aspects

  1. Root cause fix: The change at src/client/index.js:210 properly flattens the structure by spreading both options and options.properties at the same level.

  2. Server-side defense: The server already has unwrapProperties() in src/server/handlers/tdd-handler.js:25-32 as a fallback, making this a defense-in-depth approach.

  3. Existing test coverage: The integration tests in tests/sdk/client.test.js verify the properties are sent correctly (line 377).

Issues & Concerns

1. Missing Test for Nested Properties Case

The current test at line 377 only tests flat options ({ browser: 'chrome' }), but doesn't test the actual bug scenario where users pass { properties: { url, viewport } }.

Recommendation: Add a test case like:

it('flattens nested properties object', async () => {
  await vizzlyScreenshot('test', Buffer.from('data'), {
    properties: {
      url: 'https://example.com',
      viewport: '1920x1080'
    },
    browser: 'chrome'
  });
  
  assert.strictEqual(requests.length, 1);
  assert.strictEqual(requests[0].body.properties.url, 'https://example.com');
  assert.strictEqual(requests[0].body.properties.viewport, '1920x1080');
  assert.strictEqual(requests[0].body.properties.browser, 'chrome');
  // Verify no double-nesting
  assert.strictEqual(requests[0].body.properties.properties, undefined);
});

2. Potential Property Collision

The spread order { ...options, ...options.properties } means if a user passes:

{ url: 'A', properties: { url: 'B' } }

The nested url: 'B' will override the top-level url: 'A'. This might be intentional, but should be documented or validated.

Question: Should we warn/error if there are conflicting properties, or is this override behavior intended?

3. Documentation Gap

The JSDoc at line 316 documents options.properties but doesn't explain the flattening behavior. Users might be confused about why both work:

vizzlyScreenshot('test', img, { url: 'foo' })           // Direct
vizzlyScreenshot('test', img, { properties: { url: 'foo' } }) // Nested

Recommendation: Update the JSDoc to clarify this behavior.

4. Static Site Client Impact

The static-site client at clients/static-site/src/screenshot.js:106 sets properties.url directly, which is exactly the pattern this fix addresses. Verify that:

  • The static-site CI tests now populate screenshots.url correctly
  • Preview links work in the full-screen review UI

Test Plan Completeness

The PR description mentions:

  • ✅ SDK client tests pass (32/32)
  • ⬜ Re-run static-site CI build and verify screenshots.url is populated
  • ⬜ Verify preview links appear in full-screen review UI

Recommendation: Complete the remaining test plan items before merging.

Security Considerations

No security concerns. The sanitizeScreenshotProperties() function in src/utils/security.js:213-230 already handles viewport validation.

Performance

No performance impact. The spread operation is negligible.

Code Quality

  • Clean, minimal change
  • Follows existing patterns
  • No linting issues expected

Verdict

Approve with recommendations: The core fix is correct, but please add the test case for nested properties and verify the static-site integration works as expected.

buildScreenshotCheckObject was only sending sha256, name, browser, and
viewport — no url or metadata. When the server reused screenshots by
SHA match, it copied the old null URL instead of using the new one.
@vizzly-testing
Copy link

vizzly-testing bot commented Feb 16, 2026

Vizzly - Visual Test Results

CLI Reporter - 1 change needs review
Status Count
Passed 18
Changed 1
Auto-approved 18
Changes needing review (1)

fullscreen-viewer · Firefox · 375×667 · 78.3% diff

fullscreen-viewer

Review changes

CLI TUI - Approved

5 comparisons, no changes detected.

View build


fix/flatten-screenshot-properties · 35e76e16

@Robdel12
Copy link
Contributor Author

Closing in favor of a proper fix that addresses the root cause in the client SDK. Instead of patching the server to handle double-nested properties, the client now properly separates SDK options (fullPage, threshold) from user properties before sending, so properties is always a flat object.

@Robdel12 Robdel12 closed this Feb 16, 2026
Robdel12 added a commit that referenced this pull request Feb 16, 2026
## Summary

- The client SDK was sending the entire `options` object as
`properties`, causing double-nesting when SDKs passed `{ properties: {
url } }` — the server received `properties: { properties: { url } }`
making `url` unreachable
- Now destructures SDK-internal options (`fullPage`, `threshold`) out
and flattens `options.properties` into the top-level properties object
- Adds tests verifying properties flattening and that SDK options don't
leak into properties
- Closes #221 — this fixes the root cause instead of patching around it
server-side

## Context

After the URL capture change (#220), Storybook and Static Site SDKs
passed `{ properties: { url: page.url() } }` to `vizzlyScreenshot()`.
The client blindly forwarded the whole options object as `properties`,
double-nesting the data. A server-side fallback
(`properties.properties?.url`) was deployed as a stopgap — this fix
makes that fallback unnecessary.

## Test plan

- [x] `npm test` — 1894 pass, 0 fail
- [x] `npm run lint` — clean
- [x] New test: verifies `{ properties: { url } }` flattens to `{ url }`
in the payload
- [x] New test: verifies `fullPage` and `threshold` are excluded from
properties
- [x] Existing integration tests still pass (no breaking changes)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant