swiftcnearly preview

Visual testing

Verify native appearances, state behavior, and platform boundaries.

The visual suite uses Point-Free SnapshotTesting with native NSHostingView hosts. The host renders AppKit controls, including text fields, toggles, and sliders. The library has no runtime dependency on SnapshotTesting.

Coverage

The macOS suite compares 34 reference images:

  • Four scenes: buttons, native controls, utilities, and global rules.
  • Two themes: light and dark.
  • Two widths: 320 and 720 points.
  • Two text-size environments: large and accessibility3.
  • Two additional control scenes with right-to-left layout.

Button scenes cover rest, hover, focus, press, and disabled appearances. The state previews use explicit state values for repeatable rendering. Native buttons also cover typed styles, string styles, and the disabled environment. Global scenes cover custom tokens, named classes, local overrides, subtree overrides, and sibling isolation. Animation classes appear in native buttons, state previews, and custom global rules on both platforms. These images check their static appearances against the existing references.

Hosted behavior tests check native interpolation during state entry and exit. They cover computed strings, state variants, child identity, transaction scope, and disabled transactions. The resolver tests check Reduce Motion, timing, and custom presets. The image suite does not compare intermediate animation frames. The Motion catalog has a separate geometry regression test that samples its native rendered pixels during expansion and collapse. It checks intermediate widths and heights, stable card and label positions, and immediate changes with animate-none. It also checks surface and layout animation scopes.

Composition tests compare native pixels for modifier phases and Text, Image, and Shape factories. Set SWIFTCN_COMPOSITION_ARTIFACTS=artifacts/composition to save both sides of each comparison. CI uploads these images alongside the Motion frame captures. Hosted probes check interpolation in each phase, child transactions, and state identity across all animation scopes. CI uploads the sampled frames in artifacts/motion-size.

Component tests check native editor bindings and identity across validation changes. They verify toggle binding and disabled-environment forwarding, native label style equivalence, part overrides, and field isolation. They export eight component images across both appearances, widths, and text-size environments in artifacts/components. These new component images are review artifacts; the 42 committed baseline comparisons remain separate. The native catalog also exports the interactive composition form in both appearances.

Shared element tests measure the rendered size of a native follower with a typed ID. They check matching within one namespace and isolation between namespaces. Class tests also cover local groups, nested groups, named ancestor selection, and sibling isolation. Hosted tests verify that group updates preserve the native namespace and child state. Parser tests cover group variants, composed conditions, and invalid IDs.

Argument tests compare bracket classes with equivalent native modifiers at exact pixels. They cover both themes and both text-size environments. Tests also check inherited text attributes, child identity, custom factories, and invalid arguments. CI uploads both native and class images for review. The Arguments demo exports both themes and includes a live class editor.

Container animation tests cover string and typed presets, custom rules, child identity, and disabled transactions. The catalog exports both shared element layouts in light and dark mode. CI uploads these exports and the follower images as visual evidence.

macOS controls can ignore the text-size environment. These images check rendering under that environment; they do not prove iOS Dynamic Type behavior.

The iOS suite captures eight screenshots from a dedicated iPhone 16 simulator. It covers native controls and global rules in both themes with standard and accessibility3 text sizes. The host uses iOS 18.5 in CI. The capture script waits for the host to signal readiness. The host captures its native view hierarchy and excludes system status icons and the Dynamic Island. The script deletes its own simulator after each run.

Keyboard, touch, and VoiceOver behavior still require an application host.

Local workflow

Local references use artifacts/local-baselines. They stay separate from committed references because macOS versions can produce different pixels. The iOS script selects the installed runtime that matches the selected Xcode SDK by default. Set SWIFTCN_IOS_RUNTIME to choose another installed runtime for local comparisons. Record the local references before the first comparison:

bash Scripts/visual-test.sh record local
bash Scripts/visual-test.sh verify local
python3 Scripts/ios-visual-test.py record local
python3 Scripts/ios-visual-test.py verify local

Run the native behavior tests separately:

swift test --skip SwiftCNVisualTests

Use record only when you intend to replace the selected references. Run verify during normal development. The comparison uses exact pixels. Verification fails when a reference is missing or differs. Verification never creates or replaces a reference.

Failure images appear in artifacts/visual-diffs. Each failure includes the reference, the new image, and their difference.

Continuous integration

The continuous integration (CI) job selects macOS 15 and Xcode 26.3. The committed macOS references use Tests/SwiftCNVisualTests/__Snapshots__/macos-15-xcode-26.3. Xcode 26.3 builds with the 26 SDKs. The macOS 15 host and the iOS 18.5 runtime render the pre-Liquid Glass fallbacks, so these references cover the earlier appearance. The glass-surfaces job runs the adaptive surface tests on macOS 26 and uploads glass captures as evidence. The iOS references use Tests/SwiftCNVisualTests/__Snapshots__/ios-18.5-iphone-16. The normal CI job verifies these references on each main push and pull request. The job uploads visual evidence even when verification fails.

The hosted macOS image enables Reduce Motion. The behavior step turns it off to check native interpolation. The step restores the previous setting before image comparisons. Local behavior tests respect the current accessibility setting and check suppression when Reduce Motion is on.

GitHub updates runner images over time. A runner update can change native pixels without a source change. Check the runner version before accepting a new reference after an unexpected failure.

Update committed references

  1. Push the intended source changes to a branch whose name starts with visual-baselines/.

  2. Open the Record visual baselines workflow in GitHub Actions.

  3. Download the swiftcn-baseline-candidates artifact after the workflow passes.

  4. Inspect all changed images.

  5. Copy the reviewed candidate images into their committed reference directories.

  6. Commit the reference changes with the source changes.

  7. Run the normal CI verification.

The recording workflow records candidates and verifies them in a second test process. It never commits or approves candidate images automatically. You can also start that workflow manually for a selected branch.

Component library coverage

The component suite records every catalog example in light and dark appearances on iOS. The macOS suite covers 63 entries in both appearances. Both suites also check eight representative examples at narrow widths with large text. CI pins macOS 15 with Xcode 26.3 and iOS 18.5 on iPhone 16. Verification fails on a missing or changed reference; CI never updates references automatically.

AppKit bitmap capture cannot composite the native TabView strip correctly, including an unstyled SwiftUI TabView. The macOS tab test checks native selection and item identity instead. The running gallery also supports manual keyboard and pointer review. The Tabs documentation uses native iOS captures, where the complete control renders correctly.

The command screenshot ends editing before capture. This excludes the blinking caret and keyboard inset timing from static comparisons. The interactive gallery retains native autofocus and keyboard activation.

The macOS carousel capture pins native scroller geometry before page layout. This prevents a late scrollbar-style change from shifting the centered content. The running gallery follows system scroll preferences.

Static iOS captures stop native indeterminate indicators and select the first frame of native animated images. This keeps the same indicator frame across runs. The interactive gallery retains native spinner animation.

bash Scripts/component-visual-test.sh verify ci
python3 Scripts/ios-visual-test.py verify ci --components

For a focused iOS design pass, append --only with catalog slugs, such as --only aspect-ratio chart. This captures and compares only those entries. Normal CI verification uses the complete catalog.

The manual Record visual baselines workflow also accepts optional space-separated ios_component slugs. It records those components and verifies them in a second capture run. Download and review the candidate images before updating references.

After accepting component references, run python3 Scripts/generate-components.py. This updates each documentation preview from its reviewed native reference.

On this page