Token导航 LogoToken导航TokenDH.com
开发操作浏览器github未标认证来源可访问许可证需确认审计异常

playwright-blazor-testingPlaywright blazor 测试

Agent Skill

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

总安装

5,551

周安装

236

GitHub Stars

890

下载量

1,945
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:playwright-blazor-testing(Playwright blazor 测试)
来源仓库:https://github.com/aaronontheweb/dotnet-skills
仓库路径:skills/playwright-blazor-testing
安装命令:
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill playwright-blazor-testing
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill playwright-blazor-testing

简介

用于辅助测试设计、自动化测试、用例整理和回归验证。

  • 适合编写单元测试、端到端测试或根据失败日志定位问题。
  • 使用时需确认项目测试框架、运行命令和夹具数据,避免误改逻辑。
  • 涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。
  • playwright-blazor-testing 属于开发类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Testing Blazor Applications with Playwright

When to Use This Skill

Use this skill when:

  • Writing end-to-end UI tests for Blazor Server or WebAssembly applications
  • Testing interactive components, forms, and user workflows
  • Verifying authentication and authorization flows
  • Testing SignalR-based real-time updates in Blazor Server
  • Capturing screenshots for visual regression testing
  • Testing responsive designs and mobile emulation
  • Debugging UI issues with browser developer tools

Core Principles

  1. Wait for Rendering - Blazor renders asynchronously; use proper wait strategies
  2. Test Attributes - Use data-test or data-testid attributes for stable selectors
  3. Headless by Default - Run tests headless in CI, headed for local debugging
  4. Handle Error UI - Always check for #blazor-error-ui to catch unhandled exceptions
  5. Avoid Network Wait States - Blazor navigation doesn't trigger network loads; wait for DOM changes
  6. Pin Browser Channels - Use specific browser channels (msedge, chrome) for reproducibility

Required NuGet Packages

<ItemGroup>
  <PackageReference Include="Microsoft.Playwright" Version="*" />
  <PackageReference Include="Microsoft.Playwright.MSTest" Version="*" />
  <!-- OR for xUnit -->
  <PackageReference Include="xunit" Version="*" />
  <PackageReference Include="xunit.runner.visualstudio" Version="*" />
</ItemGroup>

Installation

Before running tests, install Playwright browsers:

pwsh -Command "playwright install --with-deps"

Pattern 1: Basic Playwright Setup

using Microsoft.Playwright;

public class PlaywrightFixture : IAsyncLifetime
{
    private IPlaywright? _playwright;
    private IBrowser? _browser;

    public IBrowser Browser => _browser
        ?? throw new InvalidOperationException("Browser not initialized");

    public async Task InitializeAsync()
    {
        _playwright = await Playwright.CreateAsync();

        _browser = await _playwright.Chromium.LaunchAsync(new()
        {
            Headless = true,
            // For CI/debugging, you might want:
            // Headless = Environment.GetEnvironmentVariable("CI") != null,
            // SlowMo = 100 // Slow down actions for debugging
        });
    }

    public async Task DisposeAsync()
    {
        if (_browser is not null)
            await _browser.DisposeAsync();

        _playwright?.Dispose();
    }
}

Pattern 2: Navigation in Blazor Apps

Initial Page Load (Classic Navigation)

[Fact]
public async Task InitialPageLoad()
{
    var page = await _fixture.Browser.NewPageAsync();

    // First load is classic HTTP navigation
    await page.GotoAsync("https://localhost:5001");

    // Wait for Blazor to initialize
    await page.WaitForSelectorAsync("h1:has-text('Welcome')");

    Assert.True(await page.IsVisibleAsync("h1:has-text('Welcome')"));
}

In-App Navigation (No Page Reload)

Blazor uses client-side routing, so subsequent navigations don't trigger page reloads:

[Fact]
public async Task InternalNavigation()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync("https://localhost:5001");

    // Method 1: Click a navigation link
    await page.GetByRole(AriaRole.Link, new() { Name = "Counter" })
        .ClickAsync();

    // Wait for the new page content (NOT network idle!)
    await page.WaitForSelectorAsync("h1:has-text('Counter')");

    // Method 2: Programmatic navigation (Blazor 8+)
    await page.EvaluateAsync("window.Blazor.navigateTo('/fetchdata')");
    await page.WaitForSelectorAsync("h1:has-text('Weather')");

    // Method 3: Direct URL navigation (causes full reload)
    await page.GotoAsync("https://localhost:5001/counter");
    await page.WaitForSelectorAsync("h1:has-text('Counter')");
}

Wait Strategies for Blazor

// ❌ DON'T: Wait for network idle (Blazor doesn't reload pages)
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);

// ✅ DO: Wait for specific DOM elements
await page.WaitForSelectorAsync("h1:has-text('My Page')");

// ✅ DO: Wait for element visibility
await page.Locator("[data-test='content']").WaitForAsync();

// ✅ DO: Wait for URL change
await page.WaitForURLAsync("**/counter");

Pattern 3: Stable Selectors with Test Attributes

In Your Blazor Components

<!-- Add data-test attributes for stable selectors -->
<button data-test="submit-button" @onclick="HandleSubmit">
    Submit
</button>

<input data-test="username-input" @bind="Username" />

<div data-test="result-container">
    @Result
</div>

In Your Tests

[Fact]
public async Task FormSubmission()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync(baseUrl);

    // Use GetByTestId for elements with data-test attributes
    await page.GetByTestId("username-input").FillAsync("testuser");
    await page.GetByTestId("password-input").FillAsync("password123");
    await page.GetByTestId("submit-button").ClickAsync();

    // Verify result
    var result = await page.GetByTestId("result-container").TextContentAsync();
    Assert.Contains("Success", result);
}

Pattern 4: Handling Authentication

Interactive Login

[Fact]
public async Task LoginFlow()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync($"{baseUrl}/login");

    // Fill login form
    await page.FillAsync("input[name='username']", "alice");
    await page.FillAsync("input[name='password']", "P@ssw0rd");
    await page.ClickAsync("button[type='submit']");

    // Wait for redirect to dashboard
    await page.WaitForURLAsync("**/dashboard");

    // Verify logged in
    var username = await page.TextContentAsync("[data-test='user-name']");
    Assert.Equal("alice", username);
}

Cookie Injection (Faster)

[Fact]
public async Task AuthenticatedAccess_ViaCookie()
{
    var page = await _fixture.Browser.NewPageAsync();

    // Inject authentication cookie
    await page.Context.AddCookiesAsync(new[]
    {
        new Cookie
        {
            Name = ".AspNetCore.Cookies",
            Value = GenerateAuthCookie("alice"),
            Url = baseUrl,
            Secure = true,
            HttpOnly = true
        }
    });

    // Navigate directly to protected page
    await page.GotoAsync($"{baseUrl}/dashboard");

    // Already authenticated!
    var username = await page.TextContentAsync("[data-test='user-name']");
    Assert.Equal("alice", username);
}

private string GenerateAuthCookie(string username)
{
    // Generate a valid authentication cookie
    // This requires access to your app's cookie encryption keys
    // OR use a test endpoint that generates valid cookies
    // OR perform actual login once and reuse the cookie
}

OAuth/External Provider Mocking

// Use route interception to mock OAuth redirects
await page.RouteAsync("**/signin-microsoft", async route =>
{
    // Intercept OAuth redirect and return mock response
    await route.FulfillAsync(new()
    {
        Status = 302,
        Headers = new Dictionary<string, string>
        {
            ["Location"] = $"{baseUrl}/signin-callback?code=mock_auth_code"
        }
    });
});

Pattern 5: Click Events and Touch Interactions

[Fact]
public async Task ClickInteractions()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync(baseUrl);

    // Standard click
    await page.GetByText("Click Me").ClickAsync();

    // Right-click
    await page.ClickAsync("[data-test='context-menu']", new()
    {
        Button = MouseButton.Right
    });

    // Double-click
    await page.DblClickAsync("[data-test='item']");

    // Hover then click dropdown
    var menu = page.Locator("#profile-menu");
    await menu.HoverAsync();
    await menu.GetByText("Sign out").ClickAsync();

    // Touch events (mobile emulation)
    await page.EmulateMediaAsync(new() { Media = Media.Screen });
    await page.Touchscreen.TapAsync(150, 300);
}

Pattern 6: Form Handling

[Fact]
public async Task ComplexForm()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync($"{baseUrl}/form");

    // Text input
    await page.FillAsync("[data-test='name']", "John Doe");

    // Select dropdown
    await page.SelectOptionAsync("[data-test='country']", "US");

    // Checkbox
    await page.CheckAsync("[data-test='terms']");

    // Radio button
    await page.CheckAsync("[data-test='option-a']");

    // File upload
    await page.SetInputFilesAsync("[data-test='file-input']",
        "/path/to/test-file.pdf");

    // Submit
    await page.ClickAsync("[data-test='submit']");

    // Wait for success message
    await page.WaitForSelectorAsync("[data-test='success-message']");
}

Pattern 7: Handling Blazor Error UI

Blazor shows an error overlay when unhandled exceptions occur. Always check for this:

public static async Task AssertNoBlazorErrors(this IPage page)
{
    var errorUi = page.Locator("#blazor-error-ui");

    if (await errorUi.IsVisibleAsync())
    {
        var errorText = await errorUi.InnerTextAsync();
        Assert.Fail($"Blazor error occurred: {errorText}");
    }
}

[Fact]
public async Task Page_ShouldNotHaveErrors()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync(baseUrl);

    // Perform some actions
    await page.ClickAsync("[data-test='action-button']");

    // Verify no errors occurred
    await page.AssertNoBlazorErrors();
}

Pattern 8: Testing Real-Time Updates (SignalR)

Blazor Server uses SignalR for real-time communication:

[Fact]
public async Task RealTimeUpdates()
{
    // Open two browser contexts (simulating two users)
    var page1 = await _fixture.Browser.NewPageAsync();
    var page2 = await _fixture.Browser.NewPageAsync();

    await page1.GotoAsync($"{baseUrl}/drawing");
    await page2.GotoAsync($"{baseUrl}/drawing");

    // User 1 draws something
    await page1.ClickAsync("[data-test='draw-button']");
    await page1.Mouse.ClickAsync(100, 100);

    // User 2 should see the update
    await page2.WaitForSelectorAsync("[data-test='drawing-canvas']");

    // Verify both pages show the same content
    var canvas1 = await page1.GetByTestId("drawing-canvas")
        .GetAttributeAsync("data-strokes");
    var canvas2 = await page2.GetByTestId("drawing-canvas")
        .GetAttributeAsync("data-strokes");

    Assert.Equal(canvas1, canvas2);
}

Pattern 9: Screenshot and Visual Testing

[Fact]
public async Task CaptureScreenshots()
{
    var page = await _fixture.Browser.NewPageAsync();
    await page.GotoAsync(baseUrl);

    // Full page screenshot
    await page.ScreenshotAsync(new()
    {
        Path = "screenshots/homepage.png",
        FullPage = true
    });

    // Element screenshot
    var header = page.Locator("header");
    await header.ScreenshotAsync(new()
    {
        Path = "screenshots/header.png"
    });

    // Screenshot with viewport size
    await page.SetViewportSizeAsync(1920, 1080);
    await page.ScreenshotAsync(new()
    {
        Path = "screenshots/desktop.png"
    });

    // Mobile viewport
    await page.SetViewportSizeAsync(375, 667);
    await page.ScreenshotAsync(new()
    {
        Path = "screenshots/mobile.png"
    });
}

Pattern 10: Running Against HTTPS with Dev Certs

public async Task InitializeAsync()
{
    _playwright = await Playwright.CreateAsync();

    _browser = await _playwright.Chromium.LaunchAsync(new()
    {
        Headless = true,
        // Ignore certificate errors for local dev certs
        Args = new[] { "--ignore-certificate-errors" }
    });
}

For stricter setups, export and trust the dev certificate:

dotnet dev-certs https --export-path cert.pfx -p YourPassword

Common Selectors for Blazor Components

// By role (best for accessibility)
await page.GetByRole(AriaRole.Button, new() { Name = "Submit" });
await page.GetByRole(AriaRole.Link, new() { Name = "Home" });
await page.GetByRole(AriaRole.Heading, new() { Name = "Welcome" });

// By test ID
await page.GetByTestId("user-profile");

// By text content
await page.GetByText("Hello, World!");

// By label (for inputs)
await page.GetByLabel("Email Address");

// By placeholder
await page.GetByPlaceholder("Enter your name");

// CSS selectors (use sparingly)
await page.Locator(".mud-button-primary");
await page.Locator("#login-form");

// XPath (use as last resort)
await page.Locator("xpath=//button[contains(text(), 'Submit')]");

Parallelization Considerations

Blazor Server uses SignalR websockets. Multiple Playwright tests can saturate connections:

// Limit parallel execution for Blazor Server tests
[Collection("Blazor Server")]
public class BlazorServerTests { }

// In AssemblyInfo.cs or test startup
[assembly: CollectionBehavior(MaxParallelThreads = 2)]

Blazor WebAssembly doesn't have this limitation and can run fully parallel.

CI/CD Integration

GitHub Actions

name: Playwright Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: 9.0.x

    - name: Install Playwright Browsers
      run: pwsh -Command "playwright install --with-deps"

    - name: Build
      run: dotnet build -c Release

    - name: Run Playwright Tests
      run: |
        dotnet test tests/YourApp.UITests \
          --no-build \
          -c Release \
          --logger trx

    - name: Upload Screenshots
      uses: actions/upload-artifact@v3
      if: failure()
      with:
        name: playwright-screenshots
        path: "**/screenshots/"

    - name: Upload Test Results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: test-results
        path: "**/TestResults/*.trx"

Debugging Tips

  1. Run Headed - Set Headless = false to watch tests execute
  2. Slow Motion - Add SlowMo = 500 to slow down actions
  3. Pause Execution - Call await page.PauseAsync() to open Playwright Inspector
  4. Console Logs - Capture browser console: page.Console += (_, msg) => Console.WriteLine(msg.Text);
  5. Network Traffic - Monitor requests: page.Request += (_, req) => Console.WriteLine(req.Url);
  6. Screenshots on Failure - Always capture screenshots in catch blocks

Best Practices

  1. Use data-test attributes - More stable than CSS classes or IDs
  2. Prefer semantic selectors - Use roles, labels, and text content
  3. Wait for specific elements - Don't use blanket delays
  4. Check for Blazor errors - Always verify #blazor-error-ui is not visible
  5. Test with multiple viewports - Verify responsive design
  6. Reuse browser contexts - Faster than creating new browsers
  7. Clean up resources - Always dispose pages and browsers
  8. Use collections for Blazor Server - Avoid SignalR connection saturation
  9. Capture screenshots on failure - Essential for debugging CI failures
  10. Pin browser channels - Use specific channels for reproducibility

Advanced: Custom Wait Helpers

public static class PlaywrightExtensions
{
    public static async Task WaitForBlazorAsync(this IPage page)
    {
        // Wait for Blazor to finish rendering
        await page.EvaluateAsync(@"
            () => new Promise(resolve => {
                if (typeof Blazor !== 'undefined') {
                    resolve();
                } else {
                    const interval = setInterval(() => {
                        if (typeof Blazor !== 'undefined') {
                            clearInterval(interval);
                            resolve();
                        }
                    }, 100);
                }
            })
        ");
    }

    public static async Task WaitForNoSpinnersAsync(
        this IPage page,
        int timeout = 5000)
    {
        var locator = page.Locator(".spinner, .loading");
        await locator.WaitForAsync(new()
        {
            State = WaitForSelectorState.Hidden,
            Timeout = timeout
        });
    }

    public static async Task FillWithValidationAsync(
        this IPage page,
        string selector,
        string value)
    {
        await page.FillAsync(selector, value);

        // Trigger blur to activate validation
        await page.Locator(selector).BlurAsync();

        // Wait a bit for validation to complete
        await Task.Delay(100);
    }
}

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

38.49%
按下载量换算749

Claude

28.58%
按下载量换算556

Cursor

21.3%
按下载量换算414

Gemini CLI

9.47%
按下载量换算184

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

未通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills