
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Visual regression testing tool for Playwright with interactive mask editing - draw masks over dynamic content with a visual UI
Visual regression testing made simple for Playwright
Interactively mask dynamic content in your screenshots with a visual editor.
No more flaky tests due to timestamps, avatars, or ads.
Installation • Quick Start • Features • API • CLI
Visual regression tests often fail due to dynamic content:
Manually specifying pixel coordinates for masks is tedious and error-prone.
StageMask provides an interactive visual editor where you can:

npm install -D stagemask
Replace Playwright's built-in screenshot assertions with StageMask's fixture:
// Before
import { test, expect } from '@playwright/test';
test('homepage', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png');
});
// After
import { test } from 'stagemask';
test('homepage', async ({ page, visualSnapshot }) => {
await page.goto('https://example.com');
await visualSnapshot('homepage.png');
});
npx playwright test
First run creates baseline screenshots. Second run compares and will fail if there are differences.
npx stagemask review
This opens a visual editor where you can:
stage-masks.jsonnpx playwright test
Tests now pass because masked regions are ignored during comparison.
Del to delete, Esc to cancel, Space to panFails immediately on first mismatch - good for critical screenshots:
test('checkout flow', async ({ visualSnapshot }) => {
await visualSnapshot('cart.png'); // Fails here? Test stops
await visualSnapshot('checkout.png'); // Won't run if above failed
await visualSnapshot('confirm.png'); // Won't run if above failed
});
Collects all failures - good for reviewing multiple screenshots at once:
test('dashboard widgets', async ({ softVisualSnapshot }) => {
await softVisualSnapshot('header.png'); // Continues even if fails
await softVisualSnapshot('sidebar.png'); // Continues even if fails
await softVisualSnapshot('content.png'); // Continues even if fails
await softVisualSnapshot('footer.png'); // All failures reported at end
});
Combine both in the same test:
test('mixed assertions', async ({ visualSnapshot, softVisualSnapshot }) => {
await softVisualSnapshot('header.png'); // Soft - continues
await softVisualSnapshot('sidebar.png'); // Soft - continues
await visualSnapshot('critical-cta.png'); // Hard - stops if fails
await softVisualSnapshot('footer.png'); // Soft - continues
});
Screenshots are grouped by describe blocks and test names in the UI:
📁 Checkout Flow
🧪 cart page should display items
✗ cart-empty.png
✗ cart-with-items.png
🧪 payment form should validate
✗ payment-form.png
📁 Dashboard
🧪 widgets should render
✗ dashboard-full.png
visualSnapshot(name, options?)Takes a screenshot and compares it to the baseline. Throws immediately if different.
await visualSnapshot('screenshot.png', {
// Screenshot a specific element instead of the full page
element: page.locator('.card'),
// Add inline masks (merged with config masks)
masks: [
{ x: 10, y: 10, width: 100, height: 50 }
],
// Override comparison threshold (0-1, default: 0.1)
threshold: 0.2,
// Force update the baseline
updateBaseline: false,
// Pass through to Playwright's screenshot options
screenshotOptions: {
fullPage: true,
animations: 'disabled'
}
});
softVisualSnapshot(name, options?)Same as visualSnapshot but collects failures instead of throwing immediately. All failures are thrown at the end of the test.
await softVisualSnapshot('widget-1.png');
await softVisualSnapshot('widget-2.png');
await softVisualSnapshot('widget-3.png');
// If any failed, error thrown here with all failures listed
npx stagemask reviewLaunch the visual review UI.
npx stagemask review [options]
Options:
-p, --port <number> Port to run on (default: 5899)
-r, --results-dir <path> Custom test results directory (default: test-results)
-d, --dir <path> Project directory (default: current directory)
--no-open Don't open browser automatically
npx stagemask initCreate a new stage-masks.json config file.
npx stagemask init [options]
Options:
-d, --dir <path> Project directory
-f, --force Overwrite existing config
npx stagemask listList all configured masks.
npx stagemask list
npx stagemask ls # alias
npx stagemask portSet a custom default port (saved to config).
npx stagemask port # View current port
npx stagemask port 3000 # Set custom port
npx stagemask port --reset # Reset to default (5899)
npx stagemask clear <screenshot>Remove all masks for a specific screenshot.
npx stagemask clear homepage.png
npx stagemask exportExport mask configuration.
npx stagemask export # Print to stdout
npx stagemask export -o masks-backup.json # Save to file
npx stagemask export -s homepage.png cart.png # Export specific screenshots
npx stagemask import <file>Import mask configuration.
npx stagemask import masks-backup.json # Replace all masks
npx stagemask import masks-backup.json --merge # Merge with existing
Masks are stored in stage-masks.json in your project root:
{
"version": 1,
"threshold": 0.1,
"port": 5899,
"screenshots": {
"homepage.png": {
"name": "homepage.png",
"masks": [
{
"id": "mask_1234567890_abc123",
"x": 150,
"y": 200,
"width": 200,
"height": 50,
"reason": "Dynamic timestamp",
"createdAt": "2024-01-15T10:30:00.000Z"
}
],
"threshold": 0.15,
"updatedAt": "2024-01-15T10:30:00.000Z"
}
}
}
| Option | Type | Default | Description |
|---|---|---|---|
version | number | 1 | Config schema version |
threshold | number | 0.1 | Global pixel comparison threshold (0-1) |
port | number | 5899 | Default port for review server |
screenshots | object | {} | Per-screenshot mask configurations |
| Option | Type | Description |
|---|---|---|
masks | array | Array of mask regions |
threshold | number | Override global threshold for this screenshot |
StageMask works with your existing Playwright configuration. The test results directory can be customized:
// playwright.config.ts
export default defineConfig({
outputDir: './custom-results', // StageMask will look here
expect: {
toHaveScreenshot: {
// These don't affect StageMask, only Playwright's built-in assertions
}
}
});
Then tell StageMask where to find results:
npx stagemask review --results-dir ./custom-results
Add stage-masks.json to version control so masks are shared across the team.
// ❌ Bad
await visualSnapshot('screenshot-1.png');
// ✅ Good
await visualSnapshot('checkout-cart-empty.png');
test('all dashboard widgets render correctly', async ({ softVisualSnapshot }) => {
await softVisualSnapshot('dashboard-header.png');
await softVisualSnapshot('dashboard-sidebar.png');
await softVisualSnapshot('dashboard-main.png');
await softVisualSnapshot('dashboard-footer.png');
});
// More stable than full-page screenshots
await visualSnapshot('user-card.png', {
element: page.locator('[data-testid="user-card"]')
});
await visualSnapshot('animated-component.png', {
screenshotOptions: {
animations: 'disabled'
}
});
StageMask is designed for local development use only:
localhost only (not accessible from network)⚠️ Do not expose the review server to the internet.
npx playwright testtest-results/ directory exists and contains -actual.png and -expected.png filesnpx stagemask review -r ./custom-resultsstage-masks.json exists in your project root[stagemask] logs during test runs# Use a different port
npx stagemask review -p 3001
# Or set a default
npx stagemask port 3001
Contributions are welcome! Please read our Contributing Guide for details.
MIT © Irfan Mujagic
FAQs
Visual regression testing tool for Playwright with interactive mask editing - draw masks over dynamic content with a visual UI
We found that stagemask demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.