<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Edge Apps library 26.8.0 | Screenly</title><description>What has shipped across Screenly, newest first.</description><link>https://www.screenly.io/releases/edge-apps/26.8.0/</link><language>en</language><atom:link href="https://www.screenly.io/releases/edge-apps/26.8.0/rss.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate><item><title>Edge Apps library 26.8.0: A new captureScreenshot helper takes an Edge App screenshot in 1 call</title><link>https://www.screenly.io/releases/edge-apps/26.8.0/capture-screenshot/</link><guid isPermaLink="true">https://www.screenly.io/releases/edge-apps/26.8.0/capture-screenshot/</guid><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><category>release</category><category>developers</category><description>&lt;p&gt;A screenshot spec for an Edge App does the same 7 things for every resolution: open a browser
context at the right size, fix the clock, mock &lt;code&gt;screenly.js&lt;/code&gt;, load the page, wait for the network
to go quiet, save the image, and close the context. &lt;code&gt;captureScreenshot()&lt;/code&gt; does all of it, and your
spec keeps only what&amp;rsquo;s specific to your app:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;button type="button" class="code-copy" aria-label="Copy code" data-copy-code&gt;
&lt;svg data-copy viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;
&lt;rect x="9" y="9" width="11" height="11" rx="2"&gt;&lt;/rect&gt;
&lt;path d="M5 15V5a2 2 0 0 1 2-2h10"&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;svg data-check viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"&gt;
&lt;path d="M20 6 9 17l-5-5"&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;/button&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6ab825;font-weight:bold"&gt;import&lt;/span&gt; { test } &lt;span style="color:#6ab825;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#ed9d13"&gt;&amp;#39;@playwright/test&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6ab825;font-weight:bold"&gt;import&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; captureScreenshot,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; createMockScreenlyForScreenshots,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RESOLUTIONS,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#6ab825;font-weight:bold"&gt;from&lt;/span&gt; &lt;span style="color:#ed9d13"&gt;&amp;#39;@screenly/edge-apps/test/screenshots&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6ab825;font-weight:bold"&gt;const&lt;/span&gt; { screenlyJsContent } = createMockScreenlyForScreenshots()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6ab825;font-weight:bold"&gt;for&lt;/span&gt; (&lt;span style="color:#6ab825;font-weight:bold"&gt;const&lt;/span&gt; { width, height } &lt;span style="color:#6ab825;font-weight:bold"&gt;of&lt;/span&gt; RESOLUTIONS) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; test(&lt;span style="color:#ed9d13"&gt;`screenshot &lt;/span&gt;&lt;span style="color:#ed9d13"&gt;${&lt;/span&gt;width&lt;span style="color:#ed9d13"&gt;}&lt;/span&gt;&lt;span style="color:#ed9d13"&gt;x&lt;/span&gt;&lt;span style="color:#ed9d13"&gt;${&lt;/span&gt;height&lt;span style="color:#ed9d13"&gt;}&lt;/span&gt;&lt;span style="color:#ed9d13"&gt;`&lt;/span&gt;, &lt;span style="color:#6ab825;font-weight:bold"&gt;async&lt;/span&gt; ({ browser }) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6ab825;font-weight:bold"&gt;await&lt;/span&gt; captureScreenshot(browser, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; width,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; height,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filenamePrefix: &lt;span style="color:#ed9d13"&gt;&amp;#39;menu-board&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; screenlyJsContent,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; setupMocks: &lt;span style="color:#6ab825;font-weight:bold"&gt;async&lt;/span&gt; (page) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#6ab825;font-weight:bold"&gt;await&lt;/span&gt; page.route(&lt;span style="color:#ed9d13"&gt;&amp;#39;**/api/prices**&amp;#39;&lt;/span&gt;, (route) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; route.fulfill({ status: &lt;span style="color:#6ab825;font-weight:bold"&gt;200&lt;/span&gt;, body: &lt;span style="color:#ed9d13"&gt;&amp;#39;{&amp;#34;items&amp;#34;:[]}&amp;#39;&lt;/span&gt; }),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Each run writes &lt;code&gt;screenshots/menu-board-1920x1080.png&lt;/code&gt; and so on, 1 file per resolution.
&lt;code&gt;setupMocks&lt;/code&gt; runs before the page loads, so your routes are in place for the first request. Leave
it out when there&amp;rsquo;s nothing app-specific to mock.&lt;/p&gt;
&lt;p&gt;Keep &lt;code&gt;test()&lt;/code&gt; in your own spec file, as above. That way Playwright reports failures against your
file and line, not the library&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;Apps made with &lt;code&gt;npx @screenly/edge-apps create&lt;/code&gt; from this release on get a spec that already uses
it.&lt;/p&gt;</description></item><item><title>Edge Apps library 26.8.0: The library's version numbers moved to CalVer</title><link>https://www.screenly.io/releases/edge-apps/26.8.0/calver-version-numbers/</link><guid isPermaLink="true">https://www.screenly.io/releases/edge-apps/26.8.0/calver-version-numbers/</guid><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><category>improvement</category><category>developers</category><description>&lt;p&gt;Library releases are numbered by year and month now, so the release after 1.5.0 is 26.8.0. That&amp;rsquo;s
the same scheme Screenly, ScreenlyOS, and the CLI use.&lt;/p&gt;
&lt;p&gt;Nothing about the library&amp;rsquo;s behavior changes with the numbering, and a version you&amp;rsquo;ve already
pinned keeps resolving to exactly what it did before.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If this affects you:&lt;/strong&gt; a range like &lt;code&gt;^1.5.0&lt;/code&gt; in your Edge App&amp;rsquo;s &lt;code&gt;package.json&lt;/code&gt; won&amp;rsquo;t match a
release again. Change it to &lt;code&gt;^26.8.0&lt;/code&gt;, which covers releases through the end of 2026. The first
release of 2027 will be 27.1.0.&lt;/p&gt;
&lt;/blockquote&gt;</description></item></channel></rss>