Windows QA Engineer
CodeAlive-AI/ai-driven-development
A skill your agent uses when testing Windows 11 desktop apps (WinForms/WPF/UWP) via UFO UIA/Win32 automation MCP.
Create and maintain automated tests in Microsoft-native/.NET projects with a minimal stack — MSTest runner, System.Windows.Automation for Windows desktop, Playwright for real browser smoke.
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install JocysCom/FocusLogger qa-tester --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.ai/skills/qa-tester .claude/skills/qa-tester && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .claude/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-testerType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install JocysCom/FocusLogger qa-tester --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.ai/skills/qa-tester .agents/skills/qa-tester && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .agents/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install JocysCom/FocusLogger qa-tester --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.ai/skills/qa-tester .cursor/skills/qa-tester && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .cursor/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/JocysCom/FocusLogger.git --path .ai/skills/qa-tester--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install JocysCom/FocusLogger qa-tester --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.ai/skills/qa-tester .gemini/skills/qa-tester && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .gemini/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install JocysCom/FocusLogger qa-testerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .github/skills && cp -r skills-src/.ai/skills/qa-tester .github/skills/qa-tester && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .github/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JocysCom/FocusLogger --skill qa-tester -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install JocysCom/FocusLogger qa-tester --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JocysCom/FocusLogger.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.ai/skills/qa-tester .opencode/skills/qa-tester && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "qa-tester" agent skill from https://github.com/JocysCom/FocusLogger/tree/main/.ai/skills/qa-tester into .opencode/skills/qa-tester/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "qa-tester", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
qa-testerCreate and maintain automated tests in Microsoft-native/.NET projects with a minimal stack — MSTest runner, System.Windows.Automation for Windows desktop, Playwright for real browser smoke.
QA Tester is an agent skill from JocysCom/FocusLogger. Create and maintain automated tests in Microsoft-native/.NET projects with a minimal stack — MSTest runner, System.Windows.Automation for Windows desktop, Playwright for real browser smoke. Code-first, single-source-of-truth. Use whenever the user wants to write, review, structure, migrate, or plan tests for any .NET project (unit, API, database, WPF, WinUI, MAUI, Blazor, Razor) — even when the request says "test" without naming a framework.
Its SKILL.md is about 12k tokens, which your agent loads only when the skill is triggered. The skill folder holds 15 other files, including scripts and reference files (for example `references/legacy-bdd.md`, `references/migrations.md` and `references/onboarding-qa.md`).
It sits in Testing & QA, covering Desktop control, Cross-platform mobile apps and Browser testing. It works with .NET, Playwright, Blazor and Windows. The repository describes itself as: Find out which process or program is taking the window focus. In-game controls could temporary stop responding if other program steals the focus. The licence is GPL-3.0.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit b32c096. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 3 files in scripts/ (Python and TypeScript), which the agent can run.
Shell commands in SKILL.md call:
dotnetpythonrggopwshFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
learn.microsoft.complaywright.devopentelemetry.iogithub.combenchmarkdotnet.orgFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
TEST_USER_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
QA Tester loads about 12k tokens when it runs, and up to ~17k if it reads all its reference files. Until then it costs about 114 tokens; SKILL.md has 4,991 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from JocysCom/FocusLogger at commit b32c096, republished under its GPL-3.0 licence (© JocysCom). 4,991 words, ~12,343 tokens.
.claude/skills/qa-tester/SKILL.md (or your agent's skills folder). This skill also uses 10 other files; get the full folder from GitHub.Core philosophy: one test runner, minimum tools, executable code is the spec. The fewer different tools a codebase has, the lower the cognitive load on every engineer and AI that touches it. Prefer in-box Microsoft APIs; every extra dependency is a cost, not a feature.
Each principle has a why so you can judge edge cases instead of pattern-matching on the rule.
dotnet test on a clean checkout is the test that actually gets run.AutomationId. Never CSS tied to layout internals. Why: a fragile selector is a test that will break for reasons unrelated to what it's checking. If a stable selector doesn't exist, fix the product first.Thread.Sleep hides real race conditions and taxes everyone else's suite time.{Project}.Tests project by default. Mirror the product folder layout 1:1. Split into more projects only when a real constraint (headless CI, massive scale, different TFM) forces it. Why: a test project fractured into more pieces than the app it tests is absurd — every extra csproj duplicates infrastructure, slows the loop, and makes newcomers wonder which project to open.| Tool | Ships from | Used for |
|---|---|---|
MSTest (Microsoft.NET.Test.Sdk + MSTest.TestFramework + MSTest.TestAdapter) | NuGet / Microsoft | Everything .NET: unit, service, DB integration, API integration, Windows desktop UI via System.Windows.Automation, view-model tests for WPF/WinUI/MAUI-desktop. |
Playwright (Microsoft.Playwright + Microsoft.Playwright.MSTest) | NuGet / Microsoft | Real-browser web UI smoke. Runs under the same MSTest runner as everything else — one test explorer, one dotnet test command. |
That's the whole stack. System.Windows.Automation is in-box (no NuGet), so it doesn't appear in the package list but is the default for any Windows desktop test.
Performance collection (§6a) uses only in-box instrumentation — EventPipe / dotnet-trace, dotnet-gcdump, dotnet-counters, SQL Server Extended Events, Playwright tracing, ETW via wpr.exe — plus one Microsoft-owned load generator (Microsoft.Crank). Collectors are runtime/OS facilities, not "tools", so the two-tool rule is unchanged.
Quick exclusion list (see references/legacy-bdd.md for inherited-project support):
| Excluded | Why |
|---|---|
| Appium / WinAppDriver | Driver process, HTTP wire protocol, installer step. System.Windows.Automation does the same job in-box. Exception: MAUI mobile (Android/iOS) — keep it in a dedicated Tests/UI.Mobile/ project. |
| FlaUI | Thin wrapper around System.Windows.Automation. Use the in-box API directly. |
| Selenium | Playwright replaces it. |
| Cypress / TestCafe / WebDriverIO | Playwright replaces all of them. |
| NUnit / xUnit | One runner per repo — MSTest for Microsoft-aligned codebases. |
| SpecFlow / Reqnroll | Inherited-project support only. See references/legacy-bdd.md. |
| Testcontainers for SQL Server | Use SqlLocalDB — no Docker dependency. |
| bunit | Use WebApplicationFactory<T> + HTTP first. |
Before adding any third tool, write one sentence justifying it. If the sentence is "it would be more convenient", reject it.
| You want to test … | Use | Perf dimension it already measures (piggyback, §6a) |
|---|---|---|
| A pure business rule | MSTest unit test — direct instantiation, no framework | Method CPU, allocations, hot-path micro-regressions |
| A rule that needs HTTP / auth / DI / EF | MSTest + WebApplicationFactory<T> — in-process HTTP against the real middleware pipeline | End-to-end latency, middleware cost, per-request allocations, SQL count + text + shape (N+1 detector) |
| Real SQL Server behaviour (constraints, triggers, migrations) | MSTest + SqlLocalDB — sqllocaldb create + a connection string, no Docker | Query duration, logical reads, CPU time, actual plan, missing-index hints |
| A Razor / MVC page that renders mostly server-side | MSTest + WebApplicationFactory<T> + HttpClient — HTTP test, not a browser test | Same as integration-api |
| Genuine browser behaviour a server-side test can't express | MSTest + Playwright — keep the suite small, one or two happy-path tests per area | Navigation + resource timings, LCP/CLS/INP, long tasks, JS heap, network waterfall |
| A WPF / WinUI view-model rule | MSTest — instantiate the VM directly. Under MVVM this is 90% of desktop test coverage. | VM method CPU + allocations |
| A WPF / WinUI launch / binding / wiring check | MSTest + System.Windows.Automation — Process.Start(exe) + AutomationElement.FromHandle + InvokePattern / ValuePattern | Startup time, frame time, UI-thread stalls, managed allocs during interaction |
| A MAUI view-model rule | MSTest — direct VM instantiation | VM CPU + allocations |
| MAUI on the Windows target | MSTest + System.Windows.Automation — MAUI/Windows is a UIA tree | Same as WPF |
| MAUI on Android / iOS | Appium, isolated in Tests/UI.Mobile/ — the single intentional exception | — (mobile perf is its own discipline) |
| Concurrency, throughput ceiling, saturation, soak, memory growth under load | MSTest + Microsoft.Crank scenario in {Project}.Tests.Perf/ — the only place dedicated load/stress/soak lives. See §6a. | Thread-pool starvation, GC pressure under load, leak detection via bracketed gcdump |
Default rule of thumb: MSTest + System.Windows.Automation covers every .NET desktop scenario; MSTest + Playwright covers every web scenario. If you reach for a third tool, re-read §2.
When the user says:
Given a valid user, when they sign in, then they should see the dashboard
Write this:
// @under-test: src/Foo.Web/Pages/SignIn.razor
// @area: auth @layer: ui-web
[TestClass]
public class SignInTests : PageTest
{
[TestMethod]
[TestCategory("auth"), TestCategory("smoke")]
[Description("Valid user signs in and lands on dashboard")]
public async Task Valid_user_signs_in_and_lands_on_dashboard()
{
// Given: a valid user on the sign-in page
await Page.GotoAsync("/sign-in");
// When: they sign in with valid credentials
await Page.GetByLabel("Email").FillAsync("valid.user@example.com");
await Page.GetByLabel("Password").FillAsync(Environment.GetEnvironmentVariable("TEST_USER_PASSWORD")!);
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
// Then: the dashboard is visible
await Expect(Page).ToHaveURLAsync(new Regex("/dashboard$"));
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Dashboard" })).ToBeVisibleAsync();
}
}The Given/When/Then phrasing lives in test name + comments + [TestCategory] tags + [Description]. No .feature file, no bindings, no regex maintenance, no indirection. A reporter that lifts [Description] + [TestCategory] into Markdown covers every "but stakeholders need to read it" objection without introducing BDD tooling.
For Code ↔ UI mapping (web routes, desktop menu breadcrumbs), see the
solution-patternsskill. This section covers Code ↔ Test only.
This section is load-bearing. Renaming any convention here breaks every future locate-or-create query.
Default: one test project per product project — {Project}.Tests, sibling to {Project}. Why: a test project more complex than the app it tests is absurd; the whole point of §1.1 (minimum tool surface) applies to the test tree too. One csproj, one namespace root, one dotnet test command.
Split into multiple projects only when one of these is actually true:
{Project}.Tests (headless-safe) and {Project}.Tests.UI (requires a desktop session). Tag the UI csproj so CI can skip it in headless runs, e.g. <IsTestProject>true</IsTestProject> plus a [TestCategory("ui-interactive")] filter.net8.0 but one test layer needs net48 for COM interop.{Project}.Tests.Perf/ houses Microsoft.Crank scenarios and long-running soak tests. Its runtime constraints are genuinely different: long durations, non-parallel with the functional suite, its own CI job, statistical gating instead of pass/fail per run. See §6a.5. Do not put piggyback perf collectors here — those belong next to the functional tests whose coverage they inherit.Do not split because "separation of concerns" or "unit vs integration is cleaner" — those are solved by folder structure and [TestCategory] tags, not by csproj boundaries. Extra csprojs bring: duplicated [AssemblyInitialize] code, duplicated TestSandbox/TestHelpers, multiple InternalsVisibleTo grants, separate Directory.Packages.props entries, and separate test hosts that all have to be kept in sync. Pay that cost only when a real constraint forces it.
Naming, whichever variant you pick: {Project}.Tests or {Project}.Tests.UI. One spelling forever. Never {Project}Tests, never Test.{Project}, never {Project}.UnitTests.
Folders inside the test project mirror folders inside the product project 1:1. Never flatten — reversibility is the whole point. The product sub-path becomes the test sub-path verbatim, with Tests appended to the class-file name.
Default (single {Project}.Tests project):
| Product file | Test file |
|---|---|
{Project}/{Sub}/{Name}.cs | {Project}.Tests/{Sub}/{Name}Tests.cs |
{Project}/Controllers/{Name}Controller.cs | {Project}.Tests/Controllers/{Name}ControllerTests.cs |
{Project}/ViewModels/{Name}ViewModel.cs | {Project}.Tests/ViewModels/{Name}ViewModelTests.cs |
{Project}/Views/{Name}View.xaml (WPF) | {Project}.Tests/Views/{Name}ViewTests.cs |
{Project}/Pages/{Name}.razor (or .cshtml) | {Project}.Tests/Pages/{Name}Tests.cs |
Split form (only when §5.1's split criteria apply): the UI-requiring tests move to {Project}.Tests.UI/ using the same sub-path mirror; everything else stays in {Project}.Tests/. No other splits.
Namespace rule: every test file uses the single root namespace {Project}.Tests (or {Project}.Tests.UI), regardless of which subfolder it lives in. Why: sub-namespacing per folder forces a cross-file using hunt every time a test helper moves, with no discoverability benefit — the folder is right there in the solution explorer. The @under-test header (§5.3) is the authoritative coverage link, not the namespace.
Locating a test file: given {Project}/Foo/Bar/Baz.cs, the test is at {Project}.Tests/Foo/Bar/BazTests.cs. No lookup, no search — pure string transform. That is the whole point of the mirror.
Every test file declares the product file it covers:
// @under-test: src/Foo.Bar/Services/OrderService.cs
// @area: orders @layer: unit @ticket: JIRA-1234Rules: @under-test is required, repo-relative, forward slashes, no globs. Multi-file tests list at most three paths comma-separated; more means the test is too broad — split it. @layer is one of unit | integration-api | integration-db | ui-web | ui-wpf | ui-maui | perf. Optional: @perf-capture: always forces piggyback collection even when QA_PERF_CAPTURE is unset — use only for known hot paths whose perf you always want a baseline for.
Why this matters: reverse coverage queries become one ripgrep (rg "@under-test:\s*src/Foo.Bar/Services/OrderService.cs" Tests/) instead of a semantic search.
Adding/modifying a test for product file P:
T from §5.2.T exists → edit it.rg "@under-test:.*P" across Tests/ — if a hit exists at a non-canonical path, edit it and leave // TODO: relocate to {T}.{Name}Tests). Same rule.T with the @under-test header.scripts/test_map.py automates the §5.2 mirror check and the §5.3 @under-test header lookup. Zero required arguments — auto-discovers the {Project} + {Project}.Tests pair from the current directory. Cross-platform (Windows, Linux, macOS — runs in containers, CI, Codex).
# Coverage gap report (default — run from repo root, no arguments needed)
python test_map.py
# Output: table of OK / MISSING / ORPHAN / WRONG_HEADER per product file
# Impact analysis: which tests cover my recent changes?
python test_map.py --affected HEAD~1
# Output: direct changes + transitive dependents (2-level type-name grep) + filter string
# JSON for AI consumption
python test_map.py --affected HEAD~3 --jsonThe --affected mode produces a dotnet test --filter string that includes every mirror-path test + every crosscutting test whose @under-test header mentions any affected file + TestCategory=critical. The AI supplements this list by using whatever reference-finding capability its IDE/platform provides — e.g. find_references in VS Code (Claude Code, GitHub Copilot), find_usages in JetBrains (Roo Code), or Go to References in Visual Studio — to chase shared interfaces/base classes where string-grep may miss semantic dependents. See references/test-selection.md for the detailed workflow.
Standard severity categories for filtering (§7 naming convention applies):
| Category | Meaning | When to run |
|---|---|---|
critical | Fast unit/service tests that catch the most important regressions. See criteria below. | Every build, every commit (~10 s) |
| (no tag) | Normal importance — the default. | PR check |
stress | Concurrency, soak, memory growth. Slow (>10 s per test). | Pre-release, nightly |
ui-interactive | Needs a desktop session (System.Windows.Automation). | Developer machine |
requires-elevation | Triggers UAC / needs admin. Locked-down VMs cannot run these. | Developer machine with admin rights |
projfs | Needs Client-ProjFS Windows feature. | Developer machine with ProjFS |
What to tag critical — a test earns this tag when ALL of these are true:
critical is a 5–10 second feedback loop.critical tests must run on any machine — headless CI, a locked-down VM, a container, a developer laptop — with zero external dependencies. If it needs the Client-ProjFS feature, a desktop session, admin elevation, or a specific folder on disk, tag it with the appropriate category instead.Why this matters: dotnet test --filter "TestCategory=critical" is the developer's 10-second sanity check between edits. If critical tests include slow ProjFS integration tests, the filter becomes useless and developers stop using it. If critical tests miss the config-persistence path, a developer ships a bug that loses every user's projection list. The tag is the contract between "this is cheap enough to run constantly" and "this catches the bugs that matter most."
dotnet test --filter "TestCategory=critical" # fast dev loop
dotnet test --filter "TestCategory!=stress&TestCategory!=ui-interactive&TestCategory!=requires-elevation" # PR / CI (headless, no admin)
dotnet test --filter "TestCategory!=requires-elevation" # developer machine (has desktop, no admin)
dotnet test # release gate (full, interactive, admin)Four examples, one per major layer. Each is runnable; no pseudo-code.
// @under-test: src/Foo.Bar/Services/OrderTotals.cs
// @area: orders @layer: unit
[TestClass]
public class OrderTotalsTests
{
[TestMethod]
public void Total_sums_line_items_with_tax()
{
var items = new[] { new LineItem(10m), new LineItem(20m) };
Assert.AreEqual(33m, new OrderTotals(taxRate: 0.10m).Compute(items));
}
}WebApplicationFactory<Program> needs the product's Program.cs to expose Program as a type. In a top-level-statements project add this at the bottom of Program.cs:
public partial class Program { }Then the test (needs using System.Net.Http.Headers;, using System.Text;, using Microsoft.AspNetCore.Mvc.Testing;):
// @under-test: src/Foo.Web/Controllers/OrdersController.cs
// @area: orders @layer: integration-api
[TestClass]
public class OrdersApiTests
{
private static WebApplicationFactory<Program> _factory = null!;
private HttpClient _client = null!;
[ClassInitialize]
public static void Setup(TestContext _) => _factory = new WebApplicationFactory<Program>();
[ClassCleanup]
public static void Teardown() => _factory.Dispose();
[TestInitialize]
public void SetupClient() => _client = _factory.CreateClient();
[TestMethod, TestCategory("orders"), TestCategory("smoke")]
public async Task Get_orders_returns_empty_array_for_new_user()
{
var creds = Convert.ToBase64String(
Encoding.UTF8.GetBytes("new.user@example.com:" + Environment.GetEnvironmentVariable("TEST_USER_PASSWORD")));
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var response = await _client.GetAsync("/api/orders");
response.EnsureSuccessStatusCode();
Assert.AreEqual("[]", await response.Content.ReadAsStringAsync());
}
}Any test that was reaching for Playwright just to assert "page loads, contains X" belongs here instead. 10× faster, no browser dependency.
// @under-test: src/Foo.Web/Pages/SignIn.razor
// @area: auth @layer: ui-web
[TestClass]
public class SignInSmokeTests : PageTest
{
[TestMethod, TestCategory("auth"), TestCategory("smoke")]
public async Task Valid_user_signs_in_and_lands_on_dashboard()
{
await Page.GotoAsync("/sign-in");
await Page.GetByLabel("Email").FillAsync("valid.user@example.com");
await Page.GetByLabel("Password").FillAsync(Environment.GetEnvironmentVariable("TEST_USER_PASSWORD")!);
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
await Expect(Page).ToHaveURLAsync(new Regex("/dashboard$"));
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Dashboard" })).ToBeVisibleAsync();
}
}System.Windows.Automation (in-box)Every interactive control gets AutomationProperties.AutomationId in the format Screen.Element. No AutomationId → fix the XAML first, don't write the test.
<Button AutomationProperties.AutomationId="SignIn.Submit" Content="Sign in" />
<TextBox AutomationProperties.AutomationId="SignIn.Email" />
<PasswordBox AutomationProperties.AutomationId="SignIn.Password" />// @under-test: src/Foo.Desktop/Views/SignInView.xaml
// @area: auth @layer: ui-wpf
using System.Windows.Automation;
[TestClass]
public class SignInSmokeTests
{
private Process _proc = null!;
private AutomationElement _window = null!;
[TestInitialize]
public void Launch()
{
_proc = Process.Start(@"bin\Release\MyApp.exe")!;
_proc.WaitForInputIdle();
_window = WaitFor(() => AutomationElement.FromHandle(_proc.MainWindowHandle), TimeSpan.FromSeconds(5));
}
[TestCleanup] public void Close() { if (!_proc.HasExited) _proc.Kill(); _proc.Dispose(); }
[TestMethod, TestCategory("desktop"), TestCategory("smoke")]
public void Valid_user_signs_in()
{
ById("SignIn.Email").SetValue("valid.user@example.com");
ById("SignIn.Password").SetValue("***");
ById("SignIn.Submit").Invoke();
var heading = WaitFor(() => ById("Dashboard.Title"), TimeSpan.FromSeconds(5));
Assert.AreEqual("Dashboard", heading.Name);
}
// Minimal intent-level wrappers so test bodies don't see UIA pattern ceremony.
private Ui ById(string id) => new(_window.FindFirst(TreeScope.Descendants,
new PropertyCondition(AutomationElement.AutomationIdProperty, id))
?? throw new InvalidOperationException($"AutomationId not found: {id}"));
private readonly record struct Ui(AutomationElement Element)
{
public string Name => (string)Element.GetCurrentPropertyValue(AutomationElement.NameProperty);
public void Invoke() => ((InvokePattern)Element.GetCurrentPattern(InvokePattern.Pattern)).Invoke();
public void SetValue(string t) => ((ValuePattern)Element.GetCurrentPattern(ValuePattern.Pattern)).SetValue(t);
}
// The ONLY place polling lives. Test bodies never see a sleep.
private static T WaitFor<T>(Func<T?> probe, TimeSpan timeout) where T : class
{
var deadline = DateTime.UtcNow + timeout;
while (DateTime.UtcNow < deadline)
{
var r = probe();
if (r != null) return r;
Thread.Sleep(50);
}
throw new TimeoutException("UIA element not found within " + timeout);
}
}Zero NuGet beyond MSTest. Zero driver processes. Zero HTTP hops. using System.Windows.Automation; is the entire import. If the 50 ms poll ever proves insufficient, upgrade to Automation.AddAutomationEventHandler(...) (also in-box) for true event-based waits.
The 2-in-1 rule. Every functional test in §6 already exercises a code path end-to-end. When the env var QA_PERF_CAPTURE=1 is set (default in the perf CI job, off locally), the same test run also emits a perf/ folder of in-box telemetry. Functional coverage becomes performance coverage — if a path has a test, it has perf data. No parallel perf suite, no duplicated scenarios, no "we forgot to perf-test the refund flow" gaps.
Piggyback records, load suite gates. Per-test perf data is never asserted against absolute thresholds — that flakes on slow CI agents and encodes machine speed into your suite. Regressions are surfaced by diffing perf artifacts across runs (baseline vs. PR). Only the dedicated load suite ({Project}.Tests.Perf/) gates pass/fail, and it gates on statistical baselines across many samples — not single-run numbers.
| Layer | Collector started in [AssemblyInitialize] | Emits into perf/ |
|---|---|---|
unit | In-proc EventPipe session: Microsoft-DotNETCore-SampleProfiler + Microsoft-Windows-DotNETRuntime (GC + allocations + contention) | trace.nettrace, counters.json |
integration-api | EventPipe (as above) + EF Core DbCommandInterceptor logging every SQL text + duration + row count + OpenTelemetry with file exporter for spans | trace.nettrace, ef-queries.ndjson, otel.ndjson |
integration-db | SQL Server Extended Events session (sql_statement_completed, query_post_execution_showplan) started per [TestInitialize], stopped per [TestCleanup] + Query Store delta snapshot | sql.xel, query-store-delta.json |
ui-web | context.Tracing.StartAsync(screenshots: true, snapshots: true) + page.evaluate("performance.getEntries()") + CDP Performance.getMetrics + HAR export | playwright-trace.zip, perf-entries.json, network.har |
ui-wpf / ui-winui / MAUI-Windows | wpr.exe -start GeneralProfile -start CPU around the test window + in-proc EventPipe for managed allocs | wpr.etl, trace.nettrace |
perf (dedicated load) | Microsoft.Crank scenario + bracketed dotnet-gcdump (start + end) + dotnet-counters time series throughout the run | crank.json, gcdump-start.gcdump, gcdump-end.gcdump, counters.json, trace.nettrace |
EventPipe is the in-box .NET diagnostic pipe — Microsoft.Diagnostics.NETCore.Client drives it from test infrastructure so no external dotnet-trace process is required. ETW (wpr.exe) is the Windows equivalent for UI-thread and render work. None of this adds a NuGet dependency beyond Microsoft.Diagnostics.NETCore.Client.
Every test — piggyback or dedicated — leaves behind the same folder shape so one parser in an AI agent handles all of them:
TestResults/{run}/{FullTestName}/perf/
├── manifest.json # test name, @under-test, @layer, git sha, wall-clock start/stop, machine
├── trace.nettrace # EventPipe: CPU samples + GC + allocs + contention
├── counters.json # dotnet-counters delta across the test window
├── sql.xel # integration-* only: XEvent capture for this test's window
├── ef-queries.ndjson # integration-api only: one row per SQL, with duration + rows
├── otel.ndjson # integration-api only: OTLP spans incl. EF Core spans
└── ui/ # ui-* only
├── playwright-trace.zip
├── perf-entries.json
└── network.harThe dedicated load suite ({Project}.Tests.Perf/) adds load/crank.json and the bracketing gcdump-*.gcdump files to the same shape. Why identical shape: "show me the regression" is the same query whether the data came from a 5 ms unit test or a 10-minute soak run — one diff algorithm, one code path in the AI agent.
// TestInfrastructure/PerfCapture.cs — lives in {Project}.Tests/TestInfrastructure/
// Opt-in via env var so the local TDD loop stays fast.
[TestClass]
public static class PerfCapture
{
private static EventPipeSession? _session;
public static bool Enabled => Environment.GetEnvironmentVariable("QA_PERF_CAPTURE") == "1";
[AssemblyInitialize]
public static void Start(TestContext ctx)
{
if (!Enabled) return;
var client = new DiagnosticsClient(Process.GetCurrentProcess().Id);
var providers = new[]
{
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Verbose, 0x1 | 0x80 | 0x4000080018),
};
_session = client.StartEventPipeSession(providers, requestRundown: true);
_ = Task.Run(() => _session.EventStream.CopyTo(File.Create(PerfPaths.TraceFile)));
}
[AssemblyCleanup]
public static void Stop() { _session?.Stop(); _session?.Dispose(); }
}That's the whole piggyback machinery for unit and integration-api. Layer-specific collectors (XEvent session, Playwright tracing, wpr) live in small siblings (SqlXEventCapture.cs, PlaywrightPerfCapture.cs, WprCapture.cs) and are activated by the same env var.
// @under-test: src/Foo.Web/Controllers/OrdersController.cs
// @area: orders @layer: integration-api
[TestMethod, TestCategory("orders")]
public async Task Get_orders_returns_user_orders_with_customer_name()
{
var response = await _client.GetAsync("/api/orders?userId=42");
response.EnsureSuccessStatusCode();
// Functional assertion — the only one that gates the test.
Assert.IsTrue((await response.Content.ReadAsStringAsync()).Contains("Jane Doe"));
}With QA_PERF_CAPTURE=1, this run also emits perf/ef-queries.ndjson. If the endpoint regressed from 1 SQL statement into a per-row SELECT Customer WHERE Id = @p loop, the file contains 51 rows instead of 1. The AI agent diffs the file against the baseline, spots the N+1 immediately, and maps it back via the @under-test header to OrdersController.cs. No perf assertion, no flake, no duplicated scenario.
{Project}.Tests.Perf/ suiteThis is the fourth legitimate §5.1 split. It houses only scenarios that functional tests cannot express:
WebApplicationFactory-hosted app or a deployed env. Tool: Microsoft.Crank (github.com/dotnet/crank — the benchmarking infrastructure used by the .NET team itself for the TechEmpower runs). Emits crank.json with p50/p95/p99, RPS, errors.dotnet-gcdump before/after. Diffing the two gcdumps surfaces retained types, counts, and GC roots — the "undisposed objects pinned to root" detector, built from in-box tools only.@perf-critical methods: BenchmarkDotNet jobs, runnable under the same MSTest runner via an [TestMethod] that shells out to BDN and asserts against its statistical baseline.The suite gates on statistical baselines across samples (e.g. p95 latency must not regress by >10% with p<0.05), never on single-run numbers.
Explicitly, because the question comes up often:
integration-api and integration-db piggyback run via the EF interceptor + XEvent session. Regressions are attributed to a single test name, which maps to a single @under-test file. This catches 95% of database perf issues.{Project}.Tests.Perf/ load suite, which drives the same endpoints under Crank and captures wait stats from sys.dm_os_wait_stats deltas.You do not need a separate "database performance test" layer. The piggyback collector on your integration tests already is one.
Web: role + accessible name → label → text → data-testid → CSS → XPath (last resort). Desktop: AutomationProperties.AutomationId is mandatory on every interactive control, format Screen.Element.
Allowed: Playwright auto-waits (Expect(...).ToBeVisibleAsync()), Playwright WaitForURL / WaitForLoadState, a single named WaitFor<T> polling helper in test infrastructure, and Automation.AddAutomationEventHandler. Banned in test bodies: Thread.Sleep, Task.Delay, page.waitForTimeout. Why: sleeps hide race conditions and slow every run for everyone.
Test methods: Subject_does_thing_under_condition. Tags lowercase (smoke, auth, critical). Every test has an area tag plus a severity tag. Secrets via env vars, never committed. Generated per-test data or per-test fixtures — never hard-coded shared records that mutate.
Helper = function with intent (SignInAs(user)). Screen = small class for one window/widget, no inheritance. Create a screen only when 3+ tests share 5+ interactions on the same surface. Inline anything used once.
One logical assertion per test. Assert on user-visible state, not implementation details. Prefer Playwright web-first assertions (auto-retry).
CI: retries: 1 for UI suites. Local: retries: 0 — force fixing flakes. A test needing >1 retry is a defect, not a setting to tune.
Runs from a clean checkout with one command: dotnet test. No machine-specific paths. No driver installs (UIA is in-box; Playwright installs its own browsers via pwsh bin/Debug/net8.0/playwright.ps1 install on first build). Parallel-safe by default.
Piggyback collection (§6a) is off by default locally (fast TDD loop), on by default in the perf CI job via QA_PERF_CAPTURE=1. Per-test perf data never fails a functional test — no Assert.Less(elapsed, 100ms), no allocation-count assertions, no "expected <N SQL queries" guards inside functional test bodies. Regressions are detected by **diffing perf/ artifacts against the baseline run of the same test** (same @under-test, same @layer) and raised as review comments or CI annotations. The only place statistical gating lives is the dedicated {Project}.Tests.Perf/ load suite, which gates on p95/p99 deltas across many samples (e.g. p95 must not regress by >10% with p<0.05), never on single-run numbers. Retain the last N perf/ folders plus anything flagged as a regression; everything else is disposable.
Open the trace. 90% of the time: missing/incorrect wait, unstable selector, or shared state. Fix the root cause. Never add Sleep. Never raise the retry count.
Reproduce as the smallest possible test at the lowest possible layer (often API, not UI). Commit the failing test first, then the fix.
Detect project shape first (web / WPF / WinUI / MAUI / API / mixed), then pick from §3.
Default to code-first. Convert any Given/When/Then into executable code; preserve phrasing in name/comments/[TestCategory].
Never introduce Appium or WinAppDriver to a new project. Windows desktop uses System.Windows.Automation. Appium is allowed only for existing MAUI Android/iOS suites.
Never add a second test runner if MSTest is already present. Reject "just this once" arguments.
Never add Sleep in a test body. Polling lives inside one named WaitFor helper only.
Prefer the lowest layer that gives confidence: unit → API → UI. View-models before launching an exe.
Inspect the product for stable selectors / AutomationIds before writing the test.
Keep examples runnable. No pseudo-code.
For perf investigations, read piggyback artifacts first. Open perf/ef-queries.ndjson, perf/trace.nettrace, perf/sql.xel, or perf/perf-entries.json of the specific failing / regressed test before reaching for the dedicated load suite. The per-test folder already attributes the regression to one test name and one @under-test file — that is the cheapest possible diagnosis. Only escalate to the load suite for concurrency, saturation, or soak questions the functional tests cannot express.
Never add a perf assertion that fails a functional test. No Assert.Less(elapsed, ...), no SQL-count asserts in functional bodies, no allocation-count asserts. Perf signals flow through diff-against-baseline, not pass/fail. Adding a perf assertion to a functional test is how perf suites become flaky and get disabled.
Cite the artifact before proposing a perf fix. Every perf recommendation must quote the specific perf/ file and the row/stack/span/statement that proves the diagnosis — e.g. "ef-queries.ndjson shows 51 SELECT Customer rows where the baseline shows 1" or "trace.nettrace shows 83% of CPU in JsonSerializer.Deserialize<T> via OrdersController.Get". No artifact → no fix.
Before running tests during development, scope the run. Run python test_map.py --affected HEAD~1 --json (or the range matching your work). Read the JSON output. For shared types/interfaces/base classes, use your platform's reference-finding tool to identify transitive dependents the string-grep missed — find_references in VS Code, find_usages in JetBrains, Go to References in Visual Studio, or grep / rg as a fallback when no language server is available. Union the result with TestCategory=critical. Run only that subset. Run the full suite (dotnet test with no filter) only when the solution is complete.
Tests that require admin elevation must be safe by default. When writing a test that triggers UAC (e.g. Verb = "runas", Enable-WindowsOptionalFeature, any admin PowerShell), apply all three of these — missing any one breaks the default run:
[TestCategory("requires-elevation")].Assert.Inconclusive when the var is absent. Pattern:private static void RequireElevationOptIn()
{
if (Environment.GetEnvironmentVariable("QA_ALLOW_ELEVATION") != "1")
Assert.Inconclusive("Skipped: requires elevation. Set QA_ALLOW_ELEVATION=1 to opt in.");
}RequireElevationOptIn() as the first line of every elevation test.--filter "TestCategory!=requires-elevation" exclude them at the runner level. The guard is the safety net — even if someone runs dotnet test with no filter, the test self-skips instead of popping a UAC dialog that hangs CI or surprises a developer. A test that blocks a default dotnet test run is worse than a missing test.For inherited SpecFlow/Reqnroll projects or any migration (Appium → UIA, Selenium → Playwright, NUnit/xUnit → MSTest, page-objects → screens), load references/legacy-bdd.md or references/migrations.md — they exist so this file stays focused on the 95% case.
System.Windows.Automation covers the same tests with zero external processes.WebApplicationFactory<T> + HttpClient would prove the same thing 10× faster..feature files and step definitions.AutomationId exists.Thread.Sleep / waitForTimeout / Task.Delay in test bodies.SqlLocalDB already works..Tests.Unit + .Tests.Integration + .Tests.UI sibling projects when one {Project}.Tests would cover everything. Split only when headless CI, scale, or a different target framework actually requires it.Tests.Services.*, Tests.Provider.*). The folder is visible in the explorer and the @under-test header is the authoritative link — sub-namespaces just make every test helper move into a cross-file using hunt.Assert.Less(elapsed, 100ms), Assert.AreEqual(1, sqlCount) inside a functional body). They encode machine speed, they flake on slow CI agents, and the standard fix — raising the threshold — hides real regressions. Use diff-against-baseline instead.{Project}.Tests.Perf/ exists only for concurrency, saturation, and soak.Microsoft.Crank is the Microsoft-owned, JSON-emitting, .NET-team-maintained alternative. JMeter remains supported only for inherited bw-jmeter suites.Verb = "runas", Enable-WindowsOptionalFeature, admin PowerShell) without being tagged requires-elevation. A developer on a locked-down VM or a headless CI agent cannot approve a UAC prompt; the test hangs or crashes the run. Tag them and exclude from CI via TestCategory!=requires-elevation.WebApplicationFactory<T>: https://learn.microsoft.com/aspnet/core/test/integration-testsdotnet-trace: https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-tracedotnet-gcdump: https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-gcdumpdotnet-counters: https://learn.microsoft.com/dotnet/core/diagnostics/dotnet-countersMicrosoft.Diagnostics.NETCore.Client: https://learn.microsoft.com/dotnet/core/diagnostics/diagnostics-client-librarywpr.exe): https://learn.microsoft.com/windows-hardware/test/wpt/windows-performance-recorderMicrosoft.Crank: https://github.com/dotnet/crankLoad these only when the user's request is about that specific topic — each is relevant maybe 1 call in 20.
references/onboarding-qa.mdreferences/setup-environment.mdreferences/legacy-bdd.mdreferences/migrations.mdreferences/performance.mdscripts/ensure_prereqs.py — python ensure_prereqs.py (all), python ensure_prereqs.py playwright, python ensure_prereqs.py jmeter, python ensure_prereqs.py perfscripts/restore_playwright_demo.pyreferences/. Why: long skill files increase cognitive load for every single call.@under-test header without updating migration notes.src/Foo.Web/Pages/SignIn.razor" and confirm the path you produce matches §5.2.{Project}.Tests.Perf/ — they belong next to the functional tests whose coverage they inherit. {Project}.Tests.Perf/ is for concurrency, saturation, and soak only.rg "@under-test:" {Project}.Tests/ returns a complete coverage map.Tests/UI.Mobile/), no new SpecFlow, no NUnit, no xUnit.{Project}.Tests by default; a second {Project}.Tests.UI only if headless CI or another §5.1 constraint forces it.Sleep or retry-count bumps.perf CI job (QA_PERF_CAPTURE=1) produces a perf/ folder next to every functional test — unit, integration-api, integration-db, ui-web, ui-wpf — and the dedicated {Project}.Tests.Perf/ suite exists only for concurrency, saturation, and soak. Perf regressions are reported as diff-against-baseline, never as failing functional assertions.bw-jmeter© JocysCom, GPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 10 other files (scripts, references) in .ai/skills/qa-tester of JocysCom/FocusLogger.
Open the folder on GitHubat commit b32c096
QA Tester next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| QA Tester this skillJocysCom/FocusLogger | 213 | — | ~12k | Automated safety check: Pass | GPL-3.0 | |
| Windows QA EngineerCodeAlive-AI/ai-driven-development | 157 | — | ~1.4k | Automated safety check: Pass | MIT | |
| Playwright Rollmicrosoft/playwright-dotnet | 3k | — | ~1.9k | Automated safety check: Pass | MIT | |
| AspireSSWConsulting/SSW.VerticalSliceArchitecture | 407 | 1 repos | ~2k | Automated safety check: Pass | MIT | |
| Agentic Browser Testingpetrkindlmann/qa-skills | 165 | — | ~4.5k | Automated safety check: Pass | MIT | |
| Playwright E2E Authoringfmflurry/settings-opencode | 171 | — | ~2.4k | Automated safety check: Pass | MIT |
CodeAlive-AI/ai-driven-development
A skill your agent uses when testing Windows 11 desktop apps (WinForms/WPF/UWP) via UFO UIA/Win32 automation MCP.
microsoft/playwright-dotnet
Roll Playwright .NET to a new version
SSWConsulting/SSW.VerticalSliceArchitecture
A skill your agent uses when the user is working with an Aspire distributed application and needs to operate the AppHost or its resources through the Aspire CLI: start, restart, stop, or wait on the…
petrkindlmann/qa-skills
Goal-driven E2E testing where a browser agent (Playwright MCP / computer-use) reads a natural-language goal and explores the app via the accessibility tree to assert outcomes — no pre-written script.
fmflurry/settings-opencode
Scaffold and extend Playwright E2E tests for the gc.platform suite (tests/playwright), wiring every artifact to the real frontend (localhost:4200) + real .NET backend — never mocks.
dotnet/maui
Builds and packs .NET MAUI, installs the local workloads and runs integration tests for templates, samples and end-to-end scenarios by category.
JocysCom/FocusLogger
Update, create, improve, and synchronise this repository's AI agent instructions and related assets (including skills).
JocysCom/FocusLogger
Establish and enforce deterministic path patterns across Code ↔ UI route/menu ↔ Test for any project.
JocysCom/FocusLogger
Rasterize specific mermaid blocks inside a markdown file into PNG images.
JocysCom/FocusLogger
Gather, improve, and curate repository documentation and wiki content.
JocysCom/FocusLogger
AI-assisted pull request review workflow for Azure DevOps Git repositories.
JocysCom/FocusLogger
Generate or refresh .ai/repository-analysis.instructions.md whenever the user needs a repository-wide map of architecture, projects, technology stack, developer workflows, CI/testing, documentation…
Works with
Categories
Create and maintain automated tests in Microsoft-native/.NET projects with a minimal stack — MSTest runner, System.Windows.Automation for Windows desktop, Playwright for real browser smoke. QA Tester is an agent skill from JocysCom/FocusLogger.Automation for Windows desktop, Playwright for real browser smoke.
QA Tester fits situations like: the user wants to write; plan tests for any .NET project (unit; razor) — even when the request says test without naming a framework.
Run `npx skills add JocysCom/FocusLogger --skill qa-tester -a claude-code`. Or copy the skill folder (.ai/skills/qa-tester in JocysCom/FocusLogger) into .claude/skills/qa-tester in your project. Claude Code loads it when a task matches its description.
Run `npx skills add JocysCom/FocusLogger --skill qa-tester -a codex`. Or copy the skill folder (.ai/skills/qa-tester in JocysCom/FocusLogger) into .agents/skills/qa-tester in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add JocysCom/FocusLogger --skill qa-tester -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/qa-tester, .gemini/skills/qa-tester, .github/skills/qa-tester and .opencode/skills/qa-tester in your project.
Going by SKILL.md and its folder, QA Tester needs Python and TypeScript for the scripts in its folder, the command-line tools its instructions call (dotnet, python, rg, go and pwsh) and credentials named TEST_USER_PASSWORD. Our summary lists: Python 3; Node.js; Docker.
SKILL.md names 5 domains. As links in the text: learn.microsoft.com, playwright.dev, opentelemetry.io, github.com and benchmarkdotnet.org. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
QA Tester is published under the GPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 12k tokens (SKILL.md is roughly 49k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 4.8k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with QA Tester: Windows QA Engineer (CodeAlive-AI/ai-driven-development, 157 stars), Playwright Roll (microsoft/playwright-dotnet, 3k stars), Aspire (SSWConsulting/SSW.VerticalSliceArchitecture, 407 stars) and Agentic Browser Testing (petrkindlmann/qa-skills, 165 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
JocysCom (a GitHub organization) maintains it in JocysCom/FocusLogger, which has 213 GitHub stars. The repository holds 7 skills in this directory. The repository was last updated on June 30, 2026.
Source: JocysCom/FocusLogger on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.