自适应界面的视觉回归:测试危机模式实际看起来不同

发布: (2025年12月13日 GMT+8 22:00)
6 min read
原文: Dev.to

Source: Dev.to

介绍

状态管理容易测试,但视觉转换却不易。在危机情况下,用户实际看到的内容才是唯一重要的。本文说明如何验证紧急(或“危机”)模式不仅仅是翻转布尔值,而是真正改变了用户体验。

单元测试可以验证的内容

  • ✅ 状态正确更新
  • ✅ 正确应用 CSS 类
  • ✅ 组件已渲染

单元测试无法验证的内容

  • ❌ 按钮实际看起来更大
  • ❌ 对比度实际提升
  • ❌ 布局实际简化
  • ❌ 文本实际可读

“测试通过”和“帮助用户”之间的差距是视觉上的。截图测试弥合了这道鸿沟。

可视回归技术

在普通模式和危机模式下捕获相同组件的截图,然后验证它们在视觉上有意义的差异。

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

test.describe('Crisis Mode Visual Transformations', () => {
  test('emergency mode visually differs from normal mode', async ({ page }) => {
    // Capture normal state
    await page.goto('/pain-entry');
    const normalScreenshot = await page.screenshot();

    // Activate crisis mode
    await page.evaluate(() => {
      window.dispatchEvent(new CustomEvent('activate-crisis-mode', {
        detail: { severity: 'emergency' }
      }));
    });

    // Wait for visual transition
    await page.waitForTimeout(500); // Allow CSS transitions

    // Capture crisis state
    const crisisScreenshot = await page.screenshot();

    // Verify they're different (this is the key assertion)
    expect(Buffer.compare(normalScreenshot, crisisScreenshot)).not.toBe(0);
  });

  test('emergency mode matches approved baseline', async ({ page }) => {
    await page.goto('/pain-entry?crisis=emergency');

    await expect(page).toHaveScreenshot('emergency-mode-pain-entry.png', {
      maxDiffPixels: 100, // Allow minor anti-aliasing differences
    });
  });
});

组件级反馈

test.describe('Touch Target Scaling', () => {
  test('buttons scale up in emergency mode', async ({ page }) => {
    await page.goto('/component-preview/button');

    // Normal size
    await expect(page.locator('[data-testid="primary-button"]'))
      .toHaveScreenshot('button-normal.png');

    // Emergency size
    await page.evaluate(() => {
      document.documentElement.setAttribute('data-crisis-mode', 'emergency');
    });

    await expect(page.locator('[data-testid="primary-button"]'))
      .toHaveScreenshot('button-emergency.png');
  });
});

处理多种自适应状态

标准的可视回归对每个测试只使用单一基准,但自适应界面有许多有效状态。以疼痛追踪按钮为例,有效组合包括:

模式对比度字体大小触控目标基准
正常正常中等标准A
正常中等标准B
危机正常超大C
危机超大D

矩阵测试示例

const PREFERENCE_MATRIX = [
  { mode: 'normal', contrast: 'normal', fontSize: 'medium' },
  { mode: 'normal', contrast: 'high',   fontSize: 'medium' },
  { mode: 'normal', contrast: 'normal', fontSize: 'large' },
  { mode: 'crisis', contrast: 'normal', fontSize: 'large' },
  { mode: 'crisis', contrast: 'high',   fontSize: 'large' },
];

for (const prefs of PREFERENCE_MATRIX) {
  test(`pain entry form - ${prefs.mode}/${prefs.contrast}/${prefs.fontSize}`, async ({ page }) => {
    // Set preferences via URL params or localStorage
    await page.goto(
      `/pain-entry?mode=${prefs.mode}&contrast=${prefs.contrast}&fontSize=${prefs.fontSize}`
    );

    // Each combination has its own baseline
    const baselineName = `pain-entry-${prefs.mode}-${prefs.contrast}-${prefs.fontSize}.png`;
    await expect(page).toHaveScreenshot(baselineName);
  });
}

Storybook 与 Chromatic

如果你使用 Storybook,Chromatic 可以优雅地处理矩阵测试。

// Button.stories.tsx
export default {
  title: 'Components/Button',
  component: Button,
};

export const Normal = () => Save Entry;

export const Emergency = () => (
  
    Save Entry
  
);

export const HighContrast = () => (
  
    Save Entry
  
);

export const EmergencyHighContrast = () => (
  
    
      Save Entry
    
  
);

Chromatic 将每个 story 捕获为单独的基准,确保视觉变化是有意为之。

可访问性结构检查

视觉变化应保留语义含义。以下测试验证 DOM 结构是否正确适配。

test.describe('Crisis Mode Accessibility Structure', () => {
  test('normal mode has full navigation structure', async ({ page }) => {
    await page.goto('/pain-entry');

    await expect(page.locator('main')).toMatchAriaSnapshot(`
      - main:
        - heading "Log Pain Entry" [level=1]
        - navigation "Entry sections":
          - link "Basic Info"
          - link "Location"
          - link "Symptoms"
          - link "Triggers"
          - link "Medications"
        - form "Pain entry form":
          - group "Pain Level":
            - slider "Pain intensity"
          - group "Location":
            - button "Select body areas"
          - group "Notes":
            - textbox "Additional notes"
        - button "Save Entry"
        - button "Cancel"
    `);
  });

  test('emergency mode simplifies to essentials', async ({ page }) => {
    await page.goto('/pain-entry?crisis=emergency');

    // Emergency mode should have FEWER elements, not more
    await expect(page.locator('main')).toMatchAriaSnapshot(`
      - main:
        - heading "Quick Pain Log" [level=1]
        - form "Simplified pain entry":
          - group "How bad?":
            - slider "Pain level"
          - group "Where?":
            - button "Tap body location"
        - button "Save Now"
        - button "Need Help?"
    `);
  });
});

test('emergency mode reduces cognitive load', async ({ page }) => {
  // Count interactive elements in normal mode
  await page.goto('/pain-entry');
  const normalInteractive = await page
    .locator('[role="button"], [role="link"], input, select, textarea')
    .count();

  // Count in emergency mode
  await page.goto('/pain-entry?crisis=emergency');
  const emergencyInteractive = await page
    .locator('[role="button"], [role="link"], input, select, textarea')
    .count();

  // Emergency mode should have fewer interactive elements
  expect(emergencyInteractive).toBeLessThan(normalInteractive);

  // But not zero – core functionality must remain
  expect(emergencyInteractive).toBeGreaterThan(2);
});

对比度验证

如果对比度比例未达到 WCAG 阈值,高对比模式就毫无意义。自动化测试可以验证这一点。

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Contrast Verification', () => {
  test('normal mode meets WCAG AA (4.5:1)', async ({ page }) => {
    await page.goto('/pain-entry');

    const results = await new AxeBuilder({ page })
      .withRules(['color-contrast'])
      .analyze();

    expect(results.violations).toHaveLength(0);
  });

  test('high contrast mode exceeds WCAG AAA (7:1)', async ({ page }) => {
    await page.goto('/pain-entry?contrast=high');

    const results = await new AxeBuilder({ page })
      .withRules(['color-contrast-enhanced']) // AAA level
      .analyze();

    expect(results.violations).toHaveLength(0);
  });
});

结论

可视回归测试对自适应界面至关重要,尤其是在“危机模式”必须可靠地呈现更大的触控目标、更高的对比度和简化布局时。通过结合截图比较、矩阵测试、Storybook/Chromatic 基准、ARIA 结构快照以及对比度分析,你可以确保 UI 不仅翻转状态标记,还能为处于困境的用户提供真正更安全的体验。

Back to Blog

相关文章

阅读更多 »

SVG Fullstack 网站

文章 URL: https://github.com/icitry/SVGWebsite 评论 URL: https://news.ycombinator.com/item?id=46270597 得分: 8 评论: 1