К основному содержанию
скилл CX Codex

verify-email-snapshots — скилл для Claude Code: снапшот-тесты писем

Скилл для снапшот-тестирования HTML-писем через Verify: рендер шаблона сравнивается с утверждённым эталоном, и тест падает при расхождении.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add Aaronontheweb/dotnet-skills --skill verify-email-snapshots -a claude-code
Открыть на GitHub

Что делает verify-email-snapshots

Скилл пригодится, когда письма собираются из MJML-шаблонов или другого рендерера и надо ловить регрессии в вёрстке. Агент пишет тесты на xUnit, NUnit или MSTest, где результат рендера передаётся в Verify(html, extension: "html"). При первом запуске рядом с тестом появляется файл .verified.html — это эталон. Дальше любое изменение шаблона даёт .received.html и diff.

Динамические значения вроде дат, GUID и токенов в ссылках скилл советует вычищать скрабберами: ScrubLinesContaining, ScrubInlineGuids, регулярки через AddScrubber. Иначе тест будет падать на каждом прогоне. Для каждого варианта шаблона — отдельный тест, имя метода задаётся через UseMethodName, чтобы файлы не перезаписывали друг друга.

Перед началом работы понадобится пакет Verify.Xunit (или Verify.NUnit, Verify.MSTest) и настроенный рендерер шаблонов в DI-контейнере теста. Разбирать diff удобно в браузере или через verify.tool: verify review открывает инструмент сравнения, verify accept принимает изменения. В CI скилл советует включить падение теста при отсутствии эталонного файла — иначе новый вывод можно случайно принять за норму.

Какие задачи решает

  • поймать регрессию в вёрстке письма после правки шаблона
  • проверить, что MJML компилируется в ожидаемый HTML
  • убедиться, что подстановка переменных работает
  • сравнить вывод рендерера до и после рефакторинга
  • показать изменение письма в diff на код-ревью

Примеры запросов агенту

  • напиши снапшот-тест для шаблона UserInvitations/UserSignupInvitation через Verify
  • добавь скрабберы для дат и GUID в тест письма PasswordReset
  • сделай тест на composer SignupInvitation с проверкой темы и тела письма

Как установить verify-email-snapshots

1Через Skills CLI (Vercel)

npx skills add Aaronontheweb/dotnet-skills --skill verify-email-snapshots -a claude-code

2Вручную: положить файлы

~/.claude/skills/verify-email-snapshots/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/verify-email-snapshots/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора32

playwright-ci-caching Настраивает кэширование браузеров Playwright в GitHub Actions и Azure DevOps. Устраняет лишнюю загрузку ~400MB бинарников и сокращает сборку на 1-2 минуты в .NET-проектах. CX Codex +1 434установок dotnet-devcert-trust Разбирает, почему .NET и Aspire не доверяют HTTPS-сертификату разработчика на Linux, и доводит его до системного CA-бандла. CX Codex +1 434установок akka-net-best-practices Скилл с правилами Akka.NET: когда EventStream, когда DistributedPubSub, как разводить ошибки и супервизию. Пригодится при проектировании акторов. CX Codex +1 431установок mailpit-integration Поднимает Mailpit в .NET Aspire, чтобы письма перехватывались локально, а не уходили реальным адресатам. Пригодится при отладке шаблонов и интеграционных тестах. CX Codex +1 421установок akka-hosting-actor-patterns Паттерны для сущностных акторов в Akka.NET через Akka.Hosting. Помогает запускать один и тот же код в локальных тестах и в продакшене со сплитом на кластерный шардинг. CX Codex +1 414установок akka-net-testing-patterns Скилл для написания юнит- и интеграционных тестов акторов Akka.NET на Akka.Hosting.TestKit: внедрение зависимостей, TestProbes, проверка персистентности. CX Codex +1 411установок akka-net-management Скилл подсказывает агенту, как настроить Akka.Management и Cluster Bootstrap: динамическое обнаружение узлов, health-эндпоинты, работа в Kubernetes и Azure. CX Codex +1 409установок opentelemetry-net-instrumentation Скилл подсказывает, как расставлять трассировку, метрики и логи в .NET-коде по спецификации OpenTelemetry. Пригодится, когда добавляете телеметрию в библиотеку или приложение. CX Codex +1 408установок

Похожие

testing codewithmukesh Скилл задаёт стратегию тестирования приложений .NET 10: интеграционные тесты через WebApplicationFactory, реальные базы в Testcontainers, снапшоты Verify. CX Codex +1 2 800установок email-html-mjml Framix Скилл собирает адаптивные шаблоны писем на MJML 5.x и компилирует их в HTML, который переживает Outlook и Gmail. Пригодится, когда нужен welcome, рассылка или транзакционное письмо. CX Codex +1 945установок verify Yeachan-Heo Скилл превращает «должно работать» в проверяемые доказательства: что запускали, что прошло, что осталось непроверенным. CX Codex +1 647установок email-template-builder Alireza Rezvani Создаёт систему транзакционных писем на React Email или MJML, подключает провайдеров и добавляет предпросмотр. Подходит для новых шаблонов, миграции и локализации писем. 602установок cloudflare-email-service cloudflare Скилл для работы с Cloudflare Email Sending и Email Routing: помогает настроить отправку и приём писем в Workers. CX Codex +1 102 300установок extension-email-calendar-events caffeinelabs Скилл добавляет в приложение календарные события и рассылку iCalendar-приглашений по почте. Пригодится, если нужны встречи с гостями без ручной сборки ICS. CX Codex +1 28 400установок

Рядом в теме «Тесты и QA»

Всё по теме «Тесты и QA» →

Вопросы о verify-email-snapshots

Как установить verify-email-snapshots в Claude?

Выполните npx skills add Aaronontheweb/dotnet-skills --skill verify-email-snapshots -a claude-code. Скилл окажется в ~/.claude/skills/verify-email-snapshots/SKILL.md.

Что делает verify-email-snapshots?

Пишет снапшот-тесты для HTML-писем: рендер шаблона сравнивается с утверждённым .verified.html, и тест падает, когда вывод изменился.

Что нужно, чтобы тесты заработали?

Пакет Verify.Xunit (или Verify.NUnit, Verify.MSTest) и рендерер шаблонов, доступный из теста. Динамические значения придётся вычищать скрабберами.

Исходный текст

SKILL.md · MIT
---
name: verify-email-snapshots
description: Snapshot test email templates using Verify to catch regressions. Validates rendered HTML output matches approved baseline. Works with MJML templates and any email renderer.
user-invocable: false
---

# Snapshot Testing Email Templates with Verify

## When to Use This Skill

Use this skill when:
- Testing email template rendering for regressions
- Validating MJML templates compile to expected HTML
- Reviewing email changes in code review (diffs are visual)
- Ensuring variable substitution works correctly

**Related skills:**
- `aspnetcore/mjml-email-templates` - MJML template authoring
- `aspire/mailpit-integration` - Test email delivery locally
- `testing/snapshot-testing` - General Verify patterns

---

## Why Snapshot Test Emails?

Email templates are:
1. **Visual** - Small changes can break rendering across clients
2. **Hard to unit test** - Output is complex HTML, not simple values
3. **Prone to regression** - Template changes can have unintended effects

Snapshot testing captures the rendered HTML and fails when it changes unexpectedly.

---

## Installation

```bash
dotnet add package Verify.Xunit  # or Verify.NUnit, Verify.MSTest
```

---

## Basic Email Snapshot Test

```csharp
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
    // Arrange
    var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();

    var variables = new Dictionary<string, string>
    {
        { "PreviewText", "You've been invited to join Acme Corp" },
        { "OrganizationName", "Acme Corporation" },
        { "InviteeName", "John Doe" },
        { "InviterName", "Jane Admin" },
        { "InvitationLink", "https://example.com/invite/abc123" },
        { "ExpirationDate", "December 31, 2025" }
    };

    // Act
    var html = await renderer.RenderTemplateAsync(
        "UserInvitations/UserSignupInvitation",
        variables);

    // Assert
    await Verify(html, extension: "html");
}
```

This creates `UserSignupInvitation_RendersCorrectly.verified.html` on first run.

---

## Reviewing Email Changes

When a template changes, the test fails with a diff. Review options:

### 1. Visual Diff Tool

```bash
# Configure diff tool (one-time)
dotnet tool install -g verify.tool
verify accept  # Accept all pending changes
verify review  # Open diff tool
```

### 2. Browser Preview

Open the `.received.html` file in a browser to see the actual rendering.

### 3. IDE Integration

Most IDEs show inline diffs for `.verified.html` vs `.received.html` files.

---

## Test Each Template Variant

Create tests for each email template to catch regressions:

```csharp
public class EmailTemplateSnapshotTests : IClassFixture<EmailTestFixture>
{
    private readonly IMjmlTemplateRenderer _renderer;

    public EmailTemplateSnapshotTests(EmailTestFixture fixture)
    {
        _renderer = fixture.Services.GetRequiredService<IMjmlTemplateRenderer>();
    }

    [Fact]
    public async Task WelcomeEmail_NewUser() =>
        await VerifyTemplate("Welcome/NewUser", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "LoginUrl", "https://example.com/login" }
        });

    [Fact]
    public async Task WelcomeEmail_InvitedUser() =>
        await VerifyTemplate("Welcome/InvitedUser", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "InviterName", "Jane Admin" },
            { "OrganizationName", "Acme Corp" }
        });

    [Fact]
    public async Task PasswordReset() =>
        await VerifyTemplate("PasswordReset/PasswordReset", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "ResetLink", "https://example.com/reset/abc123" },
            { "ExpirationMinutes", "30" }
        });

    [Fact]
    public async Task PaymentReceipt() =>
        await VerifyTemplate("Billing/PaymentReceipt", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "Amount", "$10.00" },
            { "InvoiceNumber", "INV-2025-001" },
            { "Date", "January 15, 2025" }
        });

    private async Task VerifyTemplate(
        string templateName,
        Dictionary<string, string> variables)
    {
        var html = await _renderer.RenderTemplateAsync(templateName, variables);
        await Verify(html, extension: "html")
            .UseMethodName(templateName.Replace("/", "_"));
    }
}
```

---

## Scrubbing Dynamic Values

Some values change between test runs. Scrub them:

```csharp
[Fact]
public async Task EmailWithTimestamp_ScrubsDynamicValues()
{
    var html = await _renderer.RenderTemplateAsync("Welcome", variables);

    await Verify(html, extension: "html")
        .ScrubLinesContaining("Generated at:")
        .ScrubInlineGuids();  // Scrubs GUIDs in URLs
}
```

### Common Scrubbers

```csharp
// Scrub dates
.ScrubLinesContaining("Date:")
.AddScrubber(s => Regex.Replace(s, @"\d{4}-\d{2}-\d{2}", "SCRUBBED-DATE"))

// Scrub URLs with tokens
.AddScrubber(s => Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"))

// Scrub GUIDs
.ScrubInlineGuids()
```

---

## Test Fixture for Email Tests

```csharp
public class EmailTestFixture : IAsyncLifetime
{
    public IServiceProvider Services { get; private set; } = null!;

    public async Task InitializeAsync()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["SiteUrl"] = "https://example.com"
            })
            .Build());

        services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();

        Services = services.BuildServiceProvider();

        await Task.CompletedTask;
    }

    public Task DisposeAsync() => Task.CompletedTask;
}
```

---

## Composer Snapshot Tests

Test the full composer output including subject and metadata:

```csharp
[Fact]
public async Task SignupInvitation_ComposesCorrectEmail()
{
    var composer = _services.GetRequiredService<IUserEmailComposer>();

    var email = await composer.ComposeSignupInvitationAsync(
        recipientEmail: new EmailAddress("[email protected]"),
        recipientName: new PersonName("John Doe"),
        inviterName: new PersonName("Jane Admin"),
        organizationName: new OrganizationName("Acme Corp"),
        invitationUrl: new AbsoluteUri("https://example.com/invite/abc123"),
        expiresAt: new DateTimeOffset(2025, 12, 31, 0, 0, 0, TimeSpan.Zero));

    // Verify the full email object (subject, to, body)
    await Verify(new
    {
        email.To,
        email.Subject,
        HtmlBody = email.HtmlBody  // Will be stored as .html extension
    });
}
```

---

## CI Integration

### Fail on Missing Baseline

In CI, fail if no `.verified.html` file exists (prevents accidental acceptance):

```csharp
// In test setup or ModuleInitializer
VerifierSettings.ThrowOnMissingVerifiedFile();
```

### Git Configuration

Add to `.gitattributes` to improve diff handling:

```gitattributes
*.verified.html linguist-language=HTML
*.verified.html diff=html
```

---

## Best Practices

### DO

```csharp
// DO: Test each template variant
[Fact] Task WelcomeEmail_NewUser_RendersCorrectly()
[Fact] Task WelcomeEmail_InvitedUser_RendersCorrectly()

// DO: Use descriptive test names
[Fact] Task PaymentReceipt_WithRefund_ShowsRefundAmount()

// DO: Scrub dynamic values consistently
.ScrubLinesContaining("Generated at:")

// DO: Review diffs carefully before accepting
verify review
```

### DON'T

```csharp
// DON'T: Skip email testing
// DON'T: Auto-accept changes without review
verify accept --all  // Dangerous!

// DON'T: Test only happy path
// DON'T: Ignore snapshot test failures
```

---

## Workflow

1. **Create template** - Write MJML template
2. **Write test** - Add snapshot test with sample variables
3. **Run test** - First run creates `.verified.html`
4. **Review** - Open in browser, verify rendering
5. **Commit** - Include `.verified.html` in source control
6. **Iterate** - Changes fail test, review diff, accept if correct

---

## Resources

- **Verify**: https://github.com/VerifyTests/Verify
- **Verify.Xunit**: https://github.com/VerifyTests/Verify#xunit
- **Diff Tools**: https://github.com/VerifyTests/DiffEngine

© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой