vitest-plugin-vis
Version:
Vitest visual testing plugin
329 lines (249 loc) • 9.08 kB
text/mdx
import { Meta } from "@storybook/addon-docs/blocks";
<Meta title="page/hasImageSnapshot" />
# page.hasImageSnapshot
`page.hasImageSnapshot()` is a utility method that checks if an image snapshot exists for the current test.
This method allows you to conditionally execute code based on whether a snapshot has been previously created for the current test. It's particularly useful for negative testing scenarios, conditional snapshot creation, and handling different behavior for first-time runs vs. subsequent runs.
## Basic Usage
```ts
import { page } from 'vitest/browser'
it('should check if snapshot exists', async ({ expect }) => {
const hasSnapshot = await page.hasImageSnapshot()
if (!hasSnapshot) {
// First run - create baseline snapshot
await page.toMatchImageSnapshot()
} else {
// Subsequent runs - perform comparison
await expect(page.getByTestId('component')).toMatchImageSnapshot()
}
})
```
## Options
The `page.hasImageSnapshot()` method accepts an optional options object:
### `snapshotKey`
Custom key to identify the specific snapshot. If not provided, uses the default snapshot key for the test.
```ts
it('should check for specific snapshot', async ({ expect }) => {
// Check for default snapshot
const hasDefaultSnapshot = await page.hasImageSnapshot()
// Check for custom snapshot
const hasCustomSnapshot = await page.hasImageSnapshot({
snapshotKey: 'dark_mode'
})
expect(hasDefaultSnapshot).toBe(false)
expect(hasCustomSnapshot).toBe(false)
})
```
**Important**: The `snapshotKey` cannot contain dash characters (-) as they are reserved for internal path separation.
```ts
// ❌ This will throw an error
await page.hasImageSnapshot({ snapshotKey: 'invalid-key' })
// Error: Snapshot key cannot contain dash
// ✅ Use underscores or camelCase instead
await page.hasImageSnapshot({ snapshotKey: 'valid_key' })
await page.hasImageSnapshot({ snapshotKey: 'validKey' })
```
## Use Cases
### Negative Testing
Test behavior when snapshots don't exist:
```ts
it('should handle missing snapshots gracefully', async ({ expect }) => {
const hasSnapshot = await page.hasImageSnapshot({ snapshotKey: 'nonexistent' })
expect(hasSnapshot).toBe(false)
// Now create the snapshot
await page.toMatchImageSnapshot({ snapshotKey: 'nonexistent' })
// Verify it now exists
const hasSnapshotAfter = await page.hasImageSnapshot({ snapshotKey: 'nonexistent' })
expect(hasSnapshotAfter).toBe(true)
})
```
### Conditional Snapshot Creation
Handle different behavior for first-time runs vs. subsequent runs:
```ts
it('should create or compare snapshots conditionally', async ({ expect }) => {
const hasSnapshot = await page.hasImageSnapshot()
if (!hasSnapshot) {
// First run - setup and create baseline
render(<MyComponent theme="light" />)
await page.toMatchImageSnapshot()
} else {
// Subsequent runs - test different scenarios
render(<MyComponent theme="dark" />)
await expect(page.getByTestId('component')).toMatchImageSnapshot({
expectToFail: true,
snapshotKey: 'dark_theme'
})
}
})
```
### Theme or State-Specific Snapshots
Work with different themes or application states:
```ts
it('should support theme-specific snapshots', async ({ expect }) => {
// Check for dark mode snapshot
const hasDarkSnapshot = await page.hasImageSnapshot({ snapshotKey: 'dark_mode' })
if (!hasDarkSnapshot) {
// Setup dark mode and create snapshot
document.body.classList.add('dark')
render(<MyApp />)
await page.toMatchImageSnapshot({ snapshotKey: 'dark_mode' })
}
// Check for light mode snapshot
const hasLightSnapshot = await page.hasImageSnapshot({ snapshotKey: 'light_mode' })
if (!hasLightSnapshot) {
// Setup light mode and create snapshot
document.body.classList.remove('dark')
render(<MyApp />)
await page.toMatchImageSnapshot({ snapshotKey: 'light_mode' })
}
})
```
### Progressive Snapshot Building
Build up complex test scenarios progressively:
```ts
it('should build snapshots progressively', async ({ expect }) => {
// Check if we have the base component snapshot
const hasBaseSnapshot = await page.hasImageSnapshot({ snapshotKey: 'base' })
if (!hasBaseSnapshot) {
render(<BaseComponent />)
await page.toMatchImageSnapshot({ snapshotKey: 'base' })
}
// Check if we have the enhanced component snapshot
const hasEnhancedSnapshot = await page.hasImageSnapshot({ snapshotKey: 'enhanced' })
if (!hasEnhancedSnapshot) {
render(<EnhancedComponent />)
await page.toMatchImageSnapshot({ snapshotKey: 'enhanced' })
}
// Now we can safely compare both exist
expect(await page.hasImageSnapshot({ snapshotKey: 'base' })).toBe(true)
expect(await page.hasImageSnapshot({ snapshotKey: 'enhanced' })).toBe(true)
})
```
## Behavior and Limitations
### Test Context Required
The method must be called within a test context:
```ts
// ❌ This will throw an error
const hasSnapshot = await page.hasImageSnapshot()
// Error: `hasImageSnapshot()` must be called in a test.
// ✅ Call within a test
it('should work in test context', async () => {
const hasSnapshot = await page.hasImageSnapshot()
// This works correctly
})
```
### Concurrent Tests Not Supported
Cannot be used in concurrent tests because they share the same iframe environment:
```ts
// ❌ This will throw an error
it.concurrent('should not work in concurrent test', async () => {
const hasSnapshot = await page.hasImageSnapshot()
// Error: `hasImageSnapshot()` cannot be called in a concurrent test because
// concurrent tests run at the same time in the same iframe and affect each other's environment.
})
// ✅ Use regular tests
it('should work in regular test', async () => {
const hasSnapshot = await page.hasImageSnapshot()
// This works correctly
})
```
### Async Operation
Always returns a Promise, so you must use `await` or `.then()`:
```ts
// ✅ Using await
const hasSnapshot = await page.hasImageSnapshot()
// ✅ Using .then()
page.hasImageSnapshot().then(hasSnapshot => {
if (hasSnapshot) {
// Handle existing snapshot
}
})
// ❌ Don't forget await
const hasSnapshot = page.hasImageSnapshot() // This is a Promise, not a boolean!
```
## Error Handling
The method will throw errors in specific cases:
```ts
// Test context errors
try {
await page.hasImageSnapshot()
} catch (error) {
// Error: `hasImageSnapshot()` must be called in a test.
}
// Concurrent test errors
it.concurrent('concurrent test', async () => {
try {
await page.hasImageSnapshot()
} catch (error) {
// Error: `hasImageSnapshot()` cannot be called in a concurrent test because
// concurrent tests run at the same time in the same iframe and affect each other's environment.
}
})
// Snapshot key validation errors
try {
await page.hasImageSnapshot({ snapshotKey: 'invalid-key' })
} catch (error) {
// Error: Snapshot key cannot contain dash
}
```
## Integration with Other Methods
`page.hasImageSnapshot()` works seamlessly with other visual testing methods:
```ts
it('should integrate with other methods', async ({ expect }) => {
// Check if snapshot exists
const hasSnapshot = await page.hasImageSnapshot({ snapshotKey: 'integration' })
if (!hasSnapshot) {
// Create page snapshot
await page.toMatchImageSnapshot({ snapshotKey: 'integration' })
}
// Now test element snapshots
const element = page.getByTestId('component')
await expect(element).toMatchImageSnapshot({ snapshotKey: 'element' })
// Verify both snapshots exist
expect(await page.hasImageSnapshot({ snapshotKey: 'integration' })).toBe(true)
expect(await page.hasImageSnapshot({ snapshotKey: 'element' })).toBe(true)
})
```
## Best Practices
### Use Descriptive Snapshot Keys
```ts
// ✅ Good - descriptive and clear
await page.hasImageSnapshot({ snapshotKey: 'homepage_header' })
await page.hasImageSnapshot({ snapshotKey: 'modal_open_state' })
await page.hasImageSnapshot({ snapshotKey: 'dark_theme_sidebar' })
// ❌ Avoid - unclear purpose
await page.hasImageSnapshot({ snapshotKey: 'test1' })
await page.hasImageSnapshot({ snapshotKey: 'snapshot' })
```
### Handle Both Cases
Always handle both the existence and non-existence cases:
```ts
it('should handle both snapshot states', async () => {
const hasSnapshot = await page.hasImageSnapshot({ snapshotKey: 'feature' })
if (hasSnapshot) {
// Test against existing snapshot
render(<FeatureComponent />)
await page.toMatchImageSnapshot({ snapshotKey: 'feature' })
} else {
// Create initial snapshot
render(<FeatureComponent />)
await page.toMatchImageSnapshot({ snapshotKey: 'feature' })
}
})
```
### Combine with Conditional Logic
Use with environment or configuration checks:
```ts
it('should adapt to environment', async () => {
const isDarkMode = process.env.THEME === 'dark'
const snapshotKey = isDarkMode ? 'dark_mode' : 'light_mode'
const hasSnapshot = await page.hasImageSnapshot({ snapshotKey })
if (!hasSnapshot) {
// Setup appropriate theme
if (isDarkMode) {
document.body.classList.add('dark')
}
render(<App />)
await page.toMatchImageSnapshot({ snapshotKey })
}
})
```