E2E testing verifies behavior: "When I click Submit, does the form save?"
Visual testing verifies appearance: "After the CSS refactor, does the form still look right?"
They're complementary. Here's how to use both.
What E2E Catches
Simulates user interactions, verifies outcomes. Answers: does it work?
test('submit form', async ({ page }) => {
await page.goto('/contact');
await page.fill('#name', 'Jane');
await page.click('button[type="submit"]');
await expect(page.locator('.success')).toBeVisible();
});
Passes if the form submits. Doesn't check alignment, padding, or mobile layout.

What Visual Testing Catches
Screenshots compared against baselines. Answers: does it look right?
- CSS shifts form fields 20px left
- Typography makes labels unreadable
- z-index hides the submit button
- Responsive breakpoint collapses incorrectly
- Dark mode makes text invisible
Overlap
| Scenario | E2E? | Visual? |
|---|---|---|
| Button doesn't submit | Yes | No |
| Button moved off-screen | No | Yes |
| Error message wrong color | No | Yes |
| API returns 500 | Yes | No |
| Modal has broken layout | No | Yes |
When Visual Testing Matters Most
CSS Refactoring: Migrating SCSS to Tailwind. E2E passes, 15 pages have broken layouts. Visual tests catch every one.
Design System Updates: New spacing scale. Functional behavior unchanged, visual impact spans everything.
Dependency Updates: React update causes 2px shift on 40 pages. E2E passes. Visual tests flag it.
Using Both Together
Run both in parallel in CI:
jobs:
e2e-tests:
steps:
- run: npx playwright test
visual-tests:
steps:
- uses: las-team/lastest/action@main
with:
server-url: ${{ secrets.LASTEST_SERVER_URL }}
runner-token: ${{ secrets.LASTEST_RUNNER_TOKEN }}
E2E tests fail fast on broken behavior. Visual tests flag appearance changes for review. Both block the PR.

E2E: "The app is broken" (it doesn't work).
Visual: "The app looks broken" (it works but looks wrong).
Users notice both. Ship neither.
