<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Github-Actions on Özkan Pakdil Software Engineer</title>
    <link>https://ozkanpakdil.github.io/tags/github-actions/</link>
    <description>Recent content in Github-Actions on Özkan Pakdil Software Engineer</description>
    <image>
      <title>Özkan Pakdil Software Engineer</title>
      <url>https://ozkanpakdil.github.io/images/papermod-cover.png</url>
      <link>https://ozkanpakdil.github.io/images/papermod-cover.png</link>
    </image>
    <generator>Hugo -- 0.148.2</generator>
    <language>en</language>
    <copyright>Özkan Pakdil</copyright>
    <lastBuildDate>Sun, 30 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ozkanpakdil.github.io/tags/github-actions/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Playwright Resource Monitor: making CI fail when your browser tabs burn CPU</title>
      <link>https://ozkanpakdil.github.io/posts/my_collections/2026/2026-08-30-playwright-resource-monitor/</link>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://ozkanpakdil.github.io/posts/my_collections/2026/2026-08-30-playwright-resource-monitor/</guid>
      <description>&lt;p&gt;Green build, slow pages. That combination has annoyed me for years: Playwright happily reports 136/136 passing while the browser quietly burns 90% of one core on a badly-behaved tab, or the whole runner sits at 99% memory and everything gets &lt;em&gt;suspiciously&lt;/em&gt; slow. Test outcomes say nothing about &lt;strong&gt;how much resource the tests consumed&lt;/strong&gt;. Resource waste in CI is exactly the kind of thing you want to fail loudly, not discover from user complaints later.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Green build, slow pages. That combination has annoyed me for years: Playwright happily reports 136/136 passing while the browser quietly burns 90% of one core on a badly-behaved tab, or the whole runner sits at 99% memory and everything gets <em>suspiciously</em> slow. Test outcomes say nothing about <strong>how much resource the tests consumed</strong>. Resource waste in CI is exactly the kind of thing you want to fail loudly, not discover from user complaints later.</p>
<p>So I finally built it: <a href="https://github.com/ozkanpakdil/github-playwright-monitor">playwright-resource-monitor</a>, a GitHub Action that wraps your Playwright run, watches machine-wide CPU/memory <em>and</em> the worst single browser tab, fails the build when configurable thresholds are crossed, and records every run&rsquo;s peaks in the job summary plus a cross-run history.</p>
<h2 id="what-it-watches">What it watches</h2>
<p>Two layers, four thresholds, each with its own meaning:</p>
<table>
  <thead>
      <tr>
          <th>Layer</th>
          <th>What it measures</th>
          <th>Default</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Machine</strong></td>
          <td>CPU % across <strong>all cores combined</strong>, memory % of the effective RAM limit</td>
          <td>70 / 70</td>
      </tr>
      <tr>
          <td><strong>Tab</strong></td>
          <td>The <strong>worst single tab</strong> (renderer process): CPU as % of <strong>one</strong> core (yes, it can exceed 100%), memory % of the RAM limit</td>
          <td>70 / 70</td>
      </tr>
  </tbody>
</table>
<p>The per-tab layer is the one I actually care about. A machine-wide average on an 8-core runner looks serene even when one tab is melting. The average dilutes exactly the signal you want. &ldquo;Worst single renderer process&rdquo; is the meaningful question: <em>is any page we serve behaving badly?</em></p>
<h2 id="how-it-sees-your-browser-the-fun-part">How it sees your browser (the fun part)</h2>
<p>A GitHub Action is a separate process from your test run: it spawns <code>bun run test:e2e</code> (or <code>npm run test</code>) as a child and can&rsquo;t reach inside Playwright to hook anything. That constraint shaped the whole design. On Linux, the answer is the good old process table:</p>
<ul>
<li><code>/proc/&lt;pid&gt;/cmdline</code> finds Chromium processes and classifies them; <code>--type=renderer</code> processes are <strong>tabs</strong></li>
<li><code>/proc/&lt;pid&gt;/stat</code> gives CPU time deltas between polls → the tab&rsquo;s CPU consumed as <strong>% of one core</strong></li>
<li><code>/proc/&lt;pid&gt;/status</code> + a cgroup-aware limit check gives memory as <strong>% of RAM</strong></li>
</ul>
<p>Zero configuration in your project. No reporter to add, no dependency, nothing. The action also polls for a Chrome DevTools Protocol port if you want exact per-tab labels, and it injects that port for you as an environment variable.</p>
<p>The <strong>machine layer</strong> is sampled by the action itself too (deltas from <code>/proc/stat</code>, usage from <code>/proc/meminfo</code>, respecting cgroup memory limits for containerized runners). And because the action owns this sampler, machine breaches are logged <strong>live during the run</strong> as alert groups in the step log, not discovered after the fact:</p>
<pre tabindex="0"><code>::start-group::RESOURCE ALERT #1 at 12:03:41 — worst tab: renderer pid 4242
::warning::Threshold breach: tab CPU 96.4% &gt; 70% of one core
::endgroup::
</code></pre><blockquote>
<p>Optional extra: if your project uses <a href="https://github.com/cenfun/monocart-reporter">monocart-reporter</a> with <code>json: true</code>, the action automatically prefers its report and gets a machine-wide CPU/memory <em>timeline</em> in an HTML report for free. But it&rsquo;s optional. The built-in sampler covers enforcement on its own.</p></blockquote>
<h2 id="using-it">Using it</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">npm ci</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">run</span>: <span style="color:#ae81ff">npx playwright install chromium</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">ozkanpakdil/github-playwright-monitor@v1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">machine-cpu-threshold</span>: <span style="color:#ae81ff">80</span>    <span style="color:#75715e"># % of all cores combined</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">machine-memory-threshold</span>: <span style="color:#ae81ff">70</span> <span style="color:#75715e"># % of effective RAM limit</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">tab-cpu-threshold</span>: <span style="color:#ae81ff">70</span>        <span style="color:#75715e"># % of ONE core, worst single tab</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/upload-artifact@v4</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">name</span>: <span style="color:#ae81ff">resource-reports</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">path</span>: <span style="color:#ae81ff">resource-monitor/</span>
</span></span></code></pre></div><p>That&rsquo;s the whole setup. Thresholds are strictly-greater-than, so 70 means &ldquo;fail if any single tab holds more than 70% of one core&rdquo;. Set per-tab high (like 300) if you only care about runaway tabs, and keep machine thresholds meaningful for your runner&rsquo;s size.</p>
<p>Every run appends a record to a history file, rendered into the job summary as a table (newest first, linked to each run):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;outcome&#34;</span>: <span style="color:#e6db74">&#34;failed&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;machine&#34;</span>: { <span style="color:#f92672">&#34;peakCpuPercent&#34;</span>: <span style="color:#ae81ff">66.3</span>, <span style="color:#f92672">&#34;peakMemoryPercent&#34;</span>: <span style="color:#ae81ff">99.6</span>, <span style="color:#f92672">&#34;samples&#34;</span>: <span style="color:#ae81ff">45</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;tab&#34;</span>: { <span style="color:#f92672">&#34;peakCpuPercent&#34;</span>: <span style="color:#ae81ff">84.1</span>, <span style="color:#f92672">&#34;peakMemoryPercent&#34;</span>: <span style="color:#ae81ff">11.2</span>, <span style="color:#f92672">&#34;samples&#34;</span>: <span style="color:#ae81ff">88</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;breached&#34;</span>: { <span style="color:#f92672">&#34;machineMemory&#34;</span>: <span style="color:#66d9ef">true</span>, <span style="color:#f92672">&#34;tabCpu&#34;</span>: <span style="color:#66d9ef">false</span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Runners are ephemeral, so keep the trend across runs with <code>actions/cache</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/cache@v4</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">path</span>: <span style="color:#ae81ff">resource-monitor/history.json</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">key</span>: <span style="color:#ae81ff">resource-history-${{ github.run_id }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">restore-keys</span>: <span style="color:#ae81ff">resource-history-</span>
</span></span></code></pre></div><h2 id="the-verdict-rules">The verdict rules</h2>
<ul>
<li><strong>run-command fails</strong> → the action fails; the test result is authoritative.</li>
<li><strong>Any threshold breach with <code>fail-on-breach: true</code></strong> (the default) → the action fails naming each breached layer with peaks vs thresholds. With <code>false</code>, it only warns and stays green, which is handy for the first weeks on a new project while you calibrate.</li>
<li><strong>Nothing to monitor</strong> (no browser launched, no report) → the action stays <em>silent and green</em>. Wrapping a command that happens to launch no browsers is not an error.</li>
</ul>
<p>That last one matters more than it sounds: the action just wraps a shell command, and &ldquo;it did nothing weird to my run&rdquo; is a hard requirement for a CI gate.</p>
<h2 id="what-it-taught-me-about-thresholds">What it taught me about thresholds</h2>
<p>I validated the whole loop on my own <a href="https://github.com/ozkanpakdil/TrendCast">TrendCast</a> repo (a browser extension with 136 Playwright tests). Machine memory read <strong>99.6% of RAM</strong> and tripped the 70% threshold (on my laptop, not the runner). Machine memory % counts <em>the whole host</em>, other apps included, and macOS especially reads high. On a dedicated GitHub runner it&rsquo;s a healthy signal; on a shared machine, either size the machine thresholds for the runner or use <code>fail-on-breach: false</code> locally. Per-tab numbers don&rsquo;t have this problem: a tab&rsquo;s RSS is the tab&rsquo;s.</p>
<h2 id="testing-before-publishing">Testing before &ldquo;publishing&rdquo;</h2>
<p>One more thing I care about: you can consume the action by repo reference (<code>owner/repo@main</code>) with <strong>no release required</strong>. The Marketplace &ldquo;publish&rdquo; button only adds the listing (and asks for a tag when you&rsquo;re ready). The repo&rsquo;s CI runs a two-leg smoke matrix on every push: a &ldquo;native&rdquo; leg (no extra dependencies, asserting the built-in machine sampler and the <code>/proc</code> tab scanner both produce data around a real browser test) and a &ldquo;monocart&rdquo; leg. So every push is a pre-release test of both monitoring paths. When it&rsquo;s green, tag <code>v1</code>, draft a release, tick <em>Publish to the GitHub Marketplace</em>.</p>
<h2 id="limitations-honestly">Limitations, honestly</h2>
<ul>
<li>The tab layer is Linux-only (process table). On macOS/Windows runners you&rsquo;d need the CDP opt-in for tabs.</li>
<li>Machine memory on shared hosts reads high (see above).</li>
<li>Parallel CDP mode shares one debug port, so on Linux just use the <code>/proc</code> scanner.</li>
<li>Nothing to monitor → nothing enforced; the pass-through is intentional and quiet.</li>
</ul>
<p>The code is small and readable: one sampler per data source, ~30 unit tests around the parsing and threshold math. Browse it at <a href="https://github.com/ozkanpakdil/github-playwright-monitor">github.com/ozkanpakdil/github-playwright-monitor</a>, or look at the <a href="https://github.com/ozkanpakdil/TrendCast/blob/main/.github/workflows/ci.yml">TrendCast workflow</a> for a real-world usage. If your green build has ever felt slower than it should, you now have a way to catch the culprit per-tab ,and to make the build fail loudly when a page burns your CI budget.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
