[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6KWE76TM3QHGQ08PEB3KY6D":3,"skill-same:01M6KWE76TM3QHGQ08PEB3KY6D":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":14,"repositoryHtmlUrl":57,"repositoryHomepageUrl":6,"ownerHtmlUrl":58,"ownerBlogUrl":6,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6KWE76TM3QHGQ08PEB3KY6D","tunit-playwright-ui-testing",null,"Comprehensive guidance for writing coded end-to-end UI tests in C# using TUnit.Playwright. Tests inherit from PageTest — the base class manages all browser lifecycle. Covers NuGet setup, PageTest inheritance, Page Object Model, browser configuration, parallelism limiting, and CI integration. Based on the official TUnit docs at https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright.","rafsanulhasan\u002FHypermediaEngine","rafsanulhasan","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F2341142?v=4","01M6KW2HWPTZWK051N7B0HTBVG","Rafsanul Hasan","01M6KW2J0N3613382MF5DRN79H",0,"GPL-3.0","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftunit-playwright-ui-testing\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"tunit playwright ui testing","playwright","ui","tunit","testing","playwright ui testing","ui testing","page","for","object","end","tests","inheritance","model","lifecycle","ci","examples","dev","official","writing","2026-11-10 10:12:30",false,"---\nname: tunit-playwright-ui-testing\ndescription: Comprehensive guidance for writing coded end-to-end UI tests in C# using TUnit.Playwright. Tests inherit from PageTest — the base class manages all browser lifecycle. Covers NuGet setup, PageTest inheritance, Page Object Model, browser configuration, parallelism limiting, and CI integration. Based on the official TUnit docs at https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright.\n---\n\n# tunit-playwright-ui-testing\n\nThis skill guides the `sqa-engineer` to write coded, repeatable UI tests in C# for web applications in HypermediaEngine using **TUnit.Playwright** — where tests inherit from `PageTest` and the base class manages browser lifecycle automatically.\n\n> **Source:** Official TUnit docs — https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright\n\n> **When to use:** Use this skill for end-to-end tests that must be committed as `.cs` test files and run in CI via `dotnet test`. For one-time AI-driven exploratory browser tests (no `.cs` files), use `playwright-mcp-ui-testing` instead. For Blazor component-level tests, use `bunit-blazor-testing`.\n\n---\n\n## Phase 0 — Context Load (silent)\n\n1. Read `CLAUDE.md` and project conventions.\n2. Load memory: `Skill(\"manage-memory\", args: \"sqa-engineer\")`.\n3. Identify the test project that will host Playwright tests. Create a new `*.PlaywrightTests` csproj if one doesn't exist.\n4. Read the spec file at `docs\u002Fspecs\u002F\u003Cfeature-slug>.spec.md` if it exists.\n\n---\n\n## Phase 1 — NuGet Setup\n\nAdd to `Directory.Packages.props` (if not already present):\n\n```xml\n\u003CPackageReference Include=\"TUnit.Playwright\" Version=\"*\" \u002F>\n```\n\nAdd to the Playwright test project's `.csproj`:\n\n```xml\n\u003CPackageReference Include=\"TUnit.Playwright\" \u002F>\n```\n\nRun Playwright browser install after building:\n\n```bash\n# Windows (PowerShell):\npwsh bin\u002FDebug\u002Fnet10.0\u002Fplaywright.ps1 install chromium\n\n# macOS\u002FLinux:\n.\u002Fbin\u002FDebug\u002Fnet10.0\u002Fplaywright.sh install chromium\n```\n\n> `TUnit.Playwright` transitively brings in `Microsoft.Playwright`. No need to reference `Microsoft.Playwright` directly.\n\n---\n\n## Phase 2 — Simplest Possible Test (PageTest)\n\nBy inheriting from `PageTest`, the base class sets up and disposes all Playwright objects. The following properties are available in every test:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `Page` | `IPage` | The current browser page (tab) |\n| `Context` | `IBrowserContext` | The browser context |\n| `Browser` | `IBrowser` | The browser instance |\n| `Playwright` | `IPlaywright` | The Playwright instance |\n\n**Minimal test:**\n\n```csharp\nusing TUnit.Playwright;\n\npublic class Tests : PageTest\n{\n    [Test]\n    public async Task NavigatesToGitHub()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fwww.github.com\u002Fthomhurst\u002FTUnit\");\n    }\n}\n```\n\nNo setup code needed. No fixture classes. No `ClassDataSource`. Just inherit and write tests.\n\n---\n\n## Phase 3 — Real Page Interaction Tests\n\n```csharp\nusing TUnit.Core;\nusing TUnit.Assertions;\nusing TUnit.Assertions.Extensions;\nusing TUnit.Playwright;\n\npublic class LoginPageTests : PageTest\n{\n    [Test]\n    [DisplayName(\"[AC-1] Login button is visible on the login page\")]\n    public async Task Login_Button_Is_Visible()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Flogin\");\n        ILocator loginButton = Page.Locator(\"button#login\");\n        await Assert.That(await loginButton.IsVisibleAsync()).IsTrue();\n    }\n\n    [Test]\n    [DisplayName(\"[AC-2] Successful login redirects to dashboard\")]\n    public async Task Successful_Login_Redirects_To_Dashboard()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Flogin\");\n        await Page.FillAsync(\"#username\", \"testuser\");\n        await Page.FillAsync(\"#password\", \"password123\");\n        await Page.ClickAsync(\"button#login\");\n        await Page.WaitForURLAsync(\"**\u002Fdashboard\");\n        string? heading = await Page.Locator(\"h1\").TextContentAsync();\n        await Assert.That(heading).IsEqualTo(\"Dashboard\");\n    }\n}\n```\n\n---\n\n## Phase 4 — Configuring Browser Options\n\nOverride `BrowserName` and pass `BrowserTypeLaunchOptions` to the base constructor:\n\n```csharp\nusing Microsoft.Playwright;\nusing TUnit.Playwright;\n\npublic class HeadlessChromeTests : PageTest\n{\n    public HeadlessChromeTests() : base(new BrowserTypeLaunchOptions\n    {\n        Headless = Environment.GetEnvironmentVariable(\"PLAYWRIGHT_HEADLESS\") != \"false\",\n        SlowMo = 0  \u002F\u002F set to 100 for debugging — adds 100ms delay between actions\n    }) { }\n\n    public override string BrowserName => \"chromium\";  \u002F\u002F chromium | firefox | webkit\n\n    [Test]\n    public async Task Page_Title_Matches()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\");\n        string title = await Page.TitleAsync();\n        await Assert.That(title).Contains(\"Example\");\n    }\n}\n```\n\n**Browser options:**\n- `BrowserName` values: `\"chromium\"` (default), `\"firefox\"`, `\"webkit\"`\n- `Headless = true` for CI; read from env var for local flexibility\n- `SlowMo` — milliseconds to add between Playwright actions (useful for debugging)\n\n---\n\n## Phase 5 — Page Object Model\n\nKeep pages assertion-free. Page Objects return data; tests assert on it.\n\n```csharp\npublic class WeatherPage(IPage page)\n{\n    private const string Url = \"http:\u002F\u002Flocalhost:5000\u002Fweather\";\n\n    public async Task NavigateAsync()\n        => await page.GotoAsync(Url);\n\n    public async Task\u003CIReadOnlyList\u003CILocator>> GetForecastRowsAsync()\n        => await page.Locator(\"table tbody tr\").AllAsync();\n\n    public async Task\u003Cstring?> GetTitleAsync()\n        => await page.TitleAsync();\n}\n\npublic class WeatherPageTests : PageTest\n{\n    [Test]\n    [DisplayName(\"[AC-3] Weather page shows forecast rows\")]\n    public async Task WeatherPage_WhenLoaded_ShowsForecastRows()\n    {\n        WeatherPage weatherPage = new(Page);\n\n        \u002F\u002F Arrange + Act\n        await weatherPage.NavigateAsync();\n        IReadOnlyList\u003CILocator> rows = await weatherPage.GetForecastRowsAsync();\n\n        \u002F\u002F Assert\n        await Assert.That(rows.Count).IsGreaterThan(0);\n    }\n}\n```\n\n**Page Object rules:**\n- One class per page or major component.\n- No assertions inside page objects — they return data; tests assert.\n- Use `Locator` over `QuerySelector` — locators are lazy and auto-retry.\n- Prefer `GetByRole`, `GetByLabel`, `GetByText`, `GetByTestId` over CSS selectors.\n\n---\n\n## Phase 6 — Controlling Parallelism\n\nBrowser tests are resource-intensive. Use `[ParallelLimiter\u003CT>]` to cap concurrency:\n\n```csharp\nusing TUnit.Core;\nusing TUnit.Playwright;\n\npublic class BrowserParallelLimit : IParallelLimit\n{\n    public int Limit => 2;  \u002F\u002F at most 2 browser tests run at once\n}\n\n[ParallelLimiter\u003CBrowserParallelLimit>]\npublic class HeavyBrowserTests : PageTest\n{\n    [Test]\n    public async Task Test_A()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Fa\");\n        await Assert.That(await Page.TitleAsync()).IsNotNull();\n    }\n\n    [Test]\n    public async Task Test_B()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Fb\");\n        await Assert.That(await Page.TitleAsync()).IsNotNull();\n    }\n}\n```\n\n---\n\n## Phase 7 — Async Patterns\n\n```csharp\n\u002F\u002F ✅ Always await Playwright calls\nawait Page.GotoAsync(url);\nawait Page.ClickAsync(\"button[type=submit]\");\nILocator input = Page.GetByLabel(\"Email\");\nawait input.FillAsync(\"user@example.com\");\n\n\u002F\u002F ✅ Use locators with built-in retry and wait\nILocator successMsg = Page.GetByText(\"Order confirmed\");\nawait successMsg.WaitForAsync(new LocatorWaitForOptions { Timeout = 5000 });\n\n\u002F\u002F ❌ Never use .Result \u002F .Wait() — deadlocks in async context\nPage.GotoAsync(url).Result;\n\n\u002F\u002F ❌ Never use Thread.Sleep\nThread.Sleep(2000);\n```\n\n---\n\n## Phase 8 — Running Tests\n\n```bash\n# Run only Playwright tests\ndotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n\n# Run in headed mode (visible browser window) for local debugging:\n# Windows PowerShell:\n$env:PLAYWRIGHT_HEADLESS=\"false\"; dotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n# macOS\u002FLinux:\nPLAYWRIGHT_HEADLESS=false dotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n```\n\n---\n\n## Phase 9 — CI Integration (GitHub Actions)\n\n```yaml\n- name: Install Playwright browsers\n  run: pwsh tests\u002FYourProject.PlaywrightTests\u002Fbin\u002FRelease\u002Fnet10.0\u002Fplaywright.ps1 install --with-deps chromium\n\n- name: Run Playwright tests\n  run: dotnet test tests\u002FYourProject.PlaywrightTests\u002F --configuration Release\n  env:\n    PLAYWRIGHT_HEADLESS: \"true\"\n```\n\n---\n\n## Best Practices\n\n| Do | Don't |\n|----|-------|\n| Inherit from `PageTest` | Create manual browser fixture classes |\n| Use `GetByRole`, `GetByLabel`, `GetByTestId` | Reach for CSS selectors first |\n| Use `Locator.WaitForAsync()` to stabilize | Use `await Task.Delay()` |\n| Keep assertions in test methods | Put `Assert.That()` in page objects |\n| Override `BrowserName` per test class | Leave browser type implicit |\n| Use `[ParallelLimiter\u003CT>]` on heavy suites | Let all tests run in unlimited parallel |\n| Read `Headless` from env var | Hardcode `Headless = true` or `false` |\n| Use `Assert.Multiple()` for multi-property assertions | Let first failure stop the test |\n| Trace each test to an AC ID in `[DisplayName]` | Write untargeted tests |\n\n---\n\n## References\n\n- Official TUnit Playwright docs: https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright\n- Playwright for .NET API: https:\u002F\u002Fplaywright.dev\u002Fdotnet\u002Fdocs\u002Fapi\u002Fclass-page\n- TUnit docs: https:\u002F\u002Ftunit.dev\u002Fdocs\u002F\n","# tunit-playwright-ui-testing\n\nThis skill guides the `sqa-engineer` to write coded, repeatable UI tests in C# for web applications in HypermediaEngine using **TUnit.Playwright** — where tests inherit from `PageTest` and the base class manages browser lifecycle automatically.\n\n> **Source:** Official TUnit docs — https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright\n\n> **When to use:** Use this skill for end-to-end tests that must be committed as `.cs` test files and run in CI via `dotnet test`. For one-time AI-driven exploratory browser tests (no `.cs` files), use `playwright-mcp-ui-testing` instead. For Blazor component-level tests, use `bunit-blazor-testing`.\n\n---\n\n## Phase 0 — Context Load (silent)\n\n1. Read `CLAUDE.md` and project conventions.\n2. Load memory: `Skill(\"manage-memory\", args: \"sqa-engineer\")`.\n3. Identify the test project that will host Playwright tests. Create a new `*.PlaywrightTests` csproj if one doesn't exist.\n4. Read the spec file at `docs\u002Fspecs\u002F\u003Cfeature-slug>.spec.md` if it exists.\n\n---\n\n## Phase 1 — NuGet Setup\n\nAdd to `Directory.Packages.props` (if not already present):\n\n```xml\n\u003CPackageReference Include=\"TUnit.Playwright\" Version=\"*\" \u002F>\n```\n\nAdd to the Playwright test project's `.csproj`:\n\n```xml\n\u003CPackageReference Include=\"TUnit.Playwright\" \u002F>\n```\n\nRun Playwright browser install after building:\n\n```bash\n# Windows (PowerShell):\npwsh bin\u002FDebug\u002Fnet10.0\u002Fplaywright.ps1 install chromium\n\n# macOS\u002FLinux:\n.\u002Fbin\u002FDebug\u002Fnet10.0\u002Fplaywright.sh install chromium\n```\n\n> `TUnit.Playwright` transitively brings in `Microsoft.Playwright`. No need to reference `Microsoft.Playwright` directly.\n\n---\n\n## Phase 2 — Simplest Possible Test (PageTest)\n\nBy inheriting from `PageTest`, the base class sets up and disposes all Playwright objects. The following properties are available in every test:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `Page` | `IPage` | The current browser page (tab) |\n| `Context` | `IBrowserContext` | The browser context |\n| `Browser` | `IBrowser` | The browser instance |\n| `Playwright` | `IPlaywright` | The Playwright instance |\n\n**Minimal test:**\n\n```csharp\nusing TUnit.Playwright;\n\npublic class Tests : PageTest\n{\n    [Test]\n    public async Task NavigatesToGitHub()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fwww.github.com\u002Fthomhurst\u002FTUnit\");\n    }\n}\n```\n\nNo setup code needed. No fixture classes. No `ClassDataSource`. Just inherit and write tests.\n\n---\n\n## Phase 3 — Real Page Interaction Tests\n\n```csharp\nusing TUnit.Core;\nusing TUnit.Assertions;\nusing TUnit.Assertions.Extensions;\nusing TUnit.Playwright;\n\npublic class LoginPageTests : PageTest\n{\n    [Test]\n    [DisplayName(\"[AC-1] Login button is visible on the login page\")]\n    public async Task Login_Button_Is_Visible()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Flogin\");\n        ILocator loginButton = Page.Locator(\"button#login\");\n        await Assert.That(await loginButton.IsVisibleAsync()).IsTrue();\n    }\n\n    [Test]\n    [DisplayName(\"[AC-2] Successful login redirects to dashboard\")]\n    public async Task Successful_Login_Redirects_To_Dashboard()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Flogin\");\n        await Page.FillAsync(\"#username\", \"testuser\");\n        await Page.FillAsync(\"#password\", \"password123\");\n        await Page.ClickAsync(\"button#login\");\n        await Page.WaitForURLAsync(\"**\u002Fdashboard\");\n        string? heading = await Page.Locator(\"h1\").TextContentAsync();\n        await Assert.That(heading).IsEqualTo(\"Dashboard\");\n    }\n}\n```\n\n---\n\n## Phase 4 — Configuring Browser Options\n\nOverride `BrowserName` and pass `BrowserTypeLaunchOptions` to the base constructor:\n\n```csharp\nusing Microsoft.Playwright;\nusing TUnit.Playwright;\n\npublic class HeadlessChromeTests : PageTest\n{\n    public HeadlessChromeTests() : base(new BrowserTypeLaunchOptions\n    {\n        Headless = Environment.GetEnvironmentVariable(\"PLAYWRIGHT_HEADLESS\") != \"false\",\n        SlowMo = 0  \u002F\u002F set to 100 for debugging — adds 100ms delay between actions\n    }) { }\n\n    public override string BrowserName => \"chromium\";  \u002F\u002F chromium | firefox | webkit\n\n    [Test]\n    public async Task Page_Title_Matches()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\");\n        string title = await Page.TitleAsync();\n        await Assert.That(title).Contains(\"Example\");\n    }\n}\n```\n\n**Browser options:**\n- `BrowserName` values: `\"chromium\"` (default), `\"firefox\"`, `\"webkit\"`\n- `Headless = true` for CI; read from env var for local flexibility\n- `SlowMo` — milliseconds to add between Playwright actions (useful for debugging)\n\n---\n\n## Phase 5 — Page Object Model\n\nKeep pages assertion-free. Page Objects return data; tests assert on it.\n\n```csharp\npublic class WeatherPage(IPage page)\n{\n    private const string Url = \"http:\u002F\u002Flocalhost:5000\u002Fweather\";\n\n    public async Task NavigateAsync()\n        => await page.GotoAsync(Url);\n\n    public async Task\u003CIReadOnlyList\u003CILocator>> GetForecastRowsAsync()\n        => await page.Locator(\"table tbody tr\").AllAsync();\n\n    public async Task\u003Cstring?> GetTitleAsync()\n        => await page.TitleAsync();\n}\n\npublic class WeatherPageTests : PageTest\n{\n    [Test]\n    [DisplayName(\"[AC-3] Weather page shows forecast rows\")]\n    public async Task WeatherPage_WhenLoaded_ShowsForecastRows()\n    {\n        WeatherPage weatherPage = new(Page);\n\n        \u002F\u002F Arrange + Act\n        await weatherPage.NavigateAsync();\n        IReadOnlyList\u003CILocator> rows = await weatherPage.GetForecastRowsAsync();\n\n        \u002F\u002F Assert\n        await Assert.That(rows.Count).IsGreaterThan(0);\n    }\n}\n```\n\n**Page Object rules:**\n- One class per page or major component.\n- No assertions inside page objects — they return data; tests assert.\n- Use `Locator` over `QuerySelector` — locators are lazy and auto-retry.\n- Prefer `GetByRole`, `GetByLabel`, `GetByText`, `GetByTestId` over CSS selectors.\n\n---\n\n## Phase 6 — Controlling Parallelism\n\nBrowser tests are resource-intensive. Use `[ParallelLimiter\u003CT>]` to cap concurrency:\n\n```csharp\nusing TUnit.Core;\nusing TUnit.Playwright;\n\npublic class BrowserParallelLimit : IParallelLimit\n{\n    public int Limit => 2;  \u002F\u002F at most 2 browser tests run at once\n}\n\n[ParallelLimiter\u003CBrowserParallelLimit>]\npublic class HeavyBrowserTests : PageTest\n{\n    [Test]\n    public async Task Test_A()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Fa\");\n        await Assert.That(await Page.TitleAsync()).IsNotNull();\n    }\n\n    [Test]\n    public async Task Test_B()\n    {\n        await Page.GotoAsync(\"https:\u002F\u002Fexample.com\u002Fb\");\n        await Assert.That(await Page.TitleAsync()).IsNotNull();\n    }\n}\n```\n\n---\n\n## Phase 7 — Async Patterns\n\n```csharp\n\u002F\u002F ✅ Always await Playwright calls\nawait Page.GotoAsync(url);\nawait Page.ClickAsync(\"button[type=submit]\");\nILocator input = Page.GetByLabel(\"Email\");\nawait input.FillAsync(\"user@example.com\");\n\n\u002F\u002F ✅ Use locators with built-in retry and wait\nILocator successMsg = Page.GetByText(\"Order confirmed\");\nawait successMsg.WaitForAsync(new LocatorWaitForOptions { Timeout = 5000 });\n\n\u002F\u002F ❌ Never use .Result \u002F .Wait() — deadlocks in async context\nPage.GotoAsync(url).Result;\n\n\u002F\u002F ❌ Never use Thread.Sleep\nThread.Sleep(2000);\n```\n\n---\n\n## Phase 8 — Running Tests\n\n```bash\n# Run only Playwright tests\ndotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n\n# Run in headed mode (visible browser window) for local debugging:\n# Windows PowerShell:\n$env:PLAYWRIGHT_HEADLESS=\"false\"; dotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n# macOS\u002FLinux:\nPLAYWRIGHT_HEADLESS=false dotnet test --filter \"FullyQualifiedName~PlaywrightTests\"\n```\n\n---\n\n## Phase 9 — CI Integration (GitHub Actions)\n\n```yaml\n- name: Install Playwright browsers\n  run: pwsh tests\u002FYourProject.PlaywrightTests\u002Fbin\u002FRelease\u002Fnet10.0\u002Fplaywright.ps1 install --with-deps chromium\n\n- name: Run Playwright tests\n  run: dotnet test tests\u002FYourProject.PlaywrightTests\u002F --configuration Release\n  env:\n    PLAYWRIGHT_HEADLESS: \"true\"\n```\n\n---\n\n## Best Practices\n\n| Do | Don't |\n|----|-------|\n| Inherit from `PageTest` | Create manual browser fixture classes |\n| Use `GetByRole`, `GetByLabel`, `GetByTestId` | Reach for CSS selectors first |\n| Use `Locator.WaitForAsync()` to stabilize | Use `await Task.Delay()` |\n| Keep assertions in test methods | Put `Assert.That()` in page objects |\n| Override `BrowserName` per test class | Leave browser type implicit |\n| Use `[ParallelLimiter\u003CT>]` on heavy suites | Let all tests run in unlimited parallel |\n| Read `Headless` from env var | Hardcode `Headless = true` or `false` |\n| Use `Assert.Multiple()` for multi-property assertions | Let first failure stop the test |\n| Trace each test to an AC ID in `[DisplayName]` | Write untargeted tests |\n\n---\n\n## References\n\n- Official TUnit Playwright docs: https:\u002F\u002Ftunit.dev\u002Fdocs\u002Fexamples\u002Fplaywright\n- Playwright for .NET API: https:\u002F\u002Fplaywright.dev\u002Fdotnet\u002Fdocs\u002Fapi\u002Fclass-page\n- TUnit docs: https:\u002F\u002Ftunit.dev\u002Fdocs\u002F\n",{"name":5,"description":7},"main","00b2c68281930728ee390bb16cec32b88d2caac7",".github\u002Fskills\u002Ftunit-playwright-ui-testing","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine","https:\u002F\u002Fgithub.com\u002Frafsanulhasan","\r\n    MCSD || A-CSD || Software Architect\r\n\r\n","2026-06-06 07:47:06",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add rafsanulhasan\u002FHypermediaEngine --skill tunit-playwright-ui-testing",{"tool":67,"command":68},"bunx","bunx skills add rafsanulhasan\u002FHypermediaEngine --skill tunit-playwright-ui-testing",{"tool":70,"command":71},"pnpm","pnpm dlx skills add rafsanulhasan\u002FHypermediaEngine --skill tunit-playwright-ui-testing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftunit-playwright-ui-testing\u002FSKILL.md",true,[],[76,101,120,139,162,188],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":83,"publishedAt":49},"01M6KWEJD606FG3DBBRM0W4DKW","write-tests","Test implementation skill for HypermediaEngine. Takes a confirmed test plan from design-test-cases and produces compilable, runnable xUnit tests following project conventions. Validates coverage quality through mutation testing. Invoked by the sqa-engineer agent after the test plan is approved.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fwrite-tests\u002FSKILL.md",[82],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[84,85,40,33,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,37],"write tests","write","design","test","project conventions","test plan","Bash","plan","quality","xunit","read","edit","coverage","engineer","mutation testing","project","mutation",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":49},"01M6KWEF2XQ00C1Y6QCK7YHZ1H","write-documentation","Structured documentation writing workflow for HypermediaEngine. Writes or updates README.md files at the repo root and in each project directory (src\u002F, tests\u002F, samples\u002F, etc.) based on current implementation, ADRs, and architecture decisions.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fwrite-documentation\u002FSKILL.md",[],[108,85,109,48,110,111,112,90,113,114,115,116,94,95,117,99,40,118,119,37],"documentation","write documentation","directory","workflow","architecture","src","repo","decisions","README","files","updates","at",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":126,"publishedAt":49},"01M6KWEBDXTXAVPQ3BHS3YYYNT","write-adr","Writes a numbered Architecture Decision Record (ADR) for a significant architectural choice in the HypermediaEngine project. Invoked by the software-architect agent whenever a consequential design decision is made or ratified. Saves the ADR to docs\u002Farchitecture\u002Fdecisions\u002F.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fwrite-adr\u002FSKILL.md",[],[127,85,128,86,129,112,130,131,132,133,115,94,134,135,99,136,137,37,138],"adr","write adr","docs","architect","decision","choice","grep","architecture decision record","record","software","opus","architecture decision",{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":148,"publishedAt":49},"01M6KWE44A738GDK65QS02XJBG","triage","Task classification, decomposition, dependency mapping, and routing skill. Invoked by the triage-agent to break any user request into discrete, independently-routable work items with assigned priorities and agent chains.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftriage\u002FSKILL.md",[145],{"categoryNo":146,"categoryName":147,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[141,149,150,151,152,153,154,155,156,157,158,159,160,161],"routing","classification","work","user","dependency","task","priorities","decomposition","user request","mapping","with","break","request",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":172,"publishedAt":49},"01M6KWDZ3W9ZKPR7FTTTSX8H2K","system-design","Expert system design skill. Use PROACTIVELY to design systems, review architectures, and assist software engineers in learning design principles and implementing them in the code with design patterns.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fsystem-design\u002FSKILL.md",[168,171],{"categoryNo":169,"categoryName":170,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},[86,173,174,175,176,177,178,133,94,179,180,181,182,183,184,136,185,186,159,187],"system","system design","web","design systems","learning","haiku","use","design patterns","design principles","expert","patterns","systems","architectures","principles","implementing",{"skillNo":189,"name":190,"nameZh":6,"description":191,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":192,"categories":193,"keywords":194,"publishedAt":49},"01M6KWDVXNF6T2JTMV23RJGAF6","spec-driven-development","Manages the full lifecycle of a feature specification — from collaborative drafting to enforcement during implementation and testing.","https:\u002F\u002Fgithub.com\u002Frafsanulhasan\u002FHypermediaEngine\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fspec-driven-development\u002FSKILL.md",[],[195,196,197,198,199,33,43,90,133,94,85,95,200,201,202,203,204,205,206,207],"spec","spec driven development","development","spec driven","driven","enforcement","implementation","specification","analyst","full","feature","sonnet","feature specification"]