Visual Testing for Ember.js - Playwright Meets Testem
Bring visual regression testing to your Ember acceptance tests without changing how you write them. The new Ember SDK uses Testem's custom launcher system to run browsers through Playwright, capturing screenshots directly from your real test environment.
Ember developers have a unique testing story. Testem, QUnit, acceptance tests that run in a real browser - it’s a mature system. But visual testing? That’s been awkward. Either bolt on a separate tool that doesn’t understand your test environment, or skip it entirely.
We built an Ember SDK that uses Testem’s custom launcher system to run browsers through Playwright. Your tests run exactly like before, but now you can capture screenshots from inside them.
How It Works
Testem supports custom launchers - you define a command, and Testem runs it instead of opening a browser directly. The SDK registers Playwright-powered launchers for Chrome, Firefox, and Safari. When you configure Chrome, Testem spawns our launcher, which controls a real Chromium instance via Playwright.
Wrap your existing Testem config with configure(), and that’s it. QUnit assertions still work. Ember Test Helpers behave normally. You just gain the ability to add vizzlyScreenshot() calls.
Adding Screenshots to Tests
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';
import { click, visit } from '@ember/test-helpers';
import { setupApplicationTest } from 'my-app/tests/helpers';
import { module, test } from 'qunit';
module('Acceptance | dashboard', function(hooks) {
setupApplicationTest(hooks);
test('dashboard displays correctly', async function(assert) {
await visit('/dashboard');
assert.dom('[data-test-dashboard-header]').exists();
await vizzlyScreenshot('dashboard-initial', {
feature: 'dashboard',
scenario: 'empty-state',
});
await click('[data-test-add-project]');
// Capture just the modal
await vizzlyScreenshot('add-project-modal', {
feature: 'dashboard',
selector: '[data-test-modal]',
});
});
});
The vizzlyScreenshot() function waits for Ember to settle, then captures the current state. It handles the quirks of Ember’s test container - the 50% scaling, the QUnit UI chrome - so your screenshots show just the app.
Properties like feature and scenario help organize screenshots in the dashboard. The selector option captures a specific element instead of the full viewport.
Under the Hood
When your test calls vizzlyScreenshot(), here’s what happens: the browser makes an HTTP request to a local server running alongside Playwright. That server captures the screenshot using Playwright’s API, then forwards it to Vizzly.
Screenshots come from the real browser your tests run in. Not a separate headless instance. The actual browser, in the actual state, at the moment you call for a capture.
Because we’re using Playwright, you get all three major browsers - Chrome, Firefox, and Safari (WebKit). Each gets its own baseline, automatically tracked.
Mirage Compatibility
If you’re using Mirage for API mocking, you’ll need to let screenshot requests through. Otherwise Mirage intercepts them and the captures fail silently.
// mirage/config.js
export default function() {
this.passthrough(request =>
request.url.includes('127.0.0.1') &&
request.url.includes('/screenshot')
);
}
Local TDD and CI
For local development, run vizzly tdd start and get instant visual feedback at localhost:47392. Every test run shows diffs immediately.
For CI, set VIZZLY_TOKEN and screenshots upload to the cloud for team review:
- run: vizzly run "ember test"
env:
VIZZLY_TOKEN: ${{ secrets.VIZZLY_TOKEN }}
Getting Started
Install the packages:
npm install -D @vizzly-testing/ember @vizzly-testing/cli
Wrap your existing Testem config:
// testem.js
const { configure } = require('@vizzly-testing/ember');
module.exports = configure({
framework: 'qunit',
test_page: 'tests/index.html?hidepassed',
launch_in_ci: ['Chrome'],
launch_in_dev: ['Chrome'],
});
Add screenshots to your tests:
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';
test('my feature works', async function(assert) {
await visit('/my-page');
assert.dom('[data-test-heading]').exists();
// Basic screenshot
await vizzlyScreenshot('my-page-initial');
// With properties for organization
await vizzlyScreenshot('my-page-initial', {
feature: 'onboarding',
page: 'welcome',
});
// Capture a specific element
await vizzlyScreenshot('sidebar-nav', {
selector: '[data-test-sidebar]',
});
// Full page screenshot (scrolls to capture everything)
await vizzlyScreenshot('my-page-full', {
fullPage: true,
});
});
Properties create separate baselines for each combination - useful for organizing screenshots by feature, page, theme, or any other dimension you care about.
Start the TDD server and run your tests:
# Terminal 1
vizzly tdd start --open
# Terminal 2
ember test --server
The dashboard at localhost:47392 shows your screenshots and any visual diffs. Accept changes with one click, or keep iterating.
Check out the docs for the full API reference and examples.
The Ember SDK brings visual testing to a framework that already has excellent test infrastructure. It works with Testem and QUnit, not around them. Same tests, same workflow, with visual regression detection built in.