Modern QA2026Верификация дизайн-системы
Join

Course10 Visual & Accessibility Testing

Cutting-edge · Chapter 10

Верификация дизайн-системы

Updated Jul 2026

Проблема: дрифт между Figma и кодом

Разрыв между тем, что дизайнеры создают в Figma, и тем, что разработчики реализуют в коде, является постоянным источником визуальных багов. Дизайнер указывает padding: 16px. Разработчик оценивает «примерно 16» и использует padding: 1rem (что равно 16px при стандартном размере шрифта, но масштабируется иначе). За месяцы эти мелкие различия накапливаются в заметный дрифт дизайна.

Автоматическая верификация обнаруживает этот дрифт до его накопления.

Что верифицировать

Аспект Источник в Figma Цель в коде Метод верификации
Цвета Дизайн-токены CSS-переменные / тема Извлечение и сравнение hex-значений
Типографика Семейства шрифтов, размеры, начертания Вычисленные CSS-стили Автоматическое извлечение стилей
Отступы Значения padding, margin Вычисленные CSS-стили Измерение в пикселях
Скругление Значения радиуса углов CSS border-radius Сравнение вычисленных стилей
Тени Параметры drop shadow CSS box-shadow Сравнение значений
Иконки SVG-ассеты SVG в коде Сравнение хэшей файлов
Состояния компонентов Hover, active, disabled, focus Интерактивные состояния Визуальное тестирование по состояниям

Автоматическая верификация дизайн-токенов

// tests/design-system/token-verification.spec.ts
import { test, expect } from '@playwright/test';
import designTokens from '../../design-tokens/tokens.json';

test.describe('Design Token Verification', () => {
    test('color tokens match implementation', async ({ page }) => {
        await page.goto('/design-system/colors');

        for (const [name, expectedValue] of Object.entries(designTokens.colors)) {
            // Query the CSS custom property value
            const actualValue = await page.evaluate((varName) => {
                return getComputedStyle(document.documentElement)
                    .getPropertyValue(`--color-${varName}`)
                    .trim();
            }, name);

            expect(actualValue).toBe(expectedValue);
        }
    });

    test('typography tokens match implementation', async ({ page }) => {
        await page.goto('/design-system/typography');

        const typographySpecs = designTokens.typography;

        for (const [variant, spec] of Object.entries(typographySpecs)) {
            const element = page.locator(`[data-typography="${variant}"]`);

            const computedStyles = await element.evaluate((el) => {
                const styles = window.getComputedStyle(el);
                return {
                    fontFamily: styles.fontFamily,
                    fontSize: styles.fontSize,
                    fontWeight: styles.fontWeight,
                    lineHeight: styles.lineHeight,
                    letterSpacing: styles.letterSpacing,
                };
            });

            expect(computedStyles.fontSize).toBe((spec as any).fontSize);
            expect(computedStyles.fontWeight).toBe(String((spec as any).fontWeight));
            expect(computedStyles.lineHeight).toBe((spec as any).lineHeight);
        }
    });

    test('spacing scale matches design tokens', async ({ page }) => {
        await page.goto('/design-system/spacing');

        const spacingScale = designTokens.spacing;

        for (const [size, expectedPx] of Object.entries(spacingScale)) {
            const actualValue = await page.evaluate((varName) => {
                return getComputedStyle(document.documentElement)
                    .getPropertyValue(`--spacing-${varName}`)
                    .trim();
            }, size);

            expect(actualValue).toBe(expectedPx);
        }
    });
});

Интеграция с Figma API

# scripts/figma_design_drift.py
"""
Compare Figma component specifications with live implementation.
Detects drift between design and code.
"""
import requests
import json

FIGMA_TOKEN = "your-figma-api-token"
FIGMA_FILE_ID = "your-file-id"

def get_figma_component_styles(component_name: str):
    """Extract style properties from a Figma component."""
    url = f"https://api.figma.com/v1/files/{FIGMA_FILE_ID}/components"
    headers = {"X-Figma-Token": FIGMA_TOKEN}
    response = requests.get(url, headers=headers)
    components = response.json()

    for component in components.get("meta", {}).get("components", []):
        if component["name"] == component_name:
            # Get component node details
            node_url = f"https://api.figma.com/v1/files/{FIGMA_FILE_ID}/nodes?ids={component['node_id']}"
            node_response = requests.get(node_url, headers=headers)
            node_data = node_response.json()

            # Extract visual properties
            node = list(node_data["nodes"].values())[0]["document"]
            return {
                "fills": node.get("fills", []),
                "strokes": node.get("strokes", []),
                "cornerRadius": node.get("cornerRadius"),
                "padding": {
                    "top": node.get("paddingTop"),
                    "right": node.get("paddingRight"),
                    "bottom": node.get("paddingBottom"),
                    "left": node.get("paddingLeft"),
                },
                "width": node.get("absoluteBoundingBox", {}).get("width"),
                "height": node.get("absoluteBoundingBox", {}).get("height"),
            }
    return None

def compare_with_implementation(figma_styles: dict, computed_styles: dict):
    """Compare Figma specs with browser computed styles."""
    drift_report = []

    if figma_styles.get("cornerRadius"):
        figma_radius = f"{figma_styles['cornerRadius']}px"
        if computed_styles.get("borderRadius") != figma_radius:
            drift_report.append({
                "property": "border-radius",
                "figma": figma_radius,
                "implementation": computed_styles.get("borderRadius"),
            })

    # Compare padding
    for side in ["top", "right", "bottom", "left"]:
        figma_padding = figma_styles.get("padding", {}).get(side)
        if figma_padding is not None:
            figma_px = f"{figma_padding}px"
            impl_key = f"padding{side.capitalize()}"
            if computed_styles.get(impl_key) != figma_px:
                drift_report.append({
                    "property": f"padding-{side}",
                    "figma": figma_px,
                    "implementation": computed_styles.get(impl_key),
                })

    return drift_report

Рабочий процесс дизайн-токенов

Пайплайн генерации токенов

Figma (design source)
    |
    +-- Style Dictionary / Figma Tokens plugin
    |   exports tokens.json
    |
    +-- Token transformer (Style Dictionary)
    |   generates:
    |     +-- css/variables.css (CSS custom properties)
    |     +-- ts/tokens.ts (TypeScript constants)
    |     +-- ios/tokens.swift (iOS)
    |     +-- android/tokens.xml (Android)
    |
    +-- CI verification
        compares:
          +-- tokens.json vs computed CSS properties
          +-- Figma API values vs tokens.json

Пример tokens.json

{
    "colors": {
        "primary-500": "#3B82F6",
        "primary-600": "#2563EB",
        "primary-700": "#1D4ED8",
        "neutral-100": "#F3F4F6",
        "neutral-900": "#111827",
        "error-500": "#EF4444",
        "success-500": "#22C55E"
    },
    "typography": {
        "heading-1": {
            "fontSize": "36px",
            "fontWeight": 700,
            "lineHeight": "44px"
        },
        "heading-2": {
            "fontSize": "24px",
            "fontWeight": 600,
            "lineHeight": "32px"
        },
        "body": {
            "fontSize": "16px",
            "fontWeight": 400,
            "lineHeight": "24px"
        }
    },
    "spacing": {
        "xs": "4px",
        "sm": "8px",
        "md": "16px",
        "lg": "24px",
        "xl": "32px",
        "2xl": "48px"
    },
    "radii": {
        "sm": "4px",
        "md": "8px",
        "lg": "12px",
        "full": "9999px"
    }
}

Интеграция с CI

# .github/workflows/design-system.yml
name: Design System Verification
on:
  pull_request:
    paths:
      - 'src/components/**'
      - 'design-tokens/**'
      - 'src/styles/**'

jobs:
  verify-tokens:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build && npm run start &

      - name: Verify design tokens
        run: npx playwright test tests/design-system/

      - name: Check Figma drift
        run: python scripts/figma_design_drift.py --report drift-report.json
        env:
          FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}

      - name: Comment drift report on PR
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            if (fs.existsSync('drift-report.json')) {
              const report = JSON.parse(fs.readFileSync('drift-report.json'));
              if (report.length > 0) {
                const body = `## Design Drift Detected\n\n` +
                  report.map(d =>
                    `- **${d.property}**: Figma=${d.figma}, Code=${d.implementation}`
                  ).join('\n');
                github.rest.issues.createComment({
                  issue_number: context.issue.number,
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  body: body
                });
              }
            }

Верификация дизайн-системы предотвращает постепенное расхождение между дизайнерским замыслом и реализацией, которое ручное ревью неизбежно пропускает. Автоматическое сравнение спецификаций Figma и вычисленных CSS-стилей обнаруживает дрифт на уровне PR, до того как он накопится в десятках компонентов.