testing-patterns

الگوهای تست انیمیشن برای بررسی ظاهر، زمان‌بندی، دسترس‌پذیری و عملکرد

این Skill روش‌های منظم تست انیمیشن‌های UI را با Playwright، Jest و ابزارهای مرورگر ارائه می‌کند؛ از visual regression و بررسی timing تا تست prefers-reduced-motion و نرخ فریم.

با استفاده از این الگوها، خطاهای بصری، زمان‌بندی ناپایدار، ناسازگاری با دسترس‌پذیری و افت عملکرد را پیش از انتشار پیدا می‌کنید و تست‌ها را در CI قابل‌اعتمادتر اجرا می‌کنید.

چه چیزی می‌گیرید

  • نمونه تست visual regression برای حالت‌های مختلف انیمیشن با Playwright
  • الگوهای بررسی duration، delay و easing با Jest fake timers
  • تست سازگاری با prefers-reduced-motion و الزامات دسترس‌پذیری
  • روش سنجش frame rate، زمان paint و عملکرد انیمیشن
  • پیشنهادهایی برای کاهش flaky test و اجرای تست‌ها در CI

برای چه کاری خوب است

  • تیم‌های frontend که برای کامپوننت‌های متحرک تست خودکار می‌نویسند
  • پروژه‌هایی که به visual regression و کنترل دقیق timing نیاز دارند
  • محصولاتی که باید reduced motion و عملکرد روان انیمیشن را تضمین کنند

برای چه کاری نیست

  • پروژه‌هایی که هنوز انیمیشن یا رابط کاربری قابل‌تستی ندارند
  • تحلیل طراحی و ساخت انیمیشن؛ این Skill بر اعتبارسنجی و تست تمرکز دارد

دربارهٔ این اسکیل

Animation Testing Patterns

Systematic approaches to testing animations for correctness, accessibility, and performance.

Testing Categories

CategoryWhat to TestTools
Visual regressionAnimation appearancePlaywright, Percy
TimingDuration, delays, easingJest timers, Playwright
AccessibilityReduced motion, focusPlaywright, axe
PerformanceFrame rate, paint timesChrome DevTools, Lighthouse

Visual Regression Testing

Playwright Setup

import { test, expect } from '@playwright/test';

test.describe('Button animations', () => {
  test('hover state animation', async ({ page }) => {
    await page.goto('/components/button');
    const button = page.locator('.animated-button');

    await expect(button).toHaveScreenshot('button-idle.png');

    await button.hover();
    await page.waitForTimeout(300);

    await expect(button).toHaveScreenshot('button-hover.png');
  });
});

Timing Verification

Jest Timer Tests

import { render, screen, act } from '@testing-library/react';

describe('Animation timing', () => {
  beforeEach(() => jest.useFakeTimers());
  afterEach(() => jest.useRealTimers());

  test('fade-in completes in 300ms', () => {
    render(<AnimatedComponent />);
    const element = screen.getByTestId('animated-element');

    expect(element).toHaveStyle({ opacity: 0 });

    act(() => jest.advanceTimersByTime(300));

    expect(element).toHaveStyle({ opacity: 1 });
  });
});

Reduced Motion Testing

Playwright Reduced Motion

test.describe('Reduced motion compliance', () => {
  test('respects prefers-reduced-motion', async ({ page }) => {
    await page.emulateMedia({ reducedMotion: 'reduce' });
    await page.goto('/components/animated-list');

    await page.click('[data-add-item]');

    const item = page.locator('.list-item').last();
    const animationDuration = await item.evaluate(
      el => getComputedStyle(el).animationDuration
    );

    expect(animationDuration).toBe('0s');
  });
});

Jest Reduced Motion

describe('Reduced motion', () => {
  beforeEach(() => {
    Object.defineProperty(window, 'matchMedia', {
      writable: true,
      value: jest.fn().mockImplementation(query => ({
        matches: query === '(prefers-reduced-motion: reduce)',
        media: query,
        addEventListener: jest.fn(),
        removeEventListener: jest.fn(),
      })),
    });
  });

  test('uses instant transition when reduced motion preferred', () => {
    render(<AnimatedComponent />);
    const element = screen.getByTestId('animated');
    expect(element).toHaveClass('reduced-motion');
  });
});

Performance Testing

Frame Rate Testing

test('maintains 60fps during animation', async ({ page }) => {
  await page.goto('/components/heavy-animation');

  await page.tracing.start({ categories: ['devtools.timeline'] });

  await page.click('[data-start-animation]');
  await page.waitForTimeout(1000);

  const traceBuffer = await page.tracing.stop();
  const trace = JSON.parse(traceBuffer.toString());

  // Analyze frame times - 60fps = 16.67ms per frame
  const frames = trace.traceEvents
    .filter(e => e.name === 'FrameCommittedInBrowser')
    .map(e => e.ts);

  const frameTimes = frames.slice(1).map((ts, i) => ts - frames[i]);
  const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;

  expect(avgFrameTime).toBeLessThan(20000); // 20ms average
});

CI Integration

name: Animation Tests
on: [push, pull_request]

jobs:
  visual-regression:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:animations
      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: animation-test-results
          path: test-results/

Common Testing Mistakes

  1. Not waiting for animation completion - Use proper timeouts
  2. Ignoring reduced motion - Always test both preferences
  3. Screenshot timing issues - Capture at consistent animation points
  4. Missing performance baseline - Establish fps expectations
  5. Flaky visual tests - Allow reasonable pixel tolerance

Related Skills

  • motion-audit/ - Comprehensive audit checklists
  • accessibility/ - WCAG compliance requirements
  • remotion/ - Testing Remotion compositions

See references/ for complete Playwright and Jest test examples.

دانلود ZIP