<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Gomes</title>
    <description>The latest articles on DEV Community by Kevin Gomes (@kevingomes17).</description>
    <link>https://dev.to/kevingomes17</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3872863%2F01638033-a487-40ac-a46b-e0949c9891b2.jpg</url>
      <title>DEV Community: Kevin Gomes</title>
      <link>https://dev.to/kevingomes17</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevingomes17"/>
    <language>en</language>
    <item>
      <title>Test Automation (Playwright + Claude + GitHub Actions + GitHub Pages)</title>
      <dc:creator>Kevin Gomes</dc:creator>
      <pubDate>Sat, 11 Apr 2026 04:28:21 +0000</pubDate>
      <link>https://dev.to/kevingomes17/test-automation-playwright-claude-github-actions-github-pages-4g5i</link>
      <guid>https://dev.to/kevingomes17/test-automation-playwright-claude-github-actions-github-pages-4g5i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📊 &lt;strong&gt;Live Allure test report:&lt;/strong&gt; &lt;a href="https://kevingomes17.github.io/dashboard-and-playwright/" rel="noopener noreferrer"&gt;https://kevingomes17.github.io/dashboard-and-playwright/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Republished from CI on every push to &lt;code&gt;main&lt;/code&gt;. Each run appends to a 30-run history kept in the &lt;code&gt;gh-pages&lt;/code&gt; branch — pass-rate trends, duration trends, and per-test history arrows accumulate over time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;A working example of an end-to-end testing pipeline that pairs a real React + Vite dashboard with a Playwright suite generated by Claude Code, executed in GitHub Actions, and reported via &lt;strong&gt;Allure&lt;/strong&gt; with &lt;strong&gt;trend history published to GitHub Pages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The system-under-test is a microservices observability dashboard (&lt;code&gt;base-dashboard/&lt;/code&gt;) — five widgets fed by a deterministic mock data layer. The interesting half of the project is how it gets tested: &lt;strong&gt;how the tests are authored, how they run in CI, and how the report becomes a long-lived public artifact instead of a single-run zip file you have to download to read.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two sibling projects in one repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dashboard-and-playwright/
├── base-dashboard/      → React 19 + Vite + Tailwind dashboard (system-under-test)
├── playwright/          → Standalone Playwright e2e project
└── .github/workflows/   → GitHub Actions: test → generate Allure → publish to Pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architectural Goals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-assisted test authoring.&lt;/strong&gt; Use Claude Code with the &lt;code&gt;playwright-cli&lt;/code&gt; skill to generate &lt;code&gt;*.spec.ts&lt;/code&gt; files from natural-language flow descriptions, eliminating the boilerplate cycle of "open browser, find selector, copy-paste, write assertion".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic fixtures, not stub servers.&lt;/strong&gt; The dashboard's mock data layer uses a seeded RNG so charts and traces produce identical values every render. No network mocking, no test-only &lt;code&gt;data-testid&lt;/code&gt; attributes — tests use real text and ARIA roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two complementary reporters, not one.&lt;/strong&gt; Keep Playwright's built-in HTML reporter (the only place to access the interactive trace viewer) &lt;strong&gt;and&lt;/strong&gt; add Allure for severity/feature/owner grouping, behavior-driven navigation, and trend graphs across runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History as a first-class artifact.&lt;/strong&gt; Most CI pipelines treat each run as standalone — you download a zip, unzip it, look at it, throw it away. Publishing Allure to GitHub Pages keeps history accumulating in the &lt;code&gt;gh-pages&lt;/code&gt; branch so flakiness and regressions show up as trends, not point-in-time snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero authentication friction.&lt;/strong&gt; The published report is just a static site on Pages — anyone with the URL can browse pass-rate trends, severity grouping, and per-test history without logging into GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────┐
│                  1. Test Generation (local)                       │
│                                                                  │
│   Developer prompt ──→ Claude Code ──→ playwright-cli (browser)  │
│                                                                  │
│   "Verify the latency chart filters by service"                  │
│        │                                                         │
│        ▼                                                         │
│   playwright-cli open http://localhost:5173                      │
│   playwright-cli snapshot          (a11y tree → element refs)    │
│   playwright-cli click e42         (each action prints TS code)  │
│        │                                                         │
│        ▼                                                         │
│   playwright/tests/charts.spec.ts  (committed to git)            │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                  2. Test Execution                                │
│                                                                  │
│   Local                          GitHub Actions (Ubuntu)         │
│   ─────                          ────────────────────             │
│   npx playwright test            actions/checkout@v4              │
│   ↓                              actions/setup-node@v4            │
│   webServer auto-starts          actions/setup-java@v4 (Temurin)  │
│   `npm run dev` in               npm ci × 2                       │
│   ../base-dashboard              playwright install --with-deps   │
│   ↓                              npx playwright test              │
│   8 tests, ~2.5s                 8 tests, retries: 2              │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                  3. Dual Reporting Layer                          │
│                                                                  │
│   Built-in HTML reporter         allure-playwright reporter      │
│   ──────────────────────         ──────────────────────────       │
│   playwright-report/             allure-results/                  │
│   ├─ index.html                  ├─ &amp;lt;uuid&amp;gt;-result.json × 8        │
│   ├─ trace.zip per failure       ├─ &amp;lt;uuid&amp;gt;-attachment.*           │
│   └─ Interactive trace viewer    └─ epic/feature/story/severity   │
│      (DOM snapshots, network,                                     │
│       action timeline)                                            │
└──────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────┐
│                  4. History Merge + Publish                       │
│                                                                  │
│   actions/checkout@v4 (gh-pages branch, continue-on-error)       │
│      ↓                                                            │
│   cp -R gh-pages/history/* playwright/allure-results/history/    │
│      ↓                                                            │
│   npx allure generate allure-results -o allure-history --clean   │
│      ↓                                                            │
│   peaceiris/actions-gh-pages@v4 (only on main)                   │
│      ↓                                                            │
│   gh-pages branch  ──→  https://kevingomes17.github.io/           │
│                                  dashboard-and-playwright/        │
└──────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Core Components
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. AI-Assisted Test Generation (Claude Code + playwright-cli)
&lt;/h3&gt;

&lt;p&gt;The repo ships with a &lt;code&gt;playwright-cli&lt;/code&gt; skill at &lt;code&gt;.claude/skills/playwright-cli/SKILL.md&lt;/code&gt; plus reference docs (&lt;code&gt;test-generation.md&lt;/code&gt;, &lt;code&gt;playwright-tests.md&lt;/code&gt;, &lt;code&gt;element-attributes.md&lt;/code&gt;, etc.). When Claude Code is started from the repo root, it auto-discovers the skill and can drive a real browser via the globally-installed &lt;code&gt;playwright-cli&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;The workflow is &lt;strong&gt;conversational, not boilerplate-driven&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The developer describes a flow in plain English: &lt;em&gt;"Generate a Playwright test that opens the dashboard, switches the latency chart to the &lt;code&gt;payments&lt;/code&gt; service, and verifies the chart re-renders. Save it to &lt;code&gt;playwright/tests/payments-filter.spec.ts&lt;/code&gt;."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Claude opens a browser (&lt;code&gt;playwright-cli open http://localhost:5173&lt;/code&gt;) and reads the accessibility tree (&lt;code&gt;playwright-cli snapshot&lt;/code&gt;), which lists every interactive element with stable refs (&lt;code&gt;e1&lt;/code&gt;, &lt;code&gt;e2&lt;/code&gt;, …).&lt;/li&gt;
&lt;li&gt;Each action Claude takes (&lt;code&gt;click e42&lt;/code&gt;, &lt;code&gt;fill e7 "payments"&lt;/code&gt;) prints the equivalent Playwright TypeScript: &lt;code&gt;await page.getByRole('combobox').click()&lt;/code&gt;. The skill is configured so Claude collects these lines.&lt;/li&gt;
&lt;li&gt;Claude stitches the lines into a &lt;code&gt;@playwright/test&lt;/code&gt; spec, applying this repo's conventions automatically:

&lt;ul&gt;
&lt;li&gt;Scope queries to &lt;code&gt;page.locator("main")&lt;/code&gt; (the sidebar nav has its own "Services"/"Traces" buttons that would otherwise collide).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;getByRole("combobox")&lt;/code&gt; for Base UI Select triggers and &lt;code&gt;getByRole("option", { name, exact: true })&lt;/code&gt; for items rendered into a portal.&lt;/li&gt;
&lt;li&gt;Apply Allure metadata via the local &lt;code&gt;tagTest({ feature, story, severity })&lt;/code&gt; helper (covered below).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Claude runs the new test once with &lt;code&gt;npx playwright test path/to/new.spec.ts&lt;/code&gt; to verify it passes before committing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result: a 10-line natural-language prompt produces a 30–60 line spec that follows the repo's conventions and works on the first run, because Claude validates against the live mock data while writing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Deterministic Mock Data (the secret to stable tests)
&lt;/h3&gt;

&lt;p&gt;The dashboard's data layer (&lt;code&gt;base-dashboard/apps/web/src/lib/metrics/mock-client.ts&lt;/code&gt;) is a typed &lt;code&gt;MetricsClient&lt;/code&gt; interface with a &lt;code&gt;MockMetricsClient&lt;/code&gt; implementation that uses a seeded &lt;code&gt;mulberry32&lt;/code&gt; RNG. &lt;strong&gt;The same input always produces the same chart values, error counts, and trace spans.&lt;/strong&gt; This is the foundation that makes everything else possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests use real visible text as locators (&lt;code&gt;getByText("Latency (p50 / p95 / p99)")&lt;/code&gt;) instead of &lt;code&gt;data-testid&lt;/code&gt; attributes added solely for testing.&lt;/li&gt;
&lt;li&gt;No request-mocking layer, no MSW config, no fixture files — the deterministic source IS the fixture.&lt;/li&gt;
&lt;li&gt;Allure trends are meaningful — pass-rate variance reflects real regressions, not flaky randomness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the project moves to a real backend, the &lt;code&gt;MetricsClient&lt;/code&gt; interface becomes the seam: drop in a &lt;code&gt;PrometheusMetricsClient&lt;/code&gt; for production, keep &lt;code&gt;MockMetricsClient&lt;/code&gt; for tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Local Test Execution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;playwright
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt;                    &lt;span class="c"&gt;# 8 tests, headless, ~2.5s&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--ui&lt;/span&gt;               &lt;span class="c"&gt;# interactive runner, time-travel&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--headed&lt;/span&gt;           &lt;span class="c"&gt;# watch the browser drive itself&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;playwright.config.ts&lt;/code&gt; has a &lt;code&gt;webServer&lt;/code&gt; block that auto-starts the Vite dev server in &lt;code&gt;../base-dashboard&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;webServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../base-dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;reuseExistingServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;reuseExistingServer: !process.env.CI&lt;/code&gt; means locally Playwright attaches to an already-running &lt;code&gt;npm run dev&lt;/code&gt;; in CI it always starts a fresh process. One config, both environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Allure Reporter Wiring
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;playwright/playwright.config.ts&lt;/code&gt; reporter array runs all reporters in parallel — none of them disable the others:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-results&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-results&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two npm packages are needed&lt;/strong&gt;, and the distinction trips a lot of people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allure-playwright&lt;/code&gt;&lt;/strong&gt; — the reporter plugin. Hooks into Playwright's reporter API and writes raw JSON results into &lt;code&gt;allure-results/&lt;/code&gt;. Without this, Playwright has no way to emit Allure-format output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allure-commandline&lt;/code&gt;&lt;/strong&gt; — a Node wrapper around the Java-based Allure CLI. Brings the &lt;code&gt;allure&lt;/code&gt; binary into &lt;code&gt;node_modules/.bin/&lt;/code&gt; so you can run &lt;code&gt;npm run allure:generate&lt;/code&gt;/&lt;code&gt;open&lt;/code&gt;/&lt;code&gt;serve&lt;/code&gt; locally without a system-wide install. Requires a JRE on &lt;code&gt;PATH&lt;/code&gt; (the bundled binary is just a launcher).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both go in &lt;code&gt;playwright/devDependencies&lt;/code&gt;. The &lt;code&gt;package.json&lt;/code&gt; exposes three scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"allure:generate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allure generate allure-results -o allure-report --clean"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"allure:open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;"allure open allure-report"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"allure:serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"allure serve allure-results"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;allure:serve&lt;/code&gt; is the most useful one locally — it generates a temp report from &lt;code&gt;allure-results/&lt;/code&gt; and opens a browser tab in one command.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test Annotations for Behavior-Driven Grouping
&lt;/h3&gt;

&lt;p&gt;The Allure report's killer organizational view is &lt;strong&gt;Behaviors&lt;/strong&gt; — tests grouped as &lt;code&gt;Epic → Feature → Story&lt;/code&gt;. To populate it, each test calls a tiny helper at the top of its body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright/tests/_allure.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;epic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;story&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-js-commons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blocker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trivial&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tagTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;story&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Severity&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;epic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dashboard-team&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;story&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The functions are imported from &lt;code&gt;allure-js-commons&lt;/code&gt; directly (&lt;code&gt;allure-playwright&lt;/code&gt;'s own &lt;code&gt;allure&lt;/code&gt; re-export is marked deprecated in current versions). The underscore prefix on &lt;code&gt;_allure.ts&lt;/code&gt; keeps it out of Playwright's &lt;code&gt;*.spec.ts&lt;/code&gt; testMatch.&lt;/p&gt;

&lt;p&gt;Per-test usage is one block at the top of the test body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latency chart filters by service&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;tagTest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Latency chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;story&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Service filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="c1"&gt;// ...test body unchanged&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: the Allure home page Behaviors tab shows &lt;code&gt;Dashboard → Latency chart → Service filter → latency chart filters by service&lt;/code&gt; with severity badges, and the Categories view groups failures by severity automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. CI/CD: GitHub Actions Pipeline
&lt;/h3&gt;

&lt;p&gt;The full workflow (&lt;code&gt;.github/workflows/playwright.yml&lt;/code&gt;) runs on every &lt;code&gt;push&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; and on every &lt;code&gt;pull_request&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Playwright tests&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;
          &lt;span class="na"&gt;cache-dependency-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;base-dashboard/package-lock.json&lt;/span&gt;
            &lt;span class="s"&gt;playwright/package-lock.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temurin&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;17"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install base-dashboard dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;base-dashboard&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Playwright dependencies&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Chromium&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright install --with-deps chromium&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Playwright tests&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two &lt;code&gt;npm ci&lt;/code&gt; steps&lt;/strong&gt; because &lt;code&gt;base-dashboard/&lt;/code&gt; and &lt;code&gt;playwright/&lt;/code&gt; are sibling projects, not workspace members. Both lockfiles are listed in &lt;code&gt;cache-dependency-path&lt;/code&gt; so the npm cache invalidates correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;actions/setup-java@v4&lt;/code&gt; with Temurin 17&lt;/strong&gt; — required by the Allure CLI which is a JVM application. The npm-installed &lt;code&gt;allure-commandline&lt;/code&gt; is just a launcher; it needs a real JRE on &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;permissions: contents: write&lt;/code&gt;&lt;/strong&gt; at the job level — required so the default &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; can later push to the &lt;code&gt;gh-pages&lt;/code&gt; branch. Without this the deploy step will silently fail with a 403.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No browser matrix&lt;/strong&gt; — Chromium only. ~70% of users, fastest iteration. Easy to extend with a &lt;code&gt;projects&lt;/code&gt; array later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. History Merging &amp;amp; Allure Report Generation
&lt;/h3&gt;

&lt;p&gt;This is the part that makes Allure trends work. Allure history isn't magic — it's a &lt;code&gt;history/&lt;/code&gt; subfolder containing JSON files (&lt;code&gt;history.json&lt;/code&gt;, &lt;code&gt;history-trend.json&lt;/code&gt;, &lt;code&gt;categories-trend.json&lt;/code&gt;, etc.). To get trends across runs, &lt;strong&gt;the previous run's &lt;code&gt;history/&lt;/code&gt; folder has to be copied into the current run's &lt;code&gt;allure-results/&lt;/code&gt; before &lt;code&gt;allure generate&lt;/code&gt; runs&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get Allure history from gh-pages&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;
        &lt;span class="na"&gt;continue-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge previous Allure history into new results&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p playwright/allure-results/history&lt;/span&gt;
          &lt;span class="s"&gt;if [ -d gh-pages/history ]; then&lt;/span&gt;
            &lt;span class="s"&gt;cp -R gh-pages/history/* playwright/allure-results/history/ || true&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Allure report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx allure generate allure-results -o ../allure-history --clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes on what's going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;continue-on-error: true&lt;/code&gt;&lt;/strong&gt; on the gh-pages checkout — the very first workflow run doesn't have a &lt;code&gt;gh-pages&lt;/code&gt; branch yet, so the checkout 404s. The flag turns the failure into a warning so the workflow proceeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;if [ -d ... ]&lt;/code&gt; guard&lt;/strong&gt; in the merge step makes the same first-run case a no-op instead of a failure on &lt;code&gt;cp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: ${{ !cancelled() }}&lt;/code&gt;&lt;/strong&gt; on every Allure step — this ensures the Allure report is generated and published even when tests &lt;strong&gt;fail&lt;/strong&gt;. Failed runs are exactly when you want the trend data the most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No third-party Docker action.&lt;/strong&gt; An earlier draft used &lt;code&gt;simple-elf/allure-report-action@v1.9&lt;/code&gt; which was tempting because it looked turnkey, but its Dockerfile pulls &lt;code&gt;openjdk:8-jre-alpine&lt;/code&gt; — an image Docker Hub deprecated and removed in 2023. The action is unmaintained. Replacing it with two plain shell steps eliminated the dependency, made the pipeline transparent, and removed a Docker pull from every CI run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Publishing to GitHub Pages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Allure report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-history&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Allure report to gh-pages&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() &amp;amp;&amp;amp; github.ref == 'refs/heads/main' }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaceiris/actions-gh-pages@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;publish_branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;
          &lt;span class="na"&gt;publish_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allure-history&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen for every run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;actions/upload-artifact@v4&lt;/code&gt;&lt;/strong&gt; publishes the report as the &lt;code&gt;allure-report&lt;/code&gt; artifact at the bottom of the run summary page. This works for &lt;strong&gt;every&lt;/strong&gt; run including PRs — reviewers can grab the artifact, unzip, and open &lt;code&gt;index.html&lt;/code&gt; in any browser without GitHub Pages access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;peaceiris/actions-gh-pages@v4&lt;/code&gt;&lt;/strong&gt; force-pushes &lt;code&gt;allure-history/&lt;/code&gt; to the &lt;code&gt;gh-pages&lt;/code&gt; branch — but &lt;strong&gt;only on &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt;. The &lt;code&gt;if&lt;/code&gt; guard &lt;code&gt;github.ref == 'refs/heads/main'&lt;/code&gt; keeps PR runs from polluting the live site. PRs see the artifact; merges to &lt;code&gt;main&lt;/code&gt; update Pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A successful &lt;code&gt;main&lt;/code&gt; run produces a fully-static Allure site at &lt;code&gt;https://&amp;lt;owner&amp;gt;.github.io/&amp;lt;repo&amp;gt;/&lt;/code&gt; within ~30–60 seconds of the workflow finishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Viewing the Live Report
&lt;/h3&gt;

&lt;p&gt;The first time you push the workflow there's a one-time GitHub repo setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Settings → Pages → Source&lt;/strong&gt; → "Deploy from a branch" → Branch &lt;code&gt;gh-pages&lt;/code&gt; → Folder &lt;code&gt;/ (root)&lt;/code&gt; → Save.&lt;/li&gt;
&lt;li&gt;After the first successful &lt;code&gt;main&lt;/code&gt; run creates the &lt;code&gt;gh-pages&lt;/code&gt; branch, the green banner at the top of the same Settings → Pages screen shows &lt;strong&gt;"Your site is live at …"&lt;/strong&gt; within ~1 minute.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For this project the live URL is &lt;strong&gt;&lt;a href="https://kevingomes17.github.io/dashboard-and-playwright/" rel="noopener noreferrer"&gt;https://kevingomes17.github.io/dashboard-and-playwright/&lt;/a&gt;&lt;/strong&gt;. Useful deep links once you're there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#suites&lt;/code&gt;&lt;/strong&gt; — file/describe-block view (matches the spec layout)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#behaviors&lt;/code&gt;&lt;/strong&gt; — epic → feature → story view (where the &lt;code&gt;tagTest&lt;/code&gt; annotations pay off)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#categories&lt;/code&gt;&lt;/strong&gt; — failures grouped by Allure category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#graph&lt;/code&gt;&lt;/strong&gt; — pass-rate trend, severity pie, duration chart (the killer feature; populated after the second &lt;code&gt;main&lt;/code&gt; run)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#timeline&lt;/code&gt;&lt;/strong&gt; — Gantt-style view of every test in the run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per-test detail pages show a &lt;strong&gt;history arrow&lt;/strong&gt; in the top-right that lists previous runs with status, duration, and direct links — the closest thing to "git blame for test results" any reporter offers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Developer describes a flow in natural language to Claude Code in the repo root.&lt;/li&gt;
&lt;li&gt;Claude opens a browser via &lt;code&gt;playwright-cli&lt;/code&gt;, reads the accessibility tree, drives the page, and emits Playwright TypeScript for each action.&lt;/li&gt;
&lt;li&gt;Claude stitches the actions into a &lt;code&gt;*.spec.ts&lt;/code&gt; file under &lt;code&gt;playwright/tests/&lt;/code&gt;, applies the local conventions (&lt;code&gt;tagTest&lt;/code&gt;, &lt;code&gt;page.locator("main")&lt;/code&gt;, role-based locators), and runs the test once to verify.&lt;/li&gt;
&lt;li&gt;Developer commits and pushes. GitHub Actions checks out the repo, installs Node + Java + Chromium, and runs &lt;code&gt;npx playwright test&lt;/code&gt; from &lt;code&gt;playwright/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Playwright's &lt;code&gt;webServer&lt;/code&gt; config auto-starts &lt;code&gt;npm run dev&lt;/code&gt; in &lt;code&gt;../base-dashboard&lt;/code&gt; and waits for &lt;code&gt;http://localhost:5173&lt;/code&gt;. Tests execute against the deterministic mock data.&lt;/li&gt;
&lt;li&gt;Two reporters write in parallel: the built-in HTML reporter into &lt;code&gt;playwright-report/&lt;/code&gt;, the Allure reporter into &lt;code&gt;allure-results/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The workflow checks out the &lt;code&gt;gh-pages&lt;/code&gt; branch into &lt;code&gt;gh-pages/&lt;/code&gt; (continue-on-error for the first run).&lt;/li&gt;
&lt;li&gt;A shell step copies &lt;code&gt;gh-pages/history/*&lt;/code&gt; into &lt;code&gt;playwright/allure-results/history/&lt;/code&gt; so the about-to-run &lt;code&gt;allure generate&lt;/code&gt; picks up the previous run's trend data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npx allure generate&lt;/code&gt; produces a fully-static &lt;code&gt;allure-history/&lt;/code&gt; site that includes both the new run and the merged history.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;actions/upload-artifact@v4&lt;/code&gt; uploads &lt;code&gt;allure-history/&lt;/code&gt; as the &lt;code&gt;allure-report&lt;/code&gt; artifact (every run).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;peaceiris/actions-gh-pages@v4&lt;/code&gt; force-pushes &lt;code&gt;allure-history/&lt;/code&gt; to the &lt;code&gt;gh-pages&lt;/code&gt; branch (only on &lt;code&gt;main&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;GitHub Pages picks up the new commit and refreshes the live site within ~30–60 seconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Libraries &amp;amp; Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@playwright/test&lt;/code&gt;&lt;/strong&gt; — End-to-end testing framework with built-in test runner, deterministic auto-waits, ARIA-aware locators, parallel workers, retries, and &lt;code&gt;webServer&lt;/code&gt; lifecycle management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allure-playwright&lt;/code&gt;&lt;/strong&gt; — Reporter plugin that hooks into Playwright's reporter API and writes raw JSON results plus attachments into &lt;code&gt;allure-results/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allure-commandline&lt;/code&gt;&lt;/strong&gt; — npm wrapper around the Java-based Allure CLI; brings the &lt;code&gt;allure&lt;/code&gt; binary into &lt;code&gt;node_modules/.bin/&lt;/code&gt; for local report generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;allure-js-commons&lt;/code&gt;&lt;/strong&gt; — The non-deprecated facade exporting &lt;code&gt;epic&lt;/code&gt;, &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;story&lt;/code&gt;, &lt;code&gt;severity&lt;/code&gt;, &lt;code&gt;owner&lt;/code&gt;, &lt;code&gt;link&lt;/code&gt;, etc. for in-test annotations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code + &lt;code&gt;playwright-cli&lt;/code&gt; skill&lt;/strong&gt; — AI-assisted test authoring. The skill at &lt;code&gt;.claude/skills/playwright-cli/SKILL.md&lt;/code&gt; lets Claude drive a browser, read the accessibility tree, and emit Playwright TypeScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; — CI runtime. Key actions: &lt;code&gt;actions/checkout@v4&lt;/code&gt;, &lt;code&gt;actions/setup-node@v4&lt;/code&gt;, &lt;code&gt;actions/setup-java@v4&lt;/code&gt;, &lt;code&gt;actions/upload-artifact@v4&lt;/code&gt;, &lt;code&gt;peaceiris/actions-gh-pages@v4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Pages&lt;/strong&gt; — Static hosting for the published Allure report. The &lt;code&gt;gh-pages&lt;/code&gt; branch is the publish source.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Highlights
&lt;/h2&gt;

&lt;p&gt;The full &lt;code&gt;playwright.config.ts&lt;/code&gt; driving everything above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5173&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fullyParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;forbidOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-results&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-playwright&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;resultsDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allure-results&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;only-on-failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;webServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm run dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../base-dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reuseExistingServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons &amp;amp; Trade-offs
&lt;/h2&gt;

&lt;p&gt;A few things I'd flag for anyone setting up the same pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't pick "Allure vs. built-in HTML" — run both.&lt;/strong&gt; Playwright's interactive trace viewer (DOM snapshots, network log, action timeline replay) is the single most valuable failure-debugging surface in any e2e framework, and Allure cannot replicate it. Allure's strengths (history, severity grouping, BDD navigation) are orthogonal. Stack the reporters in the array; they don't fight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism first, locators second.&lt;/strong&gt; I spent zero time managing test flakiness on this project, and the entire reason is the seeded RNG in the mock data layer. If your fixtures are random, even the best locator strategy in the world produces tests that are fragile by design. Make the source-of-truth deterministic before you write the first assertion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip Docker actions for Allure.&lt;/strong&gt; The community space has several "one-step Allure for GitHub Actions" Docker actions. Most of them pin to old, unmaintained base images that get yanked from Docker Hub eventually. Two shell steps (&lt;code&gt;cp&lt;/code&gt; + &lt;code&gt;npx allure generate&lt;/code&gt;) take 30 seconds to write, never break, and you can read what they do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allure's history needs a place to live.&lt;/strong&gt; If you only ever upload Allure as a per-run artifact, you're not getting the feature people install Allure for. Either publish to Pages (this project), to an S3 bucket, or to Allure TestOps. Without persistent history, most of the report's value is left on the table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;if: ${{ !cancelled() }}&lt;/code&gt; on every report step.&lt;/strong&gt; A failing test run is exactly when you want the report. Default &lt;code&gt;if: success()&lt;/code&gt; skips the Allure steps when tests fail, which is the opposite of what you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java is unavoidable for Allure.&lt;/strong&gt; The CLI is a JVM app. &lt;code&gt;allure-commandline&lt;/code&gt; doesn't change that — it's just a launcher. CI needs &lt;code&gt;actions/setup-java&lt;/code&gt;; dev machines need a JRE. There's no pure-Node Allure CLI as of 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This project is a small, complete reference for what a modern Playwright + Allure pipeline looks like in 2026 — natural-language test authoring with Claude Code, deterministic mock data that makes locator strategies stable, two reporters running in parallel (the built-in HTML for trace viewing, Allure for grouping and trends), and a published GitHub Pages site that accumulates history across every push to &lt;code&gt;main&lt;/code&gt;. The interesting bits aren't the framework choices — they're the seams: where determinism comes from, why two reporters are better than one, how history actually gets merged across CI runs, and which third-party actions to avoid. The live report at &lt;a href="https://kevingomes17.github.io/dashboard-and-playwright/" rel="noopener noreferrer"&gt;https://kevingomes17.github.io/dashboard-and-playwright/&lt;/a&gt; is the same site you'd build for your own project by following this pattern — clone the workflow, adjust the test mappings, and you've got a public test dashboard with no infrastructure to maintain beyond a &lt;code&gt;gh-pages&lt;/code&gt; branch.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>cicd</category>
      <category>claude</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
